license import React from 'react' ; import { StyleSheet , BackHandler , NativeEventSubscription , Platform } from 'react-native' ; import { Frame , MeasureElement , MeasuringElement , Point , RenderFCProp , Overwrite , } from '../../devsupport' ; import { ModalService } from '../../theme' ; import { ModalProps } from '../modal/modal.component' ; import { PopoverView , PopoverViewElement , PopoverViewProps , } from './popoverView.component' ; import { PopoverPlacementService } from './placement.service' ; import { PlacementOptions , PopoverPlacement , PopoverPlacements , } from './type' ; type PopoverModalProps = Overwrite < ModalProps , { children ? : React . ReactElement ; } > ; export interface PopoverProps extends PopoverViewProps , PopoverModalProps { anchor : RenderFCProp ; fullWidth ? : boolean ; } export type PopoverElement = React . ReactElement < PopoverProps > ; interface State { childFrame : Frame ; anchorFrame : Frame ; forceMeasure : boolean ; } extends React . Component method method property property property property property property property property overview-example overview-example overview-example overview-example export class Popover extends React . Component < PopoverProps , State > { static defaultProps : Partial < PopoverProps > = { placement : PopoverPlacements . BOTTOM , } ; public state : State = { childFrame : Frame . zero ( ) , anchorFrame : Frame . zero ( ) , forceMeasure : false , } ; private hardwareBackSubscription : NativeEventSubscription ; private modalId : string ; private contentPosition : Point = Point . outscreen ( ) ; private placementService : PopoverPlacementService = new PopoverPlacementService ( ) ; private actualPlacement : PopoverPlacement = this . preferredPlacement ; private get preferredPlacement ( ) : PopoverPlacement { return PopoverPlacements . parse ( this . props . placement ) ; } private get contentFlexPosition ( ) { const { x : left , y : top } = this . contentPosition ; return { left , top } ; } private get backdropConfig ( ) { const { onBackdropPress , backdropStyle } = this . props ; return { onBackdropPress , backdropStyle , } ; } public show = ( ) : void => { this . modalId = ModalService . show ( this . renderMeasuringPopoverElement ( ) , this . backdropConfig ) ; } ; public hide = ( ) : void => { this . modalId = ModalService . hide ( this . modalId ) ; } ; private onHardwareBackPress = ( ) : boolean => { this . hide ( ) ; return false ; } ; public componentDidUpdate ( prevProps ) { if ( ! this . modalId && this . props . visible && ! this . state . forceMeasure ) { this . setState ( { forceMeasure : true } ) ; return ; } if ( this . modalId && ! this . props . visible ) { this . contentPosition = Point . outscreen ( ) ; this . hide ( ) ; } } public componentDidMount ( ) { if ( Platform . OS === 'android' ) { this . hardwareBackSubscription = BackHandler . addEventListener ( 'hardwareBackPress' , this . onHardwareBackPress ) ; } } public componentWillUnmount ( ) { this . hardwareBackSubscription ?. remove ( ) ; this . hide ( ) ; } private onChildMeasure = ( childFrame ) : void => { this . state . childFrame = childFrame ; if ( ! this . modalId && this . props . visible ) { this . show ( ) ; return ; } if ( this . modalId && this . props . visible ) { ModalService . update ( this . modalId , this . renderPopoverElement ( ) ) ; } } ; private onContentMeasure = ( anchorFrame ) : void => { this . state . anchorFrame = anchorFrame ; const placementOptions = this . findPlacementOptions ( anchorFrame , this . state . childFrame ) ; this . actualPlacement = this . placementService . find ( this . preferredPlacement , placementOptions ) ; const displayFrame = this . actualPlacement . frame ( placementOptions ) ; this . contentPosition = displayFrame . origin ; ModalService . update ( this . modalId , this . renderPopoverElement ( ) ) ; } ; private findPlacementOptions = ( contentFrame , childFrame ) : PlacementOptions => { const width = this . props . fullWidth ? childFrame . size . width : contentFrame . size . width ; const frame = new Frame ( contentFrame . origin . x , contentFrame . origin . y , width , contentFrame . size . height ) ; return new PlacementOptions ( frame , childFrame , Frame . window ( ) , Frame . zero ( ) ) ; } ; private renderContentElement = ( ) : React . ReactElement => { const contentElement = this . props . children ; const fullWidthStyle = { width : this . state . childFrame . size . width } ; return React . cloneElement ( contentElement , { style : [ this . props . fullWidth && fullWidthStyle , contentElement . props . style ] , } ) ; } ; private renderPopoverElement = ( ) : PopoverViewElement => { return ( < PopoverView { ... this . props } contentContainerStyle = { [ this . props . contentContainerStyle , styles . popoverView , this . contentFlexPosition ] } placement = { this . actualPlacement . reverse ( ) } > { this . renderContentElement ( ) } < /PopoverView>); } ; private renderMeasuringPopoverElement = ( ) : MeasuringElement => { return ( < MeasureElement shouldUseTopInsets = { ModalService . getShouldUseTopInsets } onMeasure = { this . onContentMeasure } > { this . renderPopoverElement ( ) } < /MeasureElement>); } ; public render ( ) { return ( < MeasureElement shouldUseTopInsets = { ModalService . getShouldUseTopInsets } force = { this . state . forceMeasure } onMeasure = { this . onChildMeasure } > { this . props . anchor ( ) } < /MeasureElement>); } } const styles = StyleSheet . create ( { popoverView : { position : 'absolute' , } , } ) ;