import { VListTileAction } from '~components/VList'
import { test } from '~util/testing'

test('VListTileAction.js', ({ mount, functionalContext }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VListTileAction, functionalContext())

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


const input = this.$createElement('input', {

keep keep keep add keep keep keep keep keep

const value = JSON.stringify(this.value)

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

/** Themes */
date-picker($material)
  .picker--date__years
    background: $material.cards

    li
      color: $material.text.primary

      &.active
        color: $theme.accent

  .picker--date__header
    color: $material.text.primary

  .picker--date__table
    table
      th
        color: $material.text.primary

      .btn
        color: $material.text.primary

        &--current
          color: $theme.accent

        &--active
          color: $material.bg-color

        &--floating
          &:after
            background: $theme.accent !important

theme(date-picker, "picker--date")

.picker--date
  color: #fff
  width: 100%

/** Years */
.picker--date__years
  background: #fff
  color: #000
  font-size: 18px
  font-weight: 400
  list-style-type: none
  max-height: 290px
  overflow: auto
  padding: 0
  text-align: center

  li
    cursor: pointer
    margin: 16px 0
    transition: .3s $transition.swing

    &:hover
      color: $theme.primary

    &.active
      color: $theme.primary
      font-size: 24px
      font-weight: 500
      margin: 20px 0

/** Title */
.picker--date__title
  justify-content: space-between
  flex-direction: column
  flex-wrap: wrap

  &-year
    align-items: center
    display: inline-flex
    font-size: 14px

  &-date
    font-size: 34px
    text-align: left

    > div
      position: relative

  &-year, &-date
    font-weight: 500
    transition: .3s $transition.swing
    width: 100%

    &:not(.active)
      cursor: pointer
      opacity: .6

    &:hover
      opacity: 1

/** Header */
.picker--date__header
  color: #000
  padding: 4px 16px

  &-selector
    align-items: center
    display: flex
    justify-content: space-between
    position: relative

    .btn
      color: #000
      margin: 0

    .icon
      cursor: pointer
      user-select: none

    &-date
      flex: 1
      text-align: center
      position: relative
      overflow: hidden

      strong
        transition: .3s $transition.swing
        display: block
        width: 100%

.picker--date
  &__table
    position: relative

  table
    transition: .3s $transition.swing
    top: 0

    thead
      th
        padding: 8px 0

    th
      color: rgba(#000, .54)
      font-weight: 600
      font-size: 12px

    th, td
      text-align: center
      width: 45px

    .btn
      margin: 0

      &:hover, .btn__content:before
        background-color: transparent !important

      &:after
        position: absolute
        content: ''
        left: 0
        top: 0
        height: 100%
        width: 100%

      &__content
        overflow: visible
        transition: none
        z-index: 1

    .btn.btn--current:not(.btn--active)
      color: $theme.primary

    .btn.btn--floating
      height: 32px
      width: 32px

      &:after
        background: $theme.primary !important
        opacity: 0
        transform: scale(0)

      &:not(.btn--active):hover
        color: #fff

        &:after
          opacity: .6
          transform: scale(1)

    .btn.btn--floating.btn--active
      color: #fff

      &:after
        background: $theme.primary !important
        opacity: 1
        transform: none

.picker--landscape
  .picker--date__years
    margin-left: 170px
    width: 330px


<script>
  import { VFadeTransition } from '../transitions'
  import VIcon from '../VIcon'

  import Colorable from '../../mixins/colorable'
  import TabFocusable from '../../mixins/tab-focusable'
  import Themeable from '../../mixins/themeable'

  import Ripple from '../../directives/ripple'

  export default {
    name: 'v-radio',

    inheritAttrs: false,

    inject: ['isMandatory'],

    components: {
      VFadeTransition,
      VIcon
    },

    mixins: [Colorable, TabFocusable, Themeable],

    directives: { Ripple },

    props: {
      disabled: Boolean,
      value: [String, Number],
      label: String
    },

    data () {
      return {
        isActive: false
      }
    },

    computed: {
      classes () {
        return this.addColorClassChecks({
          'input-group': true,
          'input-group--active': this.isActive,
          'input-group--selection-controls': true,
          'input-group--tab-focused': this.tabFocused,
          'radio': true,
          'theme--dark': this.dark,
          'theme--light': this.light
        })
      },

      icon () {
        return this.isActive ? 'radio_button_checked' : 'radio_button_unchecked'
      }
    },

    methods: {
      genInput (radio) {
        const input = this.$createElement('input', {
          ref: 'input',
          style: {
            display: 'none'
          },
          attrs: Object.assign({
            name: 'test', // from parent?
            id: this.id,
            type: 'radio',
            value: this.value
          }, this.$attrs)
        }, [this.value])

        radio.push(input)

        return this.$createElement('div', {
          class: 'input-group__input'
        }, radio)
      },
      genWrapper (radio) {
        const children = []

        children.push(this.genLabel())
        children.push(this.genInput(radio))

        return this.$createElement('div', {
          class: this.classes,
          attrs: {
            role: 'radio',
            'aria-checked': this.isActive && 'true' || 'false',
            'aria-label': this.label
          },
          on: {
            keydown: e => {
              if ([13, 32].includes(e.keyCode)) {
                e.preventDefault()
                this.click()
              }
            },
            blur: e => {
              this.$emit('blur', e)
              this.tabFocused = false
            }
          }
        }, children)
      },
      genLabel () {
        return this.$createElement('label', {
          on: {
            click: this.click
          }
        }, this.$slots.label || this.label)
      },
      click () {
        const mandatory = this.isMandatory &&
          this.isMandatory() || false

        if (!this.disabled && (!this.isActive || !mandatory)) {
          this.$refs.input.checked = true
          this.isActive = true
          this.$emit('change', this.value)
        }
      }
    },

    created() {
      // Semantic check to help people identify the reason for the inject error above it.
      if (!this.$parent || !this.$parent.$vnode || !this.$parent.$vnode.tag ||
        !this.$parent.$vnode.tag.endsWith("v-radio-group")) {
        console.warn("[Vuetify] Warn: The v-radio component must have an immediate parent of v-radio-group.")
      }
    },

    render (h) {
      const transition = h('v-fade-transition', {}, [
        h('v-icon', {
          'class': {
            'icon--radio': this.isActive
          },
          key: this.icon
        }, this.icon)
      ])

      const ripple = h('div', {
        'class': 'input-group--selection-controls__ripple',
        on: Object.assign({}, {
          click: this.click
        }, this.$listeners),
        directives: [
          {
            name: 'ripple',
            value: { center: true }
          }
        ]
      })

      return this.genWrapper([transition, ripple])
    }
  }
</script>


<script>
  import Bootable from '../../mixins/bootable'
  import Detachable from '../../mixins/detachable'
  import Overlayable from '../../mixins/overlayable'
  import Toggleable from '../../mixins/toggleable'

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

  export default {
    name: 'v-dialog',

    mixins: [Bootable, Detachable, Overlayable, Toggleable],

    directives: {
      ClickOutside
    },

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

    computed: {
      classes () {
        return {
          [(`dialog ${this.contentClass}`).trim()]: true,
          'dialog--active': this.isActive,
          'dialog--persistent': this.persistent,
          'dialog--fullscreen': this.fullscreen,
          'dialog--stacked-actions': this.stackedActions && !this.fullscreen,
          'dialog--scrollable': this.scrollable
        }
      }
    },

    watch: {
      isActive (val) {
        if (val) {
          !this.fullscreen && !this.hideOverlay && this.genOverlay()
          this.fullscreen && this.hideScroll()
          this.$refs.content.focus()
        } else {
          if (!this.fullscreen) this.removeOverlay()
          else this.showScroll()
        }
      }
    },

    mounted () {
      this.isBooted = this.isActive
      this.$vuetify.load(() => (this.isActive && this.genOverlay()))
    },

    methods: {
      closeConditional (e) {
        // close dialog if !persistent and clicked outside
        return !this.persistent
      }
    },

    render (h) {
      const children = []
      const data = {
        'class': this.classes,
        ref: 'dialog',
        directives: [
          { name: 'click-outside', value: this.closeConditional },
          { name: 'show', value: this.isActive }
        ]
      }

      if (!this.fullscreen) {
        data.style = {
          width: isNaN(this.width) ? this.width : `${this.width}px`
        }
      }

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

      const dialog = h('transition', {
        props: {
          name: this.transition || '', // If false, show nothing
          origin: this.origin
        }
      }, [h('div', data,
        this.lazy && this.isBooted || !this.lazy ? this.$slots.default : null
      )])

      children.push(h('div', {
        'class': 'dialog__content',
        ref: 'content'
      }, [dialog]))

      return h('div', {
        'class': 'dialog__container',
        style: {
          display: !this.$slots.activator && 'none' || this.fullWidth ? 'block' : 'inline-block'
        }
      }, children)
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_dialogs.styl"></style>


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

textfield($material)
  &.input-group--text-field-box
    .input-group__input
      background: $material.buttons.focused-alt

theme(textfield, "input-group--text-field")

/** Label */
.input-group--text-field label
  position: absolute
  top: $input-group-text-field-label-top
  left: 0

/** Input */
.input-group--text-field
  input,
  textarea
    caret-color: $theme.primary
    font-size: $input-font-size

.input-group--text-field input
  box-shadow: none
  flex: 1
  height: $input-height
  margin: 0
  min-width: 0
  overflow: hidden
  text-overflow: ellipsis
  white-space: nowrap

  &::placeholder
    transition: .3s $transition.swing

  + .icon
    padding: 0 6px
    transition: .3s $transition.ease-in-out

  &:focus
    outline: none

  &:disabled
    pointer-events: none

/** Textarea */
.input-group--text-field
  textarea
    flex: 1 1

    &:focus
      outline: none

.input-group--text-field.input-group--textarea
  label
    top: 13px

  textarea
    border-radius: 2px
    font-size: $input-font-size
    transition: .3s $transition.swing

  &:not(.input-group--full-width)
    label
      top: 30px
      left: 15px

    textarea
      padding: 30px 13px 0

  .input-group__details
    &:before,
    &:after
      opacity: 0

/** Counter */
.input-group--text-field
  .input-group__counter
    margin-left: auto

    &--error
      color: $theme.error !important

/** Input States */
.input-group--text-field
  &.input-group--placeholder:not(.input-group--focused):not(.input-group--dirty)
    input::placeholder
      opacity: 0

/** Types */
.input-group--text-field
  // Icons
  &.input-group--prepend-icon
    .input-group__prepend-icon
      align-items: center
      display: flex
      justify-content: flex-start
      min-width: 40px
      transition: .3s $transition.fast-in-fast-out

    .input-group__details
      margin-left: auto
      padding-left: 40px

      &:before, &:after
        margin-left: 40px
        max-width: calc(100% - 40px)

    label
      left: 40px

    input
      flex: auto

  &:not(.input-group--single-line)
    &.input-group--focused,
    &:focus
      label
        opacity: 1
        max-width: 100%

      &:not(.input-group--textarea)
        label
          transform: translate3d(0, -18px, 0) scale(.75)

      &:not(.input-group--full-width).input-group--textarea
        label
          transform: translate3d(0, -8px, 0) scale(.75)

  &.input-group--dirty
    &:not(.input-group--textarea)
      label
        transform: translate3d(0, -18px, 0) scale(.75)

    &:not(.input-group--full-width).input-group--textarea
      label
        transform: translate3d(0, -8px, 0) scale(.75)

  // Single Line
  &.input-group--single-line
    label
      transform: translate3d(0,0,0)

    &.input-group--dirty
      label
        display: none

  // Multi Line
  &.input-group--multi-line
    textarea
      padding-top: 4px

  // Full Width
  &.input-group--full-width
    padding: 16px

    label
      margin-left: $grid-gutters.lg

    .input-group__details:before, .input-group__details:after
      display: none

  // Solo
  &.input-group--solo
    background: #fff
    height: 46px
    border-radius: $card-border-radius
    padding: 0 16px
    elevation(2)

    input
      margin-top: 8px

    label
      top: 8px
      left: 50px
      transform: none !important

    &:hover
      elevation(4)

/** Prefix/Suffix */
.input-group--prefix
  &:not(.input-group--focused):not(.input-group--dirty)
    label
      left: 16px

.input-group--prefix,
.input-group--suffix
  .input-group--text-field__prefix,
  .input-group--text-field__suffix
    align-items: center
    display: inline-flex
    font-size: 16px
    margin-top: 1px

  .input-group--text-field__prefix
    margin-right: 3px

  .input-group--text-field__suffix
    margin-left: 3px

/** Box */
.input-group--text-field-box
  input, textarea
    cursor: pointer

  label
    left: 24px

  .input-group__input
    align-items: flex-end
    border-radius: 4px 4px 0 0
    cursor: pointer
    min-height: 56px

  .input-group__details
    padding: 8px 24px 0

    &:before,
    &:after
      height: 2px
      border-bottom-left-radius: 4px
      border-bottom-right-radius: 4px

  &.input-group--multi-line
    textarea
      padding-left: 24px
      padding-right: 24px

  &:not(.input-group--textarea)
    &.input-group--multi-line
      .input-group__input
        padding-top: 26px

      label
        top: 26px

    &:not(.input-group--multi-line)
      .input-group__input
        padding-left: 24px
        padding-right: 24px

      label
        top: calc(50% - 16px)

    &:not(.input-group--single-line)
      &.input-group--focused,
      &:focus,
      &.input-group--dirty
        label
          transform: translate3d(0, -10px, 0) scale(0.75)


<script>
  import Themeable from '../../mixins/themeable'

  export default {
    name: 'v-stepper',

    mixins: [Themeable],

    provide () {
      return {
        stepClick: this.stepClick
      }
    },

    data () {
      return {
        inputValue: null,
        isBooted: false,
        steps: [],
        content: [],
        isReverse: false
      }
    },

    props: {
      nonLinear: Boolean,
      altLabels: Boolean,
      vertical: Boolean,
      value: [Number, String]
    },

    computed: {
      classes () {
        return {
          'stepper': true,
          'stepper--is-booted': this.isBooted,
          'stepper--vertical': this.vertical,
          'stepper--alt-labels': this.altLabels,
          'stepper--non-linear': this.nonLinear,
          'theme--dark': this.dark,
          'theme--light': this.light
        }
      }
    },

    watch: {
      inputValue (val, prev) {
        this.isReverse = Number(val) < Number(prev)
        this.steps.forEach(i => i.toggle(this.inputValue))
        this.content.forEach(i => i.toggle(this.inputValue, this.isReverse))

        this.$emit('input', this.inputValue)
        prev && (this.isBooted = true)
      },
      value () {
        this.inputValue = this.value
      }
    },

    mounted () {
      this.$vuetify.load(this.init)
    },

    methods: {
      init () {
        this.$children.forEach(i => {
          if (i.$options._componentTag === 'v-stepper-step') {
            this.steps.push(i)
          } else if (i.$options._componentTag === 'v-stepper-content') {
            i.isVertical = this.vertical
            this.content.push(i)
          }
        })

        this.inputValue = this.value || this.steps[0].step || 1
      },
      stepClick (step) {
        this.inputValue = step
      }
    },

    render (h) {
      return h('div', {
        'class': this.classes
      }, this.$slots.default)
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_steppers.styl"></style>


}, this.$attrs)
}, [this.value])
radio.push(input)

<script>
  import Input from '../../mixins/input'

  export default {
    name: 'v-radio-group',

    mixins: [Input],

    model: {
      prop: 'inputValue',
      event: 'change'
    },

    provide () {
      return {
        isMandatory: () => this.mandatory
      }
    },

    props: {
      column: {
        type: Boolean,
        default: true
      },
      inputValue: [String, Number],
      mandatory: {
        type: Boolean,
        default: true
      },
      row: Boolean
    },

    data () {
      return {
        internalTabIndex: -1
      }
    },

    watch: {
      inputValue (val) {
        this.getRadios().forEach((radio) => {
          radio.isActive = val === radio.value
        })
      }
    },

    computed: {
      classes () {
        return {
          'radio-group': true,
          'radio-group--column': this.column,
          'radio-group--row': this.row
        }
      }
    },

    methods: {
      getRadios () {
        return this.$children
          .filter((child) => child.$el.classList.contains('radio'))
      },
      toggle (value) {
        if (this.disabled) {
          return
        }

        this.shouldValidate = true
        this.$emit('change', value)
        this.$nextTick(() => this.validate())

        this.getRadios()
          .filter(r => r.value !== value)
          .forEach(r => r.isActive = false)
      },
      radioBlur (e) {
        if (!e.relatedTarget || !e.relatedTarget.classList.contains('radio')) {
          this.shouldValidate = true
          this.$emit('blur', this.inputValue)
        }
      },
    },

    mounted () {
      this.getRadios().forEach((radio) => {
        radio.isActive = this.inputValue === radio.value
        radio.$el.tabIndex = radio.$el.tabIndex > 0 ? radio.$el.tabIndex : 0
        radio.$on('change', this.toggle)
        radio.$on('blur', this.radioBlur)
        radio.$on('focus', this.radioFocus)
      })
    },

    beforeDestroy () {
      this.getRadios().forEach((radio) => {
        radio.$off('change', this.toggle)
        radio.$off('blur', this.radioBlur)
        radio.$off('focus', this.radioFocus)
      })
    },

    render (h) {
      const data = {
        attrs: {
          role: 'radiogroup'
        }
      }
      return this.genInputGroup(this.$slots.default, data)
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_input-groups.styl"></style>
<style lang="stylus" src="../../stylus/components/_selection-controls.styl"></style>

<style lang="stylus" src="../../stylus/components/_radio-group.styl"></style>


[data-tooltip]
  position: relative

  &:before
    background: $grey.darken-2
    border-radius: 2px
    color: #FFFFFF
    content: attr(data-tooltip)
    font-size: 12px
    display: inline-block
    opacity: 0
    padding: 5px 8px
    position: absolute
    pointer-events: none
    text-transform: initial
    transition: .15s $transition.swing
    width: auto
    white-space: pre
    z-index: 99
    elevation(2)

  &:hover
    &:before
      opacity: 0.9

  &[data-tooltip-location='bottom']
    &:before
      top: 100%
      left: 50%
      transform: translate3d(-50%, -14px, 0) scale(0)
      transform-origin: center top

    &:hover
      &:before
        transform: translate3d(-50%, 14px, 0) scale(1)

  &[data-tooltip-location='top']
    &:before
      bottom: 100%
      left: 50%
      transform: translate3d(-50%, 14px, 0) scale(0)
      transform-origin: center bottom

    &:hover
      &:before
        transform: translate3d(-50%, -14px, 0) scale(1)

  &[data-tooltip-location='left']
    &:before
      right: 100%
      transform: translate3d(14px, 0, 0) scale(0)
      transform-origin: center right

    &:hover
      &:before
        transform: translate3d(-14px, 0, 0) scale(1)

  &[data-tooltip-location='right']
    &:before
      left: 100%
      transform: translate3d(-14px, 0, 0) scale(0)
      transform-origin: center left

    &:hover
      &:before
        transform: translate3d(14px, 0, 0) scale(1)

  @media $display-breakpoints.sm-and-down
    &:before
      padding: 10px 16px

    &[data-tooltip-location='bottom']
      &:hover
        &:before
          transform: translate3d(-50%, 24px, 0) scale(1)

    &[data-tooltip-location='top']
      &:hover
        &:before
          transform: translate3d(-50%, -24px, 0) scale(1)

    &[data-tooltip-location='left']
      &:hover
        &:before
          transform: translate3d(-24px, 0, 0) scale(1)

    &[data-tooltip-location='right']
      &:hover
        &:before
          transform: translate3d(24px, 0, 0) scale(1)


import { test } from '~util/testing'
import { VFlex } from '~components/VGrid'

const id = 'flex'

test('VFlex', ({ mount, functionalContext }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VFlex, functionalContext())

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

  it('should render component with custom id and match snapshot', () => {
    const wrapper = mount(VFlex, functionalContext({
      propsData: {
        id
      }
    }))

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


export default {
  data () {
    return {
      errorBucket: [],
      hasFocused: false,
      hasInput: false,
      shouldValidate: false,
      valid: false
    }
  },

  props: {
    error: {
      type: Boolean,
      default: true
    },
    errorMessages: {
      type: [String, Array],
      default: () => []
    },
    rules: {
      type: Array,
      default: () => []
    },
    validateOnBlur: Boolean
  },

  computed: {
    validations () {
      if (!Array.isArray(this.errorMessages)) {
        return [this.errorMessages]
      } else if (this.errorMessages.length > 0) {
        return this.errorMessages
      } else if (this.shouldValidate) {
        return this.errorBucket
      } else {
        return []
      }
    },
    hasError () {
      return this.validations.length > 0 ||
        this.errorMessages.length > 0
    }
  },

  watch: {
    rules: {
      handler (newVal, oldVal) {
        // TODO: This handler seems to trigger when input changes, even though
        // rules array stays the same? Solved it like this for now
        if (newVal.length === oldVal.length) return

        this.validate()
      },
      deep: true
    },
    inputValue (val) {
      // If it's the first time we're setting input,
      // mark it with hasInput
      if (!!val && !this.hasInput) this.hasInput = true

      if (this.hasInput && !this.validateOnBlur) this.shouldValidate = true
    },
    focused (val) {
      // If we're not focused, and it's the first time
      // we're defocusing, set shouldValidate to true
      if (!val && !this.hasFocused) {
        this.hasFocused = true
        this.shouldValidate = true

        this.$emit('update:error', this.errorBucket.length > 0)
      }
    },
    hasError (val) {
      if (this.shouldValidate) {
        this.valid = !val
        this.$emit('update:error', val)
      }
    }
  },

  mounted () {
    this.validate()
  },

  methods: {
    reset () {
      // TODO: Do this another way!
      // This is so that we can reset all types of inputs
      this.$emit('input', null)
      this.$emit('change', null)

      this.$nextTick(() => {
        this.shouldValidate = false
        this.hasFocused = false
      })
    },
    validate (force = false, value) {
      if (force) this.shouldValidate = true

      this.errorBucket = []

      this.rules.forEach(rule => {
        const valid = typeof rule === 'function'
          ? rule(typeof value !== 'undefined' ? value : this.inputValue)
          : rule

        if (valid !== true) {
          this.errorBucket.push(valid)
        }
      })
    }
  }
}


props: {
column: {
type: Boolean,
default: true
},
inputValue: [String, Number],
mandatory: {
type: Boolean,
default: true

keep keep keep keep keep replace keep keep keep


export default {
  props: {
    absolute: Boolean,
    bottom: Boolean,
    fixed: Boolean,
    left: Boolean,
    right: Boolean,
    top: Boolean
  }
}


export function createSimpleFunctional (c, el = 'div') {
  const name = c.replace(/__/g, '-')
  return {
    name: `v-${name}`,
    functional: true,

    render: (h, { data, children }) => {
      data.staticClass = (`${c} ${data.staticClass || ''}`).trim()

      return h(el, data, children)
    }
  }
}

export function createSimpleTransition (name, origin = 'top center 0', mode) {
  return {
    name,

    functional: true,

    props: {
      origin: {
        type: String,
        default: origin
      }
    },

    render (h, context) {
      context.data = context.data || {}
      context.data.props = { name }
      context.data.on = context.data.on || {}

      if (mode) context.data.props.mode = mode

      context.data.on.beforeEnter = el => {
        el.style.transformOrigin = origin
        el.style.webkitTransformOrigin = origin
      }

      return h('transition', context.data, context.children)
    }
  }
}

export function createJavaScriptTransition (name, functions, css = true, mode = 'in-out') {
  return {
    name,

    functional: true,

    props: {
      css: {
        type: Boolean,
        default: css
      },
      mode: {
        type: String,
        default: mode
      }
    },

    render (h, context) {
      const data = {
        props: {
          ...context.props,
          name
        },
        on: functions
      }

      return h('transition', data, context.children)
    }
  }
}

export function directiveConfig (binding, defaults = {}) {
  return Object.assign({},
    defaults,
    binding.modifiers,
    { value: binding.arg },
    binding.value || {}
  )
}

export function addOnceEventListener (el, event, cb) {
  var once = () => {
    cb()
    el.removeEventListener(event, once, false)
  }

  el.addEventListener(event, once, false)
}

export function getObjectValueByPath (obj, path) {
  // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621
  if (!path || path.constructor !== String) return
  path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
  path = path.replace(/^\./, '')           // strip a leading dot
  const a = path.split('.')
  for (var i = 0, n = a.length; i < n; ++i) {
    var k = a[i]
    if (obj instanceof Object && k in obj) {
      obj = obj[k]
    } else {
      return
    }
  }
  return obj
}

export function createRange (length) {
  return [...Array.from({ length }, (v, k) => k)]
}


code
  background: $grey.base
  color: #bd4147
  display: inline-block
  background-color: $grey.lighten-4
  border-radius: 3px
  white-space: pre-wrap
  font-size: 85%
  font-weight: 100 !important
  font-weight: 900 !important

  &:after,
  &:before
    content: "\00a0"
    letter-spacing: -1px

kbd
  @extend code
  background: $grey.darken-3
  color: #fff


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

keep keep keep keep keep replace keep keep keep keep

const { register, formState } = useForm();

describe('ConditionalField', () => {
  it('should reflect correct form state and data collection', () => {
    cy.visit('http://localhost:3000/conditionalField');
    cy.get('#state').contains(
      '{"dirtyFields":[],"isSubmitted":false,"submitCount":0,"touched":[],"isDirty":false,"isSubmitting":false,"isValid":false}',
    );

    cy.get('select[name="selectNumber"]').select('1');
    cy.get('input[name="firstName"]').type('bill');
    cy.get('input[name="lastName"]').type('luo');
    cy.get('input[name="lastName"]').blur();
    cy.get('#state').contains(
      '{"dirtyFields":["selectNumber","firstName","lastName"],"isSubmitted":false,"submitCount":0,"touched":["selectNumber","firstName","lastName"],"isDirty":true,"isSubmitting":false,"isValid":true}',
    );
    cy.get('button#submit').click();
    cy.get('#state').contains(
      '{"dirtyFields":["selectNumber","firstName","lastName"],"isSubmitted":true,"submitCount":1,"touched":["selectNumber","firstName","lastName"],"isDirty":true,"isSubmitting":false,"isValid":true}',
    );
    cy.get('#result').contains(
      '{"selectNumber":"1","firstName":"bill","lastName":"luo"}',
    );

    cy.get('select[name="selectNumber"]').select('2');
    cy.get('#state').contains(
      '{"dirtyFields":["selectNumber"],"isSubmitted":true,"submitCount":1,"touched":["selectNumber"],"isDirty":false,"isSubmitting":false,"isValid":false}',
    );
    cy.get('input[name="min"]').type('10');
    cy.get('input[name="max"]').type('2');
    cy.get('input[name="max"]').blur();
    cy.get('#state').contains(
      '{"dirtyFields":["selectNumber","min","max"],"isSubmitted":true,"submitCount":1,"touched":["selectNumber","min","max"],"isDirty":true,"isSubmitting":false,"isValid":true}',
    );
    cy.get('button#submit').click();
    cy.get('#state').contains(
      '{"dirtyFields":["selectNumber","min","max"],"isSubmitted":true,"submitCount":2,"touched":["selectNumber","min","max"],"isDirty":true,"isSubmitting":false,"isValid":true}',
    );
    cy.get('#result').contains('{"selectNumber":"2","min":"10","max":"2"}');

    cy.get('select[name="selectNumber"]').select('3');
    cy.get('#state').contains(
      '{"dirtyFields":["selectNumber"],"isSubmitted":true,"submitCount":2,"touched":["selectNumber"],"isDirty":false,"isSubmitting":false,"isValid":true}',
    );
    cy.get('input[name="notRequired"]').type('test');
    cy.get('input[name="notRequired"]').blur();
    cy.get('#state').contains(
      '{"dirtyFields":["selectNumber","notRequired"],"isSubmitted":true,"submitCount":2,"touched":["selectNumber","notRequired"],"isDirty":true,"isSubmitting":false,"isValid":true}',
    );
    cy.get('button#submit').click();
    cy.get('#result').contains('{"selectNumber":"3","notRequired":"test"}');

    cy.get('#renderCount').contains('36');
  });
});


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

export default 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>

  );
}


import React from 'react';
import ReactDOM from 'react-dom';
import { useForm } from 'react-hook-form';
import * as yup from 'yup'; // you will have to install yup

const SignupSchema = yup.object().shape({
  firstName: yup.string().required(),
  age: yup
    .number()
    .required()
    .positive()
    .integer(),
  website: yup.string().url(),
});

export default function App() {
  const { register, handleSubmit, errors } = useForm({
    validationSchema: SignupSchema,
  });
  const onSubmit = data => {
    alert(JSON.stringify(data));
  };

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

        Develop?
        Yes
        <input type="radio" name="test" value="yes" ref={register} />
        No
        <input type="radio" name="test" value="no" ref={register} />

        First Name
        <input type="text" name="firstName" ref={register} />

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

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

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

          {Object.keys(errors).length > 0 && (
            Errors: {JSON.stringify(errors, null, 2)}
          )}

      <input type="submit" />
    </form>
  );
}


## Examples

You can view the source code for most examples within their folder, or visit Code Sandbox to see how the example works live.

| Name                                    | Link                                                                                |
| --------------------------------------- | ------------------------------------------------------------------------------------ |
| Async Submit Validation                 | https://codesandbox.io/s/xrjv48o0qp                                                  |
| Async Set Form Values                   | https://codesandbox.io/s/react-hook-from-async-set-form-values-ztpd6                 |
| Async Field Validation                  | https://codesandbox.io/s/m5pj55yj7x                                                  |
| Array of Field Array                    | https://codesandbox.io/s/react-hook-form-usefieldarray-nested-arrays-x7btr           |
| Basic                                   | https://codesandbox.io/s/yj07z1639                                                   |
| Basic Validation                        | https://codesandbox.io/s/rwpz2jn6ln                                                  |
| Custom Validation                       | https://codesandbox.io/s/8n937m64o9                                                  |
| Conditional Fields                      | https://codesandbox.io/s/13ykqx4wx7                                                  |
| Custom Input                            | https://codesandbox.io/s/72j69vnk1x                                                  |
| Custom Input with ForwardRef            | https://codesandbox.io/s/rhfex-en254                                                 |
| Custom Masked Input with Controller     | https://codesandbox.io/s/morning-sunset-8n3sx                                        |
| Controlled Input                        | https://codesandbox.io/s/j36w7xkk7w                                                  |
| Controller (MUI, React-Select and etc)  | https://codesandbox.io/s/react-hook-form-controller-079xx                            |
| Compare Field Values                    | https://codesandbox.io/s/react-hook-form-getvalues-compare-field-values-qrtvx        |
| DefaultValues                           | https://codesandbox.io/s/react-hook-form-defaultvalues-n5gvx                         |
| Default/Initial Form Value              | https://codesandbox.io/s/l3mxpvmm9                                                   |
| Dirty/Touched/Submitted                 | https://codesandbox.io/s/7o2wrp86k6                                                  |
| Disable Native Validation               | https://codesandbox.io/s/keen-mccarthy-4pqn6                                         |
| Field Array                             | https://codesandbox.io/s/6j1760jkjk                                                  |
| Field Array Min Length                  | https://codesandbox.io/s/react-hook-form-fieldsarray-yup-validation-min-length-57qtb |
| FormProvider                            | https://codesandbox.io/s/react-hook-form-form-context-dkvjz                          |
| Parse and format input value            | https://codesandbox.io/s/react-hook-form-parse-and-format-textarea-furtc             |
| Modal/Toggle input                      | https://codesandbox.io/s/react-hook-form-conditional-inputs-c7n0r                    |
| Nested Fields                           | https://codesandbox.io/s/react-hook-form-nested-fields-mv1bb                         |
| Normalize/Format/Mask Field             | https://codesandbox.io/s/387z7njwzp                                                  |
| Format/Mask Field (Third Party Library) | https://codesandbox.io/s/react-hook-form-normalize-field-1jumn                       |
| Native Validation                       | https://codesandbox.io/s/react-hook-form-native-validation-ez5ww                     |
| Reset Form                              | https://codesandbox.io/s/jjm3wyqmjy                                                  |
| Remote/Trigger Form Submit              | https://codesandbox.io/s/remote-handlesubmit-w0win                                   |
| Register Error Messages                 | https://codesandbox.io/s/o766kp4z05                                                  |
| Set input/select value                  | https://codesandbox.io/s/react-hook-form-set-inputselect-value-c46ly                 |
| Set input/select Error                  | https://codesandbox.io/s/o7rxyym3q5                                                  |
| Tabs Form (MUI)                         | https://codesandbox.io/s/tabs-760h9                                                  |
| Trigger field validation                | https://codesandbox.io/s/react-hook-form-trigger-validation-w1g0m                    |
| ValidationSchema                        | https://codesandbox.io/s/928po918qr                                                  |
| Validation On Blur                      | https://codesandbox.io/s/w7p3km6nyw                                                  |
| Validation On Change                    | https://codesandbox.io/s/74zw1oqozx                                                  |
| Wizard (funnel / multi-step) form       | https://codesandbox.io/s/react-hook-form-wizard-form-13yud                           |
| Native Multiple Input                   | https://codesandbox.io/s/react-hook-form-native-multiple-input-tyhue                 |

## Types

The following table contains a list of support Types in this library.

| Type Name      | Link                                                          |
| -------------- | ------------------------------------------------------------- |
| NestedValue    | https://codesandbox.io/s/react-hook-form-nestedvalue-dujyc    |
| SubmitHandler  | https://codesandbox.io/s/react-hook-form-submithandler-kfmqy  |
| Resolver       | https://codesandbox.io/s/react-hook-form-resolver-juwui       |
| UseFormMethods | https://codesandbox.io/s/react-hook-form-useformmethods-mycp2 |
| Control        | https://codesandbox.io/s/react-hook-form-control-yq73k        |


it('should call reRender method with removeFieldEventListenerAndRef when onDomRemove method is executed with ReactNative', async () => {

keep keep keep keep replace keep keep keep keep


import get from '../utils/get';
import { FieldErrors, FieldRefs } from '../types/form';

export default <TFieldValues>(
  fields: FieldRefs<TFieldValues>,
  fieldErrors: FieldErrors<TFieldValues>,
) => {
  for (const key in fields) {
    if (get(fieldErrors, key)) {
      const field = fields[key];

      if (field) {
        if (field.ref.focus) {
          field.ref.focus();

          break;
        } else if (field.options) {
          field.options[0].ref.focus();

          break;
        }
      }
    }
  }
};


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

export default function App() {
  const { register, handleSubmit, errors } = useForm();
  const onSubmit = data => {
    alert(JSON.stringify(data));
  };
  const intialValues = {
    firstName: 'bill',
    lastName: 'luo',
    email: 'bluebill1049@hotmail.com',
    age: -1,
  };

  return (

      <form onSubmit={handleSubmit(onSubmit)}>

          First Name
          <input
            defaultValue={intialValues.firstName}
            name="firstName"
            placeholder="bill"
            ref={register({
              validate: value => value !== 'bill',
            })}
          />

        {errors.firstName &&
Your name is not bill
}

          Last Name
          <input
            defaultValue={intialValues.lastName}
            name="lastName"
            placeholder="luo"
            ref={register({
              validate: value => value.length > 3,
            })}
          />

        {errors.lastName &&
Your last name is less than 3 characters
}

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

          Age
          <input
            defaultValue={intialValues.age}
            name="age"
            placeholder="0"
            type="text"
            ref={register({
              validate: {
                positiveNumber: value => parseFloat(value) > 0,
                lessThanHundred: value => parseFloat(value) < 200,
              },
            })}
          />

        {errors.age && errors.age.type === 'positiveNumber' && (

Your age is invalid

        )}
        {errors.age && errors.age.type === 'lessThanHundred' && (

Your age should be greater than 200

        )}

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

  );
}


keep keep keep keep add keep keep keep keep

.tooltip,
.tooltip > span
height: inherit

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

/* Theme */
toolbar($material)
  background-color: $material.app-bar
  color: $material.text.primary

theme(toolbar, "toolbar")

.toolbar
  bootable()
  elevation(4)

  display: block
  position: relative
  width: 100%
  will-change: padding-left

  .input-group--solo
    .input-group__details
      display: none

  .input-group--single-line:not(.input-group--solo)
    padding: 0

    label
      top: auto

/** Children */
.toolbar
  .tabs
    width: 100%

  &__title
    font-size: $headings.h6.size
    font-weight: $headings.h6.weight
    letter-spacing: $headings.h6.letter-spacing
    margin-left: 16px
    white-space: nowrap
    overflow: hidden
    text-overflow: ellipsis

  &__content, &__extension
    align-items: center
    display: flex

    > .list
      flex: 1 1 auto
      margin: 0 !important // come back to this
      max-height: 100%

    > .btn:last-child, > .menu:first-child
      margin-right: 8px

    > .btn:first-child, > .menu:first-child
      margin-left: 8px

    > *:not(.btn):not(.menu):first-child:not(:only-child)
      margin-left: $grid-gutters.lg

    > *:not(.btn):not(.menu):last-child:not(:only-child)
      margin-right: $grid-gutters.lg

  &__items
    display: flex
    height: inherit
    max-width: 100%
    padding: 0

    .btn
      align-items: stretch

    .btn,
    .menu,
    .menu__activator
      height: inherit
      margin: 0

  @media $display-breakpoints.xs-only
    .toolbar__content,
    .toolbar__extension
      > .btn:last-child
        margin-right: 17px

      > .btn:first-child
        margin-left: 17px

      > *:not(.btn):not(.menu):first-child:not(:only-child)
        margin-left: $grid-gutters.xl

      > *:not(.btn):not(.menu):last-child:not(:only-child)
        margin-right: $grid-gutters.xl

  @media all and (max-width: $grid-breakpoints.sm) and (orientation: portrait)
    toolbar-content(7px)

/** Types */
.toolbar
  &--card
    border-radius: $card-border-radius $card-border-radius 0 0
    elevation(0)

  &--fixed
    position: fixed
    z-index: 2

  &--fixed, &--absolute
    top: 0
    left: 0

  &--absolute
    position: absolute
    z-index 2

  &--floating
    display: inline-flex
    margin: 16px
    width: auto

  &--clipped
    z-index: 3

  &--prominent
    toolbar-content(15px)

  &--dense
    toolbar-content(7px)

toolbar-content(left)
  .toolbar__content,
  .toolbar__extension
    > *:first-child
      margin-left: left

    > *:last-child
      margin-right: left


import VBtnToggle from './VBtnToggle'
import VBtn from '../VBtn'
import VIcon from '../VIcon'
import { test } from '@util/testing'
import Vue from 'vue'

function createBtn (val = null, buttonProps = {}) {
  const options = {
    props: Object.assign({ flat: true }, buttonProps)
  }
  if (val) options.attrs = { value: val }

  return Vue.component('test', {
    components: {
      VBtn,
      VIcon
    },
    render (h) {
      return h('v-btn', options, [h('v-icon', 'add')])
    }
  })
}

function createFakeBtn() {
  return Vue.component('v-btn', {
    inject: ['buttonGroup'],
    methods: {
      testUnregister() {
        this.buttonGroup.unregister(this)
      }
    },
    mounted() {
      this.buttonGroup.register(this)
    },
    render (h) {
      return h('div')
    }
  })
}

test('VBtnToggle.vue', ({ mount }) => {
  it('should not allow empty value when mandatory prop is used', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: 0,
        mandatory: true
      },
      slots: {
        default: [
          createBtn(),
          createBtn(),
          createBtn()
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.instance().updateValue(0)

    await wrapper.vm.$nextTick()

    expect(change).not.toBeCalled()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should allow new value when mandatory prop is used', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: 1,
        mandatory: true
      },
      slots: {
        default: [
          createBtn(),
          createBtn(),
          createBtn()
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.instance().updateValue(0)

    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(0)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should not allow empty value when mandatory prop is used with multiple prop', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: [1],
        mandatory: true,
        multiple: true
      },
      slots: {
        default: [
          createBtn(),
          createBtn(),
          createBtn()
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.instance().updateValue(1)

    await wrapper.vm.$nextTick()

    expect(change).not.toBeCalled()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should allow new value when mandatory prop is used with multiple prop', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: [1],
        mandatory: true,
        multiple: true
      },
      slots: {
        default: [
          createBtn(),
          createBtn(),
          createBtn()
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.instance().updateValue(2)

    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith([1, 2])
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should use button value attribute if available', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: 'center'
      },
      slots: {
        default: [
          createBtn('left'),
          createBtn('center'),
          createBtn('right')
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.instance().updateValue(2)

    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith('right')
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should allow deselecting a value when mandatory prop is used with multiple prop', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: [1, 2],
        mandatory: true,
        multiple: true
      },
      slots: {
        default: [
          createBtn(),
          createBtn(),
          createBtn()
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.instance().updateValue(2)

    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith([1])
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should preserve mandatory invariant when selected child is unregistered', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: 1,
        mandatory: true
      },
      slots: {
        default: [
          createBtn(),
          createFakeBtn()
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.vm.$children[1].testUnregister()
    wrapper.update()

    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(0)
  })

  it('should not set new value when not mandatory and selected child is unregistered', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: 1,
      },
      slots: {
        default: [
          createBtn(),
          createFakeBtn()
        ]
      }
    })

    const change = jest.fn()
    wrapper.instance().$on('change', change)

    wrapper.vm.$children[1].testUnregister()
    wrapper.update()

    await wrapper.vm.$nextTick()

    expect(change).not.toBeCalled()
  })

  it('should have btn with data-only-child if only one selected', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: 0
      },
      slots: {
        default: [
          createBtn(),
          createBtn()
        ]
      }
    })

    const btn = wrapper.find('.btn')[0]

    expect(btn.getAttribute('data-only-child')).toBe('true')
  })

  it('should toggle values of any type', async () => {
    const values = [true, false, null, 6, 'foo', { key: 'value' }, ['arrayyy']]
    const verifyValues = [true, 1, 2, 6, 'foo', { key: 'value' }, ['arrayyy']]
    const buttons = values.map(v => createBtn(v))
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: null
      },
      slots: { default: buttons }
    })

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

    wrapper.find('button').forEach((button, i) => {
      button.trigger('click')

      expect(change).toBeCalledWith(verifyValues[i])
    })

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

  it('should toggle active state of button', async () => {
    const wrapper = mount(VBtnToggle, {
      propsData: {
        inputValue: 'foo'
      },
      slots: {
        default: [
          createBtn('foo', { color: 'error' }),
          createBtn('bar', { color: 'success' })
        ]
      }
    })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.buttons[0].$data.isActive).toBe(true)
    expect(wrapper.vm.buttons[1].$data.isActive).toBe(false)

    wrapper.setProps({ inputValue: 'bar' })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.buttons[0].$data.isActive).toBe(false)
    expect(wrapper.vm.buttons[1].$data.isActive).toBe(true)
  })
})


// Components
import VTimePickerTitle from './VTimePickerTitle'
import VTimePickerClock from './VTimePickerClock'

// Mixins
import Picker from '../../mixins/picker'

// Utils
import { createRange } from '../../util/helpers'
import pad from '../VDatePicker/util/pad'

const rangeHours24 = createRange(24)
const rangeHours12am = createRange(12)
const rangeHours12pm = rangeHours12am.map(v => v + 12)
const rangeMinutes = createRange(60)

export default {
  name: 'v-time-picker',

  components: {
    VTimePickerTitle,
    VTimePickerClock
  },

  mixins: [Picker],

  data () {
    return {
      inputHour: null,
      inputMinute: null,
      period: 'am',
      selectingHour: true
    }
  },

  props: {
    allowedHours: Function,
    allowedMinutes: Function,
    format: {
      type: String,
      default: 'ampm',
      validator (val) {
        return ['ampm', '24hr'].includes(val)
      }
    },
    min: String,
    max: String,
    scrollable: Boolean,
    value: null
  },

  computed: {
    isAllowedHourCb () {
      if (!this.min && !this.max) return this.allowedHours

      const minHour = this.min ? this.min.split(':')[0] : 0
      const maxHour = this.max ? this.max.split(':')[0] : 23

      return val => {
        return val >= minHour * 1 &&
          val <= maxHour * 1 &&
          (!this.allowedHours || this.allowedHours(val))
      }
    },
    isAllowedMinuteCb () {
      const isHourAllowed = !this.allowedHours || this.allowedHours(this.inputHour)
      if (!this.min && !this.max) {
        return isHourAllowed ? this.allowedMinutes : () => false
      }

      const [minHour, minMinute] = this.min ? this.min.split(':') : [0, 0]
      const [maxHour, maxMinute] = this.max ? this.max.split(':') : [23, 59]
      const minTime = minHour * 60 + minMinute * 1
      const maxTime = maxHour * 60 + maxMinute * 1

      return val => {
        const time = 60 * this.inputHour + val
        return time >= minTime &&
          time <= maxTime &&
          isHourAllowed &&
          (!this.allowedMinutes || this.allowedMinutes(val))
      }
    },
    isAmPm () {
      return this.format === 'ampm'
    }
  },

  watch: {
    value: 'setInputData'
  },

  methods: {
    emitValue () {
      if (this.inputHour != null && this.inputMinute != null) {
        this.$emit('input', `${pad(this.inputHour)}:${pad(this.inputMinute)}`)
      }
    },
    setPeriod (period) {
      this.period = period
      if (this.inputHour != null) {
        const newHour = this.inputHour + (period === 'am' ? -12 : 12)
        this.inputHour = this.firstAllowed('hour', newHour)
        this.emitValue()
      }
    },
    setInputData (value) {
      if (value == null) {
        this.inputHour = null
        this.inputMinute = null
        return
      }

      if (value instanceof Date) {
        this.inputHour = value.getHours()
        this.inputMinute = value.getMinutes()
      } else {
        const [, hour, minute, , period] = value.trim().toLowerCase().match(/^(\d+):(\d+)(:\d+)?([ap]m)?$/, '') || []

        this.inputHour = period ? this.convert12to24(parseInt(hour, 10), period) : parseInt(hour, 10)
        this.inputMinute = parseInt(minute, 10)
      }

      this.period = this.inputHour < 12 ? 'am' : 'pm'
    },
    convert24to12 (hour) {
      return hour ? ((hour - 1) % 12 + 1) : 12
    },
    convert12to24 (hour, period) {
      return hour % 12 + (period === 'pm' ? 12 : 0)
    },
    onInput (value) {
      if (this.selectingHour) {
        this.inputHour = this.isAmPm ? this.convert12to24(value, this.period) : value
      } else {
        this.inputMinute = value
      }
      this.emitValue()
    },
    onChange () {
      if (!this.selectingHour) {
        this.$emit('change', this.value)
      }

      this.selectingHour = !this.selectingHour
    },
    firstAllowed (type, value) {
      const allowedFn = type === 'hour' ? this.isAllowedHourCb : this.isAllowedMinuteCb
      if (!allowedFn) return value

      // TODO: clean up
      const range = type === 'minute'
        ? rangeMinutes
        : (this.isAmPm
          ? (value < 12
            ? rangeHours12am
            : rangeHours12pm)
          : rangeHours24)
      const first = range.find(v => allowedFn((v + value) % range.length + range[0]))
      return ((first || 0) + value) % range.length + range[0]
    },
    genClock () {
      return this.$createElement('v-time-picker-clock', {
        props: {
          allowedValues: this.selectingHour ? this.isAllowedHourCb : this.isAllowedMinuteCb,
          color: this.color,
          dark: this.dark,
          double: this.selectingHour && !this.isAmPm,
          format: this.selectingHour ? (this.isAmPm ? this.convert24to12 : val => val) : val => pad(val, 2),
          max: this.selectingHour ? (this.isAmPm && this.period === 'am' ? 11 : 23) : 59,
          min: this.selectingHour && this.isAmPm && this.period === 'pm' ? 12 : 0,
          scrollable: this.scrollable,
          size: this.width - ((!this.fullWidth && this.landscape) ? 80 : 20),
          step: this.selectingHour ? 1 : 5,
          value: this.selectingHour ? this.inputHour : this.inputMinute
        },
        on: {
          input: this.onInput,
          change: this.onChange
        },
        ref: 'clock'
      })
    },
    genPickerBody () {
      return this.$createElement('div', {
        staticClass: 'time-picker-clock__container',
        style: {
          width: `${this.width}px`,
          height: `${this.width - ((!this.fullWidth && this.landscape) ? 60 : 0)}px`
        },
        key: this.selectingHour
      }, [this.genClock()])
    },
    genPickerTitle () {
      return this.$createElement('v-time-picker-title', {
        props: {
          ampm: this.isAmPm,
          hour: this.inputHour,
          minute: this.inputMinute,
          period: this.period,
          selectingHour: this.selectingHour
        },
        on: {
          'update:selectingHour': value => (this.selectingHour = value),
          'update:period': this.setPeriod
        },
        ref: 'title',
        slot: 'title'
      })
    }
  },

  mounted () {
    this.setInputData(this.value)
  },

  render (h) {
    return this.genPicker('picker--time')
  }
}


import '../../stylus/components/_input-groups.styl'
import '../../stylus/components/_selection-controls.styl'
import '../../stylus/components/_switch.styl'

// Mixins
import Rippleable from '../../mixins/rippleable'
import Selectable from '../../mixins/selectable'

// Directives
import Touch from '../../directives/touch'

export default {
  name: 'v-switch',

  mixins: [Rippleable, Selectable],

  directives: { Touch },

  computed: {
    classes () {
      const classes = {
        'input-group--selection-controls switch': true
      }

      if (this.hasError) {
        classes['error--text'] = true
      } else {
        return this.addTextColorClassChecks(classes)
      }

      return classes
    },
    rippleClasses () {
      return {
        'input-group--selection-controls__ripple': true,
        'input-group--selection-controls__ripple--active': this.isActive
      }
    },
    containerClasses () {
      return {
        'input-group--selection-controls__container': true,
        'input-group--selection-controls__container--light': this.light,
        'input-group--selection-controls__container--disabled': this.disabled
      }
    },
    toggleClasses () {
      return {
        'input-group--selection-controls__toggle': true,
        'input-group--selection-controls__toggle--active': this.isActive
      }
    }
  },

  methods: {
    onSwipeLeft () {
      if (this.isActive) this.toggle()
    },
    onSwipeRight () {
      if (!this.isActive) this.toggle()
    }
  },

  render (h) {
    const container = h('div', {
      'class': this.containerClasses
    }, [
      h('div', { 'class': this.toggleClasses }),
      this.genRipple({
        directives: [{
          name: 'touch',
          value: {
            left: this.onSwipeLeft,
            right: this.onSwipeRight
          }
        }]
      })
    ])

    return this.genInputGroup([container])
  }
}


@import '../bootstrap'

.tooltip
  height: inherit
  position: relative

  > span
    height: inherit

  &__content
    background: $grey.darken-2
    border-radius: 2px
    color: #FFFFFF
    font-size: 12px
    display: inline-block
    padding: 5px 8px
    position: absolute
    text-transform: initial
    transition: .15s $transition.swing
    width: auto
    elevation(2)

    &[class*="-active"] {
      pointer-events: none
    }

  @media $display-breakpoints.sm-and-down
    .tooltip__content
      padding: 10px 16px


@import '../bootstrap'
@import '../components/_ripples'
@import '../theme'

button($material)
  color: $material.text.primary

  &.btn--disabled
    color: $material.buttons.disabled !important

    .icon
      color: $material.buttons.disabled !important

    &:not(.btn--icon):not(.btn--flat)
      background-color: $material.buttons.focused !important

  &:not(.btn--icon):not(.btn--flat)
    background-color: $material.app-bar

theme(button, "btn")

/** Base Spec */
.btn
  align-items: center
  border-radius: $button-border-radius
  display: inline-flex
  height: $button-height
  flex: 0 1 auto
  font-size: $button-font-size
  font-weight: $button-font-weight
  justify-content: center
  margin: $button-margin
  min-width: $button-min-width
  outline: 0
  text-transform: $button-text-transform
  text-decoration: none
  transition: .3s $transition.swing, color 1ms
  position: relative
  vertical-align: middle
  user-select: none

/** Psuedo */
.btn
  &__content:before
    border-radius: inherit
    color: inherit
    content: ''
    position: absolute
    left: 0
    top: 0
    height: 100%
    opacity: .12
    transition: $primary-transition
    width: 100%

/** Sizes */
.btn
  &--small
    font-size: $button-small-font-size
    height: $button-small-height

    .btn__content
      padding: $button-small-padding

  &--large
    font-size: $button-large-font-size
    height: $button-large-height

    .btn__content
      padding: $button-large-padding

/** Content */
.btn
  &--active,
  &:hover,
  &:focus
    .btn__content:before
      background-color: currentColor

  &__content
    align-items: center
    border-radius: inherit
    color: inherit
    display: flex
    height: inherit
    flex: 1 0 auto
    justify-content: center
    margin: 0 auto
    padding: $button-padding
    transition: $primary-transition
    white-space: nowrap
    width: inherit

/** Icons */
.btn .btn__content .icon
  color: inherit

/** Types */
.btn
  &--flat
    background-color: transparent !important
    box-shadow: none !important

  &:not(.btn--depressed)
    will-change: box-shadow
    elevation(2)

    &:active
      elevation(8)

    // Fixes bug where IE11 moves
    // button content when clicked
    // https://stackoverflow.com/questions/10305658/prevent-button-from-shifting-during-click-in-ie
    &:active,
    &:focus
      .btn__content
        position: relative
        top: 0
        left: 0

  &--icon
    background: transparent
    box-shadow: none !important
    border-radius: 50%
    justify-content: center
    min-width: 0
    width: $button-height

    &.btn--small
      width: $button-small-height

    &.btn--large
      width: $button-large-height

    .btn__content:before
      border-radius: 50%

  &--floating
    border-radius: 50%
    min-width: 0
    height: $button-floating-width
    width: $button-floating-width
    padding: 0
    elevation(6)

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

    &:active
      elevation(12)

    .btn__content
      flex: 1 1 auto
      margin: 0
      padding: 0

    &:after
      border-radius: 50%

    .btn__content
      :not(:only-child)
        transition: $primary-transition

        &:first-child
          opacity: 1

        &:last-child
          opacity: 0
          transform: rotate(-45deg)

        &:last-child,
        &:first-child
          // https://stackoverflow.com/questions/14729492/css3-transform-rotate-causing-1px-shift-in-chrome
          -webkit-backface-visibility: hidden
          position: absolute
          left: 0
          top: 0

    &.btn--active
      .btn__content
        :not(:only-child)
          &:first-child
            opacity: 0
            transform: rotate(45deg)

          &:last-child
            opacity: 1
            transform: rotate(0)

    .icon
      height: inherit
      width: inherit

    &.btn--small
      height: $button-floating-small-width
      width: $button-floating-small-width

      .icon
        font-size: $button-floating-small-icon-width

    &.btn--large
      height: $button-floating-large-width
      width: $button-floating-large-width

      .icon
        font-size: $button-floating-large-icon-width

  &--reverse
    .btn__content
      flex-direction: row-reverse

    &.btn--column
      .btn__content
        flex-direction: column-reverse

  &--fixed, &--absolute
    margin: 0

  // Position must have prepended .btn
  // to account for tooltip
  // TODO: Remove this once tooltips are
  // converted to elements
  &.btn--absolute
    position: absolute

  &.btn--fixed
    position: fixed

  &--top
    &:not(.btn--absolute)
      top: $grid-gutters.lg

    &.btn--absolute
      top: -($button-floating-width / 2)

      &.btn--small
        top: -($button-floating-small-width / 2)

      &.btn--large
        top: -($button-floating-large-width / 2)

  &--bottom
    &:not(.btn--absolute)
      bottom: $grid-gutters.lg

    &.btn--absolute
      bottom: -($button-floating-width / 2)

      &.btn--small
        bottom: -($button-floating-small-width / 2)

      &.btn--large
        bottom: -($button-floating-large-width / 2)

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

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

/** Disabled */
.btn
  &.btn--disabled
    box-shadow: none !important
    pointer-events: none

.btn--icon
  .btn__content
    padding: 0

/** Loader */
.btn
  &--loader
    pointer-events: none

    .btn__content
      opacity: 0

  &__loading
    align-items: center
    display: flex
    height: 100%
    justify-content: center
    left: 0
    position: absolute
    top: 0
    width: 100%

    .icon
      &--left
        margin-right: 1rem
        line-height: inherit

      &--right
        margin-left: 1rem
        line-height: inherit

/** Custom Buttons */
.btn
  &.btn--outline
    border: 1px solid currentColor
    background: transparent !important
    box-shadow: none

    &:hover
      box-shadow: none

  &--block
    display: flex
    flex: 1
    margin: $button-margin-y 0
    width: 100%

  &--round
    border-radius: 28px

    &:after
      border-radius: 28px

/** Button w/ directional Icon */
.btn
  .icon
    &--right
      margin-left: 16px

    &--left
      margin-right: 16px

/** Themes */
.btn
  &.primary,
  &.secondary,
  &.accent,
  &.success,
  &.error,
  &.warning,
  &.info
    color: $material-dark.text.primary


import VExpansionPanel from './VExpansionPanel'
import VExpansionPanelContent from './VExpansionPanelContent'

export { VExpansionPanel, VExpansionPanelContent }

/* istanbul ignore next */
VExpansionPanel.install = function install (Vue) {
  Vue.component(VExpansionPanel.name, VExpansionPanel)
  Vue.component(VExpansionPanelContent.name, VExpansionPanelContent)
}

export default VExpansionPanel


// For converting XYZ to sRGB
const srgbForwardMatrix = [
  [3.2406, -1.5372, -0.4986],
  [-0.9689, 1.8758, 0.0415],
  [0.0557, -0.2040, 1.0570]
]

// Forward gamma adjust
const srgbForwardTransform = C => (
  C <= 0.0031308
    ? C * 12.92
    : 1.055 * C ** (1 / 2.4) - 0.055
)

// For converting sRGB to XYZ
const srgbReverseMatrix = [
  [0.4124, 0.3576, 0.1805],
  [0.2126, 0.7152, 0.0722],
  [0.0193, 0.1192, 0.9505]
]

// Reverse gamma adjust
const srgbReverseTransform = C => (
  C <= 0.04045
    ? C / 12.92
    : ((C + 0.055) / 1.055) ** 2.4
)

function clamp (value) {
  return Math.max(0, Math.min(1, value))
}

export function fromXYZ (xyz) {
  const rgb = Array(3)
  const transform = srgbForwardTransform
  const matrix = srgbForwardMatrix

  // Matrix transform, then gamma adjustment
  for (let i = 0; i < 3; ++i) {
    rgb[i] = Math.round(clamp(transform(
      matrix[i][0] * xyz[0] +
      matrix[i][1] * xyz[1] +
      matrix[i][2] * xyz[2]
    )) * 255)
  }

  // Rescale back to [0, 255]
  return (rgb[0] << 16) + (rgb[1] << 8) + (rgb[2] << 0)
}

export function toXYZ (rgb) {
  const xyz = Array(3)
  const transform = srgbReverseTransform
  const matrix = srgbReverseMatrix

  // Rescale from [0, 255] to [0, 1] then adjust sRGB gamma to linear RGB
  const r = transform((rgb >> 16 & 0xff) / 255)
  const g = transform((rgb >> 8 & 0xff) / 255)
  const b = transform((rgb >> 0 & 0xff) / 255)

  // Matrix color space transform
  for (let i = 0; i < 3; ++i) {
    xyz[i] = matrix[i][0] * r + matrix[i][1] * g + matrix[i][2] * b
  }

  return xyz
}


position: relative
> span
height: inherit
&__content
background: $grey.darken-2
border-radius: 2px

keep keep replace replace replace keep keep keep


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

/** Themes */

btn-toggle($material)
  background: $material.cards

  .btn
    color: $material.text.primary

    &.btn--active:not(:last-child):not([data-only-child])
      border-right-color: $material.buttons.disabled

theme(btn-toggle, "btn-toggle")

.btn-toggle
  display: inline-flex
  border-radius: 2px
  transition: $primary-transition
  will-change: background, box-shadow

  .btn
    justify-content: center
    min-width: auto
    width: auto
    padding: 0 8px
    margin: 0
    opacity: .4
    border-radius: 0

    &:not(:last-child)
      border-right: 1px solid transparent

    &:after
      display: none

    &.btn--active
      opacity: 1

    &__content
      padding: 0

    span + .icon
      font-size: initial
      margin-left: 10px

    &:first-child
      border-radius: 2px 0 0 2px

    &:last-child
      border-radius: 0 2px 2px 0

  &--selected
    elevation(2)


import VPicker from './VPicker'

/* istanbul ignore next */
VPicker.install = function install (Vue) {
  Vue.component(VPicker.name, VPicker)
}

export default VPicker


import VBtn from './VBtn'

/* istanbul ignore next */
VBtn.install = function install (Vue) {
  Vue.component(VBtn.name, VBtn)
}

export default VBtn


keep keep keep keep add keep keep keep keep keep

export function getScrollParent (el?: HTMLElement) {
  while (el) {
    if (hasScrollbar(el)) return el
    el = el.parentElement!
  }

  return document.scrollingElement as HTMLElement
}

export function getScrollParents (el?: HTMLElement) {
  const elements: HTMLElement[] = []
  while (el) {
    if (hasScrollbar(el)) elements.push(el)
    el = el.parentElement!
  }

  return elements
}

export function hasScrollbar (el?: Element | null) {
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return false

  const style = window.getComputedStyle(el)
  return style.overflowY === 'scroll' || (style.overflowY === 'auto' && el.scrollHeight > el.clientHeight)
}


// @ts-nocheck
/* eslint-disable */

// Components
import { VExpandTransition } from '../transitions'
import { VIcon } from '../VIcon'
import VTreeview from './VTreeview'

// Mixins
import { inject as RegistrableInject } from '../../mixins/registrable'
import Colorable from '../../mixins/colorable'

// Utils
import mixins, { ExtractVue } from '../../util/mixins'
import { getObjectValueByPath, createRange } from '../../util/helpers'

// Types
import { VNode, VNodeChildren, PropType } from 'vue'
import { PropValidator } from 'vue/types/options'

type VTreeViewInstance = InstanceType<typeof VTreeview>

const baseMixins = mixins(
  Colorable,
  RegistrableInject('treeview')
)

interface options extends ExtractVue<typeof baseMixins> {
  treeview: VTreeViewInstance
}

export const VTreeviewNodeProps = {
  activatable: Boolean,
  activeClass: {
    type: String,
    default: 'v-treeview-node--active',
  },
  color: {
    type: String,
    default: 'primary',
  },
  disablePerNode: Boolean,
  expandIcon: {
    type: String,
    default: '$subgroup',
  },
  indeterminateIcon: {
    type: String,
    default: '$checkboxIndeterminate',
  },
  itemChildren: {
    type: String,
    default: 'children',
  },
  itemDisabled: {
    type: String,
    default: 'disabled',
  },
  itemKey: {
    type: String,
    default: 'id',
  },
  itemText: {
    type: String,
    default: 'name',
  },
  loadChildren: Function as PropType<(item: any) => Promise<void>>,
  loadingIcon: {
    type: String,
    default: '$loading',
  },
  offIcon: {
    type: String,
    default: '$checkboxOff',
  },
  onIcon: {
    type: String,
    default: '$checkboxOn',
  },
  openOnClick: Boolean,
  rounded: Boolean,
  selectable: Boolean,
  selectedColor: {
    type: String,
    default: 'accent',
  },
  shaped: Boolean,
  transition: Boolean,
  selectionType: {
    type: String as PropType<'leaf' | 'independent'>,
    default: 'leaf',
    validator: (v: string) => ['leaf', 'independent'].includes(v),
  },
}

/* @vue/component */
const VTreeviewNode = baseMixins.extend<options>().extend({
  name: 'v-treeview-node',

  inject: {
    treeview: {
      default: null,
    },
  },

  props: {
    level: Number,
    item: {
      type: Object,
      default: () => null,
    } as PropValidator<Record<string, unknown> | null>,
    parentIsDisabled: Boolean,
    ...VTreeviewNodeProps,
  },

  data: () => ({
    hasLoaded: false,
    isActive: false, // Node is selected (row)
    isIndeterminate: false, // Node has at least one selected child
    isLoading: false,
    isOpen: false, // Node is open/expanded
    isSelected: false, // Node is selected (checkbox)
  }),

  computed: {
    disabled (): boolean {
      return (
        getObjectValueByPath(this.item, this.itemDisabled) ||
        (!this.disablePerNode && (this.parentIsDisabled && this.selectionType === 'leaf'))
      )
    },
    key (): string {
      return getObjectValueByPath(this.item, this.itemKey)
    },
    children (): any[] | null {
      const children = getObjectValueByPath(this.item, this.itemChildren)
      return children && children.filter((child: any) => !this.treeview.isExcluded(getObjectValueByPath(child, this.itemKey)))
    },
    text (): string {
      return getObjectValueByPath(this.item, this.itemText)
    },
    scopedProps (): object {
      return {
        item: this.item,
        leaf: !this.children,
        selected: this.isSelected,
        indeterminate: this.isIndeterminate,
        active: this.isActive,
        open: this.isOpen,
      }
    },
    computedIcon (): string {
      if (this.isIndeterminate) return this.indeterminateIcon
      else if (this.isSelected) return this.onIcon
      else return this.offIcon
    },
    hasChildren (): boolean {
      return !!this.children && (!!this.children.length || !!this.loadChildren)
    },
  },

  created () {
    this.treeview.register(this)
  },

  beforeDestroy () {
    this.treeview.unregister(this)
  },

  methods: {
    checkChildren (): Promise<void> {
      return new Promise<void>(resolve => {
        // TODO: Potential issue with always trying
        // to load children if response is empty?
        if (!this.children || this.children.length || !this.loadChildren || this.hasLoaded) return resolve()

        this.isLoading = true
        resolve(this.loadChildren(this.item))
      }).then(() => {
        this.isLoading = false
        this.hasLoaded = true
      })
    },
    open () {
      this.isOpen = !this.isOpen
      this.treeview.updateOpen(this.key, this.isOpen)
      this.treeview.emitOpen()
    },
    genLabel () {
      const children = []

      if (this.$scopedSlots.label) children.push(this.$scopedSlots.label(this.scopedProps))
      else children.push(this.text)

      return this.$createElement('div', {
        slot: 'label',
        staticClass: 'v-treeview-node__label',
      }, children)
    },
    genPrependSlot () {
      if (!this.$scopedSlots.prepend) return null

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__prepend',
      }, this.$scopedSlots.prepend(this.scopedProps))
    },
    genAppendSlot () {
      if (!this.$scopedSlots.append) return null

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__append',
      }, this.$scopedSlots.append(this.scopedProps))
    },
    genContent () {
      const children = [
        this.genPrependSlot(),
        this.genLabel(),
        this.genAppendSlot(),
      ]

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__content',
      }, children)
    },
    genToggle () {
      return this.$createElement(VIcon, {
        staticClass: 'v-treeview-node__toggle',
        class: {
          'v-treeview-node__toggle--open': this.isOpen,
          'v-treeview-node__toggle--loading': this.isLoading,
        },
        slot: 'prepend',
        on: {
          click: (e: MouseEvent) => {
            e.stopPropagation()

            if (this.isLoading) return

            this.checkChildren().then(() => this.open())
          },
        },
      }, [this.isLoading ? this.loadingIcon : this.expandIcon])
    },
    genCheckbox () {
      return this.$createElement(VIcon, {
        staticClass: 'v-treeview-node__checkbox',
        props: {
          color: this.isSelected || this.isIndeterminate ? this.selectedColor : undefined,
          disabled: this.disabled,
        },
        on: {
          click: (e: MouseEvent) => {
            e.stopPropagation()

            if (this.isLoading) return

            this.checkChildren().then(() => {
              // We nextTick here so that items watch in VTreeview has a chance to run first
              this.$nextTick(() => {
                this.isSelected = !this.isSelected
                this.isIndeterminate = false

                this.treeview.updateSelected(this.key, this.isSelected)
                this.treeview.emitSelected()
              })
            })
          },
        },
      }, [this.computedIcon])
    },
    genLevel (level: number) {
      return createRange(level).map(() => this.$createElement('div', {
        staticClass: 'v-treeview-node__level',
      }))
    },
    genNode () {
      const children = [this.genContent()]

      if (this.selectable) children.unshift(this.genCheckbox())

      if (this.hasChildren) {
        children.unshift(this.genToggle())
      } else {
        children.unshift(...this.genLevel(1))
      }

      children.unshift(...this.genLevel(this.level))

      return this.$createElement('div', this.setTextColor(this.isActive && this.color, {
        staticClass: 'v-treeview-node__root',
        class: {
          [this.activeClass]: this.isActive,
        },
        on: {
          click: () => {
            if (this.openOnClick && this.hasChildren) {
              this.checkChildren().then(this.open)
            } else if (this.activatable && !this.disabled) {
              this.isActive = !this.isActive
              this.treeview.updateActive(this.key, this.isActive)
              this.treeview.emitActive()
            }
          },
        },
      }), children)
    },
    genChild (item: any, parentIsDisabled: boolean) {
      return this.$createElement(VTreeviewNode, {
        key: getObjectValueByPath(item, this.itemKey),
        props: {
          activatable: this.activatable,
          activeClass: this.activeClass,
          item,
          selectable: this.selectable,
          selectedColor: this.selectedColor,
          color: this.color,
          disablePerNode: this.disablePerNode,
          expandIcon: this.expandIcon,
          indeterminateIcon: this.indeterminateIcon,
          offIcon: this.offIcon,
          onIcon: this.onIcon,
          loadingIcon: this.loadingIcon,
          itemKey: this.itemKey,
          itemText: this.itemText,
          itemDisabled: this.itemDisabled,
          itemChildren: this.itemChildren,
          loadChildren: this.loadChildren,
          transition: this.transition,
          openOnClick: this.openOnClick,
          rounded: this.rounded,
          shaped: this.shaped,
          level: this.level + 1,
          selectionType: this.selectionType,
          parentIsDisabled,
        },
        scopedSlots: this.$scopedSlots,
      })
    },
    genChildrenWrapper () {
      if (!this.isOpen || !this.children) return null

      const children = [this.children.map(c => this.genChild(c, this.disabled))]

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__children',
      }, children)
    },
    genTransition () {
      return this.$createElement(VExpandTransition, [this.genChildrenWrapper()])
    },
  },

  render (h): VNode {
    const children: VNodeChildren = [this.genNode()]

    if (this.transition) children.push(this.genTransition())
    else children.push(this.genChildrenWrapper())

    return h('div', {
      staticClass: 'v-treeview-node',
      class: {
        'v-treeview-node--leaf': !this.hasChildren,
        'v-treeview-node--click': this.openOnClick,
        'v-treeview-node--disabled': this.disabled,
        'v-treeview-node--rounded': this.rounded,
        'v-treeview-node--shaped': this.shaped,
        'v-treeview-node--selected': this.isSelected,
      },
      attrs: {
        'aria-expanded': String(this.isOpen),
      },
    }, children)
  },
})

export default VTreeviewNode


// Utilities
import type { ComponentInternalInstance } from 'vue'
import { getCurrentInstance as _getCurrentInstance } from 'vue'
import { toKebabCase } from '@/util/helpers'

export function getCurrentInstance (name: string, message?: string) {
  const vm = _getCurrentInstance()

  if (!vm) {
    throw new Error(`[Vuetify] ${name} ${message || 'must be called from inside a setup function'}`)
  }

  return vm
}

export function getCurrentInstanceName (name = 'composables') {
  return toKebabCase(getCurrentInstance(name).type?.name)
}

let _uid = 0
let _map = new WeakMap<ComponentInternalInstance, number>()
export function getUid () {
  const vm = getCurrentInstance('getUid')

  if (_map.has(vm)) return _map.get(vm)!
  else {
    const uid = _uid++
    _map.set(vm, uid)
    return uid
  }
}
getUid.reset = () => {
  _uid = 0
  _map = new WeakMap()
}


<template>
  <v-slider
    v-model="slider"
    class="align-center"
    :max="max"
    :min="min"
    hide-details
  >
    <template v-slot:append>
      <v-text-field
        v-model="slider"
        hide-details
        single-line
        density="compact"
        type="number"
        style="width: 70px"
      ></v-text-field>
    </template>
  </v-slider>
</template>

<script>
  export default {
    data () {
      return {
        min: -50,
        max: 90,
        slider: 40,
      }
    },
  }
</script>


keep keep keep keep keep replace replace keep keep keep

scrollStrategy: keyof typeof scrollStrategies | ((data: ScrollStrategyData, props?: StrategyProps) => void)

// Utilities
import { convertToUnit, getScrollParents, hasScrollbar, IN_BROWSER, propsFactory } from '@/util'
import { effectScope, nextTick, onScopeDispose, watchEffect } from 'vue'
import { requestNewFrame } from './requestNewFrame'

// Types
import type { EffectScope, PropType, Ref } from 'vue'

export interface ScrollStrategyData {
  root: Ref<HTMLElement | undefined>
  contentEl: Ref<HTMLElement | undefined>
  activatorEl: Ref<HTMLElement | undefined>
  isActive: Ref<boolean>
  updateLocation: Ref<((e: Event) => void) | undefined>
}

const scrollStrategies = {
  none: null,
  close: closeScrollStrategy,
  block: blockScrollStrategy,
  reposition: repositionScrollStrategy,
}

interface StrategyProps {
  scrollStrategy: keyof typeof scrollStrategies | ((data: ScrollStrategyData) => void)
}

export const makeScrollStrategyProps = propsFactory({
  scrollStrategy: {
    type: [String, Function] as PropType<StrategyProps['scrollStrategy']>,
    default: 'block',
    validator: (val: any) => typeof val === 'function' || val in scrollStrategies,
  },
})

export function useScrollStrategies (
  props: StrategyProps,
  data: ScrollStrategyData
) {
  if (!IN_BROWSER) return

  let scope: EffectScope | undefined
  watchEffect(async () => {
    scope?.stop()

    if (!(data.isActive.value && props.scrollStrategy)) return

    scope = effectScope()
    await nextTick()
    scope.run(() => {
      if (typeof props.scrollStrategy === 'function') {
        props.scrollStrategy(data)
      } else {
        scrollStrategies[props.scrollStrategy]?.(data)
      }
    })
  })
}

function closeScrollStrategy (data: ScrollStrategyData) {
  function onScroll (e: Event) {
    data.isActive.value = false
  }

  bindScroll(data.activatorEl.value ?? data.contentEl.value, onScroll)
}

function blockScrollStrategy (data: ScrollStrategyData) {
  const scrollElements = [...new Set([
    ...getScrollParents(data.activatorEl.value),
    ...getScrollParents(data.contentEl.value),
  ])].filter(el => !el.classList.contains('v-overlay-scroll-blocked'))
  const scrollbarWidth = window.innerWidth - document.documentElement.offsetWidth

  const scrollableParent = (el => hasScrollbar(el) && el)(data.root.value?.offsetParent || document.documentElement)
  if (scrollableParent) {
    data.root.value!.classList.add('v-overlay--scroll-blocked')
  }

  scrollElements.forEach((el, i) => {
    el.style.setProperty('--v-body-scroll-x', convertToUnit(-el.scrollLeft))
    el.style.setProperty('--v-body-scroll-y', convertToUnit(-el.scrollTop))
    el.style.setProperty('--v-scrollbar-offset', convertToUnit(scrollbarWidth))
    el.classList.add('v-overlay-scroll-blocked')
  })

  onScopeDispose(() => {
    scrollElements.forEach((el, i) => {
      const x = parseFloat(el.style.getPropertyValue('--v-body-scroll-x'))
      const y = parseFloat(el.style.getPropertyValue('--v-body-scroll-y'))

      el.style.removeProperty('--v-body-scroll-x')
      el.style.removeProperty('--v-body-scroll-y')
      el.style.removeProperty('--v-scrollbar-offset')
      el.classList.remove('v-overlay-scroll-blocked')

      el.scrollLeft = -x
      el.scrollTop = -y
    })
    if (scrollableParent) {
      data.root.value!.classList.remove('v-overlay--scroll-blocked')
    }
  })
}

function repositionScrollStrategy (data: ScrollStrategyData) {
  let slow = false
  let raf = -1

  function update (e: Event) {
    requestNewFrame(() => {
      const start = performance.now()
      data.updateLocation.value?.(e)
      const time = performance.now() - start
      slow = time / (1000 / 60) > 2
    })
  }

  bindScroll(data.activatorEl.value ?? data.contentEl.value, e => {
    if (slow) {
      // If the position calculation is slow,
      // defer updates until scrolling is finished.
      // Browsers usually fire one scroll event per frame so
      // we just wait until we've got two frames without an event
      cancelAnimationFrame(raf)
      raf = requestAnimationFrame(() => {
        raf = requestAnimationFrame(() => {
          update(e)
        })
      })
    } else {
      update(e)
    }
  })
}

/** @private */
function bindScroll (el: HTMLElement | undefined, onScroll: (e: Event) => void) {
  const scrollElements = [document, ...getScrollParents(el)]
  scrollElements.forEach(el => {
    el.addEventListener('scroll', onScroll, { passive: true })
  })

  onScopeDispose(() => {
    scrollElements.forEach(el => {
      el.removeEventListener('scroll', onScroll)
    })
  })
}


<template>
  <v-row>
    <v-col
      v-for="n in 9"
      :key="n"
      class="d-flex child-flex"
      cols="4"
    >
      <v-img
        :src="`https://picsum.photos/500/300?image=${n * 5 + 10}`"
        :lazy-src="`https://picsum.photos/10/6?image=${n * 5 + 10}`"
        aspect-ratio="1"
        cover
        class="bg-grey-lighten-2"
      >
        <template v-slot:placeholder>
          <v-row
            class="fill-height ma-0"
            align="center"
            justify="center"
          >
            <v-progress-circular
              indeterminate
              color="grey-lighten-5"
            ></v-progress-circular>
          </v-row>
        </template>
      </v-img>
    </v-col>
  </v-row>
</template>


@import './_variables.scss'

.v-date-picker-years
  font-size: $date-picker-years-font-size
  font-weight: $date-picker-years-font-weight
  height: $date-picker-years-portrait-height
  list-style-type: none
  overflow: auto
  text-align: $date-picker-years-item-align

  &.v-date-picker-years
    padding: 0

  li
    cursor: pointer
    padding: $date-picker-years-item-padding
    transition: none

    &.active
      font-size: $date-picker-years-active-font-size
      font-weight: $date-picker-years-active-font-weight
      padding: $date-picker-years-active-padding

    &:hover
      background: $date-picker-years-item-hover-background

.v-picker--landscape
  .v-date-picker-years
    padding: 0
    height: $date-picker-years-landscape-height


// @ts-nocheck
/* eslint-disable */

// import { parseDate } from '../util/timestamp'
// import VCalendar from '../VCalendar'
import {
  mount,
  Wrapper,
  MountOptions,
} from '@vue/test-utils'
// import { ExtractVue } from '../../../util/mixins'

describe.skip('VCalendar', () => {
  type Instance = ExtractVue<typeof VCalendar>
  let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
  beforeEach(() => {
    mountFunction = (options?: MountOptions<Instance>) => {
      return mount(VCalendar, {
        // https://github.com/vuejs/vue-test-utils/issues/1130
        sync: false,
        mocks: {
          $vuetify: {
            lang: {
              current: 'en-US',
            },
          },
        },
        ...options,
      })
    }
  })

  it('should render day view', async () => {
    const wrapper = mountFunction({
      propsData: {
        type: 'day',
        start: '2018-01-29',
        end: '2018-02-04',
        now: '2019-02-17',
      },
      methods: {
        getNow: () => parseDate(new Date('2019-02-17')),
      },
    })

    expect(wrapper.classes('v-calendar-daily')).toBeTruthy()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render 4-day view', async () => {
    const wrapper = mountFunction({
      propsData: {
        type: '4day',
        start: '2018-01-29',
        end: '2018-02-04',
        now: '2019-02-17',
      },
      methods: {
        getNow: () => parseDate(new Date('2019-02-17')),
      },
    })

    expect(wrapper.classes('v-calendar-daily')).toBeTruthy()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render week view', async () => {
    const wrapper = mountFunction({
      propsData: {
        type: 'week',
        start: '2018-01-29',
        end: '2018-02-04',
        now: '2019-02-17',
      },
      methods: {
        getNow: () => parseDate(new Date('2019-02-17')),
      },
    })

    expect(wrapper.classes('v-calendar-daily')).toBeTruthy()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render month view', async () => {
    const wrapper = mountFunction({
      propsData: {
        type: 'month',
        start: '2018-01-29',
        end: '2018-02-04',
        now: '2019-02-17',
      },
      methods: {
        getNow: () => parseDate(new Date('2019-02-17')),
      },
    })

    expect(wrapper.classes('v-calendar-monthly')).toBeTruthy()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should parse value', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: '2019-02-02',
        start: '2019-01-29',
        end: '2019-02-04',
      },
    })

    expect(wrapper.vm.parsedValue.date).toBe('2019-02-02')
  })

  it('should parse start', async () => {
    const wrapper = mountFunction({
      propsData: {
        start: '2019-01-29',
        end: '2019-02-04',
      },
    })

    expect(wrapper.vm.parsedValue.date).toBe('2019-01-29')
  })

  it('should go to correct day when using next/prev public functions', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: '2019-01-11',
        type: 'day',
        weekdays: [1, 2, 3, 4, 5],
      },
    })

    const input = jest.fn(value => wrapper.setProps({ value }))
    wrapper.vm.$on('input', input)

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

    wrapper.vm.next()
    await wrapper.vm.$nextTick()

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

    wrapper.vm.prev()
    await wrapper.vm.$nextTick()

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


if (typeof props.scrollStrategy === 'function') {


/// <reference types="../../../../types/cypress" />

// Components
import { VBtn } from '@/components/VBtn'
import { VBtnGroup } from '..'

const colors = ['success', 'info', 'warning', 'error', 'invalid'] as const
const densities = ['default', 'comfortable', 'compact'] as const
const variants = ['elevated', 'flat', 'tonal', 'outlined', 'text', 'plain'] as const

// TODO: screenshot tests
describe('VBtnGroup', () => {
  describe('color', () => {
    it('should render set length', () => {
      cy.mount(() => (
        <>
          { colors.map(color => (
            <VBtnGroup color={ color }>
              <VBtn>{ color } Button 1</VBtn>
              <VBtn>Button 2</VBtn>
              <VBtn>Button 3</VBtn>
            </VBtnGroup>
          )) }
        </>
      ))
        .get('.v-btn-group')
        .should('have.length', colors.length)
    })
  })

  describe('density', () => {
    it('supports density props', () => {
      cy.mount(() => (
        <>
          { densities.map(density => (
            <VBtnGroup color="primary" density={ density }>
              <VBtn>{ density } Button 1</VBtn>
              <VBtn>Button 2</VBtn>
              <VBtn>Button 3</VBtn>
            </VBtnGroup>
          )) }
        </>
      ))
        .get('.v-btn-group')
        .should('have.length', densities.length)
    })
  })

  describe('variant', () => {
    it('supports variant props', () => {
      cy.mount(() => (
        <>
          { variants.map(variant => (
            <VBtnGroup color="primary" variant={ variant }>
              <VBtn>{ variant } Button 1</VBtn>
              <VBtn>Button 2</VBtn>
              <VBtn>Button 3</VBtn>
            </VBtnGroup>
          )) }
        </>
      ))
        .get('.v-btn-group')
        .should('have.length', variants.length)
    })
  })
})


---
nav: Images
meta:
  title: Image component
  description: The image component provides a flexible interface for displaying different types of images.
  keywords: images, vuetify image component, vue image component
related:
  - /components/grids
  - /components/aspect-ratios
  - /components/parallax
---

# Images

The `v-img` component is packed with features to support rich media. Combined with the [vuetify-loader](https://github.com/vuetifyjs/vuetify-loader), you can add dynamic progressive images to provide a better user experience.

<entry />

## Usage

`v-img` component is used to display a responsive image with lazy-load and placeholder.

<!-- <usage name="v-img" /> -->

## API

<api-inline />

## Examples

### Props

#### Aspect ratio

You can set a fixed aspect ratio if you want to change aspect ratio of the image.

<example file="v-img/prop-aspect-ratio" />

<promoted slug="vuetify-zero-theme-pro" />

#### Cover

If the provided aspect ratio doesn't match that of the actual image, the default behavior is to fill as much space as possible without cropping. To fill the entire available space use the `cover` prop.

<example file="v-img/prop-cover" />

#### Height

`v-img` will automatically grow to the size of its `src`, preserving the correct aspect ratio. You can limit this with the `height` and `max-height` props.

<example file="v-img/prop-max-height" />

### Slots

#### Placeholder

`v-img` has a special `placeholder` slot for placeholder to display while image's loading. Note: the example below has bad src which won't load for you to see placeholder.

<example file="v-img/slot-placeholder" />

### Misc

#### Future image formats

By default `v-img` will render a basic `<img>` element. If you want to use `.webp` images with a fallback for older browsers, you can pass a list of `<source>` elements to the `sources` slot:

```html
<v-img src="image.jpeg">
  <template #sources>
    <source srcset="image.webp">
  </template>
</v-img>
```

This will behave similarly to:

```html
<picture>
  <source srcset="image.webp">
  <img src="image.jpeg">
</picture>
```

`srcset` and `media` attributes can also be used for art direction or alternate sizes, see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) for more.

#### Grid

You can use `v-img` to make, for example, a picture gallery.

<example file="v-img/misc-grid" />

<backmatter />