<commit_before>import * as React from "react";
import PropTypes from "prop-types";
import { compose } from "recompose";
import debounce from "lodash.debounce";
import disabledTooltip, {
  disabledTooltipProps
} from "@crave/farmblocks-hoc-disabled-tooltip";
import formInput, { formInputProps } from "@crave/farmblocks-hoc-input";
import withMessages, {
  withMessagesProps
} from "@crave/farmblocks-hoc-validation-messages";
import {
  DropdownMenuWrapper,
  DropdownItemWrapper
} from "@crave/farmblocks-dropdown";

import DropdownWrapper from "./styledComponents/DropdownWrapper";
import ScrollBox from "./ScrollBox";

const EnhancedInput = compose(disabledTooltip, withMessages, formInput)(
  "input"
);
EnhancedInput.displayName = "EnhancedInput";

class SearchField extends React.Component {
  state = {
    highlightedIndex: -1
  };

  debouncedOnChange = debounce(this.props.onChange, this.props.debounceDelay);
  onChange = event => {
    // retain synthetic events for async use.
    // https://reactjs.org/docs/events.html#event-pooling
    event && event.persist && event.persist();

    this.debouncedOnChange(event);
  };

  componentDidUpdate = prevProps => {
    if (
      prevProps.debounceDelay !== this.props.debounceDelay ||
      prevProps.onChange !== this.props.onChange
    ) {
      this.debouncedOnChange && this.debouncedOnChange.cancel();
      this.debouncedOnChange = debounce(
        this.props.onChange,
        this.props.debounceDelay
      );
    }
  };

  componentWillUnmount = () => {
    this.debouncedOnChange.cancel(); // Avoid a debounced event to trigger after the component is unmounted
  };

  changeHighlight = modifier => {
    this.setState(prevState => {
      const highlightedIndex = Math.max(
        Math.min(
          prevState.highlightedIndex + modifier,
          this.props.items.length - 1
        ),
        -1
      );
      this.scroller && this.scroller.centerChildByIndex(highlightedIndex);
      return {
        highlightedIndex
      };
    });
  };

  onKeyDown = event => {
    const { key } = event;
    switch (key) {
      case "Enter":
        this.debouncedOnChange.flush();
        break;
      case "ArrowUp":
        event.preventDefault();
        this.changeHighlight(-1);
        break;
      case "ArrowDown":
        event.preventDefault();
        this.changeHighlight(1);
        break;
    }
  };

  _renderItem = (item, highlighted) => (
    <DropdownItemWrapper key={item.value} highlighted={highlighted}>
      <div>{item.label}</div>
    </DropdownItemWrapper>
  );

  render() {
    const {
      width,
      onChange,
      debounceDelay,
      maxMenuHeight,
      items,
      onScrollReachEnd,
      ...inputProps
    } = this.props;
    return (
      <DropdownWrapper style={{ width }}>
        <EnhancedInput
          onChange={this.onChange}
          type="search"
          clearIcon="wg-edit"
          displayBlock
          onKeyDown={this.onKeyDown}
          {...inputProps}
        />
        {!this.props.disabled &&
          items && (
            <DropdownMenuWrapper>
              <ScrollBox
                maxHeight={maxMenuHeight}
                onReachEnd={onScrollReachEnd}
                ref={node => (this.scroller = node)}
              >
                {items.map((item, index) => {
                  return this._renderItem(
                    item,
                    index === this.state.highlightedIndex
                  );
                })}
              </ScrollBox>
            </DropdownMenuWrapper>
          )}
      </DropdownWrapper><commit_msg>feat(search-field): show/hide menu with input focus<commit_after>import * as React from "react";
import PropTypes from "prop-types";
import { compose } from "recompose";
import debounce from "lodash.debounce";
import disabledTooltip, {
  disabledTooltipProps
} from "@crave/farmblocks-hoc-disabled-tooltip";
import formInput, { formInputProps } from "@crave/farmblocks-hoc-input";
import withMessages, {
  withMessagesProps
} from "@crave/farmblocks-hoc-validation-messages";
import {
  DropdownMenuWrapper,
  DropdownItemWrapper
} from "@crave/farmblocks-dropdown";

import DropdownWrapper from "./styledComponents/DropdownWrapper";
import ScrollBox from "./ScrollBox";

const EnhancedInput = compose(disabledTooltip, withMessages, formInput)(
  "input"
);
EnhancedInput.displayName = "EnhancedInput";

class SearchField extends React.Component {
  state = {
    highlightedIndex: -1,
    focused: false
  };

  debouncedOnChange = debounce(this.props.onChange, this.props.debounceDelay);
  onChange = event => {
    // retain synthetic events for async use.
    // https://reactjs.org/docs/events.html#event-pooling
    event && event.persist && event.persist();

    this.debouncedOnChange(event);
  };

  componentDidUpdate = prevProps => {
    if (
      prevProps.debounceDelay !== this.props.debounceDelay ||
      prevProps.onChange !== this.props.onChange
    ) {
      this.debouncedOnChange && this.debouncedOnChange.cancel();
      this.debouncedOnChange = debounce(
        this.props.onChange,
        this.props.debounceDelay
      );
    }
  };

  componentWillUnmount = () => {
    this.debouncedOnChange.cancel();
  };

  changeHighlight = modifier => {
    this.setState(prevState => {
      const highlightedIndex = Math.max(
        Math.min(
          prevState.highlightedIndex + modifier,
          this.props.items.length - 1
        ),
        -1
      );
      this.scroller && this.scroller.centerChildByIndex(highlightedIndex);
      return {
        highlightedIndex
      };
    });
  };

  onKeyDown = event => {
    const { key } = event;
    switch (key) {
      case "Enter":
        this.debouncedOnChange.flush();
        break;
      case "ArrowUp":
        event.preventDefault();
        this.changeHighlight(-1);
        break;
      case "ArrowDown":
        event.preventDefault();
        this.changeHighlight(1);
        break;
    }
  };

  onFocus = () => this.setState({ focused: true });
  onBlur = () => this.setState({ focused: false });
  preventBlur = event => {
    event.preventDefault();
  };

  _renderItem = (item, highlighted) => (
    <DropdownItemWrapper key={item.value} highlighted={highlighted}>
      <div>{item.label}</div>
    </DropdownItemWrapper>
  );

  render() {
    const {
      width,
      onChange,
      debounceDelay,
      maxMenuHeight,
      items,
      onScrollReachEnd,
      ...inputProps
    } = this.props;
    return (
      <DropdownWrapper style={{ width }}>
        <EnhancedInput
          onChange={this.onChange}
          type="search"
          clearIcon="wg-edit"
          displayBlock
          onKeyDown={this.onKeyDown}
          onFocus={this.onFocus}
          onBlur={this.onBlur}
          innerRef={node => (this.input = node)}
          {...inputProps}
        />
        {!this.props.disabled &&
          this.state.focused &&
          items && (
            <DropdownMenuWrapper onMouseDown={this.preventBlur}>
              <ScrollBox
                maxHeight={maxMenuHeight}
                onReachEnd={onScrollReachEnd}
                ref={node => (this.scroller = node)}
              >
                {items.map((item, index) => {
                  return this._renderItem(
                    item,
                    index === this.state.highlightedIndex
                  );
                })}
              </ScrollBox>
            </DropdownMenuWrapper>
          )}
      </DropdownWrapper>
{'fraction_non_alphanumeric': 0.12090545938748336, 'fraction_numerical': 0.0013315579227696406, 'mean_word_length': 2.2472978815391267, 'pattern_counts': {'":': 6, '<': 25, '<?xml version=': 0, '>': 47, 'https://': 2, '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': '3003649', 'n_tokens_mistral': 2207, 'n_tokens_neox': 2138, 'n_words': 387}