Files
Panayot Cankov 1cbb1e8d0d feat(webpack): mark the CSS type for stylable views explicitly (#5257)
We want webpack's uglification to mangle function and class names
but that's what the current implementation of the CSS in {N} relys on
to get the CSS type for each view when targeted by CSS type selectors.

The implementation is changed a little so now the CSS type can be set
directly on the prototype of each View class or for TS, through decorator.

BREAKING CHANGES:


Extending classes requires marking the derived class with @CSSType
The root classes are not marked with CSSType and classes derived from ViewBase and View
will continue to work as expected. More concrete view classes (Button, Label, etc.) are
marked with @CSSType now and store their cssType on the prototype suppressing the previous
implementation that looked up the class function name. So clien classes that derive from one of
our @CSSType decorated classes will now have to be marked with @CSSType.
2018-03-12 16:34:25 +02:00

379 lines
14 KiB
TypeScript

import { LayoutBase, View, Style, CssProperty, isIOS, ShorthandProperty, makeValidator, makeParser, unsetValue, CSSType } from "../layout-base";
export * from "../layout-base";
export type Basis = "auto" | number;
export const ORDER_DEFAULT = 1;
export const FLEX_GROW_DEFAULT = 0.0;
export const FLEX_SHRINK_DEFAULT = 1.0;
export type FlexDirection = "row" | "row-reverse" | "column" | "column-reverse";
export namespace FlexDirection {
export const ROW: "row" = "row";
export const ROW_REVERSE: "row-reverse" = "row-reverse";
export const COLUMN: "column" = "column";
export const COLUMN_REVERSE: "column-reverse" = "column-reverse";
export const isValid = makeValidator<FlexDirection>(ROW, ROW_REVERSE, COLUMN, COLUMN_REVERSE);
export const parse = makeParser<FlexDirection>(isValid);
}
export type FlexWrap = "nowrap" | "wrap" | "wrap-reverse";
export namespace FlexWrap {
export const NOWRAP: "nowrap" = "nowrap";
export const WRAP: "wrap" = "wrap";
export const WRAP_REVERSE: "wrap-reverse" = "wrap-reverse";
export const isValid = makeValidator<FlexWrap>(NOWRAP, WRAP, WRAP_REVERSE);
export const parse = makeParser<FlexWrap>(isValid);
}
export type JustifyContent = "flex-start" | "flex-end" | "center" | "space-between" | "space-around";
export namespace JustifyContent {
export const FLEX_START: "flex-start" = "flex-start";
export const FLEX_END: "flex-end" = "flex-end";
export const CENTER: "center" = "center";
export const SPACE_BETWEEN: "space-between" = "space-between";
export const SPACE_AROUND: "space-around" = "space-around";
export const isValid = makeValidator<JustifyContent>(FLEX_START, FLEX_END, CENTER, SPACE_BETWEEN, SPACE_AROUND);
export const parse = makeParser<JustifyContent>(isValid);
}
export type FlexBasisPercent = number;
export namespace FlexBasisPercent {
export const DEFAULT: number = -1;
}
export type AlignItems = "flex-start" | "flex-end" | "center" | "baseline" | "stretch";
export namespace AlignItems {
export const FLEX_START: "flex-start" = "flex-start";
export const FLEX_END: "flex-end" = "flex-end";
export const CENTER: "center" = "center";
export const BASELINE: "baseline" = "baseline";
export const STRETCH: "stretch" = "stretch";
export const isValid = makeValidator<AlignItems>(FLEX_START, FLEX_END, CENTER, BASELINE, STRETCH);
export const parse = makeParser<AlignItems>(isValid);
}
export type AlignContent = "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "stretch";
export namespace AlignContent {
export const FLEX_START: "flex-start" = "flex-start";
export const FLEX_END: "flex-end" = "flex-end";
export const CENTER: "center" = "center";
export const SPACE_BETWEEN: "space-between" = "space-between";
export const SPACE_AROUND: "space-around" = "space-around";
export const STRETCH: "stretch" = "stretch";
export const isValid = makeValidator<AlignContent>(FLEX_START, FLEX_END, CENTER, SPACE_BETWEEN, SPACE_AROUND, STRETCH);
export const parse = makeParser<AlignContent>(isValid);
}
export type Order = number;
export namespace Order {
export function isValid(value): boolean {
return isFinite(parseInt(value));
}
export const parse = parseInt;
}
export type FlexGrow = number;
export namespace FlexGrow {
export function isValid(value: any): boolean {
const parsed = parseInt(value);
return isFinite(parsed) && value >= 0;
}
export const parse = parseFloat;
}
export type FlexShrink = number;
export namespace FlexShrink {
export function isValid(value: any): boolean {
const parsed = parseInt(value);
return isFinite(parsed) && value >= 0;
}
export const parse = parseFloat;
}
export type FlexWrapBefore = boolean;
export namespace FlexWrapBefore {
export function isValid(value) {
if (typeof value === "boolean") {
return true;
}
if (typeof value === "string") {
const str = value.trim().toLowerCase();
return str === "true" || str === "false";
}
return false;
}
export function parse(value: string): FlexWrapBefore {
return value && value.toString().trim().toLowerCase() === "true";
}
}
export type AlignSelf = "auto" | AlignItems;
export namespace AlignSelf {
export const AUTO: "auto" = "auto";
export const FLEX_START: "flex-start" = "flex-start";
export const FLEX_END: "flex-end" = "flex-end";
export const CENTER: "center" = "center";
export const BASELINE: "baseline" = "baseline";
export const STRETCH: "stretch" = "stretch";
export const isValid = makeValidator<AlignSelf>(AUTO, FLEX_START, FLEX_END, CENTER, BASELINE, STRETCH);
export const parse = makeParser<AlignSelf>(isValid);
}
function validateArgs(element: View): View {
if (!element) {
throw new Error("element cannot be null or undefinied.");
}
return element;
}
/**
* A common base class for all cross platform flexbox layout implementations.
*/
@CSSType("FlexboxLayout")
export abstract class FlexboxLayoutBase extends LayoutBase {
get flexDirection(): FlexDirection {
return this.style.flexDirection;
}
set flexDirection(value: FlexDirection) {
this.style.flexDirection = value;
}
get flexWrap(): FlexWrap {
return this.style.flexWrap;
}
set flexWrap(value: FlexWrap) {
this.style.flexWrap = value;
}
get justifyContent(): JustifyContent {
return this.style.justifyContent;
}
set justifyContent(value: JustifyContent) {
this.style.justifyContent = value;
}
get alignItems(): AlignItems {
return this.style.alignItems;
}
set alignItems(value: AlignItems) {
this.style.alignItems = value;
}
get alignContent(): AlignContent {
return this.style.alignContent;
}
set alignContent(value: AlignContent) {
this.style.alignContent = value;
}
public static setOrder(view: View, order: number) {
validateArgs(view).style.order = order;
}
public static getOrder(view: View): number {
return validateArgs(view).style.order;
}
public static setFlexGrow(view: View, grow: number) {
validateArgs(view).style.flexGrow = grow;
}
public static getFlexGrow(view: View) {
return validateArgs(view).style.flexGrow;
}
public static setFlexShrink(view: View, shrink: number) {
validateArgs(view).style.flexShrink = shrink;
}
public static getFlexShrink(view: View): number {
return validateArgs(view).style.flexShrink;
}
public static setAlignSelf(view: View, align: AlignSelf) {
validateArgs(view).style.alignSelf = align;
}
public static getAlignSelf(view: View): AlignSelf {
return validateArgs(view).style.alignSelf;
}
public static setFlexWrapBefore(view: View, wrap: boolean) {
validateArgs(view).style.flexWrapBefore = wrap;
}
public static getFlexWrapBefore(view: View): boolean {
return validateArgs(view).style.flexWrapBefore;
}
}
FlexboxLayoutBase.prototype.recycleNativeView = "auto";
export const flexDirectionProperty = new CssProperty<Style, FlexDirection>({ name: "flexDirection", cssName: "flex-direction", defaultValue: FlexDirection.ROW, affectsLayout: isIOS, valueConverter: FlexDirection.parse });
flexDirectionProperty.register(Style);
export const flexWrapProperty = new CssProperty<Style, FlexWrap>({ name: "flexWrap", cssName: "flex-wrap", defaultValue: "nowrap", affectsLayout: isIOS, valueConverter: FlexWrap.parse });
flexWrapProperty.register(Style);
export const justifyContentProperty = new CssProperty<Style, JustifyContent>({ name: "justifyContent", cssName: "justify-content", defaultValue: JustifyContent.FLEX_START, affectsLayout: isIOS, valueConverter: JustifyContent.parse });
justifyContentProperty.register(Style);
export const alignItemsProperty = new CssProperty<Style, AlignItems>({ name: "alignItems", cssName: "align-items", defaultValue: AlignItems.STRETCH, affectsLayout: isIOS, valueConverter: AlignItems.parse });
alignItemsProperty.register(Style);
export const alignContentProperty = new CssProperty<Style, AlignContent>({ name: "alignContent", cssName: "align-content", defaultValue: AlignContent.STRETCH, affectsLayout: isIOS, valueConverter: AlignContent.parse });
alignContentProperty.register(Style);
export const orderProperty = new CssProperty<Style, Order>({ name: "order", cssName: "order", defaultValue: ORDER_DEFAULT, valueConverter: Order.parse });
orderProperty.register(Style);
Object.defineProperty(View.prototype, "order", {
get(this: View): Order {
return this.style.order;
},
set(this: View, value: Order) {
this.style.order = value;
},
enumerable: true,
configurable: true
});
export const flexGrowProperty = new CssProperty<Style, FlexGrow>({ name: "flexGrow", cssName: "flex-grow", defaultValue: FLEX_GROW_DEFAULT, valueConverter: FlexGrow.parse });
flexGrowProperty.register(Style);
Object.defineProperty(View.prototype, "flexGrow", {
get(this: View): FlexGrow {
return this.style.flexGrow;
},
set(this: View, value: FlexGrow) {
this.style.flexGrow = value;
},
enumerable: true,
configurable: true
});
export const flexShrinkProperty = new CssProperty<Style, FlexShrink>({ name: "flexShrink", cssName: "flex-shrink", defaultValue: FLEX_SHRINK_DEFAULT, valueConverter: FlexShrink.parse });
flexShrinkProperty.register(Style);
Object.defineProperty(View.prototype, "flexShrink", {
get(this: View): FlexShrink {
return this.style.flexShrink;
},
set(this: View, value: FlexShrink) {
this.style.flexShrink = value;
},
enumerable: true,
configurable: true
});
export const flexWrapBeforeProperty = new CssProperty<Style, FlexWrapBefore>({ name: "flexWrapBefore", cssName: "flex-wrap-before", defaultValue: false, valueConverter: FlexWrapBefore.parse });
flexWrapBeforeProperty.register(Style);
Object.defineProperty(View.prototype, "flexWrapBefore", {
get(this: View): FlexWrapBefore {
return this.style.flexWrapBefore;
},
set(this: View, value: FlexWrapBefore) {
this.style.flexWrapBefore = value;
},
enumerable: true,
configurable: true
});
export const alignSelfProperty = new CssProperty<Style, AlignSelf>({ name: "alignSelf", cssName: "align-self", defaultValue: AlignSelf.AUTO, valueConverter: AlignSelf.parse });
alignSelfProperty.register(Style);
Object.defineProperty(View.prototype, "alignSelf", {
get(this: View): AlignSelf {
return this.style.alignSelf;
},
set(this: View, value: AlignSelf) {
this.style.alignSelf = value;
},
enumerable: true,
configurable: true
});
// flex-flow: <flex-direction> || <flex-wrap>
const flexFlowProperty = new ShorthandProperty<Style, string>({
name: "flexFlow", cssName: "flex-flow",
getter: function (this: Style) {
return `${this.flexDirection} ${this.flexWrap}`;
},
converter: function (value: string) {
const properties: [CssProperty<any, any>, any][] = [];
if (value === unsetValue) {
properties.push([flexDirectionProperty, value]);
properties.push([flexWrapProperty, value]);
} else {
const trimmed = value && value.trim();
if (trimmed) {
const values = trimmed.split(/\s+/);
if (values.length >= 1 && FlexDirection.isValid(values[0])) {
properties.push([flexDirectionProperty, FlexDirection.parse(values[0])]);
}
if (value.length >= 2 && FlexWrap.isValid(values[1])) {
properties.push([flexWrapProperty, FlexWrap.parse(values[1])]);
}
}
}
return properties;
}
})
flexFlowProperty.register(Style);
// flex: inital | auto | none | <flex-grow> <flex-shrink> || <flex-basis>
const flexProperty = new ShorthandProperty<Style, string>({
name: "flex", cssName: "flex",
getter: function (this: Style) {
return `${this.flexGrow} ${this.flexShrink}`;
},
converter: function (value: string) {
const properties: [CssProperty<any, any>, any][] = [];
if (value === unsetValue) {
properties.push([flexGrowProperty, value]);
properties.push([flexShrinkProperty, value]);
} else {
const trimmed = value && value.trim();
if (trimmed) {
const values = trimmed.split(/\s+/);
if (values.length === 1) {
switch (values[0]) {
case "inital":
properties.push([flexGrowProperty, 0]);
properties.push([flexShrinkProperty, 1]);
// properties.push([flexBasisProperty, FlexBasis.AUTO])
break;
case "auto":
properties.push([flexGrowProperty, 1]);
properties.push([flexShrinkProperty, 1]);
// properties.push([flexBasisProperty, FlexBasis.AUTO])
break;
case "none":
properties.push([flexGrowProperty, 0]);
properties.push([flexShrinkProperty, 0]);
// properties.push([flexBasisProperty, FlexBasis.AUTO])
break;
default:
if (FlexGrow.isValid(values[0])) {
properties.push([flexGrowProperty, FlexGrow.parse(values[0])]);
properties.push([flexShrinkProperty, 1]);
// properties.push([flexBasisProperty, 0])
}
}
}
if (values.length >= 2) {
if (FlexGrow.isValid(values[0]) && FlexShrink.isValid(values[1])) {
properties.push([flexGrowProperty, FlexGrow.parse(values[0])]);
properties.push([flexShrinkProperty, FlexShrink.parse(values[1])]);
}
}
// if (value.length >= 3) {
// properties.push({ property: flexBasisProperty, value: FlexBasis.parse(values[2])})
// }
}
}
return properties;
}
})
flexProperty.register(Style);