<commit_before>// @flow

import React from 'react';
import { merge, set as timmSet } from 'timm';
import {
  KEYS,
  UNICODE,
  NULL_STRING,
  IS_IOS,
  IS_MOBILE_OR_TABLET,
} from '../gral/constants';
import {
  flexContainer,
  flexItem,
  GLOW,
  inputReset,
  INPUT_DISABLED,
} from '../gral/styles';
import {
  createShortcut,
  registerShortcut,
  unregisterShortcut,
} from '../gral/keys';
import type { Choice, KeyboardEventPars } from '../gral/types';
import { isAncestorNode, memoize } from '../gral/helpers';
import type { Theme } from '../gral/themeContext';
import Input from '../hocs/input';
import type { InputHocPublicProps } from '../hocs/input';
import { ListPicker, LIST_SEPARATOR_KEY } from './listPicker';
import IosFloatWrapper from './iosFloatWrapper';
import { floatAdd, floatDelete, floatUpdate } from '../components/floats';
import Icon from '../components/icon';
import type { SelectProps } from './selectTypes';

const LIST_SEPARATOR = {
  value: LIST_SEPARATOR_KEY,
  label: LIST_SEPARATOR_KEY,
};

const toInternalValue = val =>
  val != null ? JSON.stringify(val) : NULL_STRING;
const toExternalValue = val => {
  if (val === NULL_STRING) return null;
  try {
    return JSON.parse(val);
  } catch (err) {
    // eslint-disable-next-line no-console
    console.warn('SelectCustom: error parsing JSON', val);
    return null;
  }
};
const isNull = val => val === NULL_STRING;

const MANAGE_FOCUS_AUTONOMOUSLY = IS_MOBILE_OR_TABLET;

// ==========================================
// Declarations
// ==========================================
type PublicProps = {
  ...$Exact<InputHocPublicProps>, // common to all inputs (check the docs!)
  ...$Exact<SelectProps>,
  inlinePicker?: boolean,
  id?: string,
};

type Props = {
  ...$Exact<PublicProps>,
  // Input HOC
  theme: Theme,
  curValue: string,
  onChange: Function,
  registerOuterRef: Function,
  fFocused: boolean,
  onFocus: Function,
  onBlur: Function,
};

// ==========================================
// Component
// ==========================================
class BaseSelectCustom extends React.Component<Props> {
  fInit = false;
  prevExtFocused: boolean;
  fFloat = false;
  floatId: ?string;
  items: Array<Choice>;
  refTitle: ?Object;

  refPicker: any = React.createRef();

  componentDidMount() {
    window.addEventListener('click', this.onClickWindow);
    this.registerShortcuts();
  }

  componentWillUnmount() {
    if (this.floatId != null) floatDelete(this.floatId);
    window.removeEventListener('click', this.onClickWindow);
    this.unregisterShortcuts();
  }

  componentDidUpdate() {
    this.renderFloat();
  }

  // ==========================================
  // Imperative API
  // ==========================================
  // Process ESC ourselves; pass down other keystrokes
  doKeyDown(keyDown: KeyboardEventPars) {
    if (keyDown.which === KEYS.esc && !this.props.inlinePicker) {
      this.fFloat = !this.fFloat;
      this.forceUpdate();
    } else {
      const target = this.refPicker.current;
      if (target && target.doKeyDown) target.doKeyDown(keyDown);
    }
  }

  // ==========================================
  prepareRender() {
    this.items = this.prepareItems(this.props.items, this.props.required);

    // Only when props change
    const { fFocused } = this.props;
    if (!this.fInit || fFocused !== this.prevExtFocused) {
      this.prevExtFocused = fFocused;
      this.fFloat = fFocused;
    }
    this.fInit = true;
  }

  // ==========================================
  render() {
    this.prepareRender();
    const { children } = this.props;
    let out;
    if (this.props.inlinePicker) {
      out = this.renderPicker();
    } else if (children) {
      out = this.renderProvidedTitle(children);
    } else {
      out = this.renderDefaultTitle();
    }
    return out;
  }

  renderProvidedTitle(children) {
    const elTitle = React.cloneElement(children, {
      ref: this.registerTitleRef,
      onClick: this.onClickTitle,
    });
    return IS_IOS ? (
      <span style={style.providedTitleWrapperForIos}>
        {elTitle}
        {this.renderFloatForIos()}
      </span>
    ) : (
      elTitle
    );
  }

  renderDefaultTitle() {
    const { curValue, lang } = this.props;
    let label = UNICODE.nbsp;
    if (curValue !== NULL_STRING) {
      const item = this.items.find(o => o.value === curValue);
      if (item) {
        label =
          typeof item.label === 'function' ? item.label(lang) : item.label;
      }
    }
    const caretIcon = this.fFloat ? 'caret-up' : 'caret-down';
    return (
      <span
        ref={this.registerTitleRef}
        className="giu-select-custom"
        onMouseDown={this.onMouseDownTitle}
        onClick={this.onClickTitle}
        style={style.title(this.props)}
      >
        <span style={style.titleText}>{label}</span>
        <Icon icon={caretIcon} skipTheme style={style.caret(this.props)} />
        {IS_IOS && this.renderFloatForIos()}
      </span>
    );
  }

  renderFloat() {
    if (this.props.inlinePicker) return;
    if (IS_IOS) return;
    const { fFloat } = this;

    // Remove float
    // if (!fFloat && this.floatId != null) {
    //   floatDelete(this.floatId);
    //   this.floatId = null;
    //   this.props.onCloseFloat && this.props.onCloseFloat();
    //   return;
    // }

    // Create or update float
    if (fFloat) {
      const { floatPosition, floatAlign } = this.props;
      const floatOptions = {
        position: floatPosition,
        align: floatAlign,
        limitSize: true,
        getAnchorNode: () => this.refTitle,
        children: this.renderPicker(),
      };
      if (this.floatId == null) {<commit_msg>Minor<commit_after>// @flow

import React from 'react';
import { merge, set as timmSet } from 'timm';
import {
  KEYS,
  UNICODE,
  NULL_STRING,
  IS_IOS,
  IS_MOBILE_OR_TABLET,
} from '../gral/constants';
import {
  flexContainer,
  flexItem,
  GLOW,
  inputReset,
  INPUT_DISABLED,
} from '../gral/styles';
import {
  createShortcut,
  registerShortcut,
  unregisterShortcut,
} from '../gral/keys';
import type { Choice, KeyboardEventPars } from '../gral/types';
import { isAncestorNode, memoize } from '../gral/helpers';
import type { Theme } from '../gral/themeContext';
import Input from '../hocs/input';
import type { InputHocPublicProps } from '../hocs/input';
import { ListPicker, LIST_SEPARATOR_KEY } from './listPicker';
import IosFloatWrapper from './iosFloatWrapper';
import { floatAdd, floatDelete, floatUpdate } from '../components/floats';
import Icon from '../components/icon';
import type { SelectProps } from './selectTypes';

const LIST_SEPARATOR = {
  value: LIST_SEPARATOR_KEY,
  label: LIST_SEPARATOR_KEY,
};

const toInternalValue = val =>
  val != null ? JSON.stringify(val) : NULL_STRING;
const toExternalValue = val => {
  if (val === NULL_STRING) return null;
  try {
    return JSON.parse(val);
  } catch (err) {
    // eslint-disable-next-line no-console
    console.warn('SelectCustom: error parsing JSON', val);
    return null;
  }
};
const isNull = val => val === NULL_STRING;

const MANAGE_FOCUS_AUTONOMOUSLY = IS_MOBILE_OR_TABLET;

// ==========================================
// Declarations
// ==========================================
type PublicProps = {
  ...$Exact<InputHocPublicProps>, // common to all inputs (check the docs!)
  ...$Exact<SelectProps>,
  inlinePicker?: boolean,
  id?: string,
};

type Props = {
  ...$Exact<PublicProps>,
  // Input HOC
  theme: Theme,
  curValue: string,
  onChange: Function,
  registerOuterRef: Function,
  fFocused: boolean,
  onFocus: Function,
  onBlur: Function,
};

// ==========================================
// Component
// ==========================================
class BaseSelectCustom extends React.Component<Props> {
  fInit = false;
  prevExtFocused: boolean;
  fFloat = false;
  floatId: ?string;
  items: Array<Choice>;
  refTitle: ?Object;

  refPicker: any = React.createRef();

  componentDidMount() {
    window.addEventListener('click', this.onClickWindow);
    this.registerShortcuts();
  }

  componentWillUnmount() {
    if (this.floatId != null) floatDelete(this.floatId);
    window.removeEventListener('click', this.onClickWindow);
    this.unregisterShortcuts();
  }

  componentDidUpdate() {
    this.renderFloat();
  }

  // ==========================================
  // Imperative API
  // ==========================================
  // Process ESC ourselves; pass down other keystrokes
  doKeyDown(keyDown: KeyboardEventPars) {
    if (keyDown.which === KEYS.esc && !this.props.inlinePicker) {
      this.fFloat = !this.fFloat;
      this.forceUpdate();
    } else {
      const target = this.refPicker.current;
      if (target && target.doKeyDown) target.doKeyDown(keyDown);
    }
  }

  // ==========================================
  prepareRender() {
    this.items = this.prepareItems(this.props.items, this.props.required);

    // Only when props change
    const { fFocused } = this.props;
    if (!this.fInit || fFocused !== this.prevExtFocused) {
      this.prevExtFocused = fFocused;
      this.fFloat = fFocused;
    }
    this.fInit = true;
  }

  // ==========================================
  render() {
    this.prepareRender();
    const { children } = this.props;
    let out;
    if (this.props.inlinePicker) {
      out = this.renderPicker();
    } else if (children) {
      out = this.renderProvidedTitle(children);
    } else {
      out = this.renderDefaultTitle();
    }
    return out;
  }

  renderProvidedTitle(children) {
    const elTitle = React.cloneElement(children, {
      ref: this.registerTitleRef,
      onClick: this.onClickTitle,
    });
    return IS_IOS ? (
      <span style={style.providedTitleWrapperForIos}>
        {elTitle}
        {this.renderFloatForIos()}
      </span>
    ) : (
      elTitle
    );
  }

  renderDefaultTitle() {
    const { curValue, lang } = this.props;
    let label = UNICODE.nbsp;
    if (curValue !== NULL_STRING) {
      const item = this.items.find(o => o.value === curValue);
      if (item) {
        label =
          typeof item.label === 'function' ? item.label(lang) : item.label;
      }
    }
    const caretIcon = this.fFloat ? 'caret-up' : 'caret-down';
    return (
      <span
        ref={this.registerTitleRef}
        className="giu-select-custom"
        onMouseDown={this.onMouseDownTitle}
        onClick={this.onClickTitle}
        style={style.title(this.props)}
      >
        <span style={style.titleText}>{label}</span>
        <Icon icon={caretIcon} skipTheme style={style.caret(this.props)} />
        {IS_IOS && this.renderFloatForIos()}
      </span>
    );
  }

  renderFloat() {
    if (this.props.inlinePicker) return;
    if (IS_IOS) return;
    const { fFloat } = this;

    // Remove float
    if (!fFloat && this.floatId != null) {
      floatDelete(this.floatId);
      this.floatId = null;
      this.props.onCloseFloat && this.props.onCloseFloat();
      return;
    }

    // Create or update float
    if (fFloat) {
      const { floatPosition, floatAlign } = this.props;
      const floatOptions = {
        position: floatPosition,
        align: floatAlign,
        limitSize: true,
        getAnchorNode: () => this.refTitle,
        children: this.renderPicker(),
      };
      if (this.floatId == null) {
{'fraction_non_alphanumeric': 0.19021547156481808, 'fraction_numerical': 0.0, 'mean_word_length': 3.081081081081081, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 37, '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': '7645662', 'n_tokens_mistral': 3592, 'n_tokens_neox': 3525, 'n_words': 744}