<commit_before>import styles from './DropdownLayout.scss';
import React from 'react';
import classNames from 'classnames';
import WixComponent from '../WixComponent';
import isEqual from 'lodash.isequal';
import isobject from 'lodash.isobject';
import trim from 'lodash.trim';
import isstring from 'lodash.isstring';
import has from 'lodash.has';

const modulu = (n, m) => {
  const remain = n % m;
  return remain >= 0 ? remain : remain + m;
};

const NOT_HOVERED_INDEX = -1;

class DropdownLayout extends WixComponent {

  constructor(props) {
    super(props);

    this.state = {
      hovered: NOT_HOVERED_INDEX,
    };

    this._onSelect = this._onSelect.bind(this);
    this._onMouseLeave = this._onMouseLeave.bind(this);
    this._onMouseEnter = this._onMouseEnter.bind(this);
    this._onKeyDown = this._onKeyDown.bind(this);
    this._onClose = this._onClose.bind(this);
    this.onClickOutside = this.onClickOutside.bind(this);
  }

  isLegalOption(option) {
    return isobject(option) && has(option, 'id') && trim(option.id).length > 0 &&
        has(option, 'value') && (React.isValidElement(option.value) || (isstring(option.value) && trim(option.value).length > 0));
  }

  onClickOutside() {
    const {visible, onClickOutside} = this.props;
    if (visible && onClickOutside) {
      onClickOutside();
    }
  }

  _onSelect(index) {
    const {options, onSelect, selectedId} = this.props;
    if (index >= 0 && index < options.length) {
      const newSelectedId = options[index].id;
      if (newSelectedId !== selectedId && onSelect) {
        onSelect(options[index]);
        return true;
      }
    }
    return false;
  }

  _onMouseEnter(index) {
    if (this.isSelectableOption(this.props.options[index])) {
      this.setState({hovered: index});
    }
  }

  _onMouseLeave() {
    this.setState({
      hovered: NOT_HOVERED_INDEX
    });
  }

  hoverNextStep(step) {
    const {options} = this.props;

    if (!options.some(this.isSelectableOption)) {
      return;
    }

    let newHovered = this.state.hovered;
    do {
      newHovered = Math.abs(modulu(Math.max(newHovered + step, -1), options.length));
    } while (!this.isSelectableOption(options[newHovered]));

    this.setState({hovered: newHovered});
    this.options.scrollTop = (newHovered - 2) * parseInt(styles.option_height);
  }

  _onKeyDown(event) {
    if (!this.props.visible) {
      return false;
    }

    switch (event.key) {
      case 'ArrowDown': {
        this.hoverNextStep(1);
        break;
      }

      case 'ArrowUp': {
        this.hoverNextStep(-1);
        break;
      }

      case 'Enter': {
        if (!this._onSelect(this.state.hovered)) {
          return false;
        }
        break;
      }

      case 'Tab': {
        this._onSelect(this.state.hovered);
        return true;
      }

      case 'Escape': {
        this._onClose();
        break;
      }

      default: {
        return false;<commit_msg>fixed bad behaviour when pressing select while hovered on disabled o… (#243)<commit_after>import styles from './DropdownLayout.scss';
import React from 'react';
import classNames from 'classnames';
import WixComponent from '../WixComponent';
import isEqual from 'lodash.isequal';
import isobject from 'lodash.isobject';
import trim from 'lodash.trim';
import isstring from 'lodash.isstring';
import has from 'lodash.has';

const modulu = (n, m) => {
  const remain = n % m;
  return remain >= 0 ? remain : remain + m;
};

const NOT_HOVERED_INDEX = -1;

class DropdownLayout extends WixComponent {

  constructor(props) {
    super(props);

    this.state = {
      hovered: NOT_HOVERED_INDEX,
    };

    this._onSelect = this._onSelect.bind(this);
    this._onMouseLeave = this._onMouseLeave.bind(this);
    this._onMouseEnter = this._onMouseEnter.bind(this);
    this._onKeyDown = this._onKeyDown.bind(this);
    this._onClose = this._onClose.bind(this);
    this.onClickOutside = this.onClickOutside.bind(this);
  }

  isLegalOption(option) {
    return isobject(option) && has(option, 'id') && trim(option.id).length > 0 &&
        has(option, 'value') && (React.isValidElement(option.value) || (isstring(option.value) && trim(option.value).length > 0));
  }

  onClickOutside() {
    const {visible, onClickOutside} = this.props;
    if (visible && onClickOutside) {
      onClickOutside();
    }
  }

  _onSelect(index) {
    const {options, onSelect, selectedId} = this.props;
    if (index >= 0 && index < options.length) {
      const newSelectedId = options[index].id;
      if (newSelectedId !== selectedId && onSelect) {
        onSelect(options[index]);
        return true;
      }
    }
    return false;
  }

  _onMouseEnter(index) {
    if (this.isSelectableOption(this.props.options[index])) {
      this.setState({hovered: index});
    }
  }

  _onMouseLeave() {
    this.setState({
      hovered: NOT_HOVERED_INDEX
    });
  }

  hoverNextStep(step) {
    const {options} = this.props;

    if (!options.some(this.isSelectableOption)) {
      return;
    }

    let newHovered = this.state.hovered;
    do {
      newHovered = Math.abs(modulu(Math.max(newHovered + step, -1), options.length));
    } while (!this.isSelectableOption(options[newHovered]));

    this.setState({hovered: newHovered});
    this.options.scrollTop = (newHovered - 2) * parseInt(styles.option_height);
  }

  _onKeyDown(event) {
    if (!this.props.visible) {
      return false;
    }

    switch (event.key) {
      case 'ArrowDown': {
        this.hoverNextStep(1);
        break;
      }

      case 'ArrowUp': {
        this.hoverNextStep(-1);
        break;
      }

      case 'Enter': {
        if (!this._onSelect(this.state.hovered)) {
          return false;
        }
        break;
      }

      case 'Tab': {
        return this._onSelect(this.state.hovered);
      }

      case 'Escape': {
        this._onClose();
        break;
      }

      default: {
        return false;
{'fraction_non_alphanumeric': 0.13808801213960548, 'fraction_numerical': 0.0035407182599898838, 'mean_word_length': 2.752055660974067, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 13, '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': '6596311', 'n_tokens_mistral': 1981, 'n_tokens_neox': 1911, 'n_words': 381}