<commit_before>/** @flow */
import {
  getUpdatedOffsetForIndex,
  getVisibleCellIndices,
  initCellMetadata,
  initOnRowsRenderedHelper,
  prefixStyleSheet
} from '../utils'
import cn from 'classnames'
import raf from 'raf'
import React, { Component, PropTypes } from 'react'
import shouldPureComponentUpdate from 'react-pure-render/function'

/**
 * Specifies the number of miliseconds during which to disable pointer events while a scroll is in progress.
 * This improves performance and makes scrolling smoother.
 */
const IS_SCROLLING_TIMEOUT = 150

/**
 * It is inefficient to create and manage a large list of DOM elements within a scrolling container
 * if only a few of those elements are visible. The primary purpose of this component is to improve
 * performance by only rendering the DOM nodes that a user is able to see based on their current
 * scroll position.
 *
 * This component renders a simple list of elements with a fixed height. It is similar to the
 * 'react-infinite' library's `Infinite` component but offers some additional functionality such as
 * the ability to programmatically scroll to ensure that a specific row/item is visible within the
 * container.
 */
export default class VirtualScroll extends Component {
  shouldComponentUpdate = shouldPureComponentUpdate

  static propTypes = {
    /** Optional CSS class name */
    className: PropTypes.string,
    /** Height constraint for list (determines how many actual rows are rendered) */
    height: PropTypes.number.isRequired,
    /** Optional renderer to be used in place of rows when rowsCount is 0 */
    noRowsRenderer: PropTypes.func,
    /**
     * Callback invoked with information about the slice of rows that were just rendered.
     * ({ startIndex, stopIndex }): void
     */
    onRowsRendered: PropTypes.func,
    /** Either a fixed row height (number) or a function that returns the height of a row given its index. */
    rowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired,
    /** Responsbile for rendering a row given an index */
    rowRenderer: PropTypes.func.isRequired,
    /** Number of rows in list. */
    rowsCount: PropTypes.number.isRequired,
    /** Row index to ensure visible (by forcefully scrolling if necessary) */
    scrollToIndex: PropTypes.number,
    /** Specifies presentational styles for component. */
    styleSheet: PropTypes.object
  }

  static defaultProps = {
    noRowsRenderer: () => null,
    onRowsRendered: () => null
  }

  constructor (props, context) {
    super(props, context)

    this.state = {
      computeCellMetadataOnNextUpdate: false,
      isScrolling: false,
      styleSheet: prefixStyleSheet(props.styleSheet || VirtualScroll.defaultStyleSheet),
      scrollTop: 0
    }

    // Invokes onRowsRendered callback only when start/stop row indices change
    this._OnRowsRenderedHelper = initOnRowsRenderedHelper()

    this._onKeyPress = this._onKeyPress.bind(this)
    this._onScroll = this._onScroll.bind(this)
    this._onWheel = this._onWheel.bind(this)
  }

  /**
   * Forced recompute of row heights.
   * This function should be called if dynamic row heights have changed but nothing else has.
   * Since VirtualScroll receives a :rowsCount it has no way of knowing if the underlying list data has changed.
   */
  recomputeRowHeights () {
    this.setState({
      computeCellMetadataOnNextUpdate: true
    })
  }

  /**
   * Scroll the list to ensure the row at the specified index is visible.
   * This method exists so that a user can forcefully scroll to the same row twice.
   * (The :scrollToIndex property would not change in that case, so it would not be picked up by the component.)
   */
  scrollToRow (scrollToIndex) {
    this._updateScrollTopForScrollToIndex(scrollToIndex)
  }

  componentDidMount () {
    const { onRowsRendered, scrollToIndex } = this.props

    if (scrollToIndex >= 0) {
      // Without setImmediate() the initial scrollingContainer.scrollTop assignment doesn't work
      this._scrollTopId = setImmediate(() => {
        this._scrollTopId = null
        this._updateScrollTopForScrollToIndex()
      })
    }

    // Update onRowsRendered callback
    this._OnRowsRenderedHelper({
      onRowsRendered,
      startIndex: this._renderedStartIndex,
      stopIndex: this._renderedStopIndex
    })
  }

  componentDidUpdate (prevProps, prevState) {
    const { height, onRowsRendered, rowsCount, rowHeight, scrollToIndex } = this.props
    const { scrollTop } = this.state

    // Make sure any changes to :scrollTop (from :scrollToIndex) get applied
    if (scrollTop >= 0 && scrollTop !== prevState.scrollTop) {
      // TODO this.refs.scrollingContainer.scrollTop = scrollTop
    }

    const hasScrollToIndex = scrollToIndex >= 0 && scrollToIndex < rowsCount
    const sizeHasChanged = (
      height !== prevProps.height ||
      !prevProps.rowHeight ||
      (
        typeof rowHeight === 'number' &&
        rowHeight !== prevProps.rowHeight
      )
    )

    // If we have a new scroll target OR if height/row-height has changed,
    // We should ensure that the scroll target is visible.
    if (hasScrollToIndex && (sizeHasChanged || scrollToIndex !== prevProps.scrollToIndex)) {
      this._updateScrollTopForScrollToIndex()

    // If we don't have a selected item but list size or number of children have decreased,
    // Make sure we aren't scrolled too far past the current content.
    } else if (!hasScrollToIndex && (height < prevProps.height || rowsCount < prevProps.rowsCount)) {
      const calculatedScrollTop = getUpdatedOffsetForIndex({
        cellMetadata: this._cellMetadata,
        containerSize: height,<commit_msg>Reverted accidental comment that disabled arrow scrolling<commit_after>/** @flow */
import {
  getUpdatedOffsetForIndex,
  getVisibleCellIndices,
  initCellMetadata,
  initOnRowsRenderedHelper,
  prefixStyleSheet
} from '../utils'
import cn from 'classnames'
import raf from 'raf'
import React, { Component, PropTypes } from 'react'
import shouldPureComponentUpdate from 'react-pure-render/function'

/**
 * Specifies the number of miliseconds during which to disable pointer events while a scroll is in progress.
 * This improves performance and makes scrolling smoother.
 */
const IS_SCROLLING_TIMEOUT = 150

/**
 * It is inefficient to create and manage a large list of DOM elements within a scrolling container
 * if only a few of those elements are visible. The primary purpose of this component is to improve
 * performance by only rendering the DOM nodes that a user is able to see based on their current
 * scroll position.
 *
 * This component renders a simple list of elements with a fixed height. It is similar to the
 * 'react-infinite' library's `Infinite` component but offers some additional functionality such as
 * the ability to programmatically scroll to ensure that a specific row/item is visible within the
 * container.
 */
export default class VirtualScroll extends Component {
  shouldComponentUpdate = shouldPureComponentUpdate

  static propTypes = {
    /** Optional CSS class name */
    className: PropTypes.string,
    /** Height constraint for list (determines how many actual rows are rendered) */
    height: PropTypes.number.isRequired,
    /** Optional renderer to be used in place of rows when rowsCount is 0 */
    noRowsRenderer: PropTypes.func,
    /**
     * Callback invoked with information about the slice of rows that were just rendered.
     * ({ startIndex, stopIndex }): void
     */
    onRowsRendered: PropTypes.func,
    /** Either a fixed row height (number) or a function that returns the height of a row given its index. */
    rowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired,
    /** Responsbile for rendering a row given an index */
    rowRenderer: PropTypes.func.isRequired,
    /** Number of rows in list. */
    rowsCount: PropTypes.number.isRequired,
    /** Row index to ensure visible (by forcefully scrolling if necessary) */
    scrollToIndex: PropTypes.number,
    /** Specifies presentational styles for component. */
    styleSheet: PropTypes.object
  }

  static defaultProps = {
    noRowsRenderer: () => null,
    onRowsRendered: () => null
  }

  constructor (props, context) {
    super(props, context)

    this.state = {
      computeCellMetadataOnNextUpdate: false,
      isScrolling: false,
      styleSheet: prefixStyleSheet(props.styleSheet || VirtualScroll.defaultStyleSheet),
      scrollTop: 0
    }

    // Invokes onRowsRendered callback only when start/stop row indices change
    this._OnRowsRenderedHelper = initOnRowsRenderedHelper()

    this._onKeyPress = this._onKeyPress.bind(this)
    this._onScroll = this._onScroll.bind(this)
    this._onWheel = this._onWheel.bind(this)
  }

  /**
   * Forced recompute of row heights.
   * This function should be called if dynamic row heights have changed but nothing else has.
   * Since VirtualScroll receives a :rowsCount it has no way of knowing if the underlying list data has changed.
   */
  recomputeRowHeights () {
    this.setState({
      computeCellMetadataOnNextUpdate: true
    })
  }

  /**
   * Scroll the list to ensure the row at the specified index is visible.
   * This method exists so that a user can forcefully scroll to the same row twice.
   * (The :scrollToIndex property would not change in that case, so it would not be picked up by the component.)
   */
  scrollToRow (scrollToIndex) {
    this._updateScrollTopForScrollToIndex(scrollToIndex)
  }

  componentDidMount () {
    const { onRowsRendered, scrollToIndex } = this.props

    if (scrollToIndex >= 0) {
      // Without setImmediate() the initial scrollingContainer.scrollTop assignment doesn't work
      this._scrollTopId = setImmediate(() => {
        this._scrollTopId = null
        this._updateScrollTopForScrollToIndex()
      })
    }

    // Update onRowsRendered callback
    this._OnRowsRenderedHelper({
      onRowsRendered,
      startIndex: this._renderedStartIndex,
      stopIndex: this._renderedStopIndex
    })
  }

  componentDidUpdate (prevProps, prevState) {
    const { height, onRowsRendered, rowsCount, rowHeight, scrollToIndex } = this.props
    const { scrollTop } = this.state

    // Make sure any changes to :scrollTop (from :scrollToIndex) get applied
    if (scrollTop >= 0 && scrollTop !== prevState.scrollTop) {
      this.refs.scrollingContainer.scrollTop = scrollTop
    }

    const hasScrollToIndex = scrollToIndex >= 0 && scrollToIndex < rowsCount
    const sizeHasChanged = (
      height !== prevProps.height ||
      !prevProps.rowHeight ||
      (
        typeof rowHeight === 'number' &&
        rowHeight !== prevProps.rowHeight
      )
    )

    // If we have a new scroll target OR if height/row-height has changed,
    // We should ensure that the scroll target is visible.
    if (hasScrollToIndex && (sizeHasChanged || scrollToIndex !== prevProps.scrollToIndex)) {
      this._updateScrollTopForScrollToIndex()

    // If we don't have a selected item but list size or number of children have decreased,
    // Make sure we aren't scrolled too far past the current content.
    } else if (!hasScrollToIndex && (height < prevProps.height || rowsCount < prevProps.rowsCount)) {
      const calculatedScrollTop = getUpdatedOffsetForIndex({
        cellMetadata: this._cellMetadata,
        containerSize: height,
{'fraction_non_alphanumeric': 0.08052039381153306, 'fraction_numerical': 0.0014064697609001407, 'mean_word_length': 3.4011605415860733, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 15, 'https://': 0, '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': '3089369', 'n_tokens_mistral': 3047, 'n_tokens_neox': 2976, 'n_words': 1102}