.display-4
  font-size: $headings.h1.size !important
  font-weight: $headings.h1.weight
  line-height: $headings.h1.line-height !important
  letter-spacing: $headings.h1.letter-spacing !important
  font-family: $headings.h1.font-family !important

.display-3
  font-size: $headings.h2.size !important
  font-weight: $headings.h2.weight
  line-height: $headings.h2.line-height !important
  letter-spacing: $headings.h2.letter-spacing !important
  font-family: $headings.h2.font-family !important

.display-2
  font-size: $headings.h3.size !important
  font-weight: $headings.h3.weight
  line-height: $headings.h3.line-height !important
  letter-spacing: $headings.h3.letter-spacing !important
  font-family: $headings.h3.font-family !important

.display-1
  font-size: $headings.h4.size !important
  font-weight: $headings.h4.weight
  line-height: $headings.h4.line-height !important
  letter-spacing: $headings.h4.letter-spacing !important
  font-family: $headings.h4.font-family !important

.headline
  font-size: $headings.h5.size !important
  font-weight: $headings.h5.weight
  line-height: $headings.h5.line-height !important
  letter-spacing: $headings.h5.letter-spacing !important
  font-family: $headings.h5.font-family !important

.title
  font-size: $headings.h6.size !important
  font-weight: $headings.h6.weight
  line-height: $headings.h6.line-height !important
  letter-spacing: $headings.h6.letter-spacing !important
  font-family: $headings.h6.font-family !important

.subheading
  font-size: $headings.subheading.size !important
  font-weight: $headings.subheading.weight

.body-2
  font-size: $headings.body-2.size !important
  font-weight: $headings.body-2.weight

.body-1
  font-size: $headings.body-1.size !important
  font-weight: $headings.body-1.weight

.caption
  font-size: $headings.caption.size !important
  font-weight: $headings.caption.weight

.v-btn
  font-size: $headings.button.size
  font-weight: $headings.button.weight

p
  margin-bottom: $spacers.three.y


theme: { isDark: this.$vuetify.dark || this.dark }

keep keep keep keep replace replace replace replace replace replace replace replace replace keep keep keep keep keep

let dialog = h('div', data, this.showLazyContent(this.$slots.default))

export default {
  dataIterator: {
    rowsPerPageText: 'Записей на странице:',
    rowsPerPageAll: 'Все',
    pageText: '{0}-{1} из {2}',
    noResultsText: 'Не найдено подходящих записей',
    nextPage: 'Следующая страница',
    prevPage: 'Предыдущая страница'
  },
  dataTable: {
    rowsPerPageText: 'Строк на странице:'
  },
  noDataText: 'Отсутствуют данные'
}


export default {
  dataIterator: {
    rowsPerPageText: 'Items per page:',
    rowsPerPageAll: 'All',
    pageText: '{0}-{1} of {2}',
    noResultsText: 'No matching records found',
    nextPage: 'Next page',
    prevPage: 'Previous page'
  },
  dataTable: {
    rowsPerPageText: 'Rows per page:'
  },
  noDataText: 'No data available'
}


keep keep keep keep keep replace keep keep keep

import '../../stylus/components/_dialogs.styl'

// Mixins
import Dependent from '../../mixins/dependent'
import Detachable from '../../mixins/detachable'
import Overlayable from '../../mixins/overlayable'
import Returnable from '../../mixins/returnable'
import Stackable from '../../mixins/stackable'
import Toggleable from '../../mixins/toggleable'

// Directives
import ClickOutside from '../../directives/click-outside'

// Helpers
import { getZIndex, convertToUnit } from '../../util/helpers'
import SlotProvider from '../../util/SlotProvider'

/* @vue/component */
export default {
  name: 'v-dialog',

  directives: {
    ClickOutside
  },

  mixins: [
    Dependent,
    Detachable,
    Overlayable,
    Returnable,
    Stackable,
    Toggleable
  ],

  props: {
    disabled: Boolean,
    persistent: Boolean,
    fullscreen: Boolean,
    fullWidth: Boolean,
    noClickAnimation: Boolean,
    maxWidth: {
      type: [String, Number],
      default: 'none'
    },
    origin: {
      type: String,
      default: 'center center'
    },
    width: {
      type: [String, Number],
      default: 'auto'
    },
    scrollable: Boolean,
    transition: {
      type: [String, Boolean],
      default: 'dialog-transition'
    }
  },

  data () {
    return {
      animate: false,
      animateTimeout: null,
      stackClass: 'v-dialog__content--active',
      stackMinZIndex: 200
    }
  },

  computed: {
    classes () {
      return {
        [(`v-dialog ${this.contentClass}`).trim()]: true,
        'v-dialog--active': this.isActive,
        'v-dialog--persistent': this.persistent,
        'v-dialog--fullscreen': this.fullscreen,
        'v-dialog--scrollable': this.scrollable,
        'v-dialog--animated': this.animate
      }
    },
    contentClasses () {
      return {
        'v-dialog__content': true,
        'v-dialog__content--active': this.isActive
      }
    }
  },

  watch: {
    isActive (val) {
      if (val) {
        this.show()
      } else {
        this.removeOverlay()
        this.unbind()
      }
    }
  },

  mounted () {
    this.isBooted = this.isActive
    this.isActive && this.show()
  },

  beforeDestroy () {
    if (typeof window !== 'undefined') this.unbind()
  },

  methods: {
    animateClick () {
      this.animate = false
      // Needed for when clicking very fast
      // outside of the dialog
      this.$nextTick(() => {
        this.animate = true
        clearTimeout(this.animateTimeout)
        this.animateTimeout = setTimeout(() => (this.animate = false), 150)
      })
    },
    closeConditional (e) {
      // If the dialog content contains
      // the click event, or if the
      // dialog is not active
      if (this.$refs.content.contains(e.target) ||
        !this.isActive
      ) return false

      // If we made it here, the click is outside
      // and is active. If persistent, and the
      // click is on the overlay, animate
      if (this.persistent) {
        if (!this.noClickAnimation &&
          this.overlay === e.target
        ) this.animateClick()

        return false
      }

      // close dialog if !persistent, clicked outside and we're the topmost dialog.
      // Since this should only be called in a capture event (bottom up), we shouldn't need to stop propagation
      return getZIndex(this.$refs.content) >= this.getMaxZIndex()
    },
    show () {
      !this.fullscreen && !this.hideOverlay && this.genOverlay()
      this.fullscreen && this.hideScroll()
      this.$refs.content.focus()
      this.$listeners.keydown && this.bind()
    },
    bind () {
      window.addEventListener('keydown', this.onKeydown)
    },
    unbind () {
      window.removeEventListener('keydown', this.onKeydown)
    },
    onKeydown (e) {
      this.$emit('keydown', e)
    }
  },

  render (h) {
    const children = []
    const data = {
      'class': this.classes,
      ref: 'dialog',
      directives: [
        {
          name: 'click-outside',
          value: () => (this.isActive = false),
          args: {
            closeConditional: this.closeConditional,
            include: this.getOpenDependentElements
          }
        },
        { name: 'show', value: this.isActive }
      ],
      on: {
        click: e => { e.stopPropagation() }
      }
    }

    if (!this.fullscreen) {
      data.style = {
        maxWidth: this.maxWidth === 'none' ? undefined : convertToUnit(this.maxWidth),
        width: this.width === 'auto' ? undefined : convertToUnit(this.width)
      }
    }

    if (this.$slots.activator) {
      children.push(h('div', {
        staticClass: 'v-dialog__activator',
        'class': {
          'v-dialog__activator--disabled': this.disabled
        },
        on: {
          click: e => {
            e.stopPropagation()
            if (!this.disabled) this.isActive = !this.isActive
          }
        }
      }, [this.$slots.activator]))
    }

    let dialog = h('div', data, [
      this.$createElement(SlotProvider, {
        props: {
          provide: {
            theme: { isDark: this.$vuetify.dark || this.dark }
          }
        }
      }, this.showLazyContent(this.$slots.default))
    ])
    if (this.transition) {
      dialog = h('transition', {
        props: {
          name: this.transition,
          origin: this.origin
        }
      }, [dialog])
    }

    children.push(h('div', {
      'class': this.contentClasses,
      attrs: {
        tabIndex: '-1',
        ...this.getScopeIdAttrs()
      },
      style: { zIndex: this.activeZIndex },
      ref: 'content'
    }, [dialog]))

    return h('div', {
      staticClass: 'v-dialog__container',
      style: {
        display: (!this.$slots.activator || this.fullWidth) ? 'block' : 'inline-block'
      }
    }, children)
  }
}


import VDatePickerDateTable from '@/components/VDatePicker/VDatePickerDateTable'
import { test } from '@/test'

test('VDatePickerDateTable.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component and match snapshot for multiple selection', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        multiple: true,
        selectedDates: ['2005-11-03', '2005-11-05', '2005-11-08'],
        value: '2005-11-03'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with events (array) and match snapshot', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03',
        events: ['2005-05-03'],
        eventColor: 'red'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with events (function) and match snapshot', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03',
        events: date => date === '2005-05-03',
        eventColor: 'red'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with events colored by object and match snapshot', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03',
        events: ['2005-05-03', '2005-05-04'],
        eventColor: {'2005-05-03': 'red', '2005-05-04': 'blue lighten-1'}
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with events colored by function and match snapshot', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03',
        events: ['2005-05-03', '2005-05-04'],
        eventColor: date => ({'2005-05-03': 'red'}[date])
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should match snapshot with first day of week', function () {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03',
        firstDayOfWeek: 2
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should watch tableDate value and run transition', async () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03'
      }
    })

    wrapper.setProps({
      tableDate: '2005-06'
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.find('table')[0].element.className).toBe('tab-transition-enter tab-transition-enter-active')
  })

  it('should watch tableDate value and run reverse transition', async () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03'
      }
    })

    wrapper.setProps({
      tableDate: '2005-04'
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.find('table')[0].element.className).toBe('tab-reverse-transition-enter tab-reverse-transition-enter-active')
  })

  it('should emit event when date button is clicked', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03'
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)

    wrapper.find('tbody button')[0].trigger('click')
    expect(input).toBeCalledWith('2005-05-01')
  })

  it('should not emit event when disabled month button is clicked', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        current: '2005-07',
        value: '2005-11-03',
        allowedDates: () => false
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)

    wrapper.find('tbody button')[0].trigger('click')
    expect(input).not.toBeCalled()
  })

  it('should emit tableDate event when scrolled and scrollable', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05',
        scrollable: true
      }
    })

    const tableDate = jest.fn()
    wrapper.vm.$on('tableDate', tableDate)

    wrapper.trigger('wheel')
    expect(tableDate).toBeCalledWith('2005-06')
  })

  it('should not emit tableDate event when scrolled and not scrollable', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05'
      }
    })

    const tableDate = jest.fn()
    wrapper.vm.$on('tableDate', tableDate)

    wrapper.trigger('wheel')
    expect(tableDate).not.toBeCalled()
  })

  // TODO
  it.skip('should emit tableDate event when swiped', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05'
      }
    })

    const tableDate = jest.fn()
    wrapper.vm.$on('tableDate', tableDate)

    wrapper.trigger('touchstart')
    wrapper.trigger('touchend')
    expect(tableDate).toBeCalledWith('2005-06')
  })

  it('should change tableDate when touch is called', () => {
    const wrapper = mount(VDatePickerDateTable, {
      propsData: {
        tableDate: '2005-05'
      }
    })

    const tableDate = jest.fn()
    wrapper.vm.$on('tableDate', tableDate)

    wrapper.vm.touch(1)
    expect(tableDate).toBeCalledWith('2005-06')
    wrapper.vm.touch(-1)
    expect(tableDate).toBeCalledWith('2005-04')
  })
})


import { deepEqual } from '../util/helpers'
import { inject as RegistrableInject } from './registrable'
import { consoleError } from '../util/console'

// Mixins
import Colorable from './colorable'

/* @vue/component */
export default {
  name: 'validatable',

  mixins: [
    Colorable,
    RegistrableInject('form')
  ],

  props: {
    error: Boolean,
    errorCount: {
      type: [Number, String],
      default: 1
    },
    errorMessages: {
      type: [String, Array],
      default: () => []
    },
    messages: {
      type: [String, Array],
      default: () => []
    },
    rules: {
      type: Array,
      default: () => []
    },
    success: Boolean,
    successMessages: {
      type: [String, Array],
      default: () => []
    },
    validateOnBlur: Boolean
  },

  data: () => ({
    errorBucket: [],
    hasColor: false,
    hasFocused: false,
    hasInput: false,
    isResetting: false,
    valid: false
  }),

  computed: {
    hasError () {
      return this.internalErrorMessages.length > 0 ||
        this.errorBucket.length > 0 ||
        this.error
    },
    externalError () {
      return this.internalErrorMessages.length > 0 || this.error
    },
    // TODO: Add logic that allows the user to enable based
    // upon a good validation
    hasSuccess () {
      return this.successMessages.length > 0 ||
        this.success
    },
    hasMessages () {
      return this.validations.length > 0
    },
    hasState () {
      return this.hasSuccess || (this.shouldValidate && this.hasError)
    },
    internalErrorMessages () {
      return this.errorMessages || ''
    },
    shouldValidate () {
      return this.externalError || (!this.isResetting && (
        this.validateOnBlur
          ? this.hasFocused && !this.isFocused
          : (this.hasInput || this.hasFocused)
      ))
    },
    validations () {
      return this.validationTarget.slice(0, this.errorCount)
    },
    validationState () {
      if (this.hasError && this.shouldValidate) return 'error'
      if (this.hasSuccess) return 'success'
      if (this.hasColor) return this.color
      return null
    },
    validationTarget () {
      const target = this.internalErrorMessages.length > 0
        ? this.errorMessages
        : this.successMessages.length > 0
          ? this.successMessages
          : this.messages

      // String
      if (!Array.isArray(target)) {
        return [target]
      // Array with items
      } else if (target.length > 0) {
        return target
      // Currently has validation
      } else if (this.shouldValidate) {
        return this.errorBucket
      } else {
        return []
      }
    }
  },

  watch: {
    rules: {
      handler (newVal, oldVal) {
        if (deepEqual(newVal, oldVal)) return
        this.validate()
      },
      deep: true
    },
    internalValue () {
      // If it's the first time we're setting input,
      // mark it with hasInput
      this.hasInput = true
      this.validateOnBlur || this.$nextTick(this.validate)
    },
    isFocused (val) {
      if (!val) {
        this.hasFocused = true
        this.validateOnBlur && this.validate()
      }
    },
    isResetting () {
      setTimeout(() => {
        this.hasInput = false
        this.hasFocused = false
        this.isResetting = false
      }, 0)
    },
    hasError (val) {
      if (this.shouldValidate) {
        this.$emit('update:error', val)
      }
    }
  },

  beforeMount () {
    this.validate()
  },

  created () {
    this.form && this.form.register(this)
  },

  beforeDestroy () {
    this.form && this.form.unregister(this)
  },

  methods: {
    /** @public */
    reset () {
      this.isResetting = true
      this.internalValue = Array.isArray(this.internalValue)
        ? []
        : undefined
    },
    /** @public */
    resetValidation () {
      this.isResetting = true
    },
    /** @public */
    validate (force = false, value = this.internalValue) {
      const errorBucket = []

      if (force) this.hasInput = this.hasFocused = true

      for (let index = 0; index < this.rules.length; index++) {
        const rule = this.rules[index]
        const valid = typeof rule === 'function' ? rule(value) : rule

        if (valid === false || typeof valid === 'string') {
          errorBucket.push(valid)
        } else if (valid !== true) {
          consoleError(`Rules should return a string or boolean, received '${typeof valid}' instead`, this)
        }
      }

      this.errorBucket = errorBucket
      this.valid = errorBucket.length === 0

      return this.valid
    }
  }
}


@import '../bootstrap'

.v-speed-dial
  position: relative

  &--absolute
    position: absolute

  &--fixed
    position: fixed

  &--fixed,
  &--absolute
    z-index: 4

    & > .v-btn--floating
      margin: 0

  &--top
    &:not(.v-speed-dial--absolute)
      top: $grid-gutters.lg

    &.v-speed-dial--absolute
      top: 50%
      transform: translateY(-50%)

  &--bottom
    &:not(.v-speed-dial--absolute)
      bottom: $grid-gutters.lg

    &.v-speed-dial--absolute
      bottom: 50%
      transform: translateY(50%)

  &--left
    left: $grid-gutters.lg

  &--right
    right: $grid-gutters.lg

  &--direction
    &-left,
    &-right
      .v-speed-dial__list
        height: 100%
        top: 0

    &-top,
    &-bottom
      .v-speed-dial__list
        left: 0
        width: 100%

    &-top
      .v-speed-dial__list
        flex-direction: column-reverse
        bottom: 100%

    &-right
      .v-speed-dial__list
        flex-direction: row
        left: 100%

    &-bottom
      .v-speed-dial__list
        flex-direction: column
        top: 100%

    &-left
      .v-speed-dial__list
        flex-direction: row-reverse
        right: 100%

/** Elements */
.v-speed-dial__list
  align-items: center
  display: flex
  justify-content: center
  position: absolute

  .v-btn
    for n in (1..7)
      &:nth-child({n})
        transition-delay: "%ss" % (n * .05)


import ClickOutside from '../../directives/click-outside'

import Resize from '../../directives/resize'

import { convertToUnit } from '../../util/helpers'

import SlotProvider from '../../util/SlotProvider'

import SlotProvider from '../../../util/SlotProvider'

/* @vue/component */
export default {
  methods: {
    genActivator () {
      if (!this.$slots.activator) return null

      const options = {
        staticClass: 'v-menu__activator',
        'class': {
          'v-menu__activator--active': this.hasJustFocused || this.isActive,
          'v-menu__activator--disabled': this.disabled
        },
        ref: 'activator',
        on: {}
      }

      if (this.openOnHover) {
        options.on['mouseenter'] = this.mouseEnterHandler
        options.on['mouseleave'] = this.mouseLeaveHandler
      } else if (this.openOnClick) {
        options.on['click'] = this.activatorClickHandler
      }

      return this.$createElement('div', options, this.$slots.activator)
    },

    genTransition () {
      if (!this.transition) return this.genContent()

      return this.$createElement('transition', {
        props: {
          name: this.transition
        }
      }, [this.genContent()])
    },

    genDirectives () {
      // Do not add click outside for hover menu
      const directives = !this.openOnHover && this.closeOnClick ? [{
        name: 'click-outside',
        value: () => (this.isActive = false),
        args: {
          closeConditional: this.closeConditional,
          include: () => [this.$el, ...this.getOpenDependentElements()]
        }
      }] : []

      directives.push({
        name: 'show',
        value: this.isContentActive
      })

      return directives
    },

    genContent () {
      const options = {
        attrs: this.getScopeIdAttrs(),
        staticClass: 'v-menu__content',
        'class': {
          'v-menu__content--auto': this.auto,
          'menuable__content__active': this.isActive,
          ...this.themeClasses,
          [this.contentClass.trim()]: true
        },
        style: this.styles,
        directives: this.genDirectives(),
        ref: 'content',
        on: {
          click: e => {
            e.stopPropagation()
            if (e.target.getAttribute('disabled')) return
            if (this.closeOnContentClick) this.isActive = false
          }
        }
      }

      !this.disabled && this.openOnHover && (options.on.mouseenter = this.mouseEnterHandler)
      this.openOnHover && (options.on.mouseleave = this.mouseLeaveHandler)

      return this.$createElement('div', options, [
        this.$createElement(SlotProvider, {
          props: {
            provide: {
              theme: { isDark: this.$vuetify.dark || this.dark }
            }
          }
        }, this.showLazyContent(this.$slots.default))
      ])
    }
  }
}


@import '../bootstrap'
@import '../theme'

/* Theme */
v-table($material)
  background-color: $material.cards
  color: $material.text.primary

  thead
    tr
      &:first-child
        border-bottom: 1px solid rgba($material.fg-color, $material.divider-percent)

    th
      color: rgba($material.text-color, $material.secondary-text-percent)

  tbody
    tr
      &:not(:last-child)
        border-bottom: 1px solid rgba($material.fg-color, $material.divider-percent)

      &[active]
        background: $material.table.active

      &:hover:not(.v-datatable__expand-row)
        background: $material.table.hover

  tfoot
    tr
      border-top: 1px solid rgba($material.fg-color, $material.divider-percent)

theme(v-table, "v-table")

.v-table
  &__overflow
    width: 100%
    overflow-x: auto
    overflow-y: hidden

table.v-table
  border-radius: 2px
  border-collapse: collapse
  border-spacing: 0
  width: 100%
  max-width: 100%

  thead, tbody
    td, th
      &:not(:nth-child(1)), &:first-child
        padding: 0 24px

  thead
    tr
      height: 56px

    th
      font-weight: 500
      font-size: 12px
      transition: .3s $transition.swing
      white-space: nowrap
      user-select: none

      &.sortable
        pointer-events: auto

      > div
        width: 100%

  tbody
    tr
      transition: background $primary-transition
      will-change: background

    td, th
      height: 48px

    td
      font-weight: 400
      font-size: 13px

  .input-group--selection-controls
    padding: 0

    .input-group__details
      display: none

    &.checkbox
      .v-icon
        left: 50%
        transform: translateX(-50%)

      .input-group--selection-controls__ripple
        left: 50%
        transform: translate(-50%, -50%)

  tfoot
    tr
      height: 48px
      td
        padding: 0 24px


// Components
import VAvatar from '../VAvatar'

/* @vue/component */
export default {
  name: 'v-list-tile-avatar',

  functional: true,

  props: {
    color: String,
    size: {
      type: [Number, String],
      default: 40
    },
    tile: Boolean
  },

  render (h, { data, children, props }) {
    data.staticClass = (`v-list__tile__avatar ${data.staticClass || ''}`).trim()

    const avatar = h(VAvatar, {
      props: {
        color: props.color,
        size: props.size,
        tile: props.tile
      }
    }, [children])

    return h('div', data, [avatar])
  }
}


import pad from './pad'

/**
 * @param {String} value YYYY-MM format
 * @param {Number} sign -1 or +1
 */
export default (value, sign) => {
  const [year, month] = value.split('-').map(v => 1 * v)

  if (month + sign === 0) {
    return `${year - 1}-12`
  } else if (month + sign === 13) {
    return `${year + 1}-01`
  } else {
    return `${year}-${pad(month + sign)}`
  }
}


}
return h('div', data, [
this.genActivator(),
this.genTransition()
])
}
})

keep keep keep keep replace keep keep keep


import VAvatar from './VAvatar'

export { VAvatar }
export default VAvatar


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VListTile.vue should render <div> with <a> when using href prop 1`] = `

`;

exports[`VListTile.vue should render <div> with <button> when using to prop 1`] = `

  <button class="v-list__tile v-list__tile--link theme--light">
  </button>

`;

exports[`VListTile.vue should render with a div when href and to are not used 1`] = `

`;


import VDialog from './VDialog'

export { VDialog }
export default VDialog


import SlotProvider from '../../../util/SlotProvider'

import '../../stylus/components/_menus.styl'

import Vue from 'vue'

// Mixins
import Delayable from '../../mixins/delayable'
import Dependent from '../../mixins/dependent'
import Detachable from '../../mixins/detachable'
import Menuable from '../../mixins/menuable.js'
import Returnable from '../../mixins/returnable'
import Toggleable from '../../mixins/toggleable'

// Component level mixins
import Activator from './mixins/menu-activator'
import Generators from './mixins/menu-generators'
import Keyable from './mixins/menu-keyable'
import Position from './mixins/menu-position'

// Directives
import ClickOutside from '../../directives/click-outside'
import Resize from '../../directives/resize'

// Helpers
import { convertToUnit } from '../../util/helpers'

/* @vue/component */
export default Vue.extend({
  name: 'v-menu',

  directives: {
    ClickOutside,
    Resize
  },

  mixins: [
    Activator,
    Dependent,
    Delayable,
    Detachable,
    Generators,
    Keyable,
    Menuable,
    Position,
    Returnable,
    Toggleable
  ],

  props: {
    auto: Boolean,
    closeOnClick: {
      type: Boolean,
      default: true
    },
    closeOnContentClick: {
      type: Boolean,
      default: true
    },
    disabled: Boolean,
    fullWidth: Boolean,
    maxHeight: { default: 'auto' },
    offsetX: Boolean,
    offsetY: Boolean,
    openOnClick: {
      type: Boolean,
      default: true
    },
    openOnHover: Boolean,
    origin: {
      type: String,
      default: 'top left'
    },
    transition: {
      type: [Boolean, String],
      default: 'v-menu-transition'
    }
  },

  data () {
    return {
      defaultOffset: 8,
      maxHeightAutoDefault: '200px',
      startIndex: 3,
      stopIndex: 0,
      hasJustFocused: false,
      resizeTimeout: null
    }
  },

  computed: {
    calculatedLeft () {
      if (!this.auto) return this.calcLeft()

      return `${this.calcXOverflow(this.calcLeftAuto())}px`
    },
    calculatedMaxHeight () {
      return this.auto ? '200px' : convertToUnit(this.maxHeight)
    },
    calculatedMaxWidth () {
      return isNaN(this.maxWidth)
        ? this.maxWidth
        : `${this.maxWidth}px`
    },
    calculatedMinWidth () {
      if (this.minWidth) {
        return isNaN(this.minWidth)
          ? this.minWidth
          : `${this.minWidth}px`
      }

      const minWidth = (
        this.dimensions.activator.width +
        this.nudgeWidth +
        (this.auto ? 16 : 0)
      )

      const calculatedMaxWidth = isNaN(parseInt(this.calculatedMaxWidth))
        ? minWidth
        : parseInt(this.calculatedMaxWidth)

      return `${Math.min(
        calculatedMaxWidth,
        minWidth
      )}px`
    },
    calculatedTop () {
      if (!this.auto || this.isAttached) return this.calcTop()

      return `${this.calcYOverflow(this.calcTopAuto())}px`
    },
    styles () {
      return {
        maxHeight: this.calculatedMaxHeight,
        minWidth: this.calculatedMinWidth,
        maxWidth: this.calculatedMaxWidth,
        top: this.calculatedTop,
        left: this.calculatedLeft,
        transformOrigin: this.origin,
        zIndex: this.zIndex || this.activeZIndex
      }
    },
    tileHeight () {
      return this.dense ? 36 : 48
    }
  },

  watch: {
    activator (newActivator, oldActivator) {
      this.removeActivatorEvents(oldActivator)
      this.addActivatorEvents(newActivator)
    },
    isContentActive (val) {
      this.hasJustFocused = val
    }
  },

  methods: {
    activate () {
      // This exists primarily for v-select
      // helps determine which tiles to activate
      this.getTiles()
      // Update coordinates and dimensions of menu
      // and its activator
      this.updateDimensions()
      // Start the transition
      requestAnimationFrame(this.startTransition)
      // Once transitioning, calculate scroll position
      setTimeout(this.calculateScroll, 50)
    },
    closeConditional () {
      return this.isActive && this.closeOnClick
    },
    onResize () {
      if (!this.isActive) return

      // Account for screen resize
      // and orientation change
      // eslint-disable-next-line no-unused-expressions
      this.$refs.content.offsetWidth
      this.updateDimensions()

      // When resizing to a smaller width
      // content width is evaluated before
      // the new activator width has been
      // set, causing it to not size properly
      // hacky but will revisit in the future
      clearTimeout(this.resizeTimeout)
      this.resizeTimeout = setTimeout(this.updateDimensions, 100)
    }
  },

  render (h) {
    const data = {
      staticClass: 'v-menu',
      class: { 'v-menu--inline': !this.fullWidth && this.$slots.activator },
      directives: [{
        arg: 500,
        name: 'resize',
        value: this.onResize
      }],
      on: {
        keydown: this.onKeyDown
      }
    }

    return h('div', data, [
      this.genActivator(),
      this.genTransition()
    ])
  }
})


# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at john.j.leider@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VCarousel.js should render component and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

`;

exports[`VCarousel.js should render component in RTL mode and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

`;

exports[`VCarousel.js should render component with custom duration between image cycles and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

`;

exports[`VCarousel.js should render component with custom icon and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

    <button type="button"
            class="v-btn v-btn--icon v-btn--small theme--dark v-carousel__controls__item v-carousel__controls__item--active"
    >

          stop

    </button>
    <button type="button"
            class="v-btn v-btn--icon v-btn--small theme--dark v-carousel__controls__item"
    >

          stop

    </button>
    <button type="button"
            class="v-btn v-btn--icon v-btn--small theme--dark v-carousel__controls__item"
    >

          stop

    </button>

`;

exports[`VCarousel.js should render component with custom next icon and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          stop

    </button>

`;

exports[`VCarousel.js should render component with custom prev icon and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          stop

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

`;

exports[`VCarousel.js should render component with image cycling off and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

`;

exports[`VCarousel.js should render component with selected active item 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

    <button type="button"
            class="v-btn v-btn--icon v-btn--small theme--dark v-carousel__controls__item"
    >

          fiber_manual_record

    </button>
    <button type="button"
            class="v-btn v-btn--icon v-btn--small theme--dark v-carousel__controls__item v-carousel__controls__item--active"
    >

          fiber_manual_record

    </button>
    <button type="button"
            class="v-btn v-btn--icon v-btn--small theme--dark v-carousel__controls__item"
    >

          fiber_manual_record

    </button>

`;

exports[`VCarousel.js should render component without delimiters 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

`;

exports[`VCarousel.js should render component without next icon and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_left

    </button>

`;

exports[`VCarousel.js should render component without prev icon and match snapshot 1`] = `

    <button type="button"
            class="v-btn v-btn--icon theme--dark"
    >

          chevron_right

    </button>

`;


// Styles
import '../../stylus/components/_sliders.styl'

// Components
import { VScaleTransition } from '../transitions'

// Extensions
import VInput from '../VInput'

// Directives
import ClickOutside from '../../directives/click-outside'

// Utilities
import {
  addOnceEventListener,
  convertToUnit,
  createRange,
  keyCodes,
  deepEqual
} from '../../util/helpers'
import { consoleWarn } from '../../util/console'

/* @vue/component */
export default {
  name: 'v-slider',

  directives: { ClickOutside },

  extends: VInput,

  props: {
    alwaysDirty: Boolean,
    inverseLabel: Boolean,
    label: String,
    min: {
      type: [Number, String],
      default: 0
    },
    max: {
      type: [Number, String],
      default: 100
    },
    range: Boolean,
    step: {
      type: [Number, String],
      default: 1
    },
    ticks: {
      type: [Boolean, String],
      default: false,
      validator: v => typeof v === 'boolean' || v === 'always'
    },
    tickLabels: {
      type: Array,
      default: () => ([])
    },
    tickSize: {
      type: [Number, String],
      default: 1
    },
    thumbColor: {
      type: String,
      default: null
    },
    thumbLabel: {
      type: [Boolean, String],
      default: null,
      validator: v => typeof v === 'boolean' || v === 'always'
    },
    thumbSize: {
      type: [Number, String],
      default: 32
    },
    trackColor: {
      type: String,
      default: null
    },
    value: [Number, String]
  },

  data: vm => ({
    app: {},
    isActive: false,
    keyPressed: 0,
    lazyValue: typeof vm.value !== 'undefined' ? vm.value : Number(vm.min),
    oldValue: null
  }),

  computed: {
    classes () {
      return {
        'v-input--slider': true,
        'v-input--slider--ticks': this.showTicks,
        'v-input--slider--inverse-label': this.inverseLabel,
        'v-input--slider--ticks-labels': this.tickLabels.length > 0,
        'v-input--slider--thumb-label': this.thumbLabel ||
          this.$scopedSlots.thumbLabel
      }
    },
    showTicks () {
      return this.tickLabels.length > 0 ||
        (!this.disabled && this.stepNumeric && !!this.ticks)
    },
    showThumbLabel () {
      return !this.disabled && (
        !!this.thumbLabel ||
        this.thumbLabel === '' ||
        this.$scopedSlots['thumb-label']
      )
    },
    computedColor () {
      if (this.disabled) return null
      return this.validationState || this.color || 'primary'
    },
    computedTrackColor () {
      return this.disabled ? null : (this.trackColor || null)
    },
    computedThumbColor () {
      if (this.disabled || !this.isDirty) return null
      return this.validationState || this.thumbColor || this.color || 'primary'
    },
    internalValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        const { min, max } = this

        // Round value to ensure the
        // entire slider range can
        // be selected with step
        const value = this.roundValue(Math.min(Math.max(val, min), max))

        if (value === this.lazyValue) return

        this.lazyValue = value

        this.$emit('input', value)
        this.validate()
      }
    },
    stepNumeric () {
      return this.step > 0 ? parseFloat(this.step) : 0
    },
    trackFillStyles () {
      const left = this.$vuetify.rtl ? 'auto' : 0
      const right = this.$vuetify.rtl ? 0 : 'auto'
      let width = `${this.inputWidth}%`

      if (this.disabled) width = `calc(${this.inputWidth}% - 8px)`

      return {
        transition: this.trackTransition,
        left,
        right,
        width
      }
    },
    trackPadding () {
      return (
        this.isActive ||
        this.inputWidth > 0 ||
        this.disabled
      ) ? 0 : 7
    },
    trackStyles () {
      const trackPadding = this.disabled ? `calc(${this.inputWidth}% + 8px)` : `${this.trackPadding}px`
      const left = this.$vuetify.rtl ? 'auto' : trackPadding
      const right = this.$vuetify.rtl ? trackPadding : 'auto'
      const width = this.disabled
        ? `calc(${100 - this.inputWidth}% - 8px)`
        : '100%'

      return {
        transition: this.trackTransition,
        left,
        right,
        width
      }
    },
    tickStyles () {
      const size = Number(this.tickSize)

      return {
        'border-width': `${size}px`,
        'border-radius': size > 1 ? '50%' : null,
        transform: size > 1 ? `translateX(-${size}px) translateY(-${size - 1}px)` : null
      }
    },
    trackTransition () {
      return this.keyPressed >= 2 ? 'none' : ''
    },
    numTicks () {
      return Math.ceil((this.max - this.min) / this.stepNumeric)
    },
    inputWidth () {
      return (this.roundValue(this.internalValue) - this.min) / (this.max - this.min) * 100
    },
    isDirty () {
      return this.internalValue > this.min ||
        this.alwaysDirty
    }
  },

  watch: {
    min (val) {
      val > this.internalValue && this.$emit('input', parseFloat(val))
    },
    max (val) {
      val < this.internalValue && this.$emit('input', parseFloat(val))
    },
    value (val) {
      this.internalValue = val
    }
  },

  mounted () {
    // Without a v-app, iOS does not work with body selectors
    this.app = document.querySelector('[data-app]') ||
      consoleWarn('Missing v-app or a non-body wrapping element with the [data-app] attribute', this)
  },

  methods: {
    genDefaultSlot () {
      const children = [this.genLabel()]
      const slider = this.genSlider()

      this.inverseLabel
        ? children.unshift(slider)
        : children.push(slider)

      return children
    },
    genListeners () {
      return {
        blur: this.onBlur,
        click: this.onSliderClick,
        focus: this.onFocus,
        keydown: this.onKeyDown,
        keyup: this.onKeyUp
      }
    },
    genInput () {
      return this.$createElement('input', {
        attrs: {
          'aria-label': this.label,
          name: this.name,
          role: 'slider',
          tabindex: this.disabled ? -1 : this.$attrs.tabindex,
          value: this.internalValue,
          readonly: true,
          'aria-readonly': String(this.readonly)
        },
        on: this.genListeners(),
        ref: 'input'
      })
    },
    genSlider () {
      return this.$createElement('div', {
        staticClass: 'v-slider',
        'class': {
          'v-slider--is-active': this.isActive
        },
        directives: [{
          name: 'click-outside',
          value: this.onBlur
        }]
      }, this.genChildren())
    },
    genChildren () {
      return [
        this.genInput(),
        this.genTrackContainer(),
        this.genSteps(),
        this.genThumbContainer(
          this.internalValue,
          this.inputWidth,
          this.isFocused || this.isActive,
          this.onThumbMouseDown
        )
      ]
    },
    genSteps () {
      if (!this.step || !this.showTicks) return null

      const ticks = createRange(this.numTicks + 1).map(i => {
        const children = []

        if (this.tickLabels[i]) {
          children.push(this.$createElement('span', this.tickLabels[i]))
        }

        return this.$createElement('span', {
          key: i,
          staticClass: 'v-slider__ticks',
          class: {
            'v-slider__ticks--always-show': this.ticks === 'always' ||
              this.tickLabels.length > 0
          },
          style: {
            ...this.tickStyles,
            left: `${i * (100 / this.numTicks)}%`
          }
        }, children)
      })

      return this.$createElement('div', {
        staticClass: 'v-slider__ticks-container'
      }, ticks)
    },
    genThumb () {
      return this.$createElement('div', this.setBackgroundColor(this.computedThumbColor, {
        staticClass: 'v-slider__thumb'
      }))
    },
    genThumbContainer (value, valueWidth, isActive, onDrag) {
      const children = [this.genThumb()]

      const thumbLabelContent = this.getLabel(value)
      this.showThumbLabel && children.push(this.genThumbLabel(thumbLabelContent))

      return this.$createElement('div', this.setTextColor(this.computedThumbColor, {
        staticClass: 'v-slider__thumb-container',
        'class': {
          'v-slider__thumb-container--is-active': isActive,
          'v-slider__thumb-container--show-label': this.showThumbLabel
        },
        style: {
          transition: this.trackTransition,
          left: `${this.$vuetify.rtl ? 100 - valueWidth : valueWidth}%`
        },
        on: {
          touchstart: onDrag,
          mousedown: onDrag
        }
      }), children)
    },
    genThumbLabel (content) {
      const size = convertToUnit(this.thumbSize)

      return this.$createElement(VScaleTransition, {
        props: { origin: 'bottom center' }
      }, [
        this.$createElement('div', {
          staticClass: 'v-slider__thumb-label__container',
          directives: [
            {
              name: 'show',
              value: this.isFocused || this.isActive || this.thumbLabel === 'always'
            }
          ]
        }, [
          this.$createElement('div', this.setBackgroundColor(this.computedThumbColor, {
            staticClass: 'v-slider__thumb-label',
            style: {
              height: size,
              width: size
            }
          }), [content])
        ])
      ])
    },
    genTrackContainer () {
      const children = [
        this.$createElement('div', this.setBackgroundColor(this.computedTrackColor, {
          staticClass: 'v-slider__track',
          style: this.trackStyles
        })),
        this.$createElement('div', this.setBackgroundColor(this.computedColor, {
          staticClass: 'v-slider__track-fill',
          style: this.trackFillStyles
        }))
      ]

      return this.$createElement('div', {
        staticClass: 'v-slider__track__container',
        ref: 'track'
      }, children)
    },
    getLabel (value) {
      return this.$scopedSlots['thumb-label']
        ? this.$scopedSlots['thumb-label']({ value })
        : this.$createElement('span', value)
    },
    onBlur (e) {
      if (this.keyPressed === 2) return

      this.isActive = false
      this.isFocused = false
      this.$emit('blur', e)
    },
    onFocus (e) {
      this.isFocused = true
      this.$emit('focus', e)
    },
    onThumbMouseDown (e) {
      this.oldValue = this.internalValue
      this.keyPressed = 2
      const options = { passive: true }
      this.isActive = true
      this.isFocused = false

      if ('touches' in e) {
        this.app.addEventListener('touchmove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'touchend', this.onMouseUp)
      } else {
        this.app.addEventListener('mousemove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'mouseup', this.onMouseUp)
      }

      this.$emit('start', this.internalValue)
    },
    onMouseUp () {
      this.keyPressed = 0
      const options = { passive: true }
      this.isActive = false
      this.isFocused = false
      this.app.removeEventListener('touchmove', this.onMouseMove, options)
      this.app.removeEventListener('mousemove', this.onMouseMove, options)

      this.$emit('end', this.internalValue)
      if (!deepEqual(this.oldValue, this.internalValue)) {
        this.$emit('change', this.internalValue)
      }
    },
    onMouseMove (e) {
      const { value, isInsideTrack } = this.parseMouseMove(e)

      if (isInsideTrack) {
        this.setInternalValue(value)
      }
    },
    onKeyDown (e) {
      if (this.disabled || this.readonly) return

      const value = this.parseKeyDown(e)

      if (value == null) return

      this.setInternalValue(value)
      this.$emit('change', value)
    },
    onKeyUp () {
      this.keyPressed = 0
    },
    onSliderClick (e) {
      this.isFocused = true
      this.onMouseMove(e)
      this.$emit('change', this.internalValue)
    },
    parseMouseMove (e) {
      const {
        left: offsetLeft,
        width: trackWidth
      } = this.$refs.track.getBoundingClientRect()
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      // It is possible for left to be NaN, force to number
      let left = Math.min(Math.max((clientX - offsetLeft) / trackWidth, 0), 1) || 0

      if (this.$vuetify.rtl) left = 1 - left

      const isInsideTrack = clientX >= offsetLeft - 8 && clientX <= offsetLeft + trackWidth + 8
      const value = parseFloat(this.min) + left * (this.max - this.min)

      return { value, isInsideTrack }
    },
    parseKeyDown (e, value = this.internalValue) {
      if (this.disabled) return

      const { pageup, pagedown, end, home, left, right, down, up } = keyCodes

      if (![pageup, pagedown, end, home, left, right, down, up].includes(e.keyCode)) return

      e.preventDefault()
      const step = this.stepNumeric || 1
      const steps = (this.max - this.min) / step
      if ([left, right, down, up].includes(e.keyCode)) {
        this.keyPressed += 1

        const increase = this.$vuetify.rtl ? [left, up] : [right, up]
        const direction = increase.includes(e.keyCode) ? 1 : -1
        const multiplier = e.shiftKey ? 3 : (e.ctrlKey ? 2 : 1)

        value = value + (direction * step * multiplier)
      } else if (e.keyCode === home) {
        value = parseFloat(this.min)
      } else if (e.keyCode === end) {
        value = parseFloat(this.max)
      } else /* if (e.keyCode === keyCodes.pageup || e.keyCode === pagedown) */ {
        // Page up/down
        const direction = e.keyCode === pagedown ? 1 : -1
        value = value - (direction * step * (steps > 100 ? steps / 10 : 10))
      }

      return value
    },
    roundValue (value) {
      if (!this.stepNumeric) return value
      // Format input value using the same number
      // of decimals places as in the step prop
      const trimmedStep = this.step.toString().trim()
      const decimals = trimmedStep.indexOf('.') > -1
        ? (trimmedStep.length - trimmedStep.indexOf('.') - 1)
        : 0
      const offset = this.min % this.stepNumeric

      const newValue = Math.round((value - offset) / this.stepNumeric) * this.stepNumeric + offset

      return parseFloat(Math.min(newValue, this.max).toFixed(decimals))
    },
    setInternalValue (value) {
      this.internalValue = value
    }
  }
}


!this.disabled && this.openOnHover && (options.on.mouseenter = this.mouseEnterHandler)

this.openOnHover && (options.on.mouseleave = this.mouseLeaveHandler)

return this.$createElement('div', options, [

keep keep keep replace replace replace replace replace replace replace replace replace keep keep keep

return this.$createElement('div', options, this.showLazyContent(this.$slots.default))


import ClickOutside from './click-outside'
import Resize from './resize'
import Ripple from './ripple'
import Scroll from './scroll'
import Touch from './touch'

export {
  ClickOutside,
  Ripple,
  Resize,
  Scroll,
  Touch
}


import { test } from '@/test'
import VProgressCircular from '@/components/VProgressCircular'
import { compileToFunctions } from 'vue-template-compiler'

test('VProgressCircular.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        value: 33
      },
      slots: {
        default: [compileToFunctions('content')]
      }
    })

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({ value: -1 })
    const htmlMinus1 = wrapper.html()

    wrapper.setProps({ value: 0 })
    const html0 = wrapper.html()

    wrapper.setProps({ value: 100 })
    const html100 = wrapper.html()

    wrapper.setProps({ value: 101 })
    const html101 = wrapper.html()

    expect(htmlMinus1).toBe(html0)
    expect(html100).toBe(html101)
    expect(html0).not.toBe(html100)
  })

  it('should render component with color prop and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        value: 33,
        color: 'orange lighten-1'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with button prop and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        value: 33,
        button: true
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with rotate prop and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        value: 33,
        rotate: 29
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with size prop and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        value: 33,
        size: 17
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with indeterminate prop and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        indeterminate: true
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with width prop and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        value: 33,
        width: 13
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with fill prop and match snapshot', () => {
    const wrapper = mount(VProgressCircular, {
      propsData: {
        value: 33,
        fill: 'green lighten-1'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })
})


import '../../stylus/components/_system-bars.styl'

import Applicationable from '../../mixins/applicationable'
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'

/* @vue/component */
export default {
  name: 'v-system-bar',

  mixins: [
    Applicationable('bar', [
      'height',
      'window'
    ]),
    Colorable,
    Themeable
  ],

  props: {
    height: {
      type: [Number, String],
      validator: v => !isNaN(parseInt(v))
    },
    lightsOut: Boolean,
    status: Boolean,
    window: Boolean
  },

  computed: {
    classes () {
      return {
        'v-system-bar--lights-out': this.lightsOut,
        'v-system-bar--absolute': this.absolute,
        'v-system-bar--fixed': !this.absolute && (this.app || this.fixed),
        'v-system-bar--status': this.status,
        'v-system-bar--window': this.window,
        ...this.themeClasses
      }
    },
    computedHeight () {
      if (this.height) return parseInt(this.height)

      return this.window ? 32 : 24
    }
  },

  methods: {
    /**
     * Update the application layout
     *
     * @return {number}
     */
    updateApplication () {
      return this.computedHeight
    }
  },

  render (h) {
    const data = {
      staticClass: 'v-system-bar',
      'class': this.classes,
      style: {
        height: `${this.computedHeight}px`
      }
    }

    return h('div', this.setBackgroundColor(this.color, data), this.$slots.default)
  }
}


{
"name": "react-hook-form",
"version": "3.7.0",
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",

react-hook-form

eslint ./src --ext .jsx,.ts --ignore-pattern *.test.ts

jest --coverage --coverageReporters=text-lcov | coveralls

yarn lint && yarn test && yarn run clean && yarn build

react hooks form forms form-validation validation typescript react-hooks


import removeAllEventListeners from './removeAllEventListeners';

describe('removeAllEventListeners', () => {
  it('should remove all events', () => {
    const removeEventListener = jest.fn();
    const validateWithStateUpdate = jest.fn();
    const ref = {
      removeEventListener,
    };
    removeAllEventListeners(ref, validateWithStateUpdate);
    expect(removeEventListener).toBeCalledWith('input', validateWithStateUpdate);
    expect(removeEventListener).toBeCalledWith('change', validateWithStateUpdate);
    expect(removeEventListener).toBeCalledWith('blur', validateWithStateUpdate);
  });
});


import { FieldValue } from '../types';

export default function combineFieldValues(data): FieldValue {
  const output = Object.entries(data).reduce((previous, [key, value]): FieldValue => {
    const arrayIndex = key.match(/\[\d+\]$/gi);

    if (arrayIndex) {
      const index = arrayIndex[0].slice(1, -1);
      const filedName = key.substr(0, key.indexOf('['));
      if (!previous[filedName]) previous[filedName] = [];
      previous[filedName][index] = value;
    } else {
      previous[key] = value;
    }
    return previous;
  }, {});

  return Object.entries(output).reduce((previous, [key, value]): FieldValue => {
    if (Array.isArray(value)) {
      previous[key] = value.filter(Boolean);
      return previous;
    }

    previous[key] = value;
    return previous;
  }, {});
}


import React from "react";
import ReactDOM from "react-dom";
import useForm from "react-hook-form";

function App() {
  const { register, handleSubmit } = useForm();
  const onSubmit = data => {
    alert(JSON.stringify(data));
  };

  return (

      <form onSubmit={handleSubmit(onSubmit)}>

          First Name
          <input
            name="firstName"
            onChange={e => {
              if (e.target.value) {
                e.target.value = e.target.value.toUpperCase();
              }
            }}
            placeholder="bill"
            ref={register}
          />

          Last Name
          <input
            name="lastName"
            onChange={e => {
              if (e.target.value) {
                e.target.value = e.target.value.toLowerCase();
              }
            }}
            placeholder="luo"
            ref={register}
          />

          DOB
          <input
            name="DOB"
            onChange={e => {
              const value = e.target.value;
              if (value.match(/^\d{2}$/) !== null) {
                e.target.value = value + "/";
              } else if (value.match(/^\d{2}\/\d{2}$/) !== null) {
                e.target.value = value + "/";
              }
            }}
            placeholder="12/12/1999"
            ref={register}
          />

          Email
          <input
            name="email"
            placeholder="bluebill1049@hotmail.com"
            type="email"
            ref={register}
          />

        <button type="submit">Submit</button>
      </form>

  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);


it('should return undefined when ref is undefined', () => {

it('should return undefined when ref name is missing', () => {

expect(hookForm.register({ type: 'input' })).toBeUndefined();

it('should register field and call attachEventListeners method', () => {

hookForm.register({ type: 'input', name: 'test' });

keep keep keep keep keep replace keep keep keep keep keep

expect(hookForm.register({ type: 'input' }, {})).toBeUndefined();

import { Field } from '../types';
import isCheckBoxInput from '../utils/isCheckBoxInput';

export default function attachEventListeners({
  field,
  validateAndStateUpdate,
  isRadio,
}: {
  field: Field;
  isRadio: boolean;
  validateAndStateUpdate: Function | undefined;
}): void {
  const { ref } = field;
  ref.addEventListener(isCheckBoxInput(ref.type) || isRadio ? 'change' : 'input', validateAndStateUpdate);
  ref.addEventListener('blur', validateAndStateUpdate);
}


import { Heading, SubHeading } from './styles/typography';
import React from 'react';
import styled from 'styled-components';
import colors from './styles/colors';
import { AnimateGroup, Animate } from 'react-simple-animate';
import video from './assets/react-hook-form-demo-video.mp4';
import FeaturesList from './FeaturesList';
import track from "./utils/track";

const Logo = styled.svg`
  height: 80px;
  fill: white;
  top: 0;
  left: 0;
  background: #333;
  padding: 20px;
  border-radius: 15px;
  background: ${colors.lightPink};
  text-align: center;
  display: block;
  margin: -50px auto 0;

  @media (min-width: 768px) {
    display: none;
  }
`;

const Head = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  flex-direction: column;

  @media (min-width: 768px) {
    height: auto;
  }

  & > h1 {
    font-size: 40px;
    text-shadow: 2px 2px 4px ${colors.lightBlue};

    @media (min-width: 768px) {
      font-size: 65px;
    }
  }

  & > p {
    font-size: 16px;
    font-weight: 100;
  }
`;

export const HeadingButtons = styled.div`
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-column-gap: 20px;
  margin-top: 10px;

  @media (min-width: 768px) {
    grid-column-gap: 40px;
  }

  & > button {
    display: block;
    box-sizing: border-box;
    width: 100%;
    border-radius: 4px;
    padding: 9px 20px;
    margin-bottom: 10px;
    font-size: 14px;
    background: ${colors.primary};
    color: white;
    border: 1px solid ${colors.lightBlue};
    transition: 0.3s all;

    &:hover {
      opacity: 0.8;
    }

    &:active {
      background: ${colors.lightPink};
    }

    @media (min-width: 768px) {
      font-size: 20px;
      padding: 12px 20px;
      font-weight: 300;
      margin-bottom: 40px;
    }
  }
`;

const Video = styled.video`
  width: 100%;
  border-radius: 10px;
  display: block;
  box-shadow: 0px 0 9px 0px #010817;
  margin-bottom: 100px;
  margin-top: -45px;
  background: ${colors.primary};

  @media (min-width: 768px) {
    width: 700px;
    height: 491px;
    margin: 0 auto 40px;
  }
`;

export default function Header({ homeRef, toggleApi, tabIndex }: any) {
  // @ts-ignore
  return (
    <AnimateGroup play>
      <Head>
        <Logo viewBox="0 0 100 100">
          <path d="M73.56,13.32H58.14a8.54,8.54,0,0,0-16.27,0H26.44a11,11,0,0,0-11,11V81.63a11,11,0,0,0,11,11H73.56a11,11,0,0,0,11-11V24.32A11,11,0,0,0,73.56,13.32Zm-30.92,2a1,1,0,0,0,1-.79,6.54,6.54,0,0,1,12.78,0,1,1,0,0,0,1,.79h5.38v6.55a3,3,0,0,1-3,3H40.25a3,3,0,0,1-3-3V15.32ZM82.56,81.63a9,9,0,0,1-9,9H26.44a9,9,0,0,1-9-9V24.32a9,9,0,0,1,9-9h8.81v6.55a5,5,0,0,0,5,5h19.5a5,5,0,0,0,5-5V15.32h8.81a9,9,0,0,1,9,9Z" />
          <path style={{ transform: 'translateX(-25px)' }} d="M71.6,45.92H54a1,1,0,0,0,0,2H71.6a1,1,0,0,0,0-2Z" />
          <path d="M71.6,45.92H54a1,1,0,0,0,0,2H71.6a1,1,0,0,0,0-2Z" />
          <path style={{ transform: 'translateX(-25px)' }} d="M71.1,69.49H53.45a1,1,0,1,0,0,2H71.1a1,1,0,0,0,0-2Z" />
          <path d="M71.1,69.49H53.45a1,1,0,1,0,0,2H71.1a1,1,0,0,0,0-2Z" />
        </Logo>
        <Heading>React Hook Form</Heading>
        <SubHeading>Performance, flexible and extensible forms with easy to use for validation.</SubHeading>

        <HeadingButtons>
          <button
            tabIndex={tabIndex}
            onClick={() => {
              track({
                category: 'Home CTA',
                label: 'Demo',
                action: 'go to demo'
              })
              if (homeRef.current) {
                // @ts-ignore
                homeRef.current.scrollIntoView({ behavior: 'smooth' });
              }
            }}
          >
            Demo
          </button>
          <button
            tabIndex={tabIndex}
            onClick={() => {
              toggleApi(true);
              track({
                category: 'Home CTA',
                label: 'API',
                action: 'go to API'
              })
              document.title = 'React hook form - API';
              window.history.pushState({ page: 'React hook form - API' }, 'React hook form - API', '/api');
            }}
          >
            Get Started
          </button>
        </HeadingButtons>
      </Head>

      <Video controls autoPlay playsInline muted onClick={() => {
        track({
          category: 'video',
          label: 'video',
          action: 'play/pause video'
        })
      }}>
        <source src={video} type="video/mp4" />
      </Video>

      <FeaturesList />
    </AnimateGroup>
  );
}


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`combineFieldValues should combine all the array fields 1`] = `
Object {
  "email": Array [
    "asdasd@dsad.com",
    "asdasd@.com",
  ],
  "firstName": Array [
    "asdasd",
    "asdasd",
  ],
  "lastName": Array [
    "asdasd",
    "asd",
  ],
  "test": "test",
}
`;


import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import useForm from "react-hook-form";

import "./styles.css";

export default function Form() {
  const { register, handleSubmit, reset } = useForm({
    mode: "onChange"
  });
  const onSubmit = (data, e) => {
    e.target.reset(); // reset after form submit
    alert(JSON.stringify(data));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>

        First name
        <input
          type="text"
          name="First name"
          ref={register({ required: true })}
        />

        Last name
        <input type="text" name="Last name" ref={register} />

        Email
        <input type="text" name="Email" ref={register} />

        Mobile number
        <input type="tel" name="Mobile number" ref={register} />

        Title
        <select name="Title" ref={register}>
          <option value="Mr">Mr</option>
          <option value="Mrs">Mrs</option>
          <option value="Miss">Miss</option>
          <option value="Dr">Dr</option>
        </select>

        Are you a developer?
        <input name="developer" type="radio" value="Yes" ref={register} />
        <input name="developer" type="radio" value="No" ref={register} />

      <input type="submit" />
      <input
        style={{ display: "block", marginTop: 20 }}
        type="reset"
        value="Standard Reset Field Values"
      />
      <input
        style={{ display: "block", marginTop: 20 }}
        type="reset"
        onClick={reset}
        value="Custom Reset Field Values & Errors"
      />
    </form>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Form />, rootElement);


keep keep keep keep replace replace keep keep keep

import * as React from 'react';
import useForm from './';
import { act } from 'react-dom/test-utils';
import attachEventListeners from './logic/attachEventListeners';
import getFieldsValues from './logic/getFieldsValues';
import findRemovedFieldAndRemoveListener from './logic/findRemovedFieldAndRemoveListener';
import validateWithSchema from './logic/validateWithSchema';
import validateField from './logic/validateField';
import onDomRemove from './utils/onDomRemove';
import { mount } from 'enzyme';

jest.mock('./utils/onDomRemove');
jest.mock('./logic/findRemovedFieldAndRemoveListener');
jest.mock('./logic/validateField');
jest.mock('./logic/attachEventListeners');
jest.mock('./logic/getFieldsValues');
jest.mock('./logic/validateWithSchema');

const TestHook = ({ callback }) => {
  const { errors } = callback();
  return errors ?
errors
 : null;
};

const testComponent = callback => {
  mount(<TestHook callback={callback} />);
};

let hookForm;
let hookFormWithValidationSchema;

describe('useForm', () => {
  beforeEach(() => {
    testComponent(() => {
      hookForm = useForm();
      return hookForm;
    });

    testComponent(() => {
      hookFormWithValidationSchema = useForm({
        mode: 'onSubmit',
        validationSchema: {},
      });
      return hookFormWithValidationSchema;
    });

    jest.resetAllMocks();
  });

  describe('register', () => {
    it('should return undefined when ref is undefined', () => {
      expect(hookForm.register(undefined)).toBeUndefined();
    });

    it('should return undefined when ref name is missing', () => {
      expect(hookForm.register({ type: 'input' })).toBeUndefined();
    });

    it('should register field and call attachEventListeners method', () => {
      hookForm.register({ type: 'input', name: 'test' });
      expect(attachEventListeners).toBeCalledWith({
        field: {
          mutationWatcher: undefined,
          ref: {
            name: 'test',
            type: 'input',
          },
          required: false,
          validate: undefined,
        },
        isRadio: false,
        validateAndStateUpdate: expect.any(Function),
      });
      expect(onDomRemove).toBeCalled();
    });

    it('should register field for radio type and call attachEventListeners method', () => {
      hookForm.register({ type: 'radio', name: 'test' });
      expect(attachEventListeners).toBeCalledWith({
        field: {
          mutationWatcher: undefined,
          ref: {
            name: 'test',
            type: 'radio',
          },
          required: false,
          validate: undefined,
        },
        isRadio: true,
        validateAndStateUpdate: expect.any(Function),
      });
      expect(onDomRemove).toBeCalled();
    });
  });

  describe('watch', () => {
    it('should watch individual input', () => {
      expect(hookForm.watch('test')).toBeUndefined();
      // @ts-ignore
      getFieldsValues.mockImplementation((_, name) => {
        if (name === 'test') {
          return 'data';
        }
      });
      expect(hookForm.watch('test')).toBe('data');
    });

    it('should watch array of inputs', () => {
      expect(hookForm.watch(['test', 'test1'])).toBeUndefined();
      // @ts-ignore
      getFieldsValues.mockImplementation((_, name) => {
        if (name && name.includes('test1') && name.includes('test')) {
          return ['data1', 'data2'];
        }
      });
      expect(hookForm.watch(['test', 'test1'])).toEqual(['data1', 'data2']);
    });

    it('should watch every fields', () => {
      expect(hookForm.watch()).toBeUndefined();
      // @ts-ignore
      getFieldsValues.mockImplementation((_, name) => {
        if (name === undefined) {
          return ['data1', 'data2'];
        }
      });
      expect(hookForm.watch()).toEqual(['data1', 'data2']);
    });
  });

  describe('unSubscribe', () => {
    it('should remove all reference when mode change', () => {
      hookForm.register({ type: 'input', name: 'test' });
      hookForm.register({
        type: 'radio',
        name: 'test1',
        options: [{ type: 'radio', name: 'test3' }, { type: 'radio', name: 'test4' }],
      });
      expect(attachEventListeners).toBeCalledWith({
        field: {
          mutationWatcher: undefined,
          ref: {
            name: 'test',
            type: 'input',
          },
          required: false,
          validate: undefined,
        },
        isRadio: false,
        validateAndStateUpdate: expect.any(Function),
      });
      hookForm.register({ type: 'input', name: 'test' });
      act(() => {
        hookForm.unSubscribe();
      });
      expect(findRemovedFieldAndRemoveListener).toBeCalled();
      hookForm.register({ type: 'input', name: 'test' });
      expect(attachEventListeners).toBeCalledTimes(3);
    });
  });

  describe('handleSubmit', () => {
    it('should invoke the callback when validation pass', async () => {
      const callback = jest.fn();
      const preventDefault = jest.fn();
      const persist = jest.fn();
      await hookForm.handleSubmit(callback)({
        preventDefault,
        persist,
      });
      expect(callback).toBeCalled();
    });

    it('should not invoke callback when there are errors', async () => {
      hookForm.register({ value: '', type: 'input', name: 'test' }, { required: true });
      const callback = jest.fn();
      // @ts-ignore
      validateField.mockImplementation(async () => {
        return { test: { type: 'test' } };
      });
      const test = hookForm.handleSubmit(callback);
      await test({
        preventDefault: () => {},
        persist: () => {},
      });
      expect(callback).not.toBeCalled();
    });
  });

  describe('handleSubmit with validationSchema', () => {
    it('should invoke callback when error not found', async () => {
      hookFormWithValidationSchema.register({ value: '', type: 'input', name: 'test' }, { required: true });
      const callback = jest.fn();
      // @ts-ignore
      getFieldsValues.mockImplementation(async () => {
        return { test: 'test' };
      });
      // @ts-ignore
      validateWithSchema.mockImplementation(async () => {
        return {};
      });
      await hookFormWithValidationSchema.handleSubmit(callback)({
        preventDefault: () => {},
        persist: () => {},
      });
      expect(callback).toBeCalled();
    });
  });
});


import React from 'react';
import ReactDOM from 'react-dom';
import useForm from 'react-hook-form';

function App() {
  const { register, watch, handleSubmit } = useForm();
  const onSubmit = data => {
    alert(JSON.stringify(data));
  };

  const moreDetail = watch('moreDetail');

  return (

      <form onSubmit={handleSubmit(onSubmit)}>

          First Name
          <input name="firstName" placeholder="Bill" ref={register} />

          Last Name
          <input name="lastName" placeholder="Luo" ref={register} />

          Email
          <input name="email" placeholder="bluebill1049@hotmail.com" type="email" ref={register} />

          More Details
          <input name="moreDetail" type="checkbox" ref={register} />

        {moreDetail && (

            Interests
            <input type="text" name="Interests" ref={register} />

        )}
        <button type="submit">Submit</button>
      </form>

  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);


import React from 'react';
import { Title, H1 } from './styles/typography';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { xonokai } from 'react-syntax-highlighter/dist/styles/prism';
import styled from 'styled-components';
import formikCode from './codeExamples/formikCode';
import reactHookFormCode from './codeExamples/reactHookFormCode';
import reduxFormCode from './codeExamples/reduxFormCode';

const GridView = styled.section`
  @media (min-width: 1100px) {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-column-gap: 40px;
  }

  & > section {
    scroll-snap-align: start;
  }

  & pre {
    background: none !important;
  }

  & h4 {
    text-align: center;
  }
`;

export default function CodeCompareSection() {
  return (

          {`</>`} Library Code Comparison

          Reducing the amount of code you have to write is one of the goals for React Hook Form. To illustrate, let's
          look at a very simple form validation among some of the most popular form validation libraries.

      <GridView>

          <Title>
            <svg
              style={{
                display: 'inline',
                width: 30,
                fill: 'white',
              }}
              viewBox="0 0 270 210"
            >
              <path d="M245.1,75.9c-5-4.9-11.5-7.5-18.6-7.3c-6.9,0.1-13.3,3-18.1,8.2c-4.8,5.1-7.3,11.8-7.1,18.6c0.2,5.9,2.5,11.4,6.3,15.8  c-10,12.4-19.1,18.2-27.8,17.5c-11.2-0.9-23.1-12.5-35.6-34.8c4.2-3.3,7.3-7.7,8.9-12.9c2.2-7.1,1.1-14.8-2.9-21.8  c-4.3-7.5-12.6-11.7-22.2-11.4c-0.1,0-0.2,0-0.2,0c-9.6-0.3-17.9,3.9-22.2,11.4c-4,7-5,14.8-2.9,21.8c1.6,5.1,4.7,9.6,8.9,12.9  c-12.5,22.2-24.4,33.9-35.6,34.8c-8.7,0.7-17.8-5.1-27.8-17.5c3.8-4.4,6-9.9,6.3-15.8c0.3-6.8-2.3-13.6-7.1-18.6  c-4.8-5.1-11.2-8-18.1-8.2c-7-0.1-13.6,2.5-18.6,7.3c-5,4.9-7.7,11.4-7.7,18.4c0,13.5,10.5,24.6,23.7,25.7l7.5,73.1  c0.9,8.7,8.1,15.2,16.8,15.2h153.5c8.7,0,16-6.5,16.8-15.2l7.5-73.1c13.3-1,23.7-12.1,23.7-25.7C252.8,87.3,250.1,80.8,245.1,75.9z   M227.1,110.2c-0.6,0-1.3-0.1-1.9-0.1c-1.3-0.2-2.6,0.2-3.7,1c-1,0.8-1.7,2-1.8,3.4l-7.9,77.6c-0.4,3.6-3.4,6.3-7,6.3H51.2  c-3.6,0-6.6-2.7-7-6.3l-7.9-77.6c-0.1-1.3-0.8-2.5-1.8-3.4c-0.9-0.7-2-1.1-3.1-1.1c-0.2,0-0.4,0-0.6,0c-0.6,0.1-1.2,0.1-1.9,0.1  c-8.7,0-15.8-7.1-15.8-15.9c0-4.3,1.7-8.3,4.8-11.3c3.1-3,7.2-4.6,11.4-4.5c4.2,0.1,8.1,1.9,11.1,5.1c3,3.1,4.5,7.3,4.4,11.4  c-0.2,4.6-2.4,8.9-6.1,11.8c-2.1,1.7-2.5,4.7-0.9,6.9c13.3,17.7,26.1,25.8,39.1,24.9c15.6-1.2,30.6-15.6,45.7-44  c0.6-1.2,0.8-2.6,0.3-3.8c-0.4-1.3-1.3-2.3-2.5-2.9c-3.9-1.9-6.9-5.5-8.2-9.6c-1.4-4.5-0.7-9.4,2-14c2.4-4.2,7.3-6.6,13.1-6.5  c0.4,0.1,0.8,0.1,1.2,0c5.8-0.1,10.7,2.2,13.1,6.5c2.7,4.7,3.4,9.5,2,14c-1.3,4.2-4.3,7.7-8.2,9.6c-1.2,0.6-2.1,1.6-2.5,2.9  c-0.4,1.3-0.3,2.7,0.3,3.8c15.1,28.4,30.1,42.8,45.7,44c13.1,1,25.9-7.1,39.1-24.9c1.6-2.2,1.2-5.2-0.9-6.9  c-3.7-2.9-5.9-7.2-6.1-11.8c-0.2-4.1,1.4-8.3,4.4-11.4c3-3.2,6.9-5,11.1-5.1h0c4.3-0.1,8.4,1.5,11.4,4.5c3.1,3,4.8,7,4.8,11.3  C242.9,103.1,235.8,110.2,227.1,110.2z" />
            </svg>{' '}
            React Hook Form
          </Title>
          <SyntaxHighlighter
            customStyle={{
              border: 'none',
            }}
            style={xonokai}
            language={'jsx'}
          >
            {reactHookFormCode}
          </SyntaxHighlighter>

          <Title>Formik</Title>
          <SyntaxHighlighter
            customStyle={{
              border: 'none',
            }}
            style={xonokai}
            language={'jsx'}
          >
            {formikCode}
          </SyntaxHighlighter>

          <Title>Redux Form</Title>
          <SyntaxHighlighter
            customStyle={{
              border: 'none',
            }}
            style={xonokai}
            language={'jsx'}
          >
            {reduxFormCode}
          </SyntaxHighlighter>

      </GridView>

  );
}


# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build
/dist

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*


keep keep keep keep keep replace replace keep keep keep keep keep


# React Hook Form

## Install and start

create a `.env` file and include the following line

`SKIP_PREFLIGHT_CHECK=true`

    $ yarn && yarn start
    or
    $ npm install && npm start


export default function track({ label, category, action }) {
  // @ts-ignore
  if (window.gtag) {
    // @ts-ignore
    window.gtag('event', 'click', {
      event_category: category,
      event_action: action,
      event_label: label,
    });
  }
}


import isRadioInput from './isRadioInput';

describe('isRadioInput', () => {
  it('should return true when type is radio', () => {
    expect(isRadioInput('radio')).toBeTruthy();
  });
});


keep keep keep replace replace keep keep keep

import { useRef, useState, useEffect } from 'react';
import getFieldsValues from './logic/getFieldsValues';
import getFieldValue from './logic/getFieldValue';
import validateField from './logic/validateField';
import findRemovedFieldAndRemoveListener from './logic/findRemovedFieldAndRemoveListener';
import attachEventListeners from './logic/attachEventListeners';
import validateWithSchema from './logic/validateWithSchema';
import combineFieldValues from './logic/combineFieldValues';
import shouldUpdateWithError from './logic/shouldUpdateWithError';
import warnMissingRef from './utils/warnMissingRef';
import modeChecker from './utils/validationModeChecker';
import onDomRemove from './utils/onDomRemove';
import isRadioInput from './utils/isRadioInput';
import isEmptyObject from './utils/isEmptyObject';
import filterUndefinedErrors from './utils/filterUndefinedErrors';
import {
  Props,
  Field,
  ErrorMessages,
  Ref,
  SubmitPromiseResult,
  FieldsObject,
  VoidFunction,
  UseFormFunctions,
  RegisterFunction,
  FieldValue,
  RegisterInput,
} from './types';
import isCheckBoxInput from './utils/isCheckBoxInput';

export default function useForm(
  { mode, validationSchema, defaultValues }: Props = {
    mode: 'onSubmit',
    defaultValues: {},
  },
): UseFormFunctions {
  const fieldsRef = useRef<FieldsObject>({});
  const errorsRef = useRef<ErrorMessages>({});
  const submitCountRef = useRef<number>(0);
  const touchedFieldsRef = useRef<string[]>([]);
  const watchFieldsRef = useRef<{ [key: string]: boolean }>({});
  const isWatchAllRef = useRef<boolean>(false);
  const isSubmittingRef = useRef<boolean>(false);
  const isSubmittedRef = useRef<boolean>(false);
  const isDirtyRef = useRef<boolean>(false);
  const reRenderForm = useState({})[1];
  const validateAndStateUpdateRef = useRef<Function>();

  validateAndStateUpdateRef.current = validateAndStateUpdateRef.current
    ? validateAndStateUpdateRef.current
    : async ({ target: { name }, type }: Ref): Promise<void> => {
        const fields = fieldsRef.current;
        const errors = errorsRef.current;
        const ref = fields[name];
        const isBlurType = type === 'blur';
        const { isOnChange, isOnSubmit, isOnBlur } = modeChecker(mode);
        const onSubmitModeNotSubmitted = !isSubmittedRef.current && (isOnSubmit || !mode);
        const isWatchAll = isWatchAllRef.current;
        const shouldUpdateWatchMode = isWatchAll || watchFieldsRef.current[name];
        const shouldUpdateValidateMode = isOnChange || (isOnBlur && isBlurType);
        let shouldUpdateState = isWatchAll;

        if (!isDirtyRef.current) {
          isDirtyRef.current = true;
          shouldUpdateState = true;
        }

        if (!touchedFieldsRef.current.includes(name)) {
          touchedFieldsRef.current.push(name);
          shouldUpdateState = true;
        }

        if (onSubmitModeNotSubmitted && shouldUpdateWatchMode) return reRenderForm({});

        if (validationSchema) {
          const result = getFieldsValues(fields);
          const schemaValidateErrors = (await validateWithSchema(validationSchema, result)) || {};
          const error = schemaValidateErrors[name];
          const shouldUpdate = ((!error && errors[name]) || error) && shouldUpdateValidateMode;

          if (shouldUpdate || shouldUpdateWatchMode) {
            const errorsCopy = { ...errors, ...{ [name]: error } };
            if (!error) delete errorsCopy[name];

            errorsRef.current = errorsCopy;
            return reRenderForm({});
          }
        } else {
          const error = await validateField(ref, fields);
          const shouldUpdate = shouldUpdateWithError({
            errors,
            error,
            onSubmitModeNotSubmitted,
            isOnBlur,
            isBlurType,
            name,
          });

          if (shouldUpdate || shouldUpdateValidateMode || shouldUpdateWatchMode) {
            const errorsCopy = { ...filterUndefinedErrors(errors), ...error };
            if (!error[name]) delete errorsCopy[name];

            errorsRef.current = errorsCopy;
            return reRenderForm({});
          }
        }

        if (shouldUpdateState) reRenderForm({});
      };

  const removeEventListener: Function = findRemovedFieldAndRemoveListener.bind(
    null,
    fieldsRef.current,
    validateAndStateUpdateRef.current,
  );

  const setValue = (name: string, value: string | number | boolean): void => {
    const field = fieldsRef.current[name];
    if (!field) return;
    const elm = field.ref;
    if (elm.type) elm[isCheckBoxInput(elm.type) ? 'checked' : 'value'] = value;
  };

  function registerIntoFieldsRef(elementRef, data = { required: false, validate: undefined }): void {
    if (elementRef && !elementRef.name) return warnMissingRef(elementRef);

    const { name, type, value } = elementRef;
    const { required, validate } = data;
    const inputData = {
      ...data,
      ref: elementRef,
    };
    const fields = fieldsRef.current;
    const isRadio = isRadioInput(type);
    const field = fields[name];
    const existRadioOptionIndex =
      isRadio && field && Array.isArray(field.options)
        ? field.options.findIndex(({ ref }): boolean => value === ref.value)
        : -1;

    if ((!isRadio && field) || (isRadio && existRadioOptionIndex > -1)) return;

    if (isRadio) {
      if (!field) fields[name] = { options: [], required, validate, ref: { type: 'radio', name } };
      if (validate) fields[name].validate = validate;

      (fields[name].options || []).push({
        ...inputData,
        mutationWatcher: onDomRemove(elementRef, (): Function => removeEventListener(inputData, true)),
      });
    } else {
      fields[name] = {
        ...inputData,
        mutationWatcher: onDomRemove(elementRef, (): Function => removeEventListener(inputData, true)),
      };
    }

    if (defaultValues && defaultValues[name]) {
      setValue(name, defaultValues[name]);
    }

    attachEventListeners({
      field: isRadio ? (fields[name].options || [])[(fields[name].options || []).length - 1] : fields[name],
      isRadio,
      validateAndStateUpdate: validateAndStateUpdateRef.current,
    });
  }

  function watch(
    filedNames?: string | string[] | undefined,
    defaultValue?: string | string[] | undefined,
  ): FieldValue | FieldValue[] | undefined {
    const watchFields = watchFieldsRef.current;

    if (typeof filedNames === 'string') {
      watchFields[filedNames] = true;
    } else if (Array.isArray(filedNames)) {
      filedNames.forEach(
        (name): void => {
          watchFields[name] = true;
        },
      );
    } else {
      isWatchAllRef.current = true;
      watchFieldsRef.current = {};
    }

    const values = getFieldsValues(fieldsRef.current, filedNames);
    const result = values === undefined || isEmptyObject(values) ? undefined : values;
    return result === undefined ? defaultValue : result;
  }

  function register(data: Ref | RegisterInput): RegisterFunction | undefined {
    if (!data) return;
    if (data.type) {
      if (!data.name) {
        warnMissingRef(data);
        return;
      }
      registerIntoFieldsRef(data);
    }

    return (ref): void => ref && registerIntoFieldsRef(ref, data);
  }

  const handleSubmit = (callback: (Object, e) => void): Function => async (e): Promise<void> => {
    if (e) {
      e.preventDefault();
      e.persist();
    }
    let fieldErrors;
    let fieldValues;
    const fields = fieldsRef.current;
    const currentFieldValues = Object.values(fields);
    isSubmittingRef.current = true;
    reRenderForm({});

    if (validationSchema) {
      fieldValues = getFieldsValues(fields);
      fieldErrors = await validateWithSchema(validationSchema, fieldValues);
    } else {
      const { errors, values }: SubmitPromiseResult = await currentFieldValues.reduce(
        async (previous: Promise<SubmitPromiseResult>, field: Field): Promise<SubmitPromiseResult> => {
          const resolvedPrevious = await previous;
          const {
            ref,
            ref: { name },
          } = field;

          if (!fields[name]) return Promise.resolve(resolvedPrevious);

          const fieldError = await validateField(field, fields);

          if (fieldError[name]) {
            resolvedPrevious.errors = { ...(resolvedPrevious.errors || {}), ...fieldError };
            return Promise.resolve(resolvedPrevious);
          }

          resolvedPrevious.values[name] = getFieldValue(fields, ref);
          return Promise.resolve(resolvedPrevious);
        },
        Promise.resolve({
          errors: {},
          values: {},
        }),
      );

      fieldErrors = {
        ...errors,
        ...filterUndefinedErrors(errorsRef.current),
      };
      fieldValues = values;
    }

    isSubmittedRef.current = true;
    submitCountRef.current += 1;
    isSubmittingRef.current = false;

    if (isEmptyObject(fieldErrors)) {
      callback(combineFieldValues(fieldValues), e);
    } else {
      errorsRef.current = fieldErrors;
      reRenderForm({});
    }
  };

  const unSubscribe = (): void => {
    fieldsRef.current &&
      Object.values(fieldsRef.current).forEach(
        (field: Field): void => {
          const { ref, options } = field;
          isRadioInput(ref.type) && Array.isArray(options)
            ? options.forEach((fieldRef): void => removeEventListener(fieldRef, true))
            : removeEventListener(field, true);
        },
      );
    fieldsRef.current = {};
    watchFieldsRef.current = {};
    errorsRef.current = {};
    isWatchAllRef.current = false;
    isSubmittedRef.current = false;
    isDirtyRef.current = false;
    touchedFieldsRef.current = [];
  };

  const reset = (): void => {
    Object.values(fieldsRef.current)[0]
      .ref.closest('form')
      .reset();
    unSubscribe();
    reRenderForm({});
  };

  const setError = (name: string, type: string, message?: string, ref?: Ref): void => {
    errorsRef.current[name] = {
      type,
      message,
      ref,
      isManual: true,
    };
    reRenderForm({});
  };

  useEffect((): VoidFunction => unSubscribe, [mode]);

  return {
    register,
    handleSubmit,
    watch,
    unSubscribe,
    reset,
    setError,
    setValue,
    errors: errorsRef.current,
    formState: {
      dirty: isDirtyRef.current,
      isSubmitted: isSubmittedRef.current,
      submitCount: submitCountRef.current,
      touched: touchedFieldsRef.current,
      isSubmitting: isSubmittingRef.current,
    },
  };
}


import React from 'react';
import styled from 'styled-components';
import formik from './assets/formik.png';
import hookFrom from './assets/hookForm.png';
import reduxForm from './assets/reduxForm.png';
import { H1, Title } from './styles/typography';
import Link from './styles/link';
import { SimpleImg } from 'react-simple-img';
import colors from './styles/colors';

const Wrapper = styled.section`
  text-align: center;

  & > p {
    max-width: 1033px;
    display: block;
    margin: 0 auto;
  }

  & > img {
    max-width: 100%;
    border-radius: 4px;

    @media (min-width: 768px) {
      border-radius: 10px;
    }
  }

  & > h2 {
    margin-left: auto;
    margin-right: auto;
    max-width: 600px;
  }
`;

const ImgSection = styled.div`
  display: flex;
  flex-direction: column;

  @media (min-width: 768px) {
    flex-direction: row;
  }

  & > img {
    align-self: center;
    border-radius: 4px;
    max-width: 100%;
    margin: 20px 0;

    @media (min-width: 768px) {
      max-width: 80%;
      margin: 0;
      margin-bottom: 40px;
    }
  }

  & ul {
    max-width: 100%;
    list-style: none;
    min-width: 250px;
    padding-left: 0;
    margin: 0;

    & > li {
      text-align: left;
      padding: 2px 0;
    }
  }
`;

export default function CodePerfCompareSection() {
  return (
    <Wrapper>

Performance Comparison

        The following results are captured under 6x CPU slow down on App start with Chrome devtool's performance tab,
        and code is from section above 'Library Code Comparison'.

      <Title
        style={{
          marginTop: 50,
        }}
      >
        React Hook Form
      </Title>
      <ImgSection>

No. of mount: 1

No. of committing change: 1

            Total time:{' '}

              1800ms

        <SimpleImg src={hookFrom} placeholder={false} alt="React Hook Form performance" />
      </ImgSection>
      <Title>Formik</Title>
      <ImgSection>

No. of mount: 6

No. of committing change: 1

            Total time:{' '}

              2070ms

        <SimpleImg src={formik} placeholder={false} alt="Formik performance" />
      </ImgSection>
      <Title>Redux Form</Title>
      <ImgSection>

No. of mount: 17

No. of committing change: 2

No. of update: 2

            Total time:{' '}

              2380ms

        <SimpleImg src={reduxForm} placeholder={false} alt="Redux Form performance" />
      </ImgSection>

        ⚠ Wants to see more intense performance test?{' '}
        <Link href="https://github.com/bluebill1049/react-hook-form-performance-compare">
          Check out the result for 1000 fields within a form here
        </Link>
        .

    </Wrapper>
  );
}


import styled from 'styled-components';
import colors from './colors';

export const SubHeading = styled.p`
  font-size: 13px;
  padding: 0 20px;
  margin-top: 0;
  text-align: center;
  color: ${colors.lightPink};

  @media (min-width: 768px) {
    font-size: 16px;
    margin-bottom: 30px;
  }
`;

export const Heading = styled.h1`
  font-weight: 100;
  text-align: center;
  margin-bottom: 10px;

  @media (min-width: 768px) {
    font-size: 55px !important;
    margin-bottom: 10px;
  }
`;

export const H1 = styled.h1`
  font-size: 24px;
  display: inline-block;
  margin: 40px auto 30px;
  font-weight: 300;
  padding-bottom: 5px;
  border-bottom: 2px solid ${colors.secondary};

  @media (min-width: 768px) {
    font-size: 36px;
  }
`;

export const H5 = styled.h5`
  font-size: 18px;
  text-align: center;
  font-weight: 500;
  border-bottom: 1px solid ${colors.lightPink};
  margin: 0;
  padding-bottom: 8px;

  & > code {
    font-size: 18px !important;
  }
`;

export const HeadingWithTopMargin = styled(Heading)`
  @media (min-width: 768px) {
    margin-top: 30px;
  }
`;

export const Title = styled.h2`
  text-align: center;
  font-weight: 200;
  margin-top: 10px;
  font-size: 1.1rem;
  overflow: hidden;
  margin-bottom: 30px;
  position: relative;

  @media (min-width: 768px) {
    margin-top: 20px;
    font-size: 1.5rem;
  }

  &:before,
  &:after {
    background-color: #4f6294;
    content: '';
    display: inline-block;
    height: 1px;
    position: relative;
    vertical-align: middle;
    width: 50%;
  }

  &:before {
    right: 0.5em;
    margin-left: -50%;
  }

  &:after {
    left: 0.5em;
    margin-right: -50%;
  }
`;

export const Error = styled.p`
  font-size: 12px;
  color: ${colors.lightPink};
`;


Fixes #

## Proposed Changes

  -
  -
  -


if (elm.type) elm[isCheckBoxInput(elm.type) ? 'checked' : 'value'] = value;

function registerIntoFieldsRef(elementRef, data = { required: false, validate: undefined }): void {

if (elementRef && !elementRef.name) return warnMissingRef(elementRef);

const { name, type, value } = elementRef;

function registerIntoFieldsRef(elementRef, data: RegisterInput | undefined): void {


export default str => {
  const el = document.createElement('textarea');
  el.value = str;
  el.setAttribute('readonly', '');
  el.style.position = 'absolute';
  el.style.left = '-9999px';
  document.body.appendChild(el);
  el.select();
  document.execCommand('copy');
  document.body.removeChild(el);
};


keep keep keep replace keep keep keep keep

const { required = false, validate = undefined } = data || {};


import filterUndefinedErrors from './filterUndefinedErrors';

describe('filterUndefinedErrors', () => {
  it('should filter out undefined errors fields', () => {
    const errors = {
      test: { type: 'test' },
      bill: { type: undefined },
    };
    // @ts-ignore
    expect(filterUndefinedErrors(errors)).toEqual({ test: { type: 'test' } });
  });
});


import React from 'react';
import ReactDOM from 'react-dom';
import useForm from 'react-hook-form';

function App() {
  const { register, errors, handleSubmit } = useForm({
    mode: 'onChange',
  });
  const onSubmit = data => {
    alert(JSON.stringify(data));
  };

  return (

      <form onSubmit={handleSubmit(onSubmit)}>

          First Name
          <input name="firstName" placeholder="bill" ref={register({ required: true })} />
          {errors.firstName && 'This is required'}

          Last Name
          <input name="lastName" placeholder="luo" ref={register({ required: true })} />
          {errors.lastName && 'This is required'}

            Email

          <input name="email" ref={register({ required: true })} />
          {errors.email && 'This is required'}

        <button type="submit">Submit</button>
      </form>

  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);


? field.options.findIndex(({ ref }): boolean => value === ref.value)

if ((!isRadio && field) || (isRadio && existRadioOptionIndex > -1)) return;

if (!field) fields[name] = { options: [], required, validate, ref: { type: 'radio', name } };

if (!type || (!isRadio && field) || (isRadio && existRadioOptionIndex > -1)) return;