react-selectable icon indicating copy to clipboard operation
react-selectable copied to clipboard

How to add horizontal autoscrolling when I select items in one long line with a horizontal scrollbar?

Open danilvalov opened this issue 8 years ago • 3 comments

I have a string with elements. When there are a lot of them, a scrollbar appears. But I can't continue selecting elements outside of the visible area. How to do it?

danilvalov avatar Nov 02 '17 15:11 danilvalov

I solved this problem for me. But I don't know how to add my solution to the original repository. My solution is:

  1. Install react-selectable from my repository (branch: add-support-for-external-scrolling):
npm install git://github.com/danilvalov/react-selectable.git#add-support-for-external-scrolling --save
  1. Use the following example:
class ExampleWithHorizontalScrolling extends Component {
  constructor () {
    super();

    this._isSelecting = false;      // state of selection: active or not
    this._scrollingTimer = null;    // scrolling setTimeout
    this._startingScrollLeft = 0;   // starting container scrollLeft (when we start to select items)

    this.onBeginSelection = this.onBeginSelection.bind(this);
    this.onEndSelection = this.onEndSelection.bind(this);
    this.startScrollingTimer = this.startScrollingTimer.bind(this);
    this.stopScrollingTimer = this.stopScrollingTimer.bind(this);
    this.updateScrollLeft = this.updateScrollLeft.bind(this);
    this.handleSelection = this.handleSelection.bind(this);
  }

  scrollToDirection (direction) {
    const {container, selectable} = this.refs;

    if (container.scrollLeft >= container.scrollWidth) {
      this.stopScrollingTimer();

      return;
    }

    container.scrollLeft = container.scrollLeft + (5 * direction);  // '5' is a scrolling step
    selectable.changeScrollOffsets(container.scrollLeft - this._startingScrollLeft);
  }

  onBeginSelection () {
    const {container} = this.refs;

    this._isSelecting = true;
    this._startingScrollLeft = container.scrollLeft;
  }

  onEndSelection () {
    if (!this._scrollingTimer) {
      return;
    }

    this._isSelecting = false;
    this._startingScrollLeft = 0;

    setTimeout(() => {
      this.stopScrollingTimer();
    });
  }

  startScrollingTimer (direction) {
    if (!this._isSelecting) {
      return;
    }

    this._scrollingTimer = setTimeout(() => {
      requestAnimationFrame(() => {
        this.startScrollingTimer(direction);
      });

      this.scrollToDirection(direction);
    }, 50);
  }

  stopScrollingTimer () {
    if (!this._scrollingTimer) {
      return;
    }

    clearTimeout(this._scrollingTimer);
    this._scrollingTimer = 0; // fix for IE
  }

  updateScrollLeft (e) {
    // when we change `scrollLeftShift` in the `react-scrollable`
    // we don't have `event` variable and we just need to ignore the action
    if (typeof e === 'undefined') {
      return;
    }

    // stop prevous scrolling setTimeout
    this.stopScrollingTimer();

    const {container} = this.refs;

    const containerPosition = container.getBoundingClientRect();
    const mousePositionX = e.pageX;
    const canScrollLeft = container.scrollLeft > 0;
    const canScrollRight = container.scrollWidth - container.clientWidth() > container.scrollLeft;

    if (
      (
        containerPosition.left < mousePositionX ||   // if mouse isn't outside of container (left side)
        !canScrollLeft                               // if we can't scroll to left (scrollLeft is not '0')
      ) &&
      (
        containerPosition.right > mousePositionX ||  // if mouse isn't outside of container (right side)
        !canScrollRight                              // if we can't scroll to right
      )
    ) {
      return;
    }

    // detect scrolling direction
    const direction =
      containerPosition.right < mousePositionX &&
      canScrollRight
        ? 1
        : -1;

    this.scrollToDirection(direction);               // do one step to direction
    this.startScrollingTimer(direction);             // start scrolling timer
  }

  handleSelection (selectedKeys, e) {
    this.updateScrollLeft(e);

    // selectedKeys handling
  }

  render () {
    return (
      <div
        ref='container'
        style={{
          width: '600px',
          overflowX: 'auto',
          whiteSpace: 'nowrap'   // or `float: left` for every <Item>
        }}
      >
        <SelectableGroup
          ref='selectable'
          onBeginSelection={this.onBeginSelection}
          onEndSelection={this.onEndSelection}
          onSelection={this.handleSelection}
        >
          {
              this.props.items.map((item, index) => (
                <Item
                  key={index}
                  data={item}
                />
              ))
          }
        </SelectableGroup>
      </div>
    );
  }
}

danilvalov avatar Nov 06 '17 15:11 danilvalov

I'll reopen the issue again. Maybe someone has the best solution.

danilvalov avatar Nov 06 '17 15:11 danilvalov

is this auto scrolling feature added to master?

dev-kanishk avatar Jan 22 '21 12:01 dev-kanishk