<commit_before>import numeral from 'numeral'
import React, { PureComponent } from 'react'
import { styled } from 'rt-theme'
import { convertNotionalShorthandToNumericValue } from './utils'

const NUMERAL_FORMAT = '0,000,000[.]00'
const ENTER = 'Enter'
const CHAR_CODE_DOT = 46
const CHAR_CODE_0 = 48
const CHAR_CODE_9 = 57
const CHAR_CODE_UNIT_SEP = 31
const SHORTCUT_CHAR_CODES = [75, 77, 107, 109]
const MAX_NOTIONAL_VALUE = 1000000000
export const DEFAULT_NOTIONAL_VALUE = '1000000'

const CurrencyPairSymbol = styled('span')`
  opacity: 0.59;
  font-size: 0.625rem;
  line-height: 1.2rem;
  padding-right: 0.375rem;
`

const InputWrapper = styled.div`
  display: flex;
`

export const Input = styled.input<{ inputIsValid: boolean }>`
  background: none;
  text-align: center;
  outline: none;
  border: none;
  font-size: 0.75rem;
  width: 85px;
  transition: box-shadow 0.2s ease;
  padding: 2px 0;
  ${({ inputIsValid, theme }) =>
    inputIsValid
      ? `
  .spot-tile:hover & {
    box-shadow: 0px 1px 0px ${theme.core.textColor};
  }

  .spot-tile:hover &:focus,
  &:focus {
    box-shadow: 0px 1px 0px ${theme.template.blue.normal};
  }
  `
      : `
  box-shadow: 0px 1px 0px ${theme.template.red.normal}
  `};
`

interface Props {
  currencyPairSymbol: string
  notional: string
  updateNotional: (notional: string) => void
}

export default class NotionalInput extends PureComponent<Props> {
  private inputRef = React.createRef<HTMLInputElement>()

  state = {
    inputIsValid: true,
  }

  render() {
    const { currencyPairSymbol, notional } = this.props
    const { inputIsValid } = this.state
    const formattedSize = numeral(notional).format(NUMERAL_FORMAT)
    return (
      <InputWrapper>
        <CurrencyPairSymbol>{currencyPairSymbol}</CurrencyPairSymbol>
        <Input
          type="text"
          ref={this.inputRef}
          defaultValue={formattedSize}
          onFocus={this.handleFocus}
          onChange={this.handleInputChange}
          onBlur={this.handleUpdateCausedByEvent}
          onKeyPress={this.handleKeyPressNotionalInput}
          inputIsValid={inputIsValid}
        />
      </InputWrapper>
    )
  }

  handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.select()
  }

  handleKeyPressNotionalInput = (event: React.KeyboardEvent<HTMLInputElement>) => {
    const charCode = event.charCode

    if (event.key === ENTER) {
      this.handleUpdateCausedByEvent(event)
    } else if (charCode === CHAR_CODE_DOT) {
      // only allow one dot
      if (event.currentTarget.value.match(/\./g)) {
        event.nativeEvent.stopImmediatePropagation()
        event.preventDefault()
      }
    } else if (!this.inputIsAllowed(charCode)) {
      event.nativeEvent.stopImmediatePropagation()
      event.preventDefault()
    }
  }

  handleInputChange = (event: React.FormEvent<HTMLInputElement>) => {
    // If user deletes all chars, set value back to DEFAULT_NOTIONAL_VALUE instead of empty string
    let value = event.currentTarget.value.trim() || DEFAULT_NOTIONAL_VALUE
    const numericValue = convertNotionalShorthandToNumericValue(value)
    if (numericValue >= MAX_NOTIONAL_VALUE) {
      value = DEFAULT_NOTIONAL_VALUE
      this.triggerInputWarningMessage()
    }

    if (!isNaN(numericValue)) {
      // user may be trying to enter decimals. In that case, format and update only when completed.
      const lastTwoChars = value.substr(-2)
      if (lastTwoChars.indexOf('.') === -1) {
        // propagate change back to dom node's value
        this.formatAndUpdateValue(value)<commit_msg>Improving handling of the dot when replacing a text selection in the input field.<commit_after>import numeral from 'numeral'
import React, { PureComponent } from 'react'
import { styled } from 'rt-theme'
import { convertNotionalShorthandToNumericValue } from './utils'

const NUMERAL_FORMAT = '0,000,000[.]00'
const ENTER = 'Enter'
const CHAR_CODE_DOT = 46
const CHAR_CODE_0 = 48
const CHAR_CODE_9 = 57
const CHAR_CODE_UNIT_SEP = 31
const SHORTCUT_CHAR_CODES = [75, 77, 107, 109]
const MAX_NOTIONAL_VALUE = 1000000000
export const DEFAULT_NOTIONAL_VALUE = '1000000'

const CurrencyPairSymbol = styled('span')`
  opacity: 0.59;
  font-size: 0.625rem;
  line-height: 1.2rem;
  padding-right: 0.375rem;
`

const InputWrapper = styled.div`
  display: flex;
`

export const Input = styled.input<{ inputIsValid: boolean }>`
  background: none;
  text-align: center;
  outline: none;
  border: none;
  font-size: 0.75rem;
  width: 85px;
  transition: box-shadow 0.2s ease;
  padding: 2px 0;
  ${({ inputIsValid, theme }) =>
    inputIsValid
      ? `
  .spot-tile:hover & {
    box-shadow: 0px 1px 0px ${theme.core.textColor};
  }

  .spot-tile:hover &:focus,
  &:focus {
    box-shadow: 0px 1px 0px ${theme.template.blue.normal};
  }
  `
      : `
  box-shadow: 0px 1px 0px ${theme.template.red.normal}
  `};
`

interface Props {
  currencyPairSymbol: string
  notional: string
  updateNotional: (notional: string) => void
}

export default class NotionalInput extends PureComponent<Props> {
  private inputRef = React.createRef<HTMLInputElement>()

  state = {
    inputIsValid: true,
  }

  render() {
    const { currencyPairSymbol, notional } = this.props
    const { inputIsValid } = this.state
    const formattedSize = numeral(notional).format(NUMERAL_FORMAT)
    return (
      <InputWrapper>
        <CurrencyPairSymbol>{currencyPairSymbol}</CurrencyPairSymbol>
        <Input
          type="text"
          ref={this.inputRef}
          defaultValue={formattedSize}
          onFocus={this.handleFocus}
          onChange={this.handleInputChange}
          onBlur={this.handleUpdateCausedByEvent}
          onKeyPress={this.handleKeyPressNotionalInput}
          inputIsValid={inputIsValid}
        />
      </InputWrapper>
    )
  }

  handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.select()
  }

  handleKeyPressNotionalInput = (event: React.KeyboardEvent<HTMLInputElement>) => {
    const { charCode, currentTarget, nativeEvent } = event
    if (event.key === ENTER) {
      this.handleUpdateCausedByEvent(event)
    } else if (charCode === CHAR_CODE_DOT) {
      // only allow one dot unless the existing dot is in the text selection while replacing existing text.
      const { value, selectionStart, selectionEnd } = currentTarget
      const textWithoutSelection = value.replace(value.substring(selectionStart, selectionEnd), '')
      if (textWithoutSelection.match(/\./g)) {
        nativeEvent.stopImmediatePropagation()
        event.preventDefault()
      }
    } else if (!this.inputIsAllowed(charCode)) {
      nativeEvent.stopImmediatePropagation()
      event.preventDefault()
    }
  }

  handleInputChange = (event: React.FormEvent<HTMLInputElement>) => {
    // If user deletes all chars, set value back to DEFAULT_NOTIONAL_VALUE instead of empty string
    let value = event.currentTarget.value.trim() || DEFAULT_NOTIONAL_VALUE
    const numericValue = convertNotionalShorthandToNumericValue(value)
    if (numericValue >= MAX_NOTIONAL_VALUE) {
      value = DEFAULT_NOTIONAL_VALUE
      this.triggerInputWarningMessage()
    }

    if (!isNaN(numericValue)) {
      // user may be trying to enter decimals. In that case, format and update only when completed.
      const lastTwoChars = value.substr(-2)
      if (lastTwoChars.indexOf('.') === -1) {
        // propagate change back to dom node's value
        this.formatAndUpdateValue(value)
{'fraction_non_alphanumeric': 0.10998262728852065, 'fraction_numerical': 0.021114526259521583, 'mean_word_length': 3.3997648442092885, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, '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': '2369484', 'n_tokens_mistral': 2484, 'n_tokens_neox': 2334, 'n_words': 536}