flow import chalk from 'chalk' ; import { bool } from 'prop-types' ; import React , { Component } from 'react' ; import stringWidth from 'string-width' ; import camelCase from 'camelcase' ; import widestLine from 'widest-line' ; import termSize from 'term-size' ; import ansiAlign from 'ansi-align' ; import cliBoxes from 'cli-boxes' ; import warning from 'fbjs/lib/warning' ; import normalizeColor from '../../modules/normalizeColor' ; import StyleSheetflattenStyle from '../StyleSheet/flattenStyle' ; function asArray ( value ) { if ( Array . isArray ( value ) ) { return value . filter ( Boolean ) ; } return asArray ( [ value ] ) ; } const NL = '\n' ; const PAD = ' ' ; function paddingFromStyle ( style ) { return { top : style . paddingTop || style . paddingVertical || style . padding || 0 , bottom : style . paddingBottom || style . paddingVertical || style . padding || 0 , left : style . paddingLeft || style . paddingHorizontal || style . padding || 0 , right : style . paddingRight || style . paddingHorizontal || style . padding || 0 , } ; } function marginFromStyle ( style ) { return { top : style . marginTop || style . marginVertical || style . margin || 0 , bottom : style . marginBottom || style . marginVertical || style . margin || 0 , left : style . marginLeft || style . marginHorizontal || style . margin || 0 , right : style . marginRight || style . marginHorizontal || style . margin || 0 , } ; } const getBorderChars = ( borderStyle ) : { [ key ] : string ; } => { const sides = [ 'topLeft' , 'topRight' , 'bottomRight' , 'bottomLeft' , 'vertical' , 'horizontal' , ] ; let chararacters = { } ; if ( typeof borderStyle === 'string' ) { chararacters = cliBoxes [ borderStyle ] ; if ( ! chararacters ) { throw new TypeError ( `Invalid border style: ${ borderStyle }` ) ; } } else { for ( const side of sides ) { if ( ! borderStyle [ side ] || typeof borderStyle [ side ] !== 'string' ) { throw new TypeError ( `Invalid border style: ${ side }` ) ; } } chararacters = borderStyle ; } return chararacters ; } ; const isHex = ( color ) : boolean => ! ! color . match ( /^#[0-f]{3}(?:[0-f]{3})?$/i ) ; const isColorValid = ( color ) : boolean => { if ( typeof color === 'string' ) { return color in chalk || isHex ( color ) ; } return false ; } ; const getBGColorFn = ( color ) : any => { if ( isHex ( color ) ) { return chalk . bgHex ( color ) ; } const tag = camelCase ( [ 'bg' , color ] ) ; return chalk [ tag ] ; } ; const colorizeText = ( text , style ) : string => { if ( style . color ) { const color = normalizeColor ( style . color ) ; if ( color ) { return chalk . rgb ( ... color ) ( text ) ; } } return text ; } ; function resolveColor ( style , stylePropKey ) { if ( stylePropKey in style ) { return normalizeColor ( style [ stylePropKey ] ) || null ; } return null ; } const BORDER_COLOR_STYLES = [ 'borderTopColor' , 'borderBottomColor' , 'borderLeftColor' , 'borderRightColor' , ] ; function borderColorFromStyle ( style ) { const resolvedStyle = { } ; for ( const styleProp of BORDER_COLOR_STYLES ) { const color = resolveColor ( style , styleProp ) ; if ( color ) resolvedStyle [ styleProp ] = color ; } if ( Object . keys ( resolvedStyle ) . length !== BORDER_COLOR_STYLES . length ) { const styleProp = 'borderColor' ; const color = resolveColor ( style , styleProp ) ; if ( color ) resolvedStyle [ styleProp ] = color ; } return { top : resolvedStyle . borderTopColor || resolvedStyle . borderColor , bottom : resolvedStyle . borderBottomColor || resolvedStyle . borderColor , left : resolvedStyle . borderLeftColor || resolvedStyle . borderColor , right : resolvedStyle . borderRightColor || resolvedStyle . borderColor , } ; } function resolveBorderStyle ( style ) { if ( 'borderStyle' in style ) { return style . borderStyle ; } if ( 'borderWidth' in style && style . borderWidth > 0 ) { if ( style . borderWidth <= 0.5 ) { return 'single' ; } else { return 'bold' ; } } else if ( 'borderRadius' in style && style . borderRadius > 0 ) { return 'round' ; } return 'single' ; } const BORDER_WIDTH_STYLES = [ 'borderTopWidth' , 'borderLeftWidth' , 'borderBottomWidth' , 'borderRightWidth' , ] ; function styleHasBorders ( style ) { for ( const styleProp of [ ... BORDER_WIDTH_STYLES , 'borderWidth' , ... BORDER_COLOR_STYLES , 'borderColor' , 'borderStyle' , ] ) { if ( styleProp in style ) { return true ; } } return false ; } function withStyle ( { children , style } ) { const flattenStyle = StyleSheetflattenStyle ( style ) || { } ; const textAlign = flattenStyle . textAlign || 'left' ; const alignSelf = flattenStyle . alignSelf || 'flex-start' ; const hasBorders = styleHasBorders ( flattenStyle ) ; const borderColors = hasBorders && borderColorFromStyle ( style ) ; const backgroundColor = flattenStyle . backgroundColor ? normalizeColor ( flattenStyle . backgroundColor ) : null ; const borderStyle = hasBorders && resolveBorderStyle ( flattenStyle ) ; children = ansiAlign ( children , { align : textAlign } ) ; children = colorizeText ( children , flattenStyle ) ; if ( asArray ( flattenStyle . fontWeight ) . includes ( 'bold' ) ) { children = chalk . bold ( children ) ; } if ( [ 'italic' , 'oblique' ] . includes ( flattenStyle . fontStyle ) ) { children = chalk . italic ( children ) ; } const _textDecorationStyle = asArray ( flattenStyle . textDecorationStyle ) ; if ( _textDecorationStyle . includes ( 'underline' ) ) { children = chalk . underline ( children ) ; } if ( _textDecorationStyle . includes ( 'line-through' ) ) { children = chalk . strikethrough ( children ) ; } let lines = children . split ( NL ) ; const chars = getBorderChars ( borderStyle || 'single' ) ; const padding = paddingFromStyle ( flattenStyle ) ; const margin = marginFromStyle ( flattenStyle ) ; if ( padding . top > 0 ) { lines = new Array ( padding . top ) . fill ( '' ) . concat ( lines ) ; } if ( padding . bottom > 0 ) { lines = lines . concat ( new Array ( padding . bottom ) . fill ( '' ) ) ; } const colorizeBackground = ( content , style ) : string => { if ( backgroundColor ) { return chalk . bgRgb ( ... backgroundColor ) ( content ) ; } return content ; } ; const colorizeBorder = ( border , side ) : string => { if ( borderColors [ side ] ) { border = chalk . rgb ( ... borderColors [ side ] ) ( border ) ; } return border ; } ; const contentWidth = widestLine ( children ) + padding . left + padding . right ; const paddingLeft = PAD . repeat ( padding . left ) ; const { columns } = termSize ( ) ; let marginLeft = PAD . repeat ( margin . left ) ; if ( alignSelf === 'center' ) { const padWidth = Math . max ( ( columns - contentWidth ) / 2 , 0 ) ; marginLeft = PAD . repeat ( padWidth ) ; } else if ( alignSelf === 'flex-end' ) { const padWidth = Math . max ( columns - contentWidth - margin . right - 2 , 0 ) ; marginLeft = PAD . repeat ( padWidth ) ; } const horizontal = chars . horizontal . repeat ( contentWidth ) ; const top = hasBorders ? colorizeBorder ( NL . repeat ( margin . top ) + marginLeft + chars . topLeft + horizontal + chars . topRight , 'top' ) : marginLeft ; const bottom = hasBorders ? colorizeBorder ( marginLeft + chars . bottomLeft + horizontal + chars . bottomRight + NL . repeat ( margin . bottom ) , 'bottom' ) : marginLeft ; const leftSide = hasBorders ? colorizeBorder ( chars . vertical , 'left' ) : '' ; const rightSide = hasBorders ? colorizeBorder ( chars . vertical , 'right' ) : '' ; const middle = lines . map ( ( line ) : string => { const paddingRight = PAD . repeat ( contentWidth - stringWidth ( line ) - padding . left ) ; return ( marginLeft + leftSide + colorizeBackground ( paddingLeft + line + paddingRight , flattenStyle ) + rightSide ) ; } ) . join ( NL ) ; return top + NL + middle + NL + bottom ; } class Text extends Component < any > { static displayName = 'Text' ; static childContextTypes = { isInAParentText : bool , } ; static contextTypes = { isInAParentText : bool , } ; getChildContext ( ) { return { isInAParentText : true } ; } render ( ) { const { dir , numberOfLines , onPress , selectable , style , adjustsFontSizeToFit , allowFontScaling , ellipsizeMode , lineBreakMode , maxFontSizeMultiplier , minimumFontScale , onLayout , onLongPress , pressRetentionOffset , selectionColor , suppressHighlighting , textBreakStrategy , tvParallaxProperties , ... otherProps } = this . props ; const { isInAParentText } = this . context ; if ( process . env . NODE_ENV !== 'production' ) { warning ( this . props . className == null , 'Using the "className" prop on <Text> is deprecated.' ) ; } const component = isInAParentText ? 'span' : 'div' ; const transformChildren = ( children ) => { const { unstable__transformChildren , style = { } as any , } = this . props as { [ key ] : any ; style : any ; } ; children = withStyle ( { children , style } ) ; if ( unstable__transformChildren ) { children = unstable__transformChildren ( children ) ; } return children ; } ; return React . createElement ( component , { ... otherProps , unstable__transformChildren : transformChildren , style : { flexDirection : 'row' } , } ) ; } _createEnterHandler ( fn ) { return ( e ) => { if ( e . keyCode === 13 ) { fn && fn ( e ) ; } } ; } _createPressHandler ( fn ) { return ( e ) => { e . stopPropagation ( ) ; fn && fn ( e ) ; } ; } } export default Text ;