import React from 'react' ; import styled from 'styled-components' ; import PropTypes from 'prop-types' ; import ReactDOM from 'react-dom' ; import Transition from './Transition' ; import { ownerWindow , ownerDocument } from '../utils' ; import Modal from '../Modal' ; function getOffsetTop ( rect , vertical ) { let offset = 0 ; if ( typeof vertical === 'number' ) { offset = vertical ; } else if ( vertical === 'center' ) { offset = rect . height / 2 ; } else if ( vertical === 'bottom' ) { offset = rect . height ; } return offset ; } function getOffsetLeft ( rect , horizontal ) { let offset = 0 ; if ( typeof horizontal === 'number' ) { offset = horizontal ; } else if ( horizontal === 'center' ) { offset = rect . width / 2 ; } else if ( horizontal === 'right' ) { offset = rect . width ; } return offset ; } function getTransformOriginValue ( transformOrigin ) { return [ transformOrigin . horizontal , transformOrigin . vertical ] . map ( n => { return typeof n === 'number' ? `${ n }px` : n ; } ) . join ( ' ' ) ; } function getScrollParent ( parent , child ) { let element = child ; let scrollTop = 0 ; while ( element && element !== parent ) { element = element . parentNode ; scrollTop += element . scrollTop ; } return scrollTop ; } function getAnchorEl ( anchorEl ) { return typeof anchorEl === 'function' ? anchorEl ( ) : anchorEl ; } export default class Popover extends React . Component { handleGetOffsetTop = getOffsetTop ; handleGetOffsetLeft = getOffsetLeft ; constructor ( ) { super ( ) ; if ( typeof window !== 'undefined' ) { this . handleResize = ( ) => { if ( ! this . props . open ) { return ; } this . setPositioningStyles ( this . paperRef ) ; } ; } } componentDidMount ( ) { if ( this . props . action ) { this . props . action ( { updatePosition : this . handleResize , } ) ; } } setPositioningStyles = element => { const positioning = this . getPositioningStyle ( element ) ; if ( positioning . top !== null ) { element . style . top = positioning . top ; } if ( positioning . left !== null ) { element . style . left = positioning . left ; } element . style . transformOrigin = positioning . transformOrigin ; } ; getPositioningStyle = element => { const { anchorEl , anchorReference , marginThreshold } = this . props ; const contentAnchorOffset = this . getContentAnchorOffset ( element ) ; const elemRect = { width : element . offsetWidth , height : element . offsetHeight , } ; const transformOrigin = this . getTransformOrigin ( elemRect , contentAnchorOffset ) ; if ( anchorReference === 'none' ) { return { top : null , left : null , transformOrigin : getTransformOriginValue ( transformOrigin ) , } ; } const anchorOffset = this . getAnchorOffset ( contentAnchorOffset ) ; let top = anchorOffset . top - transformOrigin . vertical ; let left = anchorOffset . left - transformOrigin . horizontal ; const bottom = top + elemRect . height ; const right = left + elemRect . width ; const containerWindow = ownerWindow ( getAnchorEl ( anchorEl ) ) ; const heightThreshold = containerWindow . innerHeight - marginThreshold ; const widthThreshold = containerWindow . innerWidth - marginThreshold ; if ( top < marginThreshold ) { const diff = top - marginThreshold ; top -= diff ; transformOrigin . vertical += diff ; } else if ( bottom > heightThreshold ) { const diff = bottom - heightThreshold ; top -= diff ; transformOrigin . vertical += diff ; } if ( left < marginThreshold ) { const diff = left - marginThreshold ; left -= diff ; transformOrigin . horizontal += diff ; } else if ( right > widthThreshold ) { const diff = right - widthThreshold ; left -= diff ; transformOrigin . horizontal += diff ; } return { top : `${ top }px` , left : `${ left }px` , transformOrigin : getTransformOriginValue ( transformOrigin ) , } ; } ; getAnchorOffset ( contentAnchorOffset ) { const { anchorEl , anchorOrigin } = this . props ; const anchorElement = getAnchorEl ( anchorEl ) || ownerDocument ( this . paperRef ) . body ; const anchorRect = anchorElement . getBoundingClientRect ( ) ; const anchorVertical = contentAnchorOffset === 0 ? anchorOrigin . vertical : 'center' ; return { top : anchorRect . top + this . handleGetOffsetTop ( anchorRect , anchorVertical ) , left : anchorRect . left + this . handleGetOffsetLeft ( anchorRect , anchorOrigin . horizontal ) , } ; } getContentAnchorOffset ( element ) { const { getContentAnchorEl , anchorReference } = this . props ; let contentAnchorOffset = 0 ; if ( getContentAnchorEl && anchorReference === 'anchorEl' ) { const contentAnchorEl = getContentAnchorEl ( element ) ; if ( contentAnchorEl && element . contains ( contentAnchorEl ) ) { const scrollTop = getScrollParent ( element , contentAnchorEl ) ; contentAnchorOffset = contentAnchorEl . offsetTop + contentAnchorEl . clientHeight / 2 - scrollTop || 0 ; } } return contentAnchorOffset ; } getTransformOrigin ( elemRect , contentAnchorOffset = 0 ) { const { transformOrigin } = this . props ; const vertical = this . handleGetOffsetTop ( elemRect , transformOrigin . vertical ) + contentAnchorOffset ; const horizontal = this . handleGetOffsetLeft ( elemRect , transformOrigin . horizontal ) ; return { vertical , horizontal , } ; } handleEntering = element => { if ( this . props . onEntering ) { this . props . onEntering ( element ) ; } this . setPositioningStyles ( element ) ; } ; render ( ) { const { anchorEl , children , container : containerProp , open , popoverCss , ... other } = this . props ; const container = containerProp || ( anchorEl ? ownerDocument ( getAnchorEl ( anchorEl ) ) . body : undefined ) ; return ( < Modal container = { container } open = { open } BackdropProps = { { invisible : true } } { ... other } > < Transition onEntering = { this . handleEntering } > < StyledPopover popoverCss = { popoverCss } data - mui - test = "Popover" ref = { ref => { this . paperRef = ReactDOM . findDOMNode ( ref ) ; } } > { children } < /StyledPopover> < /Transition> < /Modal>); } } Popover . propTypes = { param { object } actions action : PropTypes . func , /**
     * This is the DOM element, or a function that returns the DOM element,
     * that may be used to set the position of the popover.
     */ anchorEl : PropTypes . oneOfType ( [ PropTypes . object , PropTypes . func ] ) , /**
     * This is the point on the anchor where the popover's
     * `anchorEl` will attach to. This is not used when the
     * anchorReference is 'anchorPosition'.
     *
     * Options:
     * vertical: [top, center, bottom];
     * horizontal: [left, center, right].
     */ anchorOrigin : PropTypes . shape ( { horizontal : PropTypes . oneOfType ( [ PropTypes . number , PropTypes . oneOf ( [ 'left' , 'center' , 'right' ] ) , ] ) . isRequired , vertical : PropTypes . oneOfType ( [ PropTypes . number , PropTypes . oneOf ( [ 'top' , 'center' , 'bottom' ] ) , ] ) . isRequired , } ) , /**
     * This is the position that may be used
     * to set the position of the popover.
     * The coordinates are relative to
     * the application's client area.
     */ anchorPosition : PropTypes . shape ( { left : PropTypes . number . isRequired , top : PropTypes . number . isRequired , } ) , anchorReference : PropTypes . oneOf ( [ 'anchorEl' , 'anchorPosition' , 'none' ] ) , /**
     * The content of the component.
     */ children : PropTypes . node , /**
     * A node, component instance, or function that returns either.
     * The `container` will passed to the Modal component.
     * By default, it uses the body of the anchorEl's top-level document object,
     * so it's simply `document.body` most of the time.
     */ container : PropTypes . oneOfType ( [ PropTypes . object , PropTypes . func ] ) , /**
     */ /**
     * This function is called in order to retrieve the content anchor element.
     * It's the opposite of the `anchorEl` property.
     * The content anchor element should be an element inside the popover.
     * It's used to correctly scroll and set the position of the popover.
     * The positioning strategy tries to make the content anchor element just above the
     * anchor element.
     */ getContentAnchorEl : PropTypes . func , /**
     * Specifies how close to the edge of the window the popover can appear.
     */ marginThreshold : PropTypes . number , param { object } event param { string } reason onClose : PropTypes . func , /**
     * Callback fired before the component is entering.
     */ onEnter : PropTypes . func , /**
     * Callback fired when the component has entered.
     */ onEntered : PropTypes . func , /**
     * Callback fired when the component is entering.
     */ onEntering : PropTypes . func , /**
     * If `true`, the popover is visible.
     */ open : PropTypes . bool . isRequired , /**
     * Properties applied to the [`Paper`](/api/paper/) element.
     */ PaperProps : PropTypes . object , ignore role : PropTypes . string , /**
     * This is the point on the popover which
     * will attach to the anchor's origin.
     *
     * Options:
     * vertical: [top, center, bottom, x(px)];
     * horizontal: [left, center, right, x(px)].
     */ transformOrigin : PropTypes . shape ( { horizontal : PropTypes . oneOfType ( [ PropTypes . number , PropTypes . oneOf ( [ 'left' , 'center' , 'right' ] ) , ] ) . isRequired , vertical : PropTypes . oneOfType ( [ PropTypes . number , PropTypes . oneOf ( [ 'top' , 'center' , 'bottom' ] ) , ] ) . isRequired , } ) , } ; Popover . defaultProps = { anchorReference : 'anchorEl' , anchorOrigin : { vertical : 'top' , horizontal : 'left' , } , marginThreshold : 16 , transformOrigin : { vertical : 'top' , horizontal : 'left' , } , } ; export const StyledPopover = styled . div `
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.24);
  border-radius: 4px;
  max-width: calc(100% - 32px);
  max-height: calc(100% - 32px);
  min-height: 16px;
  min-width: 16px;
  outline: none;
  overflow-x: hidden;
  overflow-y: auto;
  position: absolute;
  ${ props => props . popoverCss && props . popoverCss ( props ) }
` ;