import { NodePath } from '@babel/traverse' ; import * as t from '@babel/types' ; import type { TamaguiInternalConfig } from '@tamagui/core' ; import { getStylesAtomic , mediaObjectToString } from '@tamagui/core-node' ; import { ViewStyle } from 'react-native' ; import { MEDIA_SEP } from '../constants' ; import { StyleObject , Ternary } from '../types' ; import { isInsideTamagui , isPresent } from './extractHelpers' ; export function extractMediaStyle ( ternary , jsxPath , tamaguiConfig , sourcePath , importance = 0 , shouldPrintDebug = false ) { const mt = getMediaQueryTernary ( ternary , jsxPath , sourcePath ) ; if ( ! mt ) { return null ; } const { key } = mt ; const mq = tamaguiConfig . media [ key ] ; if ( ! mq ) { console . error ( `Media query "${ key }" not found: ${ Object . keys ( tamaguiConfig . media ) }` ) ; return null ; } const getStyleObj = ( styleObj , negate = false ) => { return styleObj ? { styleObj , negate } : null ; } ; const styleOpts = [ getStyleObj ( ternary . consequent , false ) , getStyleObj ( ternary . alternate , true ) , ] . filter ( isPresent ) ; if ( shouldPrintDebug && ! styleOpts . length ) { console . log ( '  media query, no styles?' ) ; return null ; } const mediaKeys = Object . keys ( tamaguiConfig . media ) ; const mediaKeyPrecendence = mediaKeys . reduce ( ( acc , cur , i ) => { acc [ cur ] = new Array ( importance + 1 ) . fill ( ':root' ) . join ( '' ) ; return acc ; } , { } ) ; let mediaStyles = [ ] ; for ( const { styleObj , negate } of styleOpts ) { const styles = getStylesAtomic ( styleObj ) ; const singleMediaStyles = styles . map ( ( style ) => { const negKey = negate ? '0' : '' ; const ogPrefix = style . identifier . slice ( 0 , style . identifier . indexOf ( '-' ) + 1 ) ; const identifier = `${ style . identifier . replace ( ogPrefix , `${ ogPrefix }${ MEDIA_SEP }${ key }${ negKey }${ MEDIA_SEP }` ) }` ; const className = `.${ identifier }` ; const mediaSelector = mediaObjectToString ( tamaguiConfig . media [ key ] ) ; const screenStr = negate ? 'not all' : 'screen' ; const mediaQuery = `${ screenStr } and ${ mediaSelector }` ; const precendencePrefix = mediaKeyPrecendence [ key ] ; const styleInner = style . rules [ 0 ] . replace ( style . identifier , identifier ) ; let styleRule = '' ; if ( styleInner . includes ( '@media' ) ) { styleRule = styleInner . replace ( '{' , ` and ${ mediaQuery } {` ) ; } else { styleRule = `@media ${ mediaQuery } { ${ precendencePrefix } ${ styleInner } }` ; } return { ... style , identifier , className , rules : [ styleRule ] , } ; } ) ; if ( shouldPrintDebug ) { console . log ( '  media styles:' , importance , singleMediaStyles . map ( x => x . identifier ) . join ( ', ' ) ) ; } mediaStyles = [ ... mediaStyles , ... singleMediaStyles ] ; } ternary . remove ( ) ; return { mediaStyles , ternaryWithoutMedia : mt . ternaryWithoutMedia } ; } function getMediaQueryTernary ( ternary , jsxPath , sourcePath ) { if ( t . isLogicalExpression ( ternary . test ) && ternary . test . operator === '&&' ) { const mediaLeft = getMediaInfoFromExpression ( ternary . test . left , jsxPath , sourcePath , ternary . inlineMediaQuery ) ; if ( mediaLeft ) { return { ... mediaLeft , ternaryWithoutMedia : { ... ternary , test : ternary . test . right , } , } ; } } const result = getMediaInfoFromExpression ( ternary . test , jsxPath , sourcePath , ternary . inlineMediaQuery ) ; if ( result ) { return { ... result , ternaryWithoutMedia : null , } ; } return null ; } function getMediaInfoFromExpression ( test , jsxPath , sourcePath , inlineMediaQuery ? ) { if ( inlineMediaQuery ) { return { key : inlineMediaQuery , bindingName : inlineMediaQuery , } ; } if ( t . isMemberExpression ( test ) && t . isIdentifier ( test . object ) && t . isIdentifier ( test . property ) ) { const name = test . object [ 'name' ] ; const key = test . property [ 'name' ] ; const bindings = jsxPath . scope . getAllBindings ( ) ; const binding = bindings [ name ] ; if ( ! binding ) return false ; const bindingNode = binding . path ?. node ; if ( ! t . isVariableDeclarator ( bindingNode ) || ! bindingNode . init ) return false ; if ( ! isValidMediaCall ( jsxPath , bindingNode . init , sourcePath ) ) return false ; return { key , bindingName : name } ; } if ( t . isIdentifier ( test ) ) { const key = test . name ; const node = jsxPath . scope . getBinding ( test . name ) ?. path ?. node ; if ( ! t . isVariableDeclarator ( node ) ) return false ; if ( ! node . init || ! isValidMediaCall ( jsxPath , node . init , sourcePath ) ) return false ; return { key , bindingName : key } ; } return null ; } export function isValidMediaCall ( jsxPath , init , sourcePath ) { if ( ! t . isCallExpression ( init ) ) return false ; if ( ! t . isIdentifier ( init . callee ) ) return false ; if ( init . callee . name !== 'useMedia' ) return false ; const bindings = jsxPath . scope . getAllBindings ( ) ; const mediaBinding = bindings [ 'useMedia' ] ; if ( ! mediaBinding ) return false ; const useMediaImport = mediaBinding . path . parent ; if ( ! t . isImportDeclaration ( useMediaImport ) ) return false ; if ( useMediaImport . source . value !== 'tamagui' ) { if ( ! isInsideTamagui ( sourcePath ) ) { return false ; } } return true ; }