import { LayoutBase } from '../layout-base'; import { View, CSSType } from '../../core/view'; import { CssProperty, ShorthandProperty, makeParser, makeValidator, unsetValue } from '../../core/properties'; import { Style } from '../../styling/style'; 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'; export const ROW_REVERSE = 'row-reverse'; export const COLUMN = 'column'; export const COLUMN_REVERSE = 'column-reverse'; export const isValid = makeValidator(ROW, ROW_REVERSE, COLUMN, COLUMN_REVERSE); export const parse = makeParser(isValid); } export type FlexWrap = 'nowrap' | 'wrap' | 'wrap-reverse'; export namespace FlexWrap { export const NOWRAP = 'nowrap'; export const WRAP = 'wrap'; export const WRAP_REVERSE = 'wrap-reverse'; export const isValid = makeValidator(NOWRAP, WRAP, WRAP_REVERSE); export const parse = makeParser(isValid); } export type JustifyContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around'; export namespace JustifyContent { export const FLEX_START = 'flex-start' as const; export const FLEX_END = 'flex-end' as const; export const CENTER = 'center' as const; export const SPACE_BETWEEN = 'space-between'; export const SPACE_AROUND = 'space-around'; export const isValid = makeValidator(FLEX_START, FLEX_END, CENTER, SPACE_BETWEEN, SPACE_AROUND); export const parse = makeParser(isValid); } export type FlexBasisPercent = number; export namespace FlexBasisPercent { export const DEFAULT = -1; } export type AlignItems = 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch'; export namespace AlignItems { export const FLEX_START = 'flex-start'; export const FLEX_END = 'flex-end'; export const CENTER = 'center'; export const BASELINE = 'baseline'; export const STRETCH = 'stretch'; export const isValid = makeValidator(FLEX_START, FLEX_END, CENTER, BASELINE, STRETCH); export const parse = makeParser(isValid); } export type AlignContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'stretch'; export namespace AlignContent { export const FLEX_START = 'flex-start'; export const FLEX_END = 'flex-end'; export const CENTER = 'center'; export const SPACE_BETWEEN = 'space-between'; export const SPACE_AROUND = 'space-around'; export const STRETCH = 'stretch'; export const isValid = makeValidator(FLEX_START, FLEX_END, CENTER, SPACE_BETWEEN, SPACE_AROUND, STRETCH); export const parse = makeParser(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'; export const FLEX_START = 'flex-start'; export const FLEX_END = 'flex-end'; export const CENTER = 'center'; export const BASELINE = 'baseline'; export const STRETCH = 'stretch'; export const isValid = makeValidator(AUTO, FLEX_START, FLEX_END, CENTER, BASELINE, STRETCH); export const parse = makeParser(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({ name: 'flexDirection', cssName: 'flex-direction', defaultValue: FlexDirection.ROW, affectsLayout: global.isIOS, valueConverter: FlexDirection.parse, }); flexDirectionProperty.register(Style); export const flexWrapProperty = new CssProperty({ name: 'flexWrap', cssName: 'flex-wrap', defaultValue: 'nowrap', affectsLayout: global.isIOS, valueConverter: FlexWrap.parse, }); flexWrapProperty.register(Style); export const justifyContentProperty = new CssProperty({ name: 'justifyContent', cssName: 'justify-content', defaultValue: JustifyContent.FLEX_START, affectsLayout: global.isIOS, valueConverter: JustifyContent.parse, }); justifyContentProperty.register(Style); export const alignItemsProperty = new CssProperty({ name: 'alignItems', cssName: 'align-items', defaultValue: AlignItems.STRETCH, affectsLayout: global.isIOS, valueConverter: AlignItems.parse, }); alignItemsProperty.register(Style); export const alignContentProperty = new CssProperty({ name: 'alignContent', cssName: 'align-content', defaultValue: AlignContent.STRETCH, affectsLayout: global.isIOS, valueConverter: AlignContent.parse, }); alignContentProperty.register(Style); export const orderProperty = new CssProperty({ 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({ 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({ 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({ 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({ 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: || const flexFlowProperty = new ShorthandProperty({ name: 'flexFlow', cssName: 'flex-flow', getter: function (this: Style) { return `${this.flexDirection} ${this.flexWrap}`; }, converter: function (value: string) { const properties: [CssProperty, 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 | || const flexProperty = new ShorthandProperty({ name: 'flex', cssName: 'flex', getter: function (this: Style) { return `${this.flexGrow} ${this.flexShrink}`; }, converter: function (value: string) { const properties: [CssProperty, 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);