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

exports[`VSelect should be clearable with prop, dirty and multi select 1`] = `

        1

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                        check_box_outline_blank

                        check_box

                    1

                        check_box_outline_blank

                    2

      clear

`;

exports[`VSelect should be clearable with prop, dirty and multi select 2`] = `

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                        check_box

                        check_box_outline_blank

                    1

                        check_box_outline_blank

                    2

      arrow_drop_down

`;

exports[`VSelect should be clearable with prop, dirty and single select 1`] = `

        1

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    1

                    2

      clear

`;

exports[`VSelect should be clearable with prop, dirty and single select 2`] = `

      <input disabled="disabled"
             tabindex="-1"
             class="input-group--select__autocomplete"
             style="display: none;"
      >

                    1

                    2

      arrow_drop_down

`;


style.type = 'text/css'
document.head.appendChild(style)
this.style = style
},
updateTheme (classes) {
this.style.innerHTML = classes
}
}
}

keep keep keep replace replace replace keep keep keep

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

/* Theme */
input-group($material)
  input,
  textarea
    color: $material.text.primary

    &:disabled
      color: $material.text.disabled

  &:not(.input-group--error) .input-group__messages
    color: $material.text.secondary

  &.input-group--textarea:not(.input-group--full-width)
    .input-group__input
      border: 2px solid $material.text.secondary

  // This is specific for selects
  &.input-group--dirty
    .input-group__selections__comma
      &:not(.input-group__selections__comma--active)
        color: $material.text.primary

  &:not(.input-group--error)
    label
      color: $material.text.secondary

    &.input-group--disabled
      label,
      .input-group__selections__comma
        color: $material.text.disabled

    .input-group__details:before
      background-color: $material.input-bottom-line

    &:not(.input-group--focused):not(.input-group--disabled)
      .input-group__input
        .input-group__append-icon,
        .input-group__prepend-icon
          &:not(:hover)
            color: $material.text.secondary

      &:not(.input-group--disabled):not(.input-group--overflow):not(.input-group--segmented):not(.input-group--editable):hover
        .input-group__details:before
          background-color: $material.text.primary

        &.input-group--textarea:not(.input-group--full-width)
          .input-group__input
            border-color: $material.text.primary

  &.input-group
    &--overflow, &--segmented, &--editable
      .input-group__input,
      .input-group__details
        &:before
          background-color: $material.dividers

  &.input-group--disabled
    .input-group__input
      .input-group__append-icon,
      .input-group__prepend-icon
        color: $material.text.disabled

    .input-group__details:before
      background-color: transparent
      background-image: linear-gradient(
        to right,
        $material.text.disabled 0,
        $material.text.disabled 33%,
        transparent 0
      )

  .input-group--text-field
    &__prefix, &__suffix
      color: $material.text.secondary

    &.input-group--disabled
      &__prefix, &__suffix
        color: $material.text.disabled

theme(input-group, "input-group")

.input-group
  display: flex
  flex: 1 1
  flex-wrap: wrap
  min-width: 24px
  padding: $input-group-padding
  position: relative
  width: 100%
  outline: none
  transition: box-shadow $primary-transition

/** Label */
.input-group
  label
    display: inline-block
    font-size: $input-font-size
    line-height: $input-height
    height: $input-height
    max-width: 90%
    min-width: 0
    overflow: hidden
    pointer-events: none
    text-align: left
    text-overflow: ellipsis
    transform-origin: top left
    transition: .4s $transition.fast-in-fast-out
    white-space: nowrap
    width: 100%
    z-index: 0

/** Blocks */
.input-group
  &__input
    display: flex
    flex: 1 0 100%
    min-width: 0
    min-height: $input-height

/** Icons */
.input-group
  &__icon-cb
    cursor: pointer

.input-group
  &.input-group--focused,
  &.input-group--error
    .input-group__append-icon,
    .input-group__prepend-icon
      color: inherit

  &.input-group--append-icon, &.input-group--selection-controls
    label
      max-width: 75%

  &.input-group--prepend-icon
    label
      max-width: 75%

  &.input-group--append-icon.input-group--prepend-icon
    label
      max-width: 65%

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

  .input-group__append-icon,
  .input-group__prepend-icon
    align-self: center
    user-select: none

/** States */
.input-group
  // Single Line
  &.input-group--single-line,
  &.input-group--solo
    &.input-group--dirty
      label
        display: none

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

    label
      top: 8px
      padding-left: 16px
      transform: none !important

    .input-group__input
      align-items: center
      padding: 8px 16px

    .input-group__details
      display: none

  &--disabled
    .input-group__details:before
      background-color: transparent
      background-position: bottom
      background-size: 3px 1px
      background-repeat: repeat-x

  // Focused
  &.input-group--text-field
    &:not(.input-group--single-line):not(.input-group--error)
      &.input-group--focused
        label
          color: inherit

        .input-group__input
          border-color: inherit

    &.input-group--focused
      &:not(.input-group--disabled)
        .input-group__details
          &:after
            transform: scaleX(1)

  // Required
  &--required
    label:after
      content: '*'

  // Error
  &.input-group--error
    label
      animation: shake .6s $transition.swing

    .input-group__messages
      color: inherit

    .input-group__details
      &:before
        background-color: currentColor

/** Details */
.input-group
  .slide-y-transition-leave,
  .slide-y-transition-leave-to
    position: absolute

  &__details
    color: inherit
    display: flex
    padding-top: 4px
    flex: 1 0 100%
    font-size: 12px
    min-height: 26px
    overflow: hidden
    position: relative
    width: 100%

    &:after, &:before
      content: ''
      position: absolute
      left: 0
      transition: .3s $transition.fast-out-slow-in

    &:after
      background-color: currentColor
      color: inherit
      top: 0
      height: 2px
      transform: scaleX(0)
      transform-origin: center center 0
      width: 100%
      z-index: 1

    &:before
      top: 0
      height: 1px
      width: 100%
      z-index: 0

  &--hide-details
    .input-group__details
      min-height: 2px
      padding: 0

  &--async-loading
    .input-group__details
      &:before,
      &:after
        display: none

  /** Async loading indicator */
  .progress-linear
    position: absolute
    top: 0
    left: 0
    margin: 0

/** Hint */
.input-group
  &__hint
    // flex: 1 0
    transition: .3s $transition.fast-in-fast-out

/** Error */
.input-group
  .input-group__error
    color: inherit
    flex: 1 0
    transition: .3s $transition.fast-in-fast-out

/** Types */
.input-group
  &--overflow,
  &--segmented,
  &--editable
    &.input-group--active
      background-color: #fff


export default {
  data: () => ({
    style: null
  }),

  watch: {
    type () {
      this.applyType()
    },
    '$vuetify.theme': {
      deep: true,
      handler () {
        this.applyType()
      }
    }
  },

  mounted () {
    this.genStyle()
    this.genThemeClasses()
    this.applyType()
  },

  methods: {
    applyType () {
      this.genThemeClasses()
      this.$vuetify.theme.type = this.type
    },
    genThemeClasses () {
      this.updateTheme(this.genTheme())
    },
    genTheme () {
      return this.genColors(this.$vuetify.theme)
    },
    genColors (theme) {
      return Object.keys(theme).map(key => {
        const value = theme[key]

        return (
          this.genBackgroundColor(key, value) +
          this.genTextColor(key, value)
        )
      }).join('')
    },
    genBackgroundColor (key, value) {
      return `.${key}{background-color:${value} !important;border-color:${value} !important;}`
    },
    genTextColor (key, value) {
      return `.${key}--text{color:${value} !important;}`
    },
    genStyle () {
      const style = document.createElement('style')
      style.type = 'text/css'
      document.head.appendChild(style)
      this.style = style
    },
    updateTheme (classes) {
      this.style.innerHTML = classes
    }
  }
}


language: node_js
node_js:
  - "8"

env:
  global:
    - CC_TEST_REPORTER_ID=b429c71716cd1302821cad1fdc0dd6ea964144ec975e3aad40cbaf1235afb775

before_script:
  - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
  - chmod +x ./cc-test-reporter
  - ./cc-test-reporter before-build

script:
  - yarn
  - yarn run lint
  - yarn run test:coverage
  - yarn run build

after_success:
  - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT

cache:
  yarn: true

before_deploy:
  - rm -rf release
  - mkdir -p release
  - for file in ./dist/*; do cp "$file" "${file/dist\/vuetify/release/vuetify-${TRAVIS_TAG}}"; done
  - zip -jr "release/vuetify-${TRAVIS_TAG}.zip" release/*

deploy:
  - provider: npm
    email: "john.j.leider@gmail.com"
    api_key: $NPM_API_KEY
    tag: $(node ./build/parse-npm-tag.js)
    skip_cleanup: true
    on:
      repo: vuetifyjs/vuetify
      tags: true
  - provider: releases
    api_key: $GITHUB_API_KEY
    files:
      - "release/vuetify-${TRAVIS_TAG}.zip"
    skip_cleanup: true
    on:
      tags: true


import { test } from '~util/testing'
import { mount } from 'avoriaz'
import VDataTable from './VDataTable'

test('VDataTable.vue', () => {
  function dataTableTestData () {
    return {
      propsData: {
        pagination: {
          descending: false,
          sortBy: 'col1'
        },
        headers: [
          { text: 'First Column', value: 'col1', class: 'a-string' },
          { text: 'Second Column', value: 'col2', sortable: false },
          { text: 'Third Column', value: 'col3', class: ['an', 'array'] }
        ],
        items: [
          { other: 1, col1: 'foo', col2: 'a', col3: 1 },
          { other: 2, col1: null, col2: 'b', col3: 2 },
          { other: 3, col1: undefined, col2: 'c', col3: 3 }
        ]
      }
    }
  }

  // TODO: This doesn't actually test anything
  it.skip('should be able to filter null and undefined values', async () => {
    const data = dataTableTestData()
    const pagination = data.propsData.pagination
    const wrapper = mount(VDataTable, data)

    pagination.descending = true

    expect(wrapper.vm.$props.pagination.descending).toBe(true)
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should match a snapshot', () => {
    const data = dataTableTestData()
    const wrapper = mount(VDataTable, data)

    expect(wrapper.html()).toMatchSnapshot()
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should render aria-sort attribute on column headers', async () => {
    const data = dataTableTestData()
    const wrapper = mount(VDataTable, data)

    let headers = wrapper.find('thead:first-of-type > tr:first-of-type > th')

    expect(
      headers.map(h => h.getAttribute('aria-sort'))
    ).toEqual(['ascending', 'none', 'none'])

    wrapper.setProps({ pagination: {
      sortBy: 'col3',
      descending: false
    }})

    expect(
      headers.map(h => h.getAttribute('aria-sort'))
    ).toEqual(['none', 'none', 'ascending'])

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should match not allow a null sort', async () => {
    const data = {
      propsData: {
        mustSort: true,
        headers: [
          { text: 'First Column', value: 'col1' },
          { text: 'Second Column', value: 'col2', sortable: false },
          { text: 'Third Column', value: 'col3' }
        ],
        items: [
          { other: 1, col1: 'foo', col2: 'a', col3: 1 },
          { other: 2, col1: null, col2: 'b', col3: 2 },
          { other: 3, col1: undefined, col2: 'c', col3: 3 }
        ]
      }
    }

    const wrapper = mount(VDataTable, data)

    expect(wrapper.vm.defaultPagination.descending).toBe(false)
    wrapper.vm.sort(0)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.defaultPagination.descending).toBe(false)
    wrapper.vm.sort(0)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.defaultPagination.descending).toBe(true)
    wrapper.vm.sort(0)
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.defaultPagination.descending).toBe(false)

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })
})


$shadow-key-umbra-opacity = rgba(0,0,0, .2)
$shadow-key-penumbra-opacity = rgba(0,0,0, .14)
$shadow-key-ambient-opacity = rgba(0,0,0, .12)

$shadow-key-umbra = 0px 0px 0px 0px $shadow-key-umbra-opacity,
                    0px 2px 1px -1px $shadow-key-umbra-opacity,
                    0px 3px 1px -2px $shadow-key-umbra-opacity,
                    0px 3px 3px -2px $shadow-key-umbra-opacity,
                    0px 2px 4px -1px $shadow-key-umbra-opacity,
                    0px 3px 5px -1px $shadow-key-umbra-opacity,
                    0px 3px 5px -1px $shadow-key-umbra-opacity,
                    0px 4px 5px -2px $shadow-key-umbra-opacity,
                    0px 5px 5px -3px $shadow-key-umbra-opacity,
                    0px 5px 6px -3px $shadow-key-umbra-opacity,
                    0px 6px 6px -3px $shadow-key-umbra-opacity,
                    0px 6px 7px -4px $shadow-key-umbra-opacity,
                    0px 7px 8px -4px $shadow-key-umbra-opacity,
                    0px 7px 8px -4px $shadow-key-umbra-opacity,
                    0px 7px 9px -4px $shadow-key-umbra-opacity,
                    0px 8px 9px -5px $shadow-key-umbra-opacity,
                    0px 8px 10px -5px $shadow-key-umbra-opacity,
                    0px 8px 11px -5px $shadow-key-umbra-opacity,
                    0px 9px 11px -5px $shadow-key-umbra-opacity,
                    0px 9px 12px -6px $shadow-key-umbra-opacity,
                    0px 10px 13px -6px $shadow-key-umbra-opacity,
                    0px 10px 13px -6px $shadow-key-umbra-opacity,
                    0px 10px 14px -6px $shadow-key-umbra-opacity,
                    0px 11px 14px -7px $shadow-key-umbra-opacity,
                    0px 11px 15px -7px $shadow-key-umbra-opacity

$shadow-key-penumbra = 0px 0px 0px 0px $shadow-key-penumbra-opacity,
                        0px 1px 1px 0px $shadow-key-penumbra-opacity,
                        0px 2px 2px 0px $shadow-key-penumbra-opacity,
                        0px 3px 4px 0px $shadow-key-penumbra-opacity,
                        0px 4px 5px 0px $shadow-key-penumbra-opacity,
                        0px 5px 8px 0px $shadow-key-penumbra-opacity,
                        0px 6px 10px 0px $shadow-key-penumbra-opacity,
                        0px 7px 10px 1px $shadow-key-penumbra-opacity,
                        0px 8px 10px 1px $shadow-key-penumbra-opacity,
                        0px 9px 12px 1px $shadow-key-penumbra-opacity,
                        0px 10px 14px 1px $shadow-key-penumbra-opacity,
                        0px 11px 15px 1px $shadow-key-penumbra-opacity,
                        0px 12px 17px 2px $shadow-key-penumbra-opacity,
                        0px 13px 19px 2px $shadow-key-penumbra-opacity,
                        0px 14px 21px 2px $shadow-key-penumbra-opacity,
                        0px 15px 22px 2px $shadow-key-penumbra-opacity,
                        0px 16px 24px 2px $shadow-key-penumbra-opacity,
                        0px 17px 26px 2px $shadow-key-penumbra-opacity,
                        0px 18px 28px 2px $shadow-key-penumbra-opacity,
                        0px 19px 29px 2px $shadow-key-penumbra-opacity,
                        0px 20px 31px 3px $shadow-key-penumbra-opacity,
                        0px 21px 33px 3px $shadow-key-penumbra-opacity,
                        0px 22px 35px 3px $shadow-key-penumbra-opacity,
                        0px 23px 36px 3px $shadow-key-penumbra-opacity,
                        0px 24px 38px 3px $shadow-key-penumbra-opacity

$shadow-key-ambient = 0px 0px 0px 0px $shadow-key-ambient-opacity,
                      0px 1px 3px 0px $shadow-key-ambient-opacity,
                      0px 1px 5px 0px $shadow-key-ambient-opacity,
                      0px 1px 8px 0px $shadow-key-ambient-opacity,
                      0px 1px 10px 0px $shadow-key-ambient-opacity,
                      0px 1px 14px 0px $shadow-key-ambient-opacity,
                      0px 1px 18px 0px $shadow-key-ambient-opacity,
                      0px 2px 16px 1px $shadow-key-ambient-opacity,
                      0px 3px 14px 2px $shadow-key-ambient-opacity,
                      0px 3px 16px 2px $shadow-key-ambient-opacity,
                      0px 4px 18px 3px $shadow-key-ambient-opacity,
                      0px 4px 20px 3px $shadow-key-ambient-opacity,
                      0px 5px 22px 4px $shadow-key-ambient-opacity,
                      0px 5px 24px 4px $shadow-key-ambient-opacity,
                      0px 5px 26px 4px $shadow-key-ambient-opacity,
                      0px 6px 28px 5px $shadow-key-ambient-opacity,
                      0px 6px 30px 5px $shadow-key-ambient-opacity,
                      0px 6px 32px 5px $shadow-key-ambient-opacity,
                      0px 7px 34px 6px $shadow-key-ambient-opacity,
                      0px 7px 36px 6px $shadow-key-ambient-opacity,
                      0px 8px 38px 7px $shadow-key-ambient-opacity,
                      0px 8px 40px 7px $shadow-key-ambient-opacity,
                      0px 8px 42px 7px $shadow-key-ambient-opacity,
                      0px 9px 44px 8px $shadow-key-ambient-opacity,
                      0px 9px 46px 8px $shadow-key-ambient-opacity

// MIXINS
elevation($z, important = false)
  box-shadow: $shadow-key-umbra[$z],
              $shadow-key-penumbra[$z],
              $shadow-key-ambient[$z] (important ? !important : )

elevationTransition($duration = 280ms, $easing = cubic-bezier(.4, 0, .2, 1))
  transition: box-shadow $duration $easing
  will-change: box-shadow


const darkTheme = this.dark || (!this.light && this.$vuetify.theme.type === 'dark')

const defaultTitleColor = darkTheme ? null : 'primary'

return this.headerColor || this.color || defaultTitleColor

keep keep keep keep replace keep keep keep keep keep

const darkTheme = this.dark || (!this.light && this.$vuetify.dark)


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

exports[`VPagination.vue emits an event when next or previous is clicked 1`] = `

    <button class="pagination__navigation">

        chevron_left

    </button>

    <button class="pagination__item">
      1
    </button>

    <button class="pagination__item">
      2
    </button>

    <button class="pagination__item">
      3
    </button>

    <button class="pagination__item">
      4
    </button>

    <button class="pagination__item">
      5
    </button>

    <button class="pagination__navigation">

        chevron_right

    </button>

`;

exports[`VPagination.vue should only render middle of range if length is big and value is somewhere in the middle 1`] = `

    <button class="pagination__navigation">

        chevron_left

    </button>

    <button class="pagination__item">
      1
    </button>

      ...

    <button class="pagination__item">
      41
    </button>

    <button class="pagination__item">
      42
    </button>

    <button class="pagination__item">
      43
    </button>

    <button class="pagination__item">
      44
    </button>

    <button class="pagination__item">
      45
    </button>

    <button class="pagination__item">
      46
    </button>

    <button class="pagination__item">
      47
    </button>

    <button class="pagination__item">
      48
    </button>

    <button class="pagination__item">
      49
    </button>

    <button class="pagination__item pagination__item--active primary">
      50
    </button>

    <button class="pagination__item">
      51
    </button>

    <button class="pagination__item">
      52
    </button>

    <button class="pagination__item">
      53
    </button>

    <button class="pagination__item">
      54
    </button>

    <button class="pagination__item">
      55
    </button>

    <button class="pagination__item">
      56
    </button>

    <button class="pagination__item">
      57
    </button>

    <button class="pagination__item">
      58
    </button>

      ...

    <button class="pagination__item">
      100
    </button>

    <button class="pagination__navigation">

        chevron_right

    </button>

`;

exports[`VPagination.vue should only render start and end of range if length is big 1`] = `

    <button class="pagination__navigation">

        chevron_left

    </button>

    <button class="pagination__item">
      1
    </button>

    <button class="pagination__item">
      2
    </button>

    <button class="pagination__item">
      3
    </button>

    <button class="pagination__item">
      4
    </button>

    <button class="pagination__item">
      5
    </button>

    <button class="pagination__item">
      6
    </button>

    <button class="pagination__item">
      7
    </button>

    <button class="pagination__item">
      8
    </button>

    <button class="pagination__item">
      9
    </button>

    <button class="pagination__item">
      10
    </button>

    <button class="pagination__item">
      11
    </button>

      ...

    <button class="pagination__item">
      91
    </button>

    <button class="pagination__item">
      92
    </button>

    <button class="pagination__item">
      93
    </button>

    <button class="pagination__item">
      94
    </button>

    <button class="pagination__item">
      95
    </button>

    <button class="pagination__item">
      96
    </button>

    <button class="pagination__item">
      97
    </button>

    <button class="pagination__item">
      98
    </button>

    <button class="pagination__item">
      99
    </button>

    <button class="pagination__item">
      100
    </button>

    <button class="pagination__navigation">

        chevron_right

    </button>

`;

exports[`VPagination.vue should use totalVisible prop if defined 1`] = `

    <button class="pagination__navigation">

        chevron_left

    </button>

    <button class="pagination__item">
      1
    </button>

      ...

    <button class="pagination__item">
      47
    </button>

    <button class="pagination__item">
      48
    </button>

    <button class="pagination__item">
      49
    </button>

    <button class="pagination__item pagination__item--active primary">
      50
    </button>

    <button class="pagination__item">
      51
    </button>

    <button class="pagination__item">
      52
    </button>

      ...

    <button class="pagination__item">
      100
    </button>

    <button class="pagination__navigation">

        chevron_right

    </button>

`;


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

exports[`VTextField.js should not display counter when set to false 1`] = `

    <input tabindex="0"
           type="text"
    >

      0 / 25

`;

exports[`VTextField.js should not display counter when set to false 2`] = `

    <input tabindex="0"
           type="text"
    >

`;

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

    <input tabindex="0"
           type="text"
    >

`;

exports[`VTextField.js should render component with async loading and custom progress and match snapshot 1`] = `

    <input tabindex="0"
           type="text"
    >

`;

exports[`VTextField.js should render component with async loading and match snapshot 1`] = `

    <input tabindex="0"
           type="text"
    >

`;


import { test } from '~util/testing'
import { mount } from 'avoriaz'
import VSelect from '~components/VSelect'
import VMenu from '~components/VMenu'

test('VSelect - tags', () => {
  const backspace = new Event('keydown')
  backspace.keyCode = 8

  it('should create new values when tagging', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: []
      }
    })

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

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

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

    input.element.value = 'foo'
    input.trigger('input')
    await wrapper.vm.$nextTick()
    await wrapper.vm.$nextTick()

    input.trigger('keydown.enter')
    await wrapper.vm.$nextTick()

    expect(change).toHaveBeenCalledWith(['foo'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should change selectedIndex with keyboard', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo', 'bar']
      }
    })

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

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

    for (const index of [1, 0, -1]) {
      input.trigger('keydown.left')
      await wrapper.vm.$nextTick()
      expect(wrapper.vm.selectedIndex).toBe(index)
    }

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should delete a tagged item when selected and backspace/delete is pressed', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo', 'bar']
      }
    })

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

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

    wrapper.vm.focus()

    input.trigger('keydown.left')
    expect(wrapper.vm.selectedIndex).toBe(1)

    input.trigger('keydown.delete')
    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledWith(['foo'])
    expect(wrapper.vm.selectedIndex).toBe(0)

    input.element.dispatchEvent(backspace) // Avoriaz doesn't wrap keydown.backspace
    await wrapper.vm.$nextTick()
    expect(change).toHaveBeenCalledWith([])
    expect(wrapper.vm.selectedIndex).toBe(-1)

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on tab using the first suggestion', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: [],
        items: ['bar']
      }
    })

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

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

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

    input.element.value = 'b'
    input.trigger('input')
    await wrapper.vm.$nextTick()
    input.trigger('keydown.down')
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['bar'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on tab using the current searchValue', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: [],
        items: ['bar']
      }
    })

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

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

    wrapper.vm.focus()
    await wrapper.vm.$nextTick()
    wrapper.setProps({ searchInput: 'ba' })
    await wrapper.vm.$nextTick()
    input.trigger('keydown.down')
    await wrapper.vm.$nextTick()
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()
    expect(change).toBeCalledWith(['bar'])

    wrapper.setProps({ searchInput: 'it' })
    await wrapper.vm.$nextTick()
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()
    expect(change).toBeCalledWith(['bar', 'it'])

    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on enter using the current searchValue', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: [],
        items: ['bar']
      }
    })

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

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

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

    input.element.value = 'ba'
    input.trigger('input')
    input.element.setSelectionRange(2, 2)
    await wrapper.vm.$nextTick()
    input.trigger('keydown.right')
    await wrapper.vm.$nextTick()
    input.trigger('keydown.enter')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['ba'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add a tag on left arrow and select the previous tag', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo'],
        items: ['foo', 'bar']
      }
    })

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

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

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

    input.element.value = 'b'
    input.trigger('input')
    input.trigger('keydown.left')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['foo', 'b'])
    expect(wrapper.vm.selectedIndex).toBe(0)
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should remove a duplicate tag and add it to the end', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true,
        value: ['foo', 'bar']
      }
    })

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

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

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

    input.element.value = 'foo'
    input.trigger('input')
    input.trigger('keydown.tab')
    await wrapper.vm.$nextTick()

    expect(change).toBeCalledWith(['bar', 'foo'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })

  it('should add tag with valid search value on blur', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        tags: true
      }
    })

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

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

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

    input.element.value = 'bar'
    input.trigger('input')
    await wrapper.vm.$nextTick()

    wrapper.vm.blur()
    await wrapper.vm.$nextTick() // First tick processes blur, menu sets isActive false, adds tag
    await wrapper.vm.$nextTick() // Second tick processes change after tag added

    expect(change).toBeCalledWith(['bar'])
    expect('Application is missing <v-app> component.').toHaveBeenTipped()
  })
})


@use 'sass:selector'
@use 'sass:math'
@use '../../styles/settings'
@use '../../styles/tools'
@use './variables' as *

@at-root
  @include tools.density('v-input', $autocomplete-chips-margin-bottom) using ($modifier)
    --autocomplete-chips-margin-bottom: #{0px + math.div($modifier, 2)}

.v-autocomplete
  .v-field
    .v-text-field__prefix,
    .v-text-field__suffix,
    .v-field__input,
    &.v-field
      cursor: text

  .v-field
    .v-field__input
      > input
        align-self: flex-start
        flex: 1 1

    .v-field__append-inner
      > .v-icon
        margin-inline-start: 4px
        transition: $autocomplete-transition

  .v-field:not(.v-field--focused)
    input
      min-width: 0

  .v-field--dirty
    .v-autocomplete__selection
      margin-inline-end: 2px

  .v-field--single-line
    .v-autocomplete__selection-text
      overflow: hidden
      text-overflow: ellipsis
      white-space: nowrap

.v-autocomplete
  &__content
    overflow: hidden

    @include tools.elevation($autocomplete-content-elevation)
    @include tools.rounded($autocomplete-content-border-radius)

  &__mask
    background: rgb(var(--v-theme-on-surface-variant))

  &__selection
    display: inline-flex
    letter-spacing: inherit
    line-height: inherit
    max-width: 100%

  &--active-menu
    .v-field__append-inner
      > .v-icon
        opacity: var(--v-high-emphasis-opacity)
        transform: rotate(180deg)

  &--chips
    .v-autocomplete__selection
      margin-top: $autocomplete-chips-margin-top
      margin-bottom: var(--autocomplete-chips-margin-bottom)

      &:first-child
        margin-inline-start: 0

  &--single
    &.v-text-field input
      flex: 1 1
      position: absolute
      left: 0
      right: 0
      width: 100%
      padding-inline-start: inherit
      padding-inline-end: inherit

    .v-field--active
      input
        transition: none

    .v-field--dirty:not(.v-field--focused)
      input
        opacity: 0

    .v-field--focused
      .v-autocomplete__selection
        opacity: 0


<template>
  <v-card id="create">
    <v-container fluid>
      <v-row class="child-flex">
        <v-col
          cols="12"
          sm="6"
          md="4"
        >
          <v-subheader>Options</v-subheader>
          <v-checkbox
            v-model="hover"
            label="Open on hover"
            hide-details
          ></v-checkbox>
        </v-col>
        <v-col
          cols="12"
          sm="6"
          md="4"
        >
          <v-subheader>FAB location</v-subheader>
          <v-checkbox
            v-model="top"
            label="Top"
            hide-details
          ></v-checkbox>
          <v-checkbox
            v-model="right"
            label="Right"
            hide-details
          ></v-checkbox>
          <v-checkbox
            v-model="bottom"
            label="Bottom"
            hide-details
          ></v-checkbox>
          <v-checkbox
            v-model="left"
            label="Left"
            hide-details
          ></v-checkbox>
        </v-col>
        <v-col
          cols="12"
          sm="6"
          md="4"
        >
          <v-subheader>Speed dial direction</v-subheader>
          <v-radio-group
            v-model="direction"
            hide-details
          >
            <v-radio
              value="top"
              label="Top"
            ></v-radio>
            <v-radio
              value="right"
              label="Right"
            ></v-radio>
            <v-radio
              value="bottom"
              label="Bottom"
            ></v-radio>
            <v-radio
              value="left"
              label="Left"
            ></v-radio>
          </v-radio-group>
        </v-col>
        <v-col
          cols="12"
          sm="6"
          md="4"
        >
          <v-subheader>Transition</v-subheader>
          <v-radio-group
            v-model="transition"
            hide-details
          >
            <v-radio
              value="slide-y-transition"
              label="Slide y"
            ></v-radio>
            <v-radio
              value="slide-y-reverse-transition"
              label="Slide y reverse"
            ></v-radio>
            <v-radio
              value="slide-x-transition"
              label="Slide x"
            ></v-radio>
            <v-radio
              value="slide-x-reverse-transition"
              label="Slide x reverse"
            ></v-radio>
            <v-radio
              value="scale-transition"
              label="Scale"
            ></v-radio>
          </v-radio-group>
        </v-col>
      </v-row>
    </v-container>
    <v-speed-dial
      v-model="fab"
      :top="top"
      :bottom="bottom"
      :right="right"
      :left="left"
      :direction="direction"
      :open-on-hover="hover"
      :transition="transition"
    >
      <template v-slot:activator>
        <v-btn
          v-model="fab"
          color="blue darken-2"
          dark
          fab
        >
          <v-icon v-if="fab">
            mdi-close
          </v-icon>
          <v-icon v-else>
            mdi-account-circle
          </v-icon>
        </v-btn>
      </template>
      <v-btn
        fab
        dark
        small
        color="green"
      >
        <v-icon>mdi-pencil</v-icon>
      </v-btn>
      <v-btn
        fab
        dark
        small
        color="indigo"
      >
        <v-icon>mdi-plus</v-icon>
      </v-btn>
      <v-btn
        fab
        dark
        small
        color="red"
      >
        <v-icon>mdi-delete</v-icon>
      </v-btn>
    </v-speed-dial>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      direction: 'top',
      fab: false,
      fling: false,
      hover: false,
      tabs: null,
      top: false,
      right: true,
      bottom: true,
      left: false,
      transition: 'slide-y-reverse-transition',
    }),

    watch: {
      top (val) {
        this.bottom = !val
      },
      right (val) {
        this.left = !val
      },
      bottom (val) {
        this.top = !val
      },
      left (val) {
        this.right = !val
      },
    },
  }
</script>

<style>
  /* This is for documentation purposes and will not be needed in your application */
  #create .v-speed-dial {
    position: absolute;
  }

  #create .v-btn--floating {
    position: relative;
  }
</style>


<template>
  <v-treeview
    activatable
    color="warning"
    :items="items"
  ></v-treeview>
</template>

<script>
  export default {
    data: () => ({
      items: [
        {
          id: 1,
          name: 'Applications :',
          children: [
            { id: 2, name: 'Calendar : app' },
            { id: 3, name: 'Chrome : app' },
            { id: 4, name: 'Webstorm : app' },
          ],
        },
        {
          id: 5,
          name: 'Documents :',
          children: [
            {
              id: 6,
              name: 'vuetify :',
              children: [
                {
                  id: 7,
                  name: 'src :',
                  children: [
                    { id: 8, name: 'index : ts' },
                    { id: 9, name: 'bootstrap : ts' },
                  ],
                },
              ],
            },
            {
              id: 10,
              name: 'material2 :',
              children: [
                {
                  id: 11,
                  name: 'src :',
                  children: [
                    { id: 12, name: 'v-btn : ts' },
                    { id: 13, name: 'v-card : ts' },
                    { id: 14, name: 'v-window : ts' },
                  ],
                },
              ],
            },
          ],
        },
        {
          id: 15,
          name: 'Downloads :',
          children: [
            { id: 16, name: 'October : pdf' },
            { id: 17, name: 'November : pdf' },
            { id: 18, name: 'Tutorial : html' },
          ],
        },
        {
          id: 19,
          name: 'Videos :',
          children: [
            {
              id: 20,
              name: 'Tutorials :',
              children: [
                { id: 21, name: 'Basic layouts : mp4' },
                { id: 22, name: 'Advanced techniques : mp4' },
                { id: 23, name: 'All about app : dir' },
              ],
            },
            { id: 24, name: 'Intro : mov' },
            { id: 25, name: 'Conference introduction : avi' },
          ],
        },
      ],
    }),
  }
</script>


<template>
  <v-table height="300px">

          Name

          Calories

        {{ item.name }}
        {{ item.calories }}

  </v-table>
</template>


&--selection-slot
&.v-text-field input
position: relative
padding-inline-start: 0


// Imports
@import './_variables.scss'
@import '../VAvatar/_variables.scss'

// Theme
+theme(v-skeleton-loader) using ($material)
  .v-skeleton-loader__bone::after
    background: map-get($material, 'skeleton')

  .v-skeleton-loader__avatar,
  .v-skeleton-loader__button,
  .v-skeleton-loader__chip,
  .v-skeleton-loader__divider,
  .v-skeleton-loader__heading,
  .v-skeleton-loader__image,
  .v-skeleton-loader__text
    background: map-get($material, 'dividers')

  .v-skeleton-loader__actions,
  .v-skeleton-loader__article,
  .v-skeleton-loader__card-heading,
  .v-skeleton-loader__card-text,
  .v-skeleton-loader__date-picker,
  .v-skeleton-loader__list-item,
  .v-skeleton-loader__list-item-avatar,
  .v-skeleton-loader__list-item-text,
  .v-skeleton-loader__list-item-two-line,
  .v-skeleton-loader__list-item-avatar-two-line,
  .v-skeleton-loader__list-item-three-line,
  .v-skeleton-loader__list-item-avatar-three-line,
  .v-skeleton-loader__table-heading,
  .v-skeleton-loader__table-thead,
  .v-skeleton-loader__table-tbody,
  .v-skeleton-loader__table-tfoot
    background: map-get($material, 'cards')

.v-skeleton-loader
  border-radius: $skeleton-loader-border-radius
  position: relative
  vertical-align: top

  &__actions
    padding: $skeleton-loader-actions-padding
    text-align: right

    .v-skeleton-loader__button
      display: inline-block

      &:first-child
        +ltr()
          margin-right: $skeleton-loader-actions-button-margin

        +rtl()
          margin-left: $skeleton-loader-actions-button-margin

  .v-skeleton-loader__list-item,
  .v-skeleton-loader__list-item-avatar,
  .v-skeleton-loader__list-item-text,
  .v-skeleton-loader__list-item-two-line,
  .v-skeleton-loader__list-item-avatar-two-line,
  .v-skeleton-loader__list-item-three-line,
  .v-skeleton-loader__list-item-avatar-three-line
    border-radius: $skeleton-loader-border-radius

  .v-skeleton-loader__actions,
  .v-skeleton-loader__article,
  .v-skeleton-loader__card,
  .v-skeleton-loader__card-avatar,
  .v-skeleton-loader__card-heading,
  .v-skeleton-loader__card-text,
  .v-skeleton-loader__date-picker,
  .v-skeleton-loader__date-picker-options,
  .v-skeleton-loader__date-picker-days,
  .v-skeleton-loader__list-item,
  .v-skeleton-loader__list-item-avatar,
  .v-skeleton-loader__list-item-text,
  .v-skeleton-loader__list-item-two-line,
  .v-skeleton-loader__list-item-avatar-two-line,
  .v-skeleton-loader__list-item-three-line,
  .v-skeleton-loader__list-item-avatar-three-line,
  .v-skeleton-loader__paragraph,
  .v-skeleton-loader__sentences,
  .v-skeleton-loader__table,
  .v-skeleton-loader__table-cell,
  .v-skeleton-loader__table-heading,
  .v-skeleton-loader__table-thead,
  .v-skeleton-loader__table-tbody,
  .v-skeleton-loader__table-tfoot,
  .v-skeleton-loader__table-row,
  .v-skeleton-loader__table-row-divider
    &::after
      display: none

  &__article
    .v-skeleton-loader__heading
      +ltr()
        margin: $skeleton-loader-article-heading-margin-top-left 0 $skeleton-loader-article-heading-margin-x $skeleton-loader-article-heading-margin-top-left

      +rtl()
        margin: $skeleton-loader-article-heading-margin-top-left $skeleton-loader-article-heading-margin-x 0 $skeleton-loader-article-heading-margin-top-left

    .v-skeleton-loader__paragraph
      padding: $skeleton-loader-article-paragraph-padding

  &__bone
    border-radius: inherit
    overflow: hidden
    position: relative

    &::after
      animation: $skeleton-loader-loading-animation
      content: ''
      height: 100%
      left: 0
      position: absolute
      right: 0
      top: 0
      transform: $skeleton-loader-loading-transform
      z-index: 1

  &__avatar
    border-radius: $avatar-border-radius
    height: $skeleton-loader-avatar-height
    width: $skeleton-loader-avatar-width

  &__button
    border-radius: $skeleton-loader-button-border-radius
    height: $skeleton-loader-button-height
    width: $skeleton-loader-button-width

  &__card
    .v-skeleton-loader__image
      border-radius: 0

  &__card-heading
    .v-skeleton-loader__heading
      margin: $skeleton-loader-card-heading-loader-heading-margin

  &__card-text
    padding: $skeleton-loader-card-text-padding

  &__chip
    border-radius: $skeleton-loader-chip-border-radius
    height: $skeleton-loader-chip-height
    width: $skeleton-loader-chip-width

  &__date-picker
    border-radius: $skeleton-loader-date-picker-border-radius

    .v-skeleton-loader__list-item:first-child
      .v-skeleton-loader__text
        max-width: 88px
        width: 20%

    .v-skeleton-loader__heading
      max-width: 256px
      width: 40%

  &__date-picker-days
    display: flex
    flex-wrap: wrap
    padding: $skeleton-loader-date-picker-days-padding
    margin: 0 auto

    .v-skeleton-loader__avatar
      border-radius: $skeleton-loader-border-radius
      flex: 1 1 auto
      margin: $skeleton-loader-date-picker-days-margin
      height: $skeleton-loader-date-picker-days-height
      width: $skeleton-loader-date-picker-days-width

  &__date-picker-options
    align-items: center
    display: flex
    padding: $skeleton-loader-date-picker-options-padding

    .v-skeleton-loader__avatar
      height: $skeleton-loader-date-picker-options-avatar-height
      width: $skeleton-loader-date-picker-options-avatar-width

      &:nth-child(2)
        margin-left: auto

        +ltr()
          margin-right: $skeleton-loader-date-picker-options-avatar-child-margin

        +rtl()
          margin-left: $skeleton-loader-date-picker-options-avatar-child-margin

    .v-skeleton-loader__text.v-skeleton-loader__bone:first-child
      margin-bottom: 0px
      max-width: 50%
      width: 456px

  &__divider
    border-radius: $skeleton-loader-divider-border-radius
    height: $skeleton-loader-divider-height

  &__heading
    border-radius: $skeleton-loader-heading-border-radius
    height: $skeleton-loader-heading-height
    width: 45%

  &__image
    height: $skeleton-loader-image-height
    border-radius: 0

    ~ .v-skeleton-loader__card-heading
      border-radius: 0

    &::first-child, &::last-child
      border-radius: inherit

  &__list-item
    height: $skeleton-loader-list-item-height

  &__list-item-three-line
    flex-wrap: wrap

    > *
      flex: 1 0 100%
      width: 100%

  &__list-item-avatar,
  &__list-item-avatar-two-line,
  &__list-item-avatar-three-line
    .v-skeleton-loader__avatar
      height: $skeleton-loader-item-avatar-height
      width: $skeleton-loader-item-avatar-width

  &__list-item-avatar
    height: $skeleton-loader-avatar-height

  &__list-item-two-line,
  &__list-item-avatar-two-line
    height: $skeleton-loader-item-two-line-height

  &__list-item-three-line,
  &__list-item-avatar-three-line
    height: $skeleton-loader-item-three-line-height

  &__list-item-avatar-three-line
    .v-skeleton-loader__avatar
      align-self: flex-start

  &__list-item,
  &__list-item-avatar,
  &__list-item-two-line,
  &__list-item-three-line,
  &__list-item-avatar-two-line,
  &__list-item-avatar-three-line
    align-content: center
    align-items: center
    display: flex
    flex-wrap: wrap
    padding: $skeleton-loader-item-padding

    .v-skeleton-loader__avatar
      +ltr()
        margin-right: $skeleton-loader-item-avatar-margin

      +rtl()
        margin-left: $skeleton-loader-item-avatar-margin

    .v-skeleton-loader__text:last-child,
    .v-skeleton-loader__text:only-child
      margin-bottom: 0

  &__paragraph,
  &__sentences
    flex: 1 0 auto

  &__paragraph
    &:not(:last-child)
      margin-bottom: 6px

    .v-skeleton-loader__text:nth-child(1)
      max-width: 100%

    .v-skeleton-loader__text:nth-child(2)
      max-width: 50%

    .v-skeleton-loader__text:nth-child(3)
      max-width: 70%

  &__sentences
    .v-skeleton-loader__text:nth-child(2)
      max-width: 70%

    &:not(:last-child)
      margin-bottom: 6px

  &__table-heading
    align-items: center
    display: flex
    justify-content: space-between
    padding: $skeleton-loader-table-heading-padding

    .v-skeleton-loader__heading
      max-width: 15%

    .v-skeleton-loader__text
      max-width: 40%

  &__table-thead
    display: flex
    justify-content: space-between
    padding: $skeleton-loader-table-thead-padding

    .v-skeleton-loader__heading
      max-width: 5%

  &__table-tbody
    padding: $skeleton-loader-table-tbody-padding

  &__table-tfoot
    align-items: center
    display: flex
    justify-content: flex-end
    padding: $skeleton-loader-table-tfoot-padding

    > *
      +ltr()
        margin-left: $skeleton-loader-table-tfoot-children-margin

      +rtl()
        margin-right: $skeleton-loader-table-tfoot-children-margin

    .v-skeleton-loader__avatar
      height: $skeleton-loader-table-tfoot-avatar-height
      width: $skeleton-loader-table-tfoot-avatar-width

    .v-skeleton-loader__text
      margin-bottom: 0

      &:nth-child(1)
        max-width: 128px

      &:nth-child(2)
        max-width: 64px

  &__table-row
    display: flex
    justify-content: space-between

  &__table-cell
    align-items: center
    display: flex
    height: $skeleton-loader-table-cell-height
    width: $skeleton-loader-table-cell-width

    .v-skeleton-loader__text
      margin-bottom: 0

  &__text
    border-radius: $skeleton-loader-text-border-radius
    flex: 1 0 auto
    height: $skeleton-loader-text-height
    margin-bottom: 6px

  &--boilerplate
    .v-skeleton-loader__bone:after
      display: none

  &--is-loading
    overflow: hidden

  &--tile
    border-radius: 0

    .v-skeleton-loader__bone
      border-radius: 0

@keyframes loading
  100%
    transform: translateX(100%)


// Styles
import './VTextarea.sass'
import '../VTextField/VTextField.sass'

// Components
import { filterFieldProps, makeVFieldProps } from '@/components/VField/VField'
import { filterInputProps, makeVInputProps, VInput } from '@/components/VInput/VInput'
import { VCounter } from '@/components/VCounter'
import { VField } from '@/components/VField'

// Directives
import Intersect from '@/directives/intersect'

// Composables
import { forwardRefs } from '@/composables/forwardRefs'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utilities
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { callEvent, clamp, convertToUnit, defineComponent, filterInputAttrs, useRender } from '@/util'

// Types
import type { PropType } from 'vue'

export const VTextarea = defineComponent({
  name: 'VTextarea',

  directives: { Intersect },

  inheritAttrs: false,

  props: {
    autoGrow: Boolean,
    autofocus: Boolean,
    counter: [Boolean, Number, String] as PropType<true | number | string>,
    counterValue: Function as PropType<(value: any) => number>,
    hint: String,
    persistentHint: Boolean,
    prefix: String,
    placeholder: String,
    persistentPlaceholder: Boolean,
    persistentCounter: Boolean,
    noResize: Boolean,
    rows: {
      type: [Number, String],
      default: 5,
      validator: (v: any) => !isNaN(parseFloat(v)),
    },
    maxRows: {
      type: [Number, String],
      validator: (v: any) => !isNaN(parseFloat(v)),
    },
    suffix: String,

    ...makeVInputProps(),
    ...makeVFieldProps(),
  },

  emits: {
    'click:control': (e: MouseEvent) => true,
    'update:modelValue': (val: string) => true,
  },

  setup (props, { attrs, emit, slots }) {
    const model = useProxiedModel(props, 'modelValue')
    const counterValue = computed(() => {
      return typeof props.counterValue === 'function'
        ? props.counterValue(model.value)
        : (model.value || '').toString().length
    })
    const max = computed(() => {
      if (attrs.maxlength) return attrs.maxlength as undefined

      if (
        !props.counter ||
        (typeof props.counter !== 'number' &&
        typeof props.counter !== 'string')
      ) return undefined

      return props.counter
    })

    function onIntersect (
      isIntersecting: boolean,
      entries: IntersectionObserverEntry[]
    ) {
      if (!props.autofocus || !isIntersecting) return

      (entries[0].target as HTMLInputElement)?.focus?.()
    }

    const vInputRef = ref<VInput>()
    const vFieldRef = ref<VInput>()
    const isFocused = ref(false)
    const controlHeight = ref('')
    const textareaRef = ref<HTMLInputElement>()
    const isActive = computed(() => (
      isFocused.value ||
      props.persistentPlaceholder
    ))

    const messages = computed(() => {
      return props.messages.length
        ? props.messages
        : (isActive.value || props.persistentHint) ? props.hint : ''
    })

    function onFocus () {
      if (textareaRef.value !== document.activeElement) {
        textareaRef.value?.focus()
      }

      if (!isFocused.value) isFocused.value = true
    }
    function onControlClick (e: MouseEvent) {
      onFocus()

      emit('click:control', e)
    }
    function onClear (e: MouseEvent) {
      e.stopPropagation()

      onFocus()

      nextTick(() => {
        model.value = ''

        callEvent(props['onClick:clear'], e)
      })
    }
    function onInput (e: Event) {
      model.value = (e.target as HTMLTextAreaElement).value
    }

    const sizerRef = ref<HTMLTextAreaElement>()
    function calculateInputHeight () {
      if (!props.autoGrow) return

      nextTick(() => {
        if (!sizerRef.value || !vFieldRef.value) return

        const style = getComputedStyle(sizerRef.value)
        const fieldStyle = getComputedStyle(vFieldRef.value.$el)

        const padding = parseFloat(style.getPropertyValue('--v-field-padding-top')) +
          parseFloat(style.getPropertyValue('--v-input-padding-top')) +
          parseFloat(style.getPropertyValue('--v-field-padding-bottom'))

        const height = sizerRef.value.scrollHeight
        const lineHeight = parseFloat(style.lineHeight)
        const minHeight = Math.max(
          parseFloat(props.rows) * lineHeight + padding,
          parseFloat(fieldStyle.getPropertyValue('--v-input-control-height'))
        )
        const maxHeight = parseFloat(props.maxRows!) * lineHeight + padding || Infinity

        controlHeight.value = convertToUnit(clamp(height ?? 0, minHeight, maxHeight))
      })
    }

    onMounted(calculateInputHeight)
    watch(model, calculateInputHeight)
    watch(() => props.rows, calculateInputHeight)
    watch(() => props.maxRows, calculateInputHeight)
    watch(() => props.density, calculateInputHeight)

    let observer: ResizeObserver | undefined
    watch(sizerRef, val => {
      if (val) {
        observer = new ResizeObserver(calculateInputHeight)
        observer.observe(sizerRef.value!)
      } else {
        observer?.disconnect()
      }
    })
    onBeforeUnmount(() => {
      observer?.disconnect()
    })

    useRender(() => {
      const hasCounter = !!(slots.counter || props.counter || props.counterValue)
      const hasDetails = !!(hasCounter || slots.details)
      const [rootAttrs, inputAttrs] = filterInputAttrs(attrs)
      const [{ modelValue: _, ...inputProps }] = filterInputProps(props)
      const [fieldProps] = filterFieldProps(props)

      return (
        <VInput
          ref={ vInputRef }
          v-model={ model.value }
          class={[
            'v-textarea v-text-field',
            {
              'v-textarea--prefixed': props.prefix,
              'v-textarea--suffixed': props.suffix,
              'v-text-field--prefixed': props.prefix,
              'v-text-field--suffixed': props.suffix,
              'v-textarea--auto-grow': props.autoGrow,
              'v-textarea--no-resize': props.noResize || props.autoGrow,
              'v-text-field--flush-details': ['plain', 'underlined'].includes(props.variant),
            },
          ]}
          onClick:prepend={ props['onClick:prepend'] }
          onClick:append={ props['onClick:append'] }
          { ...rootAttrs }
          { ...inputProps }
          messages={ messages.value }
        >
          {{
            ...slots,
            default: ({
              isDisabled,
              isDirty,
              isReadonly,
              isValid,
            }) => (
              <VField
                ref={ vFieldRef }
                style={{
                  '--v-textarea-control-height': controlHeight.value,
                }}
                onClick:control={ onControlClick }
                onClick:clear={ onClear }
                onClick:prependInner={ props['onClick:prependInner'] }
                onClick:appendInner={ props['onClick:appendInner'] }
                role="textbox"
                { ...fieldProps }
                active={ isActive.value || isDirty.value }
                dirty={ isDirty.value || props.dirty }
                focused={ isFocused.value }
                error={ isValid.value === false }
              >
                {{
                  ...slots,
                  default: ({
                    props: { class: fieldClass, ...slotProps },
                  }) => (
                    <>
                      { props.prefix && (

                          { props.prefix }

                      ) }

                      <textarea
                        ref={ textareaRef }
                        class={ fieldClass }
                        value={ model.value }
                        onInput={ onInput }
                        v-intersect={[{
                          handler: onIntersect,
                        }, null, ['once']]}
                        autofocus={ props.autofocus }
                        readonly={ isReadonly.value }
                        disabled={ isDisabled.value }
                        placeholder={ props.placeholder }
                        rows={ props.rows }
                        name={ props.name }
                        onFocus={ onFocus }
                        onBlur={ () => (isFocused.value = false) }
                        { ...slotProps }
                        { ...inputAttrs }
                      />

                      { props.autoGrow && (
                        <textarea
                          class={[
                            fieldClass,
                            'v-textarea__sizer',
                          ]}
                          v-model={ model.value }
                          ref={ sizerRef }
                          readonly
                          aria-hidden="true"
                        />
                      )}

                      { props.suffix && (

                          { props.suffix }

                      ) }
                    </>
                  ),
                }}
              </VField>
            ),
            details: hasDetails ? slotProps => (
              <>
                { slots.details?.(slotProps) }

                { hasCounter && (
                  <>

                    <VCounter
                      active={ props.persistentCounter || isFocused.value }
                      value={ counterValue.value }
                      max={ max.value }
                      v-slots={ slots.counter }
                    />
                  </>
                ) }
              </>
            ) : undefined,
          }}
        </VInput>
      )
    })

    return forwardRefs({}, vInputRef, vFieldRef, textareaRef)
  },
})

export type VTextarea = InstanceType<typeof VTextarea>


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

// import VData from '../VData'
import {
  mount,
  MountOptions,
  Wrapper,
} from '@vue/test-utils'

describe.skip('VData.ts', () => {
  type Instance = InstanceType<typeof VData>
  let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
  beforeEach(() => {
    mountFunction = (options?: MountOptions<Instance>) => {
      return mount(VData, {
        ...options,
        sync: false,
      })
    }
  })

  it('should render data through default scoped slot', async () => {
    const wrapper = mountFunction({
      propsData: {
        items: [
          { id: 1, text: 'foo' },
          { id: 2, text: 'bar' },
        ],
      },
      scopedSlots: {
        default (data) {
          return this.$createElement('div', data.items.map(item => this.$createElement('div', [item.text])))
        },
      },
    })

    await wrapper.vm.$nextTick()

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

  it('should ignore items length if using server-items-length', async () => {
    const render = jest.fn()
    const wrapper = mountFunction({
      propsData: {
        items: [
          { id: 1, text: 'foo' },
          { id: 2, text: 'bar' },
        ],
      },
      scopedSlots: {
        default: render,
      },
    })

    await wrapper.vm.$nextTick()
    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      pagination: expect.objectContaining({
        itemsLength: 2,
      }),
    }))

    wrapper.setProps({
      serverItemsLength: 10,
    })

    await wrapper.vm.$nextTick()
    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      pagination: expect.objectContaining({
        itemsLength: 10,
      }),
    }))
  })

  it('should group items', async () => {
    const render = jest.fn()
    const items = [
      { id: 1, text: 'foo', baz: 'one' },
      { id: 2, text: 'bar', baz: 'two' },
      { id: 3, text: 'baz', baz: 'one' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items,
        groupBy: ['baz'],
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      groupedItems: [
        {
          name: 'one',
          items: [items[0], items[2]],
        },
        {
          name: 'two',
          items: [items[1]],
        },
      ],
    }))
  })

  it('should group items with null key and missing key as a null group', async () => {
    const render = jest.fn()
    const items = [
      { id: 1, text: 'foo', baz: null },
      { id: 2, text: 'bar', baz: 'one' },
      { id: 3, text: 'baz' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items,
        groupBy: ['baz'],
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      groupedItems: [
        {
          name: '',
          items: [items[0], items[2]],
        },
        {
          name: 'one',
          items: [items[1]],
        },
      ],
    }))
  })

  it('should group items by deep keys', async () => {
    const render = jest.fn()
    const items = [
      { id: 1, text: 'foo', foo: { bar: 'one' } },
      { id: 2, text: 'bar', foo: { bar: 'two' } },
      { id: 3, text: 'baz', foo: { bar: 'one' } },
    ]

    const wrapper = mountFunction({
      propsData: {
        items,
        groupBy: ['foo.bar'],
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      groupedItems: [
        {
          name: 'one',
          items: [items[0], items[2]],
        },
        {
          name: 'two',
          items: [items[1]],
        },
      ],
    }))
  })

  it('should group items with a custom group function', async () => {
    const render = jest.fn()
    const items = [
      { id: 1, text: 'foo', value: 1 },
      { id: 2, text: 'bar', value: 4 },
      { id: 3, text: 'baz', value: 3 },
    ]

    const wrapper = mountFunction({
      propsData: {
        items,
        groupBy: ['value'],
        customGroup: function evenOddGrouper (items: any[], groupBy: string[]) {
          const key = groupBy[0]
          return items.reduce((rv, x) => {
            const group = x[key] % 2 ? 'odd' : 'even';
            (rv[group] = rv[group] || []).push(x)
            return rv
          }, {})
        },
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      groupedItems: {
        even: [items[1]],
        odd: [items[0], items[2]],
      },
    }))
  })

  it('should sort items', () => {
    const render = jest.fn()

    const unsorted = [
      { id: 1, text: 'c' },
      { id: 2, text: 'a' },
      { id: 3, text: 'd' },
      { id: 4, text: 'b' },
    ]

    const sorted = [
      { id: 2, text: 'a' },
      { id: 4, text: 'b' },
      { id: 1, text: 'c' },
      { id: 3, text: 'd' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items: unsorted,
        sortBy: ['text'],
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      items: sorted,
    }))
  })

  it('should sort items by multiple properties', async () => {
    const render = jest.fn()

    const unsorted = [
      { id: 1, foo: 'a', bar: 'b' },
      { id: 2, foo: 'b', bar: 'b' },
      { id: 3, foo: 'b', bar: 'a' },
      { id: 4, foo: 'a', bar: 'a' },
    ]

    const sorted = [
      { id: 4, foo: 'a', bar: 'a' },
      { id: 1, foo: 'a', bar: 'b' },
      { id: 3, foo: 'b', bar: 'a' },
      { id: 2, foo: 'b', bar: 'b' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items: unsorted,
        sortBy: ['foo'],
      },
      scopedSlots: {
        default: render,
      },
    })

    await wrapper.vm.$nextTick()

    wrapper.setProps({
      sortBy: ['foo', 'bar'],
    })
    await wrapper.vm.$nextTick()

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      items: sorted,
    }))
  })

  it('should paginate items', () => {
    const render = jest.fn()

    const items = [
      { id: 1, foo: 'a' },
      { id: 2, foo: 'b' },
      { id: 3, foo: 'c' },
      { id: 4, foo: 'd' },
      { id: 5, foo: 'e' },
      { id: 6, foo: 'f' },
      { id: 7, foo: 'g' },
      { id: 8, foo: 'h' },
      { id: 9, foo: 'i' },
      { id: 10, foo: 'j' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items,
        itemsPerPage: 5,
        page: 2,
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      items: items.slice(5),
    }))
  })

  it('should not sort items if disableSort is active', () => {
    const render = jest.fn()

    const items = [
      { text: 'Foo', id: 1 },
      { text: 'Bar', id: 2 },
      { text: 'Fizz', id: 3 },
      { text: 'Buzz', id: 4 },
      { text: 'Fizzbuzz', id: 5 },
    ]

    const wrapper = mountFunction({
      propsData: {
        items,
        sortBy: ['text'],
        disableSort: true,
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      items,
    }))
  })

  it('should conditionally paginate items', async () => {
    const render = jest.fn()
    const items = [
      { text: 'Foo', id: 1 },
      { text: 'Bar', id: 2 },
      { text: 'Fizz', id: 3 },
      { text: 'Buzz', id: 4 },
      { text: 'Fizzbuzz', id: 5 },
    ]

    const wrapper = mountFunction({
      propsData: { items },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      items,
    }))

    wrapper.setProps({ itemsPerPage: 2 })
    await wrapper.vm.$nextTick()

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      items: items.slice(0, 2),
    }))

    wrapper.setProps({ disablePagination: true })
    await wrapper.vm.$nextTick()

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      items,
    }))
  })

  it('should toggle sorting', async () => {
    const unsorted = [
      { id: 1, text: 'c' },
      { id: 2, text: 'a' },
      { id: 3, text: 'd' },
      { id: 4, text: 'b' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items: unsorted,
      },
      scopedSlots: {
        default (props) {
          const items = props.items.map(item => this.$createElement('div', [item.text]))
          return this.$createElement('div', {
            attrs: {
              id: 'wrapper',
            },
            on: {
              click: () => props.sort('text'),
            },
          }, items)
        },
      },
    })

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

    const el = wrapper.find('#wrapper').element
    el.click()
    await wrapper.vm.$nextTick()

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

    el.click()
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should toggle sorting on multiple properties', async () => {
    const unsorted = [
      { id: 1, text: 'c', group: 'foo' },
      { id: 2, text: 'a', group: 'bar' },
      { id: 3, text: 'd', group: 'foo' },
      { id: 4, text: 'b', group: 'bar' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items: unsorted,
      },
      scopedSlots: {
        default (props) {
          const items = props.items.map(item => this.$createElement('div', [`${item.group}-${item.text}`]))
          return this.$createElement('div', {
            attrs: {
              id: 'wrapper',
            },
            on: {
              click: () => props.sort(['group', 'text']),
            },
          }, items)
        },
      },
    })

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

    const el = wrapper.find('#wrapper').element
    el.click()
    await wrapper.vm.$nextTick()

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

  it('should toggle grouping', async () => {
    const unsorted = [
      { id: 1, text: 'c', group: 'foo' },
      { id: 4, text: 'a', group: 'bar' },
      { id: 3, text: 'd', group: 'foo' },
      { id: 2, text: 'b', group: 'bar' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items: unsorted,
      },
      scopedSlots: {
        default (props) {
          const items = props.groupedItems
            ? props.groupedItems.map(group => group.name)
            : props.items.map(item => item.text)

          return this.$createElement('div', {
            attrs: {
              id: 'wrapper',
            },
            on: {
              click: () => props.group('group'),
            },
          }, items.map(item => this.$createElement('div', [item])))
        },
      },
    })

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

    const el = wrapper.find('#wrapper').element
    el.click()
    await wrapper.vm.$nextTick()

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

  // https://github.com/vuetifyjs/vuetify/issues/10372
  it('should handle setting itemsPerPage to zero', async () => {
    const render = jest.fn()
    const wrapper = mountFunction({
      propsData: {
        items: [
          { id: 1, text: 'foo' },
          { id: 2, text: 'bar' },
        ],
        itemsPerPage: 0,
      },
      scopedSlots: {
        default: render,
      },
    })

    await wrapper.vm.$nextTick()
    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      pagination: expect.objectContaining({
        itemsPerPage: 0,
        page: 1,
        pageCount: 1,
        pageStart: 0,
        pageStop: 0,
      }),
    }))

    wrapper.setProps({
      itemsPerPage: 1,
    })

    await wrapper.vm.$nextTick()
    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      pagination: expect.objectContaining({
        itemsPerPage: 1,
        page: 1,
        pageCount: 2,
        pageStart: 0,
        pageStop: 1,
      }),
    }))
  })

  // https://github.com/vuetifyjs/vuetify/issues/10627
  it('should sort grouped column', async () => {
    const unsorted = [
      { id: 1, text: 'c', group: 'foo' },
      { id: 4, text: 'a', group: 'bar' },
      { id: 3, text: 'd', group: 'foo' },
      { id: 2, text: 'b', group: 'bar' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items: unsorted,
        groupBy: ['text'],
      },
      scopedSlots: {
        default (props) {
          return this.$createElement('div', {
            attrs: {
              id: 'wrapper',
            },
            on: {
              click: () => props.group('group'),
            },
          }, props.groupedItems.map(group => this.$createElement('div', [group.name])))
        },
      },
    })

    wrapper.setProps({ groupDesc: [false] })
    await wrapper.vm.$nextTick()

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

    wrapper.setProps({ groupDesc: [true] })
    await wrapper.vm.$nextTick()

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

  // https://github.com/vuetifyjs/vuetify/issues/11905
  it('should group items when sort is disabled', async () => {
    const render = jest.fn()
    const items = [
      { id: 1, text: 'foo', baz: 'one' },
      { id: 2, text: 'bar', baz: 'two' },
      { id: 3, text: 'baz', baz: 'one' },
    ]

    const wrapper = mountFunction({
      propsData: {
        items,
        groupBy: ['baz'],
        disableSort: true,
      },
      scopedSlots: {
        default: render,
      },
    })

    expect(render).toHaveBeenCalledWith(expect.objectContaining({
      groupedItems: [
        {
          name: 'one',
          items: [items[0], items[2]],
        },
        {
          name: 'two',
          items: [items[1]],
        },
      ],
    }))
  })
})


keep keep keep keep replace keep keep keep keep

if (!slots.selection) {
search.value = item.title
}

// Styles
import './VAutocomplete.sass'

// Components
import { makeSelectProps } from '@/components/VSelect/VSelect'
import { VCheckboxBtn } from '@/components/VCheckbox'
import { VChip } from '@/components/VChip'
import { VDefaultsProvider } from '@/components/VDefaultsProvider'
import { VList, VListItem } from '@/components/VList'
import { VMenu } from '@/components/VMenu'
import { VTextField } from '@/components/VTextField'

// Composables
import { makeFilterProps, useFilter } from '@/composables/filter'
import { makeTransitionProps } from '@/composables/transition'
import { forwardRefs } from '@/composables/forwardRefs'
import { useItems } from '@/composables/items'
import { useLocale } from '@/composables/locale'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utility
import { computed, mergeProps, nextTick, ref, watch } from 'vue'
import { genericComponent, useRender, wrapInArray } from '@/util'

// Types
import type { FilterMatch } from '@/composables/filter'
import type { InternalItem } from '@/composables/items'
import type { MakeSlots } from '@/util'
import type { VFieldSlots } from '@/components/VField/VField'
import type { VInputSlots } from '@/components/VInput/VInput'

function highlightResult (text: string, matches: FilterMatch, length: number) {
  if (Array.isArray(matches)) throw new Error('Multiple matches is not implemented')

  return typeof matches === 'number' && ~matches
    ? (
      <>
        { text.substr(0, matches) }
        { text.substr(matches, length) }
        { text.substr(matches + length) }
      </>
    )
    : text
}

type Primitive = string | number | boolean | symbol

type Val <T, ReturnObject extends boolean> = T extends Primitive
  ? T
  : (ReturnObject extends true ? T : any)

type Value <T, ReturnObject extends boolean, Multiple extends boolean> =
  Multiple extends true
    ? Val<T, ReturnObject>[]
    : Val<T, ReturnObject>

export const VAutocomplete = genericComponent<new <
  T,
  ReturnObject extends boolean = false,
  Multiple extends boolean = false,
  V extends Value<T, ReturnObject, Multiple> = Value<T, ReturnObject, Multiple>
>() => {
  $props: {
    items?: readonly T[]
    returnObject?: ReturnObject
    multiple?: Multiple
    modelValue?: Readonly<V>
    'onUpdate:modelValue'?: (val: V) => void
  } & Omit<VTextField['$props'], 'modelValue' | 'onUpdate:modelValue'>
  $slots: Omit<VInputSlots & VFieldSlots, 'default'> & MakeSlots<{
    item: [{ item: InternalItem<T>, index: number, props: Record<string, unknown> }]
    chip: [{ item: InternalItem<T>, index: number, props: Record<string, unknown> }]
    selection: [{ item: InternalItem<T>, index: number }]
    'no-data': []
  }>
}>()({
  name: 'VAutocomplete',

  props: {
    // TODO: implement post keyboard support
    // autoSelectFirst: Boolean,
    search: String,

    ...makeFilterProps({ filterKeys: ['title'] }),
    ...makeSelectProps(),
    ...makeTransitionProps({ transition: false }),
  },

  emits: {
    'update:search': (val: any) => true,
    'update:modelValue': (val: any) => true,
    'update:menu': (val: boolean) => true,
  },

  setup (props, { slots }) {
    const { t } = useLocale()
    const vTextFieldRef = ref()
    const isFocused = ref(false)
    const isPristine = ref(true)
    const menu = useProxiedModel(props, 'menu')
    const { items, transformIn, transformOut } = useItems(props)
    const search = useProxiedModel(props, 'search', '')
    const model = useProxiedModel(
      props,
      'modelValue',
      [],
      v => transformIn(wrapInArray(v)),
      v => {
        const transformed = transformOut(v)
        return props.multiple ? transformed : (transformed[0] ?? null)
      }
    )
    const { filteredItems } = useFilter(props, items, computed(() => isPristine.value ? undefined : search.value))
    const selections = computed(() => {
      return model.value.map(v => {
        return items.value.find(item => item.value === v.value) || v
      })
    })
    const selected = computed(() => selections.value.map(selection => selection.props.value))

    function onClear (e: MouseEvent) {
      model.value = []

      if (props.openOnClear) {
        menu.value = true
      }

      search.value = ''
    }
    function onClickControl () {
      if (
        (props.hideNoData && !items.value.length) ||
        props.readonly
      ) return

      menu.value = true
    }
    function onKeydown (e: KeyboardEvent) {
      if (props.readonly) return

      if (['Enter', 'ArrowDown'].includes(e.key)) {
        menu.value = true
      }

      if (['Escape'].includes(e.key)) {
        menu.value = false
      }

      if (['Enter', 'Escape', 'Tab'].includes(e.key)) {
        isPristine.value = true
      }
    }

    function onInput (e: InputEvent) {
      search.value = (e.target as HTMLInputElement).value
    }

    function onAfterLeave () {
      if (isFocused.value) isPristine.value = true
    }

    const isSelecting = ref(false)

    function select (item: InternalItem) {
      if (props.multiple) {
        const index = selected.value.findIndex(selection => selection === item.value)

        if (index === -1) {
          model.value = [...model.value, item]
          search.value = ''
        } else {
          const value = [...model.value]
          value.splice(index, 1)
          model.value = value
        }
      } else {
        model.value = [item]

        isSelecting.value = true

        search.value = item.title
        menu.value = false
        isPristine.value = true

        nextTick(() => (isSelecting.value = false))
      }
    }

    watch(isFocused, val => {
      if (val) {
        isSelecting.value = true
        search.value = props.multiple ? '' : String(selections.value.at(-1)?.props.title ?? '')
        isPristine.value = true

        nextTick(() => isSelecting.value = false)
      } else {
        menu.value = false
        search.value = ''
      }
    })

    watch(search, val => {
      if (!isFocused.value || isSelecting.value) return

      if (val) menu.value = true

      isPristine.value = !val
    })

    useRender(() => {
      const hasChips = !!(props.chips || slots.chip)

      return (
        <VTextField
          ref={ vTextFieldRef }
          modelValue={ search.value }
          onUpdate:modelValue={ v => { if (v == null) model.value = [] } }
          validationValue={ model.externalValue }
          dirty={ model.value.length > 0 }
          onInput={ onInput }
          class={[
            'v-autocomplete',
            {
              'v-autocomplete--active-menu': menu.value,
              'v-autocomplete--chips': !!props.chips,
              [`v-autocomplete--${props.multiple ? 'multiple' : 'single'}`]: true,
            },
          ]}
          appendInnerIcon={ props.menuIcon }
          readonly={ props.readonly }
          onClick:clear={ onClear }
          onClick:control={ onClickControl }
          onClick:input={ onClickControl }
          onFocus={ () => isFocused.value = true }
          onBlur={ () => isFocused.value = false }
          onKeydown={ onKeydown }
        >
          {{
            ...slots,
            default: () => (
              <>
                <VMenu
                  v-model={ menu.value }
                  activator="parent"
                  contentClass="v-autocomplete__content"
                  eager={ props.eager }
                  openOnClick={ false }
                  closeOnContentClick={ false }
                  transition={ props.transition }
                  onAfterLeave={ onAfterLeave }
                  { ...props.menuProps }
                >
                  <VList
                    selected={ selected.value }
                    selectStrategy={ props.multiple ? 'independent' : 'single-independent' }
                    onMousedown={ (e: MouseEvent) => e.preventDefault() }
                  >
                    { !filteredItems.value.length && !props.hideNoData && (slots['no-data']?.() ?? (
                      <VListItem title={ t(props.noDataText) } />
                    )) }

                    { filteredItems.value.map(({ item, matches }, index) => slots.item?.({
                      item,
                      index,
                      props: mergeProps(item.props, { onClick: () => select(item) }),
                    }) ?? (
                      <VListItem
                        key={ index }
                        { ...item.props }
                        onClick={ () => select(item) }
                      >
                        {{
                          prepend: ({ isSelected }) => props.multiple && !props.hideSelected ? (
                            <VCheckboxBtn modelValue={ isSelected } ripple={ false } />
                          ) : undefined,
                          title: () => {
                            return isPristine.value
                              ? item.title
                              : highlightResult(item.title, matches.title, search.value?.length ?? 0)
                          },
                        }}
                      </VListItem>
                    )) }
                  </VList>
                </VMenu>

                { selections.value.map((item, index) => {
                  function onChipClose (e: Event) {
                    e.stopPropagation()
                    e.preventDefault()

                    select(item)
                  }

                  const slotProps = {
                    'onClick:close': onChipClose,
                    modelValue: true,
                  }

                  return (

                      { hasChips ? (
                        <VDefaultsProvider
                          defaults={{
                            VChip: {
                              closable: props.closableChips,
                              size: 'small',
                              text: item.title,
                            },
                          }}
                        >
                          { slots.chip
                            ? slots.chip({ item, index, props: slotProps })
                            : (<VChip { ...slotProps } />)
                          }
                        </VDefaultsProvider>
                      ) : (
                        slots.selection
                          ? slots.selection({ item, index })
                          : (

                              { item.title }
                              { props.multiple && (index < selections.value.length - 1) && (
                                ,
                              ) }

                          )
                      )}

                  )
                }) }
              </>
            ),
          }}
        </VTextField>
      )
    })

    return forwardRefs({
      isFocused,
      isPristine,
      menu,
      search,
      filteredItems,
      select,
    }, vTextFieldRef)
  },
})

export type VAutocomplete = InstanceType<typeof VAutocomplete>


<template>
  <v-data-table
    dense
    :headers="headers"
    :items="desserts"
    item-key="name"
    class="elevation-1"
  ></v-data-table>
</template>

<script>
  export default {
    data: () => ({
      desserts: [
        {
          name: 'Frozen Yogurt',
          calories: 159,
          fat: 6.0,
          carbs: 24,
          protein: 4.0,
          iron: '1%',
        },
        {
          name: 'Ice cream sandwich',
          calories: 237,
          fat: 9.0,
          carbs: 37,
          protein: 4.3,
          iron: '1%',
        },
        {
          name: 'Eclair',
          calories: 262,
          fat: 16.0,
          carbs: 23,
          protein: 6.0,
          iron: '7%',
        },
        {
          name: 'Cupcake',
          calories: 305,
          fat: 3.7,
          carbs: 67,
          protein: 4.3,
          iron: '8%',
        },
        {
          name: 'Gingerbread',
          calories: 356,
          fat: 16.0,
          carbs: 49,
          protein: 3.9,
          iron: '16%',
        },
        {
          name: 'Jelly bean',
          calories: 375,
          fat: 0.0,
          carbs: 94,
          protein: 0.0,
          iron: '0%',
        },
        {
          name: 'Lollipop',
          calories: 392,
          fat: 0.2,
          carbs: 98,
          protein: 0,
          iron: '2%',
        },
        {
          name: 'Honeycomb',
          calories: 408,
          fat: 3.2,
          carbs: 87,
          protein: 6.5,
          iron: '45%',
        },
        {
          name: 'Donut',
          calories: 452,
          fat: 25.0,
          carbs: 51,
          protein: 4.9,
          iron: '22%',
        },
        {
          name: 'KitKat',
          calories: 518,
          fat: 26.0,
          carbs: 65,
          protein: 7,
          iron: '6%',
        },
      ],
      headers: [
        {
          text: 'Dessert (100g serving)',
          align: 'start',
          sortable: false,
          value: 'name',
        },
        { text: 'Calories', value: 'calories' },
        { text: 'Fat (g)', value: 'fat' },
        { text: 'Carbs (g)', value: 'carbs' },
        { text: 'Protein (g)', value: 'protein' },
        { text: 'Iron (%)', value: 'iron' },
      ],
    }),
  }
</script>


// Components
import { VList } from '..'

// Utilities
import { createVuetify } from '@/framework'
import { mount } from '@vue/test-utils'
import { describe, expect, it } from '@jest/globals'

describe('VList', () => {
  const vuetify = createVuetify()

  function mountFunction (options = {}) {
    return mount(VList, {
      global: { plugins: [vuetify] },
      ...options,
    })
  }

  it('should match a snapshot', () => {
    const wrapper = mountFunction()

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


Applies the [align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items) css property. Available options are: **start**, **center**, **end**, **baseline** and **stretch**.

Changes the **align-items** property on small and greater breakpoints.

Changes the **align-items** property on medium and greater breakpoints.

Changes the **align-items** property on large and greater breakpoints.

Changes the **align-items** property on extra large and greater breakpoints.

Applies the [align-content](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content) css property. Available options are: **start**, **center**, **end**, **space-between**, **space-around** and **stretch**.

Changes the **align-content** property on small and greater breakpoints.

Changes the **align-content** property on medium and greater breakpoints.

Changes the **align-content** property on large and greater breakpoints.

Changes the **align-content** property on extra large and greater breakpoints.

Applies the [justify-content](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content) css property. Available options are: **start**, **center**, **end**, **space-between** and **space-around**.

Changes the **justify-content** property on small and greater breakpoints.

Changes the **justify-content** property on medium and greater breakpoints.

Changes the **justify-content** property on large and greater breakpoints.

Changes the **justify-content** property on extra large and greater breakpoints.


search.value = props.multiple ? '' : String(selections.value.at(-1)?.props.title ?? '')

nextTick(() => isSelecting.value = false)

search.value = props.multiple || !!slots.selection ? '' : String(selections.value.at(-1)?.props.title ?? '')


// Styles
import './VBreadcrumbs.sass'

// Components
import { VBreadcrumbsDivider } from './VBreadcrumbsDivider'
import { VBreadcrumbsItem } from './VBreadcrumbsItem'
import { VDefaultsProvider } from '@/components/VDefaultsProvider'
import { VIcon } from '@/components/VIcon'

// Composables
import { IconValue } from '@/composables/icons'
import { makeDensityProps, useDensity } from '@/composables/density'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeTagProps } from '@/composables/tag'
import { provideDefaults } from '@/composables/defaults'
import { useBackgroundColor } from '@/composables/color'

// Utilities
import { genericComponent, useRender } from '@/util'
import { toRef } from 'vue'

// Types
import type { LinkProps } from '@/composables/router'
import type { MakeSlots } from '@/util'
import type { PropType } from 'vue'

export type BreadcrumbItem = string | (LinkProps & {
  text: string
  disabled?: boolean
})

export const VBreadcrumbs = genericComponent<new <T>() => {
  $props: {
    items?: T[]
  }
  $slots: MakeSlots<{
    default: []
    item: [T | number]
  }>
}>()({
  name: 'VBreadcrumbs',

  props: {
    activeClass: String,
    activeColor: String,
    bgColor: String,
    color: String,
    disabled: Boolean,
    divider: {
      type: String,
      default: '/',
    },
    icon: IconValue,
    items: {
      type: Array as PropType<BreadcrumbItem[]>,
      default: () => ([]),
    },

    ...makeDensityProps(),
    ...makeRoundedProps(),
    ...makeTagProps({ tag: 'ul' }),
  },

  setup (props, { slots }) {
    const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(toRef(props, 'bgColor'))
    const { densityClasses } = useDensity(props)

    const { roundedClasses } = useRounded(props)

    provideDefaults({
      VBreadcrumbsItem: {
        activeClass: toRef(props, 'activeClass'),
        activeColor: toRef(props, 'activeColor'),
        color: toRef(props, 'color'),
        disabled: toRef(props, 'disabled'),
      },
    })

    useRender(() => {
      const hasPrepend = !!(slots.prepend || props.icon)

      return (
        <props.tag
          class={[
            'v-breadcrumbs',
            backgroundColorClasses.value,
            densityClasses.value,
            roundedClasses.value,
          ]}
          style={ backgroundColorStyles.value }
        >
          { hasPrepend && (
            <VDefaultsProvider
              key="prepend"
              defaults={{
                VIcon: {
                  icon: props.icon,
                  start: true,
                },
              }}
            >
              <div class="v-breadcrumbs__prepend">
                { slots.prepend
                  ? slots.prepend()
                  : props.icon && (<VIcon />)
                }
              </div>
            </VDefaultsProvider>
          ) }

          { props.items.map((item, index, array) => (
            <>
              <VBreadcrumbsItem
                key={ index }
                disabled={ index >= array.length - 1 }
                { ...(typeof item === 'string' ? { title: item } : item) }
                v-slots={{
                  default: slots.title ? () => slots.title?.({ item, index }) : undefined,
                }}
              />

              { index < array.length - 1 && (
                <VBreadcrumbsDivider>
                  { slots.divider?.({ item, index }) ?? props.divider }
                </VBreadcrumbsDivider>
              ) }
            </>
          )) }

          { slots.default?.() }
        </props.tag>
      )
    })

    return {}
  },
})

export type VBreadcrumbs = InstanceType<typeof VBreadcrumbs>


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

// Components
// import VPicker from '../VPicker'

// Utilities
import {
  mount,
  Wrapper,
} from '@vue/test-utils'
// import { compileToFunctions } from 'vue-template-compiler'

describe.skip('VPicker.ts', () => {
  type Instance = InstanceType<typeof VPicker>
  let mountFunction: (options?: object) => Wrapper<Instance>

  beforeEach(() => {
    mountFunction = (options = {}) => {
      return mount(VPicker, {
        ...options,
      })
    }
  })

  it('should render component without title and match snapshot', () => {
    const wrapper = mountFunction({
      slots: {
        default: [compileToFunctions('default')],
      },
    })

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

  it('should render component with title and match snapshot', () => {
    const wrapper = mountFunction({
      slots: {
        default: [compileToFunctions('default')],
        title: [compileToFunctions('title')],
      },
    })

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

  it('should render flat component and match snapshot', () => {
    const wrapper = mountFunction({
      slots: {
        default: [compileToFunctions('default')],
        title: [compileToFunctions('title')],
      },
      props: {
        flat: true,
      },
    })

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

  it('should render component with elevation and match snapshot', () => {
    const wrapper = mountFunction({
      slots: {
        default: [compileToFunctions('default')],
        title: [compileToFunctions('title')],
      },
      props: {
        elevation: 15,
      },
    })

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

  it('should render dark component and match snapshot', () => {
    const wrapper = mountFunction({
      propsData: {
        dark: true,
      },
      slots: {
        default: [compileToFunctions('default')],
        title: [compileToFunctions('title')],
      },
    })

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

  it('should render colored component', () => {
    const wrapper = mountFunction({
      propsData: {
        color: 'orange lighten-1',
      },
      slots: {
        title: [compileToFunctions('title')],
      },
    })

    const title = wrapper.find('.v-picker__title')
    expect(title.classes('orange')).toBe(true)
    expect(title.classes('lighten-1')).toBe(true)
  })
})


<template>
  <div class="d-flex justify-space-around">
    <v-menu>
      <template v-slot:activator="{ props }">
        <v-btn
          color="primary"
          v-bind="props"
        >
          Activator slot
        </v-btn>
      </template>
      <v-list>
        <v-list-item
          v-for="(item, index) in items"
          :key="index"
          :value="index"
        >
          <v-list-item-title>{{ item.title }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>

    <v-btn
      color="primary"
    >
      Parent activator

      <v-menu activator="parent">
        <v-list>
          <v-list-item
            v-for="(item, index) in items"
            :key="index"
            :value="index"
          >
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
    </v-btn>

    <v-btn
      id="menu-activator"
      color="primary"
    >
      Sibling activator
    </v-btn>

    <v-menu activator="#menu-activator">
      <v-list>
        <v-list-item
          v-for="(item, index) in items"
          :key="index"
          :value="index"
        >
          <v-list-item-title>{{ item.title }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
  </div>
</template>

<script>
  export default {
    data: () => ({
      items: [
        { title: 'Click Me' },
        { title: 'Click Me' },
        { title: 'Click Me' },
        { title: 'Click Me 2' },
      ],
    }),
  }
</script>


[`v-autocomplete--${props.multiple ? 'multiple' : 'single'}`]: true,