<commit_before>/* @flow */
import React, { render, unmountComponentAtNode, createElement, createClass, DOM as E, PropTypes as T } from 'react'
import Debug from 'debug'
import merge from 'object.assign'
import * as resizeEvent from './on-resize'
import * as Layout from './layout'



let log = Debug('react-popover')

let coreStyle = {
  position: 'absolute',
  top: 0,
  left: 0,
  display: 'flex'
}

let faces = {
  above: 'down',
  right: 'left',
  below: 'up',
  left: 'right'
}

/* Flow mappings. Each map maps the flow domain to another domain. */

let flowToTipTranslations = {
  row: 'translateY',
  column: 'translateX'
}

let flowToPopoverTranslations = {
  row: 'translateX',
  column: 'translateY'
}






export default createClass({
  name: 'popover',
  mixins: [ReactLayerMixin()],
  propTypes: {
    body: T.node.isRequired,
    children: T.element.isRequired,
    preferPlace: T.string,
    tipSize: T.number,
    isOpen: T.bool,
    onOuterAction: T.func,
    enterExitTransitionDurationMs: T.number
  },
  getInitialState() {
    return {
      standing: 'above',
      exited: true, // for animation-dependent rendering, should popover close/open?
      exiting: false, // for tracking in-progress animations
      toggle: false // for business logic tracking, should popover close/open?
    }
  },
  getDefaultProps() {
    return {
      tipSize: 7,
      preferPlace: null,
      offset: 4,
      isOpen: false,
      onOuterAction: function noOperation(){},
      enterExitTransitionDurationMs: 500,
      children: null
    }
  },
  checkTargetReposition() {
    if (this.measureTargetBounds()) this.resolvePopoverLayout()
  },
  resolvePopoverLayout() {

    /* Find the optimal zone to position self. Measure the size of each zone and use the one with
    the greatest area. */

    let tb = this.targetBounds
    let zone = Layout.pickZone({ prefer: this.props.preferPlace }, this.frameBounds, tb, this.size)
    this.zone = zone
    log('zone', zone)

    this.setState({
      standing: zone.standing
    })

    let axis = Layout.axes[zone.flow]
    log('axes', axis)

    let dockingEdgeBufferLength = Math.round(getComputedStyle(this.bodyEl).borderRadius.slice(0, -2)) || 0
    let scrollSize = Layout.El.calcScrollSize(this.frameEl)
    scrollSize.main = scrollSize[axis.main.size]
    scrollSize.cross = scrollSize[axis.cross.size]

    /* When positioning self on the cross-axis do not exceed frame bounds. The strategy to achieve
    this is thus: First position cross-axis self to the cross-axis-center of the the target. Then,
    offset self by the amount that self is past the boundaries of frame. */
    let pos = Layout.calcRelPos(zone, tb, this.size)

    /* Offset allows users to control the distance betweent the tip and the target. */
    pos[axis.main.start] += this.props.offset * zone.order




    /* Constrain containerEl Position within frameEl. Try not to penetrate a visually-pleasing buffer from
    frameEl. `frameBuffer` length is based on tipSize and its offset. */

    let frameBuffer = this.props.tipSize + this.props.offset
    let hangingBufferLength = (dockingEdgeBufferLength * 2) + (this.props.tipSize * 2) + frameBuffer
    let frameCrossStart = this.frameBounds[axis.cross.start]
    let frameCrossEnd = this.frameBounds[axis.cross.end]
    let frameCrossLength = this.frameBounds[axis.cross.size]
    let frameCrossInnerLength = frameCrossLength - frameBuffer * 2
    let frameCrossInnerStart = frameCrossStart + frameBuffer
    let frameCrossInnerEnd = frameCrossEnd - frameBuffer
    let popoverCrossStart = pos[axis.cross.start]
    let popoverCrossEnd = pos[axis.cross.end]

    /* If the popover dose not fit into frameCrossLength then just position it to the `frameCrossStart`.
    popoverCrossLength` will now be forced to overflow into the `Frame` */
    if (pos.crossLength > frameCrossLength) {
      log('popoverCrossLength does not fit frame.')
      pos[axis.cross.start] = 0

    /* If the `popoverCrossStart` is forced beyond some threshold of `targetCrossLength` then bound
    it (`popoverCrossStart`). */

    } else if (tb[axis.cross.end] < hangingBufferLength) {
      log('popoverCrossStart cannot hang any further without losing target.')
      pos[axis.cross.start] = tb[axis.cross.end] - hangingBufferLength

    /* If the `popoverCrossStart` does not fit within the inner frame (honouring buffers) then
    just center the popover in the remaining `frameCrossLength`. */

    } else if (pos.crossLength > frameCrossInnerLength) {
      log('popoverCrossLength does not fit within buffered frame.')
      pos[axis.cross.start] = (frameCrossLength - pos.crossLength) / 2

    } else if (popoverCrossStart < frameCrossInnerStart) {
      log('popoverCrossStart cannot reverse without exceeding frame.')
      pos[axis.cross.start] = frameCrossInnerStart

    } else if (popoverCrossEnd > frameCrossInnerEnd) {
      log('popoverCrossEnd cannot travel without exceeding frame.')
      pos[axis.cross.start] = pos[axis.cross.start] - (pos[axis.cross.end] - frameCrossInnerEnd)
    }

    /* So far the link position has been calculated relative to the target. To calculate the absolute
    position we need to factor the `Frame`'s scroll position */

    pos[axis.cross.start] += scrollSize.cross
    pos[axis.main.start] += scrollSize.main

    /* Apply `flow` and `order` styles. This can impact subsequent measurements of height and width
    of the container. When tip changes orientation position due to changes from/to `row`/`column`
    width`/`height` will be impacted. Our layout monitoring will catch these cases and automatically
    recalculate layout. */

    this.containerEl.style.flexFlow = zone.flow
    this.bodyEl.style.order = zone.order

    /* Apply Absolute Positioning. */

    log('pos', pos)
    this.containerEl.style.top = `${pos.y}px`
    this.containerEl.style.left = `${pos.x}px`

    /* Calculate Tip Position */

    let tipCrossPos = (
      /* Get the absolute tipCrossCenter. Tip is positioned relative to containerEl
      but it aims at targetCenter which is positioned relative to frameEl... we
      need to cancel the containerEl positioning so as to hit our intended position. */
      Layout.centerOfBoundsFromBounds(zone.flow, 'cross', tb, pos)

      /* centerOfBounds does not account for scroll so we need to manually add that
      here. */
      + scrollSize.cross

      /* Center tip relative to self. We do not have to calcualte half-of-tip-size since tip-size
      specifies the length from base to tip which is half of total length already. */
      - this.props.tipSize
    )

    if (tipCrossPos < dockingEdgeBufferLength) tipCrossPos = dockingEdgeBufferLength
    else if (tipCrossPos > (pos.crossLength - dockingEdgeBufferLength) - this.props.tipSize * 2) tipCrossPos = (pos.crossLength - dockingEdgeBufferLength) - this.props.tipSize * 2

    this.tipEl.style.transform = `${flowToTipTranslations[zone.flow]}(${tipCrossPos}px)`
  },
  measurePopoverSize() {
    this.size = Layout.El.calcSize(this.containerEl)
  },
  measureTargetBounds() {
    let newTargetBounds = Layout.El.calcBounds(this.targetEl)

    if (this.targetBounds
       && Layout.equalCoords(this.targetBounds, newTargetBounds)) return false

    this.targetBounds = newTargetBounds
    return true
  },
  componentDidMount() {<commit_msg>Fix: Prevent infinite layout recursion by accounting for potential max height/width of popover so that zone choice is dealing with static dimensions. See code comment from more details. Closes #18.<commit_after>/* @flow */
import React, { render, unmountComponentAtNode, createElement, createClass, DOM as E, PropTypes as T } from 'react'
import Debug from 'debug'
import merge from 'object.assign'
import * as resizeEvent from './on-resize'
import * as Layout from './layout'



let log = Debug('react-popover')

let coreStyle = {
  position: 'absolute',
  top: 0,
  left: 0,
  display: 'flex'
}

let faces = {
  above: 'down',
  right: 'left',
  below: 'up',
  left: 'right'
}

/* Flow mappings. Each map maps the flow domain to another domain. */

let flowToTipTranslations = {
  row: 'translateY',
  column: 'translateX'
}

let flowToPopoverTranslations = {
  row: 'translateX',
  column: 'translateY'
}






export default createClass({
  name: 'popover',
  mixins: [ReactLayerMixin()],
  propTypes: {
    body: T.node.isRequired,
    children: T.element.isRequired,
    preferPlace: T.string,
    tipSize: T.number,
    isOpen: T.bool,
    onOuterAction: T.func,
    enterExitTransitionDurationMs: T.number
  },
  getInitialState() {
    return {
      standing: 'above',
      exited: true, // for animation-dependent rendering, should popover close/open?
      exiting: false, // for tracking in-progress animations
      toggle: false // for business logic tracking, should popover close/open?
    }
  },
  getDefaultProps() {
    return {
      tipSize: 7,
      preferPlace: null,
      offset: 4,
      isOpen: false,
      onOuterAction: function noOperation(){},
      enterExitTransitionDurationMs: 500,
      children: null
    }
  },
  checkTargetReposition() {
    if (this.measureTargetBounds()) this.resolvePopoverLayout()
  },
  resolvePopoverLayout() {

    /* Find the optimal zone to position self. Measure the size of each zone and use the one with
    the greatest area. */

    this.zone = Layout.pickZone({ prefer: this.props.preferPlace }, this.frameBounds, this.targetBounds, this.size)

    let tb = this.targetBounds
    let zone = this.zone
    log('zone', zone)

    this.setState({
      standing: zone.standing
    })

    let axis = Layout.axes[zone.flow]
    log('axes', axis)

    let dockingEdgeBufferLength = Math.round(getComputedStyle(this.bodyEl).borderRadius.slice(0, -2)) || 0
    let scrollSize = Layout.El.calcScrollSize(this.frameEl)
    scrollSize.main = scrollSize[axis.main.size]
    scrollSize.cross = scrollSize[axis.cross.size]

    /* When positioning self on the cross-axis do not exceed frame bounds. The strategy to achieve
    this is thus: First position cross-axis self to the cross-axis-center of the the target. Then,
    offset self by the amount that self is past the boundaries of frame. */
    let pos = Layout.calcRelPos(zone, tb, this.size)

    /* Offset allows users to control the distance betweent the tip and the target. */
    pos[axis.main.start] += this.props.offset * zone.order




    /* Constrain containerEl Position within frameEl. Try not to penetrate a visually-pleasing buffer from
    frameEl. `frameBuffer` length is based on tipSize and its offset. */

    let frameBuffer = this.props.tipSize + this.props.offset
    let hangingBufferLength = (dockingEdgeBufferLength * 2) + (this.props.tipSize * 2) + frameBuffer
    let frameCrossStart = this.frameBounds[axis.cross.start]
    let frameCrossEnd = this.frameBounds[axis.cross.end]
    let frameCrossLength = this.frameBounds[axis.cross.size]
    let frameCrossInnerLength = frameCrossLength - frameBuffer * 2
    let frameCrossInnerStart = frameCrossStart + frameBuffer
    let frameCrossInnerEnd = frameCrossEnd - frameBuffer
    let popoverCrossStart = pos[axis.cross.start]
    let popoverCrossEnd = pos[axis.cross.end]

    /* If the popover dose not fit into frameCrossLength then just position it to the `frameCrossStart`.
    popoverCrossLength` will now be forced to overflow into the `Frame` */
    if (pos.crossLength > frameCrossLength) {
      log('popoverCrossLength does not fit frame.')
      pos[axis.cross.start] = 0

    /* If the `popoverCrossStart` is forced beyond some threshold of `targetCrossLength` then bound
    it (`popoverCrossStart`). */

    } else if (tb[axis.cross.end] < hangingBufferLength) {
      log('popoverCrossStart cannot hang any further without losing target.')
      pos[axis.cross.start] = tb[axis.cross.end] - hangingBufferLength

    /* If the `popoverCrossStart` does not fit within the inner frame (honouring buffers) then
    just center the popover in the remaining `frameCrossLength`. */

    } else if (pos.crossLength > frameCrossInnerLength) {
      log('popoverCrossLength does not fit within buffered frame.')
      pos[axis.cross.start] = (frameCrossLength - pos.crossLength) / 2

    } else if (popoverCrossStart < frameCrossInnerStart) {
      log('popoverCrossStart cannot reverse without exceeding frame.')
      pos[axis.cross.start] = frameCrossInnerStart

    } else if (popoverCrossEnd > frameCrossInnerEnd) {
      log('popoverCrossEnd cannot travel without exceeding frame.')
      pos[axis.cross.start] = pos[axis.cross.start] - (pos[axis.cross.end] - frameCrossInnerEnd)
    }

    /* So far the link position has been calculated relative to the target. To calculate the absolute
    position we need to factor the `Frame`'s scroll position */

    pos[axis.cross.start] += scrollSize.cross
    pos[axis.main.start] += scrollSize.main

    /* Apply `flow` and `order` styles. This can impact subsequent measurements of height and width
    of the container. When tip changes orientation position due to changes from/to `row`/`column`
    width`/`height` will be impacted. Our layout monitoring will catch these cases and automatically
    recalculate layout. */

    this.containerEl.style.flexFlow = zone.flow
    this.bodyEl.style.order = zone.order

    /* Apply Absolute Positioning. */

    log('pos', pos)
    this.containerEl.style.top = `${pos.y}px`
    this.containerEl.style.left = `${pos.x}px`

    /* Calculate Tip Position */

    let tipCrossPos = (
      /* Get the absolute tipCrossCenter. Tip is positioned relative to containerEl
      but it aims at targetCenter which is positioned relative to frameEl... we
      need to cancel the containerEl positioning so as to hit our intended position. */
      Layout.centerOfBoundsFromBounds(zone.flow, 'cross', tb, pos)

      /* centerOfBounds does not account for scroll so we need to manually add that
      here. */
      + scrollSize.cross

      /* Center tip relative to self. We do not have to calcualte half-of-tip-size since tip-size
      specifies the length from base to tip which is half of total length already. */
      - this.props.tipSize
    )

    if (tipCrossPos < dockingEdgeBufferLength) tipCrossPos = dockingEdgeBufferLength
    else if (tipCrossPos > (pos.crossLength - dockingEdgeBufferLength) - this.props.tipSize * 2) {
      tipCrossPos = (pos.crossLength - dockingEdgeBufferLength) - this.props.tipSize * 2
    }

    this.tipEl.style.transform = `${flowToTipTranslations[zone.flow]}(${tipCrossPos}px)`
  },
  measurePopoverSize() {
    this.size = Layout.El.calcSize(this.containerEl)

    /* This is a kludge that solves a general problem very specifically for Popover.
    The problem is subtle. When Popover positioning changes such that it resolves at
    a different orientation, its Size will change because the Tip will toggle between
    extending Height or Width. The general problem of course is that calculating
    zone positioning based on current size is non-trivial if the Size can change once
    resolved to a different zone. Infinite recursion can be triggered as we noted here:
    https://github.com/littlebits/react-popover/issues/18. As an example of how this
    could happen in another way: Imagine the user changes the CSS styling of the popover
    based on whether it was `row` or `column` flow. TODO: Find a solution to generally
    solve this problem so that the user is free to change the Popover styles in any
    way at any time for any arbitrary trigger. There may be value in investigating the
    http://overconstrained.io community for its general layout system via the
    constraint-solver Cassowary. */
    if (this.zone) {
      this.size[this.zone.flow === 'row' ? 'h' : 'w'] += this.props.tipSize
    }
  },
  measureTargetBounds() {
    let newTargetBounds = Layout.El.calcBounds(this.targetEl)

    if (this.targetBounds
       && Layout.equalCoords(this.targetBounds, newTargetBounds)) return false

    this.targetBounds = newTargetBounds
    return true
  },
  componentDidMount() {
{'fraction_non_alphanumeric': 0.09493041749502983, 'fraction_numerical': 0.002360834990059642, 'mean_word_length': 3.362330623306233, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6737292', 'n_tokens_mistral': 4693, 'n_tokens_neox': 4525, 'n_words': 1536}