import { R , React } from '../libs' ; import { isBlank , toNumber , isPlainObject } from '../util' ; import { IFormatCss , IImageOptions , IBackgroundImageStyles , Falsy , GlamorValue , } from './types' ; import { css as glamorCss } from 'glamor' ; export const MEDIA_QUERY_RETINA = `@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)` ; param { string } image1x param { string } image2x param { integer } width param { integer } height export const image = ( image1x , image2x , options = { width : 10 , height : 10 } ) : IBackgroundImageStyles => { if ( ! image1x ) { throw new Error ( 'Must have at least a 1x image.' ) ; } const { width , height } = options ; const result = { width , height , backgroundImage : `url(${ image1x })` , backgroundSize : `${ width }px ${ height }px` , backgroundRepeat : 'no-repeat' , } ; if ( image2x ) { result [ MEDIA_QUERY_RETINA ] = { backgroundImage : `url(${ image2x })` , } ; } return result ; } ; const mergeAndReplace = ( key , value , target ) => { Object . assign ( target , value ) ; delete target [ key ] ; return target ; } ; const formatImage = ( key , value , target ) => { let [ image1x , image2x , width , height ] = value ; if ( R . is ( Number , image2x ) ) { height = width ; width = image2x ; image2x = undefined ; } const options = { width : width as number , height : height as number , } ; const style = image ( image1x as string , image2x as string , options ) ; mergeAndReplace ( key , style , target ) ; } ; export const toPositionEdges = ( key , value = undefined ) : { position : string ; top : number | void ; right : number | void ; bottom : number | void ; left : number | void ; } | void => { if ( value === undefined || value === null ) { return undefined ; } if ( R . is ( String , value ) && isBlank ( value ) ) { return undefined ; } if ( R . is ( Array , value ) && value . length === 0 ) { return undefined ; } if ( ! R . is ( Array , value ) ) { value = value . toString ( ) . split ( ' ' ) ; } const edges = value . map ( ( item ) => toNumber ( item ) ) ; let top ; let right ; let bottom ; let left ; const getEdge = ( index ) : number | void => { const edge = edges [ index ] ; if ( edge === null || edge === 'null' || edge === '' ) { return undefined ; } return edge ; } ; switch ( edges . length ) { case 1 : top = getEdge ( 0 ) ; bottom = getEdge ( 0 ) ; left = getEdge ( 0 ) ; right = getEdge ( 0 ) ; break ; case 2 : top = getEdge ( 0 ) ; bottom = getEdge ( 0 ) ; left = getEdge ( 1 ) ; right = getEdge ( 1 ) ; break ; case 3 : top = getEdge ( 0 ) ; left = getEdge ( 1 ) ; right = getEdge ( 1 ) ; bottom = getEdge ( 2 ) ; break ; default : top = getEdge ( 0 ) ; right = getEdge ( 1 ) ; bottom = getEdge ( 2 ) ; left = getEdge ( 3 ) ; } if ( top === undefined && right === undefined && bottom === undefined && left === undefined ) { return undefined ; } return { position : key . toLowerCase ( ) , top , right , bottom , left , } ; } ; export const formatPositionEdges = ( key , target ) => { const styles = toPositionEdges ( key , target [ key ] ) ; mergeAndReplace ( key , styles , target ) ; } ; /**
 * AbsoluteCenter
 *      - x
 *      - y
 *      - xy
 */ const formatAbsoluteCenter = ( key , value , target ) => { if ( value === true ) { value = 'xy' ; } if ( value === false || value === undefined || value === null ) { return ; } const styles = { position : 'absolute' , left : target . left , top : target . top , transform : '' , } ; const stringValue = value . toString ( ) . trim ( ) . toLowerCase ( ) ; if ( stringValue . includes ( 'x' ) ) { styles . left = '50%' ; } if ( stringValue . includes ( 'y' ) ) { styles . top = '50%' ; } let transform ; switch ( value ) { case 'yx' : case 'xy' : transform = 'translate(-50%, -50%)' ; break ; case 'x' : transform = 'translateX(-50%)' ; break ; case 'y' : transform = 'translateY(-50%)' ; break ; default : throw new Error ( `AbsoluteCenter value '${ value }' not supported.` ) ; } styles . transform = `${ target . transform || '' } ${ transform }` . trim ( ) ; mergeAndReplace ( key , styles , target ) ; } ; /**
 * Spacing on the X:Y plane.
 */ function formatSpacingPlane ( plane , prefix , key , value , target ) { const styles = { } ; switch ( plane ) { case 'x' : styles [ `${ prefix }Left` ] = value ; styles [ `${ prefix }Right` ] = value ; break ; case 'y' : styles [ `${ prefix }Top` ] = value ; styles [ `${ prefix }Bottom` ] = value ; break ; default : break ; } mergeAndReplace ( key , styles , target ) ; } /**
 * Sets up vertical scrolling including iOS momentum scrolling.
 * See:
 *    https://css-tricks.com/snippets/css/momentum-scrolling-on-ios-overflow-elements/
 */ function formatScroll ( key , value , target ) { if ( value === true ) { const styles = { overflowX : 'hidden' , overflowY : 'scroll' , WebkitOverflowScrolling : 'touch' , } ; mergeAndReplace ( key , styles , target ) ; } if ( value === false ) { const styles = { overflow : 'hidden' , } ; mergeAndReplace ( key , styles , target ) ; } } const AlignMap = { center : 'center' , left : 'flex-start' , top : 'flex-start' , start : 'flex-start' , right : 'flex-end' , bottom : 'flex-end' , end : 'flex-end' , full : 'stretch' , stretch : 'stretch' , baseline : 'baseline' , } ; function convertCrossAlignToFlex ( token ) { return AlignMap [ token ] || undefined ; } const MainAlignMap = { center : 'center' , left : 'flex-start' , top : 'flex-start' , start : 'flex-start' , right : 'flex-end' , bottom : 'flex-end' , end : 'flex-end' , spaceBetween : 'space-between' , spaceAround : 'space-around' , spaceEvenly : 'space-evenly' , } ; function convertMainAlignToFlex ( token ) { return MainAlignMap [ token ] || undefined ; } /**
 * Format a flex css helper
 * Format: [<direction>]-<crossAlignment>-<mainAlignment>
 */ function formatFlexPosition ( key , value , target ) { let direction ; let mainAlignment ; let crossAlignment ; const tokens = value . split ( '-' ) . map ( token => token . trim ( ) ) ; tokens . map ( token => { const tokenIsOneOf = ( options ) => options . includes ( token ) ; if ( direction == null && tokenIsOneOf ( [ 'horizontal' , 'vertical' ] ) ) { direction = token === 'vertical' ? 'column' : 'row' ; return ; } if ( tokenIsOneOf ( [ 'center' , 'start' , 'end' , 'left' , 'right' , 'top' , 'bottom' , 'full' , 'baseline' , ] ) ) { if ( crossAlignment == null ) { if ( direction == null && tokenIsOneOf ( [ 'left' , 'right' ] ) ) { direction = 'column' ; } if ( direction == null && tokenIsOneOf ( [ 'top' , 'bottom' ] ) ) { direction = 'row' ; } crossAlignment = convertCrossAlignToFlex ( token ) ; return ; } mainAlignment = convertMainAlignToFlex ( token ) ; return ; } if ( tokenIsOneOf ( [ 'spaceAround' , 'spaceBetween' , 'spaceEvenly' ] ) ) { mainAlignment = convertMainAlignToFlex ( token ) ; return ; } } ) ; const styles = { display : 'flex' , flexDirection : direction , alignItems : crossAlignment , justifyContent : mainAlignment , } ; mergeAndReplace ( key , styles , target ) ; } export const transformStyle = ( style = { } ) : React . CSSProperties | GlamorValue => { if ( style == null ) { return { } ; } if ( style === false ) { return { } ; } if ( ! R . is ( Object , style ) ) { return style ; } Object . keys ( style ) . forEach ( key => { const value = style [ key ] ; if ( value === false || R . isNil ( value ) ) { delete style [ key ] ; } else if ( isPlainObject ( value ) ) { style [ key ] = transformStyle ( value ) ; } else { switch ( key ) { case 'Image' : formatImage ( key , value , style ) ; break ; case 'Absolute' : formatPositionEdges ( key , style ) ; break ; case 'Fixed' : formatPositionEdges ( key , style ) ; break ; case 'AbsoluteCenter' : formatAbsoluteCenter ( key , value , style ) ; break ; case 'MarginX' : formatSpacingPlane ( 'x' , 'margin' , key , value , style ) ; break ; case 'MarginY' : formatSpacingPlane ( 'y' , 'margin' , key , value , style ) ; break ; case 'PaddingX' : formatSpacingPlane ( 'x' , 'padding' , key , value , style ) ; break ; case 'PaddingY' : formatSpacingPlane ( 'y' , 'padding' , key , value , style ) ; break ; case 'Flex' : formatFlexPosition ( key , value , style ) ; break ; case 'Scroll' : formatScroll ( key , value , style ) ; break ; default : } } } ) ; return style ; } ; /**
 * Helpers for constructing a CSS object.
 * NB: This doesn't *actually* return React.CSSProperties, but
 */ const formatCss = ( ... styles ) : GlamorValue => { const newStyles = styles . map ( transformStyle ) ; return glamorCss ( ... newStyles ) as { } ; } ; ( formatCss as any ) . image = image ; export const format = formatCss as IFormatCss ;