import { isNumber , UNDEFINED } from 'tsfn' ; import type { ReactElement } from 'react' ; import { LAYOUT_SIZE_1 , LAYOUT_SIZE_2 , LAYOUT_SIZE_3 , LAYOUT_SIZE_4 , LAYOUT_SIZE_FIT } from '../../symbols' ; import type { TLayoutSize } from './types' ; export type TOnItemSizeChange = ( index : number , value : number ) => void ; export const SIZE_INITIAL = 0 ; export const getLayoutWidth = ( item ) : TLayoutSize => item . props . width || LAYOUT_SIZE_1 ; export const getWidth = ( item ) : number | undefined => item . props . width ; export const getMinWidth = ( item ) : number | undefined => item . props . minWidth ; export const getMaxWidth = ( item ) : number | undefined => item . props . maxWidth ; export const getLayoutHeight = ( item ) : TLayoutSize => item . props . height || LAYOUT_SIZE_1 ; export const getHeight = ( item ) : number | undefined => item . props . height ; export const getMinHeight = ( item ) : number | undefined => item . props . minHeight ; export const getMaxHeight = ( item ) : number | undefined => item . props . maxHeight ; export const getNumLayoutParts = ( layoutValue ) : number => { switch ( layoutValue ) { case LAYOUT_SIZE_1 : return 1 ; case LAYOUT_SIZE_2 : return 2 ; case LAYOUT_SIZE_3 : return 3 ; case LAYOUT_SIZE_4 : return 4 ; default : return 0 ; } } ; const getSinglePartSize = ( containerSize , preOccupiedPixels , totalLayoutParts ) : number => { return Math . max ( containerSize - preOccupiedPixels , 0 ) / Math . max ( totalLayoutParts , 1 ) ; } ; const clampValue = ( currentValue , userDefinedValue ? , userDefinedMinValue ? , userDefinedMaxValue ? ) : number => { let value = currentValue ; if ( isNumber ( userDefinedValue ) ) { value = userDefinedValue ; } if ( isNumber ( userDefinedMinValue ) && value < userDefinedMinValue ) { value = userDefinedMinValue ; } if ( isNumber ( userDefinedMaxValue ) && value > userDefinedMaxValue ) { value = userDefinedMaxValue ; } return value ; } ; export const clampWidth = ( currentWidth , item ) : number => { return clampValue ( currentWidth , item . props . width , item . props . minWidth , item . props . maxWidth ) ; } ; export const clampHeight = ( currentHeight , item ) : number => { return clampValue ( currentHeight , item . props . height , item . props . minHeight , item . props . maxHeight ) ; } ; const isValueNear = ( value , limit ) : boolean => { return value > limit * 0.8 ; } ; export const calcTotal = ( values , padding , spacing ) : number => { let total = padding * 2 - spacing ; for ( let i = 0 ; i < values . length ; i ++ ) { total += values [ i ] + spacing ; } return Math . max ( total , 0 ) ; } ; export const calcMax = ( values ) : number => { let max = 0 ; for ( let i = 0 ; i < values . length ; i ++ ) { if ( values [ i ] > max ) { max = values [ i ] ; } } return max ; } ; export const equalizeArrays = ( items , itemPositions , itemRenderSizes , itemMeasuredSizes , onItemSizeChangeFunctions , onContainerSizeChange , isMeasureMode ) => { const numItems = items . length ; const shouldReportSizeChange = isMeasureMode && itemMeasuredSizes . length > numItems ; itemPositions . length = numItems ; for ( let i = itemMeasuredSizes . length ; i < numItems ; i ++ ) { itemMeasuredSizes . push ( 0 ) ; itemRenderSizes . push ( 0 ) ; } itemMeasuredSizes . length = numItems ; itemRenderSizes . length = numItems ; onItemSizeChangeFunctions . length = numItems ; if ( shouldReportSizeChange ) { onContainerSizeChange ( ) ; } } ; export const calcMeasureMainAxisLayout = ( items , itemPositions , itemRenderSizes , itemMeasuredSizes , itemMaxSizes , onItemSizeChangeFns , onItemSizeChange , getItemLayoutSize , getItemSize , getItemMinSize , getItemMaxSize , onContainerSizeChanged , containerMaxSize , padding , spacing ) => { const numItems = items . length ; let totalSize = 0 ; let shouldReportSizeChange = false ; if ( isNumber ( containerMaxSize ) ) { let numLayoutParts = 0 ; for ( let i = 0 ; i < numItems ; i ++ ) { numLayoutParts += getNumLayoutParts ( getItemLayoutSize ( items [ i ] ) ) ; } let availablePixels = containerMaxSize ; let needOneMorePass = true ; const stabilizedItems = Array ( numItems ) . fill ( false ) ; while ( needOneMorePass ) { needOneMorePass = false ; const itemMaxPartSize = availablePixels / Math . max ( numLayoutParts , 1 ) ; for ( let i = 0 ; i < numItems ; i ++ ) { const item = items [ i ] ; const itemLayoutSize = getItemLayoutSize ( item ) ; const userDefinedMaxSize = getItemMaxSize ( item ) ; const itemNumLayoutParts = getNumLayoutParts ( itemLayoutSize ) ; const itemSize = clampValue ( itemMeasuredSizes [ i ] , getItemSize ( item ) , getItemMinSize ( item ) , getItemMaxSize ( item ) ) ; const itemLayoutMaxSize = itemMaxPartSize * itemNumLayoutParts ; if ( ! stabilizedItems [ i ] ) { if ( itemLayoutSize === LAYOUT_SIZE_FIT || isNumber ( itemLayoutSize ) ) { availablePixels -= itemSize ; itemMaxSizes [ i ] = 0 ; stabilizedItems [ i ] = true ; needOneMorePass = true ; continue ; } if ( isNumber ( userDefinedMaxSize ) && userDefinedMaxSize < itemLayoutMaxSize && isValueNear ( itemSize , userDefinedMaxSize ) ) { availablePixels -= userDefinedMaxSize ; numLayoutParts -= itemNumLayoutParts ; itemMaxSizes [ i ] = userDefinedMaxSize ; stabilizedItems [ i ] = true ; needOneMorePass = true ; continue ; } itemMaxSizes [ i ] = itemLayoutMaxSize ; if ( ! isValueNear ( itemSize , itemLayoutMaxSize ) ) { availablePixels -= itemSize ; numLayoutParts -= itemNumLayoutParts ; stabilizedItems [ i ] = true ; needOneMorePass = true ; } } } } } for ( let i = 0 ; i < numItems ; i ++ ) { const item = items [ i ] ; const userSize = getItemSize ( item ) ; const size = clampValue ( itemMeasuredSizes [ i ] , userSize , getItemMinSize ( item ) , getItemMaxSize ( item ) ) ; const shouldMeasureItem = ! isNumber ( userSize ) ; shouldReportSizeChange = shouldReportSizeChange || itemRenderSizes [ i ] !== size ; itemPositions [ i ] = totalSize + padding ; itemRenderSizes [ i ] = size ; totalSize += size + spacing ; onItemSizeChangeFns [ i ] = shouldMeasureItem ? onItemSizeChange : UNDEFINED ; } if ( shouldReportSizeChange ) { onContainerSizeChanged ( ) ; } } ; export const calcMeasureCrossAxisLayout = ( items , itemPositions , itemRenderSizes , itemMeasuredSizes , itemMaxSizes , onItemSizeChangeFns , onItemSizeChange , getItemSize , getItemMinSize , getItemMaxSize , onContainerSizeChanged , containerMaxSize , padding ) => { const numItems = items . length ; const prevMaxSize = calcMax ( itemRenderSizes ) ; for ( let i = 0 ; i < numItems ; i ++ ) { const item = items [ i ] ; const userSize = getItemSize ( item ) ; const size = clampValue ( itemMeasuredSizes [ i ] , userSize , getItemMinSize ( item ) , getItemMaxSize ( item ) ) ; const shouldMeasureItem = ! isNumber ( userSize ) ; itemPositions [ i ] = padding ; itemRenderSizes [ i ] = size ; itemMaxSizes [ i ] = containerMaxSize ?? 0 ; onItemSizeChangeFns [ i ] = shouldMeasureItem ? onItemSizeChange : UNDEFINED ; } const maxSize = calcMax ( itemRenderSizes ) ; for ( let i = 0 ; i < numItems ; i ++ ) { itemRenderSizes [ i ] = maxSize ; } if ( prevMaxSize !== maxSize ) { onContainerSizeChanged ( ) ; } } ; export const calcExplicitMainAxisLayout = ( items , itemPositions , itemRenderSizes , itemMeasuredSizes , onItemSizeChangeFns , onItemSizeChange , getLayoutSize , getItemMinSize , getItemMaxSize , containerSize , padding , spacing ) => { const numItems = items . length ; let totalLayoutParts = 0 ; let preOccupiedPixels = ( numItems - 1 ) * spacing + padding * 2 ; for ( let i = 0 ; i < numItems ; i ++ ) { const item = items [ i ] ; const layoutValue = getLayoutSize ( item ) ; if ( isNumber ( layoutValue ) ) { const clampedValue = clampValue ( 0 , layoutValue , getItemMinSize ( item ) , getItemMaxSize ( item ) ) ; preOccupiedPixels += clampedValue ; itemRenderSizes [ i ] = clampedValue ; continue ; } const numParts = getNumLayoutParts ( layoutValue ) ; if ( numParts > 0 ) { totalLayoutParts += numParts ; itemRenderSizes [ i ] = 0 ; continue ; } preOccupiedPixels += itemMeasuredSizes [ i ] ; itemRenderSizes [ i ] = itemMeasuredSizes [ i ] ; } let isStabilized = false ; while ( ! isStabilized ) { isStabilized = true ; const singlePartSize = getSinglePartSize ( containerSize , preOccupiedPixels , totalLayoutParts ) ; for ( let i = 0 ; i < numItems ; i ++ ) { const item = items [ i ] ; const layoutValue = getLayoutSize ( item ) ; const numParts = getNumLayoutParts ( layoutValue ) ; let size = itemRenderSizes [ i ] ; if ( size === 0 && numParts > 0 ) { size = singlePartSize * numParts ; const clampedSize = clampValue ( size , size , getItemMinSize ( item ) , getItemMaxSize ( item ) ) ; if ( clampedSize !== size ) { itemRenderSizes [ i ] = clampedSize ; preOccupiedPixels += clampedSize ; totalLayoutParts -= numParts ; isStabilized = false ; break ; } } } } const singlePartSize = getSinglePartSize ( containerSize , preOccupiedPixels , totalLayoutParts ) ; let totalSize = 0 ; for ( let i = 0 ; i < numItems ; i ++ ) { const item = items [ i ] ; const layoutValue = getLayoutSize ( item ) ; const shouldMeasureItem = layoutValue === LAYOUT_SIZE_FIT ; if ( itemRenderSizes [ i ] === 0 ) { itemRenderSizes [ i ] = getNumLayoutParts ( layoutValue ) * singlePartSize ; } itemPositions [ i ] = totalSize + padding ; totalSize += itemRenderSizes [ i ] + spacing ; onItemSizeChangeFns [ i ] = shouldMeasureItem ? onItemSizeChange : UNDEFINED ; } } ; export const calcExplicitCrossAxisLayout = ( items , itemPositions , itemRenderSizes , onItemSizeChangeFns , containerSize , padding ) => { const numItems = items . length ; const maxSize = Math . max ( containerSize - padding * 2 , 0 ) ; for ( let i = 0 ; i < numItems ; i ++ ) { itemRenderSizes [ i ] = maxSize ; itemPositions [ i ] = padding ; onItemSizeChangeFns [ i ] = UNDEFINED ; } } ;