import Vue from 'vue'
import { test } from '@/test'
import VSelect from '@/components/VSelect'
import {
  VListTile,
  VListTileTitle,
  VListTileContent
} from '@/components/VList'

test('VSelect', ({ mount, compileToFunctions }) => {
  it('should return numeric 0', () => {
    const item = { value: 0, text: '0' }
    const wrapper = mount(VSelect, {
      propsData: {
        value: null,
        items: [item],
        multiple: true
      }
    })

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

    expect(change).toBeCalledWith([0])
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should be in an error state', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        value: null,
        items: [0, 1, 2],
        rules: [(v) => !!v || 'Required']
      }
    })

    wrapper.trigger('focus')
    await wrapper.vm.$nextTick()
    wrapper.vm.blur()
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.hasError).toBe(true)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should disable list items', () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        items: [{
          text: 'item',
          disabled: true
        }]
      }
    })

    const item = wrapper.find('div.text--disabled')[0]

    expect(item.element.getAttribute('disabled')).toBe('disabled')
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should warn when using incorrect item together with segmented prop', async () => {
    const items = [
      { text: 'Hello', callback: () => {} },
      { text: 'Hello' }
    ]

    const wrapper = mount(VSelect, {
      propsData: {
        segmented: true,
        items
      }
    })

    wrapper.vm.inputValue = items[1]

    await wrapper.vm.$nextTick()

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
    expect('items must contain both a text and callback property').toHaveBeenTipped()
  })

  it('should render buttons correctly when using items array with segmented prop', async () => {
    const items = [
      { text: 'Hello', callback: () => {} }
    ]

    const wrapper = mount(VSelect, {
      propsData: {
        segmented: true,
        items
      }
    })

    wrapper.vm.inputValue = items[0]

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render buttons correctly when using slot with segmented prop', async () => {
    const items = [
      { text: 'Hello' }
    ]

    const vm = new Vue()
    const selection = props => vm.$createElement('div', [props.item])
    const component = Vue.component('test', {
      render (h) {
        return h(VSelect, {
          props: {
            segmented: true,
            items
          },
          scopedSlots: {
            selection
          }
        })
      }
    })

    const wrapper = mount(component)

    wrapper.vm.$children[0].inputValue = items[0]

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render v-select correctly when using v-list-tile in item scope slot', async () => {
    const items = Array.from({ length: 2 }, (x, i) => ({ value: i, text: `Text ${i}` }))

    const vm = new Vue({
      components: {
        VListTile
      }
    })
    const itemSlot = ({ item, tile }) => vm.$createElement('v-list-tile', {
      on: tile.on,
      props: tile.props,
      class: item.value % 2 === 0 ? '' : 'red lighten-1'
    }, [
      item.text
    ])
    const component = Vue.component('test', {
      render (h) {
        return h(VSelect, {
          props: {
            items
          },
          scopedSlots: {
            item: itemSlot
          }
        })
      }
    })

    const wrapper = mount(component)

    wrapper.vm.$children[0].inputValue = items[0]

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render v-select correctly when not using v-list-tile in item scope slot', async () => {
    const items = Array.from({ length: 2 }, (x, i) => ({ value: i, text: `Text ${i}` }))

    const vm = new Vue({
      components: {
        VListTileTitle,
        VListTileContent
      }
    })
    const itemSlot = ({ item }) => vm.$createElement('v-list-tile-content', {
      class: item.value % 2 === 0 ? '' : 'red lighten-1'
    }, [
      vm.$createElement('v-list-tile-title', [item.value])
    ])
    const component = Vue.component('test', {
      render (h) {
        return h(VSelect, {
          props: {
            items
          },
          scopedSlots: {
            item: itemSlot
          }
        })
      }
    })

    const wrapper = mount(component)

    wrapper.vm.$children[0].inputValue = items[0]

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render v-select correctly when not using scope slot', async () => {
    const items = Array.from({ length: 2 }, (x, i) => ({ value: i, text: `Text ${i}` }))

    const component = Vue.component('test', {
      render (h) {
        return h(VSelect, {
          props: {
            items
          }
        })
      }
    })

    const wrapper = mount(component)

    wrapper.vm.$children[0].inputValue = items[0]

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should not close menu when using multiple prop', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        items: [1, 2, 3, 4],
        multiple: true
      }
    })

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

    wrapper.trigger('click')
    wrapper.trigger('blur')

    await wrapper.vm.$nextTick()

    const item = wrapper.find('div a.list__tile')[0]
    item.trigger('click')

    await wrapper.vm.$nextTick()

    expect(blur).not.toBeCalled()
    expect(wrapper.vm.isActive).toBe(true)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render aria-hidden=true on arrow icon', async () => {
    const wrapper = mount(VSelect)

    const icon = wrapper.find('.input-group__append-icon')[0]
    expect(icon.hasAttribute('aria-hidden')).toBe(true)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should display a default value', async () => {
    const wrapper = mount(VSelect, {
      propsData: {
        value: 'foo',
        items: ['foo']
      }
    })

    expect(wrapper.vm.selectedItems).toEqual(['foo'])
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should not display a default value that is not in items', async () => {
    const wrapper = mount(VSelect, {
      propsData: {
        value: 'foo',
        items: ['bar']
      }
    })

    expect(wrapper.vm.selectedItems).toHaveLength(0)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should update the displayed value when items changes', async () => {
    const wrapper = mount(VSelect, {
      propsData: {
        value: 1,
        items: []
      }
    })

    wrapper.setProps({ items: [{ text: 'foo', value: 1 }] })
    expect(wrapper.vm.selectedItems).toContainEqual({ text: 'foo', value: 1 })

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should render select menu with content class', async () => {
    const items = ['abc']

    const wrapper = mount(VSelect, {
      propsData: {
        contentClass: 'menu-class',
        items
      }
    })

    const menu = wrapper.find('.menu__content')[0]
    expect(menu.element.classList).toContain('menu-class')
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should have deletable chips', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        chips: true,
        deletableChips: true,
        tags: true,
        items: ['foo', 'bar'],
        value: ['foo']
      }
    })

    await wrapper.vm.$nextTick()
    const chip = wrapper.find('.chip')[0]

    expect(!!chip).toBe(true)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should escape items in menu', async () => {
    const wrapper = mount(VSelect, {
      propsData: {
        autocomplete: true,
        items: ['foo']
      }
    })

    const tileTitle = wrapper.find('.list__tile__title')[0]
    expect(tileTitle.html()).toMatchSnapshot()

    wrapper.setProps({ searchInput: 'str' })
    await wrapper.vm.$nextTick()
    expect(tileTitle.html()).toMatchSnapshot()

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should have the proper nudge', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        hideDetails: true,
        items: ['foo', 'bar']
      }
    })

    expect(wrapper.vm.nudgeTop).toBe(-18)

    wrapper.setProps({ autocomplete: true })

    expect(wrapper.vm.nudgeTop).toBe(0)

    wrapper.setProps({ autocomplete: false, overflow: true })

    expect(wrapper.vm.nudgeTop).toBe(2)

    wrapper.setProps({ auto: true, overflow: false })

    expect(wrapper.vm.nudgeTop).toBe(-18)

    wrapper.setProps({ auto: false, overflow: true, hideDetails: false })

    expect(wrapper.vm.nudgeTop).toBe(26)

    wrapper.setProps({ hideDetails: true })

    expect(wrapper.vm.nudgeTop).toBe(2)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should use value comparator', async () => {
    const wrapper = mount(VSelect, {
      attachToDocument: true,
      propsData: {
        multiple: true,
        items: [
          {text: 'one', value: 1},
          {text: 'two', value: 2},
          {text: 'three', value: 3}
        ],
        itemText: 'text',
        itemValue: 'value',
        valueComparator: (a, b) => Math.round(a) === Math.round(b),
        value: [3.1]
      }
    })

    expect(wrapper.vm.selectedItems).toHaveLength(1)
    expect(wrapper.vm.selectedItems[0].value).toBe(3)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should not open if readonly', async () => {
    const wrapper = mount(VSelect, {
      propsData: {
        readonly: true,
        items: ['foo', 'bar']
      }
    })

    wrapper.trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.menuIsActive).toBe(false)

    wrapper.find('.input-group__append-icon')[0].trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.menuIsActive).toBe(false)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })

  it('should reset selected index when clicked', () => {
    const wrapper = mount(VSelect, {
      propsData: {
        items: ['foo']
      }
    })

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

    wrapper.vm.selectedIndex = 0
    input.trigger('focus')
    expect(wrapper.vm.selectedIndex).toBe(0)
    wrapper.trigger('click')
    expect(wrapper.vm.selectedIndex).toBe(-1)

    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })
})


}
},
created () {
if (this.$meta) {
// Vue-meta
// Handled by metaInfo()/nuxt()

if (this.$vuetify.theme === false) return

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

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

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

  it('should render active component and match snapshot', () => {
    const wrapper = mount(VSpeedDial, {
      slots: {
        default: [compileToFunctions('test')]
      },
      data: {
        isActive: true
      }
    })

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

  it('should render component with custom direction and match snapshot', () => {
    const wrapper = mount(VSpeedDial, {
      propsData: {
        direction: 'right'
      }
    })

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

  it('should activate on click', () => {
    const wrapper = mount(VSpeedDial)

    expect(wrapper.vm.isActive).toBe(false)
    wrapper.trigger('click')
    expect(wrapper.vm.isActive).toBe(true)
  })

  it('should activate on hover', () => {
    const wrapper = mount(VSpeedDial, {
      propsData: {
        openOnHover: true
      }
    })

    expect(wrapper.vm.isActive).toBe(false)
    wrapper.trigger('mouseenter')
    expect(wrapper.vm.isActive).toBe(true)
    wrapper.trigger('mouseleave')
    expect(wrapper.vm.isActive).toBe(false)
  })
})


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

test('VDatePickerYears.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VDatePickerYears, {
      propsData: {
        value: '2000'
      }
    })

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

  it('should respect min/max props', async () => {
    const wrapper = mount(VDatePickerYears, {
      propsData: {
        min: 1234,
        max: 1238
      }
    })

    expect(wrapper.find('li:first-child')[0].element.textContent).toBe('1238')
    expect(wrapper.find('li:last-child')[0].element.textContent).toBe('1234')
  })

  it('should not allow min to be greater then max', async () => {
    const wrapper = mount(VDatePickerYears, {
      propsData: {
        min: 1238,
        max: 1234
      }
    })
    expect(wrapper.find('li').length).toBe(1)
    expect(wrapper.find('li')[0].element.textContent).toBe('1234')
    expect(wrapper.find('li')[0].element.textContent).toBe('1234')
  })

  it('should emit event on year click', async () => {
    const wrapper = mount(VDatePickerYears, {
      propsData: {
        value: 1999
      }
    })

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

    wrapper.find('li.active + li')[0].trigger('click')
    expect(input).toBeCalledWith(1998)
  })

  it('should format years', async () => {
    const wrapper = mount(VDatePickerYears, {
      propsData: {
        format: year => `(${year})`,
        min: 1001,
        max: 1001
      }
    })

    expect(wrapper.find('li')[0].element.textContent).toBe('(1001)')
  })
})


/* eslint-disable no-multi-spaces */
const THEME_DEFAULTS = {
  primary: '#1976D2',   // blue.darken2
  secondary: '#424242', // grey.darken3
  accent: '#82B1FF',    // blue.accent1
  error: '#FF5252',     // red.accent2
  info: '#2196F3',      // blue.base
  success: '#4CAF50',   // green.base
  warning: '#FFC107'    // amber.base
}

export default function theme (theme = {}) {
  return Object.assign({}, THEME_DEFAULTS, theme)
}


declare module 'vuetify/es5/util/colors' {
  interface BaseColor {
    readonly base: string
    readonly lighten5: string
    readonly lighten4: string
    readonly lighten3: string
    readonly lighten2: string
    readonly lighten1: string
    readonly darken1: string
    readonly darken2: string
    readonly darken3: string
    readonly darken4: string
  }

  interface Color extends BaseColor {
    readonly accent1: string
    readonly accent2: string
    readonly accent3: string
    readonly accent4: string
  }

  interface Shade {
    readonly black: string
    readonly white: string
    readonly transparent: string
  }

  export const red: Color
  export const pink: Color
  export const purple: Color
  export const deepPurple: Color
  export const indigo: Color
  export const blue: Color
  export const lightBlue: Color
  export const cyan: Color
  export const teal: Color
  export const green: Color
  export const lightGreen: Color
  export const lime: Color
  export const yellow: Color
  export const amber: Color
  export const orange: Color
  export const deepOrange: Color
  export const brown: BaseColor
  export const blueGrey: BaseColor
  export const grey: BaseColor
  export const shades: Shade
}


import createNativeLocaleFormatter from '@/components/VDatePicker/util/createNativeLocaleFormatter'
import { test } from '@/test'

test('VDatePicker/util/createNativeLocaleFormatter.js', ({ mount }) => {
  it('should format dates', () => {

    const formatter = createNativeLocaleFormatter(undefined, { day: 'numeric', timeZone: 'UTC' })
    expect(formatter('2013-2-07')).toBe('7')
  })

  it('should format dates if Intl is not defined', () => {
      const oldIntl = global.Intl

      global.Intl = null
      const formatter = createNativeLocaleFormatter(undefined, { day: 'numeric', timeZone: 'UTC' }, { start: 0, length: 10 })
      expect(formatter('2013-2-7')).toBe('2013-02-07')
      expect(formatter('2013-2')).toBe('2013-02-01')
      expect(formatter('2013')).toBe('2013-01-01')

      const nullFormatter = createNativeLocaleFormatter(undefined, { day: 'numeric', timeZone: 'UTC' })
      expect(nullFormatter).toBe(null)
      global.Intl = oldIntl
    })
  })


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

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

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

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

export default {
  name: 'v-menu',

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

  directives: {
    ClickOutside,
    Resize
  },

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

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

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

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

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

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

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

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

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

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

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

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

  render (h) {
    const data = {
      staticClass: 'menu',
      class: {
        'menu--disabled': this.disabled
      },
      style: {
        display: this.fullWidth ? 'block' : 'inline-block'
      },
      directives: [{
        arg: 500,
        name: 'resize',
        value: this.onResize
      }],
      on: {
        keydown: this.changeListIndex
      }
    }

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


},
defaultSort: {
type: Object,
default: () => null

import Head from './mixins/head'
import Body from './mixins/body'
import Foot from './mixins/foot'
import Progress from './mixins/progress'

export default {
  name: 'datatable',

  mixins: [Head, Body, Foot, Progress],

  data () {
    return {
      desc: null,
      page: 1,
      sorting: null,
      all: false
    }
  },

  props: {
    headers: {
      type: Array,
      default: () => []
    },
    headerText: {
      type: String,
      default: 'text'
    },
    hideActions: Boolean,
    items: {
      type: Array,
      default: () => []
    },
    itemValue: {
      default: 'value'
    },
    noDataText: {
      type: String,
      default: 'No data available in table'
    },
    noResultsText: {
      type: String,
      default: 'No matching records found'
    },
    rowsPerPage: {
      type: [Number, Object],
      default: 5
    },
    rowsPerPageItems: {
      type: Array,
      default () {
        return [
          5,
          10,
          25,
          { text: 'All', value: -1 }
        ]
      }
    },
    rowsPerPageText: {
      type: String,
      default: 'Rows per page:'
    },
    selectAll: Boolean,
    search: {
      required: false
    },
    filter: {
      type: Function,
      default: (val, search) => {
        return val !== null &&
          ['undefined', 'boolean'].indexOf(typeof val) === -1 &&
          val.toString().toLowerCase().indexOf(search) !== -1
      }
    },
    value: {
      type: Array,
      default: () => []
    },
    totalItems: {
      type: Number,
      default: null
    },
    loading: {
      type: Boolean,
      default: false
    }
  },

  computed: {
    itemsLength () {
      return this.totalItems || this.value.length
    },
    indeterminate () {
      return this.selectAll && this.someItems && !this.everyItem
    },
    everyItem () {
      return this.value.every(i => i[this.itemValue])
    },
    someItems () {
      return this.value.some(i => i[this.itemValue])
    },
    pageStart () {
      const page = this.rowsPerPage === Object(this.rowsPerPage)
        ? this.rowsPerPage.value
        : this.rowsPerPage
      return page === -1 ? 0 : (this.page - 1) * page
    },
    pageStop () {
      const page = this.rowsPerPage === Object(this.rowsPerPage)
        ? this.rowsPerPage.value
        : this.rowsPerPage
      return page === -1 ? this.itemsLength : this.page * page
    },
    filteredItems () {
      if (this.totalItems) return this.value

      let items = this.value.slice()
      const hasSearch = typeof this.search !== 'undefined' && this.search !== null

      if (hasSearch) {
        const search = this.search.toString().toLowerCase()

        items = items.filter(i => Object.keys(i).some(j => this.filter(i[j], search)))
      }

      items = items.sort((a, b) => {
        const sortA = a[this.sorting]
        const sortB = b[this.sorting]

        if (this.desc) {
          if (sortA < sortB) return 1
          if (sortA > sortB) return -1
          return 0
        } else {
          if (sortA < sortB) return -1
          if (sortA > sortB) return 1
          return 0
        }
      })

      return this.hideActions ? items : items.slice(this.pageStart, this.pageStop)
    }
  },

  watch: {
    rowsPerPage () {
      this.page === 1 && this.update()

      this.page = 1
    },
    indeterminate (val) {
      if (val) this.all = true
    },
    someItems (val) {
      if (!val) this.all = false
    },
    search () {
      this.page = 1
    },
    page () {
      this.update()
    }
  },

  methods: {
    update () {
      this.$emit('update', {
        page: this.page,
        rowsPerPage: this.rowsPerPage,
        sorting: this.sorting,
        desc: this.desc,
      })
    },
    sort (index) {
      if (this.sorting === null) {
        this.sorting = index
        this.desc = true
      } else if (this.sorting === index && this.desc) {
        this.desc = false
      } else if (this.sorting !== index) {
        this.sorting = index
        this.desc = true
      } else {
        this.sorting = null
        this.desc = null
      }

      this.update()
    },
    genTR (children, data = {}) {
      return this.$createElement('tr', data, children)
    },
    toggle (val) {
      this.all = val

      this.$emit('input', this.value.map(i => {
        i[this.itemValue] = this.filteredItems.includes(i) ? val : false

        return i
      }))
    }
  },

  mounted () {
    const header = this.headers.find(h => !('sortable' in h) || h.sortable)
    this.sort(header.value)
  },

  render (h) {
    return h('v-table-overflow', {}, [
      h('table', {
        'class': {
          'datatable table': true,
          'datatable--select-all': this.selectAll
        }
      }, [
        this.genTHead(),
        this.genTProgress(),
        this.genTBody(),
        this.hideActions ? null : this.genTFoot()
      ])
    ])
  }
}


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

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

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

    &:hover
      color: $theme.primary

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

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

  &-year
    font-size: 14px

  &-date
    font-size: 34px

    > div
      position: relative

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

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

    &:hover
      opacity: 1

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

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

    .btn
      color: #000
      margin: 0

    .icon
      cursor: pointer
      user-select: none

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

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

.picker--date
  &__table
    position: relative

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

    thead
      th
        padding: 8px 0

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

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

    .btn
      margin: 0

      &__content
        overflow: visible
        transition: none

    .btn.btn--floating.btn--current:not(.btn--active)
      color: rgba($theme.primary, .6)

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

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

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

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

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

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

.picker--date
  &.picker--dark
    .picker--date__header, .picker--date__years
      background: $grey.darken-3
      color: #fff

      .btn
        color: #fff

    .picker--date__table
      table
        th, td, .btn
          color: #fff

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

        .btn--active
          color: #000

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

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


.application--dark
  .toolbar
    background: $grey.darken-4

.toolbar
  align-items: center
  background-color: $theme.primary
  display: flex
  flex-wrap: wrap
  height: $toolbar-height
  position: relative
  padding: $toolbar-padding-y 0
  transition: .3s $transition.swing
  width: 100%
  will-change: padding-left
  elevation(4)
  z-index: 2

  > *:first-child
    margin-left: $grid-gutter

  > *:right-child
    margin-right: $grid-gutter

  @media $display-breakpoints.xs-only
    > *:first-child
      margin-left: $grid-gutter-mobile

    > *:right-child
      margin-right: $grid-gutter-mobile

  ul
    list-style: none

  li
    height: 100%

  i
    font-size: 24px

  .menu__activator
    height: 100%

  .btn:not(.toolbar__side-icon)
    margin: 0 6px

    &:first-child
      margin-left: -6px

  &--fixed, &--absolute
    + main
      padding-top: $toolbar-height

  &--fixed
    position: fixed

  &--absolute
    position: absolute

  &__sub
    flex: 1 0 100%
    padding: 24px 0 24px 72px

  &__side-icon
    display: inline-flex
    align-items: center
    position: relative
    margin-left: 10px

  &__logo
    color: #fff
    font-size: 3rem
    flex: 1;
    text-decoration: none
    padding: 0
    display: flex
    align-items: center

  &__title
    color: #fff
    font-size: $headings.h6.size
    flex: 1;
    white-space: nowrap
    overflow: hidden
    text-overflow: ellipsis

    &:not(:first-child)
      padding: 0 16px

  &__items
    list-style-type: none
    padding: 0
    background-color: inherit
    display: flex
    align-items: center
    height: 100%
    max-width: 100%

    > li
      display: flex
      align-items: center

  &__item
    align-items: center
    color: #fff
    display: flex
    padding: $toolbar-item-padding
    text-decoration: none
    transition: all 0.3s ease-out
    position: relative
    height: 100%
    white-space: nowrap

    i
      font-size: 2rem

    &:hover, &--active
      background: rgba(#000,0.1)

    &--disabled
      opacity: .5
      pointer-events: none

  .input-group
    margin: 0 0 0 16px
    flex: 1

    &--dark
      .icon
        color: #fff

    &--light
      .icon
        color: #000

    &--dark.input-group--focused.input-group--prepend-icon,
    &--dark.input-group--focused.input-group--append-icon
      .icon
        color: #fff

    &--light.input-group--focused.input-group--prepend-icon,
    &--light.input-group--focused.input-group--append-icon
      .icon
        color: #000


export default {
  data () {
    return {
      isBooted: false
    }
  },

  watch: {
    isActive () {
      this.isBooted = true
    }
  }
}


} else if (this.sorting === index && this.desc) {

keep keep keep keep replace replace keep


vuetify

cross-env TARGET=dev webpack --config build/config.js --progress --hide-modules

cross-env rimraf dist && NODE_ENV=production node build/build.js --progress --hide-modules

cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run

eslint --ext .js,.vue src test/unit/specs test/e2e/specs

Vue.js 2 Semantic Component Framework


import Icon from './Icon'

export default {
  Icon
}


import NavigationDrawer from './NavigationDrawer'

export default {
  NavigationDrawer
}


} else if (this.sorting === index && !this.desc) {

.datatable
  thead
    th.column.sortable
      cursor: pointer

      i
        color: rgba(#000, .38)
        font-size: 16px
        vertical-align: sub
        display: inline-block
        opacity: 0
        transition: .3s $transition.swing

      &:hover
        color: rgba(#000, .87)

        i
          opacity: .6

      &.active
        transform: none
        color: rgba(#000, .87)

        i
          color: rgba(#000, .87)
          opacity: 1

        &.asc
          i
            transform: rotate(-180deg)

.datatable
  tfoot
    .input-group__details
      display: none

/** Actions */
.datatable__actions
  color: rgba(#000, .54)
  display: flex
  justify-content: flex-end
  align-items: center
  font-size: 12px

  .btn
    color: inherit

    &:last-of-type
      margin-left: 18px

  &__pagination
    text-align: center
    margin: 0 26px 0 32px

  &__select
    display: flex
    align-items: center
    justify-content: center

    .input-group--select
      margin: 13px 0 13px 34px
      position: initial

      .input-group__selections__comma
        color: rgba(#000, .54) !important
        font-size: 12px
        padding-top: 6px

      .input-group__append-icon
        color: rgba(#000, .54) !important

.datatable__progress
  tr
    height: auto !important

  th
    padding: 0 !important

    .progress-linear
      top: -3px
      margin: 0 0 -3px


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

export default {
  data () {
    return {
      overlay: null
    }
  },

  props: {
    hideOverlay: Boolean
  },

  methods: {
    genOverlay () {
      const overlay = document.createElement('div')
      overlay.className = 'overlay'
      if (this.absolute) overlay.className += ' overlay--absolute'

      this.$el.parentNode.insertBefore(overlay, this.$el.nextSibling)

      setTimeout(() => {
        overlay.className += ' overlay--active'
        this.overlay = overlay
      }, 25)
    },
    removeOverlay () {
      if (!this.overlay) return

      addOnceEventListener(this.overlay, 'transitionend', () => {
        this.overlay && this.overlay.remove()
        this.overlay = null
      })

      this.overlay.className = this.overlay.className.replace('overlay--active', '')
    }
  }
}


export default {
  functional: true,

  props: {
    dark: Boolean,
    id: {
      type: String,
      default: 'app'
    }
  },

  render (h, { props, data, children }) {
    data.staticClass = data.staticClass ? `application ${data.staticClass} ` : 'application '

    const classes = {
      'application--dark': props.dark,
      'application--light': !props.dark
    }

    data.staticClass += Object.keys(classes).filter(k => classes[k]).join(' ')

    data.attrs = { 'data-app': true }
    data.domProps = { id: props.id }

    return h('div', data, children)
  }
}


export default {
  props: {
    origin: {
      type: String,
      default: 'top left'
    },
    transition: {
      type: String,
      default: 'v-scale-transition'
    }
  }
}


import Toggleable from '../../mixins/toggleable'

export default {
  name: 'chip',

  mixins: [Toggleable],

  props: {
    close: Boolean,
    label: Boolean,
    outline: Boolean,
    small: Boolean,
    value: {
      type: Boolean,
      default: true
    }
  },

  computed: {
    classes () {
      return {
        'chip': true,
        'chip--label': this.label,
        'chip--outline': this.outline,
        'chip--small': this.small,
        'chip--removable': this.close
      }
    }
  },

  render (h) {
    const children = [this.$slots.default]
    const data = {
      'class': this.classes,
      attrs: {
        tabindex: -1
      },
      directives: [{
        name: 'show',
        value: this.isActive
      }]
    }

    if (this.close) {
      const icon = h('v-icon', { props: { right: true }}, 'cancel')

      children.push(h('a', {
        'class': 'chip__close',
        domProps: { href: 'javascript:;' },
        on: {
          click: e => {
            e.preventDefault()

            this.$emit('input', false)
          }
        }
      }, [icon]))
    }

    return h('span', data, children)
  }
}


.alert
  border-radius: $alert-border-radius
  border-width: $alert-border-width
  border-style: solid
  border-color: rgba(#000, .8)
  color: $alert-color
  display: flex
  font-size: $alert-font-size
  text-align: left
  padding: $alert-padding
  position: relative
  margin: $alert-margin

  &__icon.icon, &__dismissible .icon
    align-self: center
    color: $alert-icon-color
    font-size: $alert-icon-font-size

  &__icon
    margin-right: $alert-padding

  &__dismissible
    margin-right: 0
    margin-left: $alert-padding
    transition: $primary-transition
    align-self: flex-start
    text-decoration: none

    &:hover
      color: lighten($alert-icon-color, 10%)

  > div
    display: flex
    align-items: center
    width: 100%

  &--primary
    alert($theme.primary)

  &--secondary
    alert($theme.primary)

  &--info
    alert($theme.info)

  &--error
    alert($theme.error)

  &--success
    alert($theme.success)

  &--warning
    alert($theme.warning)

  &--no-icon
    .alert__icon
      display: none

  @media screen and (max-width: $grid-breakpoints.sm)
    &__icon
      display: none


const header = this.headers.find(h => !('sortable' in h) || h.sortable)

keep keep keep keep keep replace keep keep keep keep

this.desc = this.defaultSort ? this.defaultSort.desc : this.desc

this.sorting = this.defaultSort ? this.defaultSort.field : header.value


root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true


for color_name, color_value in $theme
  global-color(color_name, color_value)

for color_name, color_value in $shades
  global-color(color_name, color_value)

if ($color-pack)
  for color_name, color in $colors
    for color_type, color_value in color
      if color_type == 'base'
        global-color(color_name, color_value)

      else if color_type != 'shades'
        global-color-accent(color_name, color_value)


import { closestParentTag } from '../../util/helpers'
import Expand from '../../mixins/expand-transition'
import Toggleable from '../../mixins/toggleable'

export default {
  name: 'list-group',

  mixins: [Expand, Toggleable],

  data () {
    return {
      isBooted: this.value,
      height: 0
    }
  },

  props: {
    group: String,
    lazy: Boolean,
    noAction: Boolean
  },

  computed: {
    classes () {
      return {
        'list--group__header': true,
        'list--group__header--active': this.isActive,
        'list--group__header--no-action': this.noAction
      }
    },
    list () {
      return closestParentTag.call(this, 'v-list')
    },
    styles () {
      return {
        height: `${this.height}px`
      }
    }
  },

  watch: {
    isActive () {
      this.isBooted = true

      if (!this.isActive) {
        this.list.listClose(this._uid)
      }
    },
    '$route' (to) {
      const isActive = this.matchRoute(to.path)

      if (this.group) {
        if (isActive && this.isActive !== isActive) {
          this.list.listClick(this._uid)
        }
        this.isActive = isActive
      }
    }
  },

  mounted () {
    if (this.group) {
      this.isActive = this.matchRoute(this.$route.path)
    }

    if (this.isActive) {
      this.list.listClick(this._uid)
    }

    this.height = this.$refs.group.scrollHeight
  },

  methods: {
    click () {
      if (!this.$refs.item.querySelector('.list__tile--disabled')) {
        this.list.listClick(this._uid)
      }
    },
    toggle (uid) {
      this.isActive = this._uid === uid
    },
    matchRoute (to) {
      if (!this.group) return false
      return to.match(this.group) !== null
    }
  },

  render (h) {
    const group = h('ul', {
      'class': 'list list--group',
      style: this.styles,
      directives: [{
        name: 'show',
        value: this.isActive
      }],
      ref: 'group'
    }, [this.lazy && !this.isBooted ? null : this.$slots.default])

    const item = h('div', {
      'class': this.classes,
      on: { click: this.click },
      ref: 'item'
    }, [this.$slots.item])

    const transition = h('transition', {
      on: {
        enter: this.enter,
        afterEnter: this.afterEnter,
        leave: this.leave
      }
    }, [group])

    return h('div', { 'class': 'list--group__container' }, [item, transition])
  }
}


!('value' in item) && console.warn('Data table headers must have a value property that corresponds to a value in the v-model array')

const icon = this.$createElement('v-icon', 'arrow_downward')

item.left && children.push(icon) || children.unshift(icon)

beingSorted = this.sorting === item.value

const icon = this.$createElement('v-icon', 'arrow_upward')


import Input from '../../mixins/input'
import { addOnceEventListener } from '../../util/helpers'

export default {
  name: 'slider',

  mixins: [Input],

  data () {
    return {
      app: {},
      isActive: false,
      inputWidth: 0
    }
  },

  props: {
    inverted: Boolean,
    min: {
      type: [Number, String],
      default: 0
    },
    max: {
      type: [Number, String],
      default: 100
    },
    step: {
      type: [Number, String],
      default: 1
    },
    thumbLabel: Boolean,
    value: [Number, String],
    vertical: Boolean
  },

  computed: {
    classes () {
      return {
        'input-group input-group--slider': true,
        'input-group--active': this.isActive,
        'input-group--dirty': this.inputValue > this.min,
        'input-group--disabled': this.disabled,
        'input-group--ticks': this.thumbLabel
      }
    },
    inputValue: {
      get () {
        return this.value
      },
      set (val) {
        val = val < this.min ? this.min : val > this.max ? this.max : val
        if (Math.ceil(val) !== Math.ceil(this.lazyValue)) {
          this.inputWidth = this.calculateWidth(val)
        }

        let value = parseInt(val)
        this.lazyValue = value

        if (value !== this.value) {
          this.$emit('input', value)
        }
      }
    },
    interval () {
      return 100 / (this.max - this.min) * this.step
    },
    thumbContainerClasses () {
      return {
        'slider__thumb-container': true,
        'slider__thumb-container--label': this.thumbLabel
      }
    },
    thumbStyles () {
      return {
        left: `${this.inputWidth}%`
      }
    },
    tickContainerStyles () {
      return {
        transform: `translate3d(-${this.interval}%, -50%, 0)`
      }
    },
    tickStyles () {
      return {
        backgroundSize: `${this.interval}% 2px`,
        transform: `translate3d(${this.interval}%, 0, 0)`
      }
    },
    trackStyles () {
      const scaleX = this.calculateScale(1 - (this.inputWidth / 100))
      const translateX = this.inputWidth < 1 && !this.thumbLabel ? `${8}px` : 0
      return {
        transform: `scaleX(${scaleX}) translateX(${translateX})`
      }
    },
    trackFillStyles () {
      const scaleX = this.calculateScale(this.inputWidth / 100)
      const translateX = this.inputWidth > 99 && !this.thumbLabel ? `${-8}px` : 0
      return {
        transform: `scaleX(${scaleX}) translateX(${translateX})`
      }
    }
  },

  watch: {
    value () {
      this.inputValue = this.value
    }
  },

  mounted () {
    this.inputValue = this.value
    this.inputWidth = this.calculateWidth(this.inputValue)
    this.app = document.querySelector('[data-app]')
  },

  methods: {
    calculateWidth (val) {
      return (val - this.min) / (this.max - this.min) * 100
    },
    calculateScale (scale) {
      if (scale < 0.02 && !this.thumbLabel) {
        return 0
      }

      return this.disabled ? scale - 0.015 : scale
    },
    onMouseDown (e) {
      this.isActive = true

      if ('touches' in e) {
        this.app.addEventListener('touchmove', this.onMouseMove, false)
        addOnceEventListener(this.app, 'touchend', this.onMouseUp)
      } else {
        this.app.addEventListener('mousemove', this.onMouseMove, false)
        addOnceEventListener(this.app, 'mouseup', this.onMouseUp)
      }
    },
    onMouseUp () {
      this.isActive = false
      this.app.removeEventListener('touchmove', this.onMouseMove, false)
      this.app.removeEventListener('mousemove', this.onMouseMove, false)
    },
    onMouseMove (e) {
      const { left: offsetLeft, width: trackWidth } = this.$refs.track.getBoundingClientRect()
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      let left = (
        ((clientX - offsetLeft) / trackWidth) * 100
      )

      left = left < 0 ? 0 : left > 100 ? 100 : left

      this.inputValue = this.min + ((left / 100) * (this.max - this.min))
    },
    sliderMove (e) {
      if (!this.isActive) {
        this.onMouseMove(e)
      }
    }
  },

  render (h) {
    const children = []
    const trackChildren = []
    const thumbChildren = []

    trackChildren.push(h('div', { 'class': 'slider__track', style: this.trackStyles }))
    trackChildren.push(h('div', { 'class': 'slider__track-fill', style: this.trackFillStyles }))
    children.push(h('div', { 'class': 'slider__track__container', ref: 'track' }, trackChildren))

    if (this.step) {
      children.push(
        h('div', { 'class': 'slider__ticks-container', style: this.tickContainerStyles }, [
          h('div', { 'class': 'slider__ticks', style: this.tickStyles })
        ])
      )
    }

    thumbChildren.push(h('div', { 'class': 'slider__thumb' }))

    if (this.thumbLabel) {
      thumbChildren.push(
        h('v-scale-transition', { props: { origin: 'bottom center' }}, [
          h('div', {
            'class': 'slider__thumb--label__container',
            directives: [
              {
                name: 'show',
                value: this.isActive
              }
            ]
          }, [
            h('div', { 'class': 'slider__thumb--label' }, [
              h('span', {}, parseInt(this.inputValue))
            ])
          ])
        ])
      )
    }

    const thumbContainer = h('div', {
      'class': this.thumbContainerClasses,
      style: this.thumbStyles,
      on: {
        touchstart: this.onMouseDown,
        mousedown: this.onMouseDown
      },
      ref: 'thumb'
    }, thumbChildren)

    children.push(thumbContainer)

    const slider = h('div', { 'class': 'slider' }, children)

    return this.genInputGroup([slider], {
      attrs: {
        role: 'slider'
      },
      on: {
        mouseup: this.sliderMove
      },
      directives: [
        {
          name: 'click-outside'
        }
      ]
    })
  }
}


i
color: rgba(#000, .87)
opacity: 1
&.asc
i
transform: rotate(-180deg)

keep keep keep keep keep replace keep keep keep


export default {
  name: 'breadcrumbs',

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

  props: {
    divider: {
      type: String,
      default: '/'
    },
    icons: Boolean
  },

  computed: {
    classes () {
      return {
        'breadcrumbs': true,
        'breadcrumbs--with-icons': this.icons
      }
    }
  },

  render (h) {
    return h('ul', {
      'class': this.classes,
      props: { items: this.items }
    }, this.$slots.default)
  }
}


import Toggleable from '../../mixins/toggleable'
import Contextualable from '../../mixins/contextualable'

export default {
  name: 'alert',

  mixins: [Contextualable, Toggleable],

  props: {
    dismissible: Boolean,
    hideIcon: Boolean
  },

  computed: {
    classes () {
      return {
        'alert': true,
        'alert--dismissible': this.dismissible,
        'alert--error': this.error,
        'alert--info': this.info,
        'alert--success': this.success,
        'alert--warning': this.warning,
        'alert--primary': this.primary,
        'alert--secondary': this.secondary
      }
    },

    mdIcon () {
      switch (true) {
        case Boolean(this.icon):
          return this.icon
        case this.error:
          return 'warning'
        case this.info:
          return 'info'
        case this.success:
          return 'check_circle'
        case this.warning:
          return 'priority_high'
      }
    }
  },

  render (h) {
    const children = [h('div', this.$slots.default)]

    !this.hideIcon && this.mdIcon && children.unshift(h('v-icon', {
      'class': 'alert__icon',
      props: { large: true }
    }, this.mdIcon))

    if (this.dismissible) {
      children.push(h('a', {
        'class': 'alert__dismissible',
        domProps: { href: 'javascript:;' },
        on: { click: () => (this.$emit('input', false)) }
      }, [h('v-icon', { props: { right: true, large: true }}, 'cancel')]))
    }

    return h('div', {
      'class': this.classes,
      directives: [{
        name: 'show',
        value: this.isActive
      }]
    }, children)
  }
}


.right
  float: right !important

.left
  float: left !important


const isActive = useProxiedModel(props, 'modelValue')

const height = computed(() => vToolbarRef.value?.contentHeight)

const { layoutItemStyles } = useLayoutItem({

priority: computed(() => parseInt(props.priority, 10)),

const height: number = vToolbarRef.value?.contentHeight ?? 0

const extensionHeight: number = vToolbarRef.value?.extensionHeight ?? 0

// Styles
import './VToolbar.sass'

// Components
import { VDefaultsProvider } from '@/components/VDefaultsProvider'
import { VImg } from '@/components/VImg'
import { VToolbarTitle } from './VToolbarTitle'

// Composables
import { makeBorderProps, useBorder } from '@/composables/border'
import { makeElevationProps, useElevation } from '@/composables/elevation'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeTagProps } from '@/composables/tag'
import { makeThemeProps, provideTheme } from '@/composables/theme'
import { provideDefaults } from '@/composables/defaults'
import { useBackgroundColor } from '@/composables/color'
import { useForwardRef } from '@/composables/forwardRef'

// Utilities
import { computed, toRef } from 'vue'
import { convertToUnit, genericComponent, pick, propsFactory, useRender } from '@/util'

// Types
import type { MakeSlots } from '@/util'
import type { ExtractPropTypes, PropType } from 'vue'

export type Density = typeof allowedDensities[number]

const allowedDensities = [null, 'prominent', 'default', 'comfortable', 'compact'] as const

export const makeVToolbarProps = propsFactory({
  absolute: Boolean,
  collapse: Boolean,
  color: String,
  density: {
    type: String as PropType<Density>,
    default: 'default',
    validator: (v: any) => allowedDensities.includes(v),
  },
  extended: Boolean,
  extensionHeight: {
    type: [Number, String],
    default: 48,
  },
  flat: Boolean,
  floating: Boolean,
  height: {
    type: [Number, String],
    default: 56,
  },
  image: String,
  title: String,

  ...makeBorderProps(),
  ...makeElevationProps(),
  ...makeRoundedProps(),
  ...makeTagProps({ tag: 'header' }),
  ...makeThemeProps(),
}, 'v-toolbar')

export const VToolbar = genericComponent<new () => {
  $slots: MakeSlots<{
    default: []
    image: [{ image: string }]
    prepend: []
    append: []
    title: []
    extension: []
  }>
}>()({
  name: 'VToolbar',

  props: makeVToolbarProps(),

  setup (props, { slots }) {
    const { borderClasses } = useBorder(props)
    const { elevationClasses } = useElevation(props)
    const { roundedClasses } = useRounded(props)
    const { themeClasses } = provideTheme(props)
    const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(toRef(props, 'color'))
    const contentHeight = computed(() => (
      Number(props.height) +
      (props.density === 'prominent' ? Number(props.height) : 0) -
      (props.density === 'comfortable' ? 8 : 0) -
      (props.density === 'compact' ? 16 : 0)
    ))

    provideDefaults({
      VBtn: {
        flat: true,
        variant: 'text',
      },
      VTextField: {
        hideDetails: true,
      },
    })

    useRender(() => {
      const hasTitle = !!(props.title || slots.title)
      const hasImage = !!(slots.image || props.image)
      const isExtended = !!(props.extended || slots.extension)

      return (
        <props.tag
          class={[
            'v-toolbar',
            {
              'v-toolbar--absolute': props.absolute,
              'v-toolbar--collapse': props.collapse,
              'v-toolbar--flat': props.flat,
              'v-toolbar--floating': props.floating,
              [`v-toolbar--density-${props.density}`]: true,
            },
            backgroundColorClasses.value,
            borderClasses.value,
            elevationClasses.value,
            roundedClasses.value,
            themeClasses.value,
          ]}
          style={[
            backgroundColorStyles.value,
          ]}
        >
          { hasImage && (

              <VDefaultsProvider
                defaults={{
                  VImg: {
                    cover: true,
                    src: props.image,
                  },
                }}
                scoped
              >
                { slots.image ? slots.image?.() : (<VImg />) }
              </VDefaultsProvider>

          ) }

            { slots.prepend && (

                { slots.prepend?.() }

            ) }

            { hasTitle && (
              <VToolbarTitle text={ props.title }>
                {{ text: slots.title }}
              </VToolbarTitle>
            ) }

            { slots.default?.() }

            { slots.append && (

                { slots.append?.() }

            ) }

          { isExtended && (

              { slots.extension?.() }

          ) }
        </props.tag>
      )
    })

    return useForwardRef({ contentHeight })
  },
})

export type VToolbar = InstanceType<typeof VToolbar>

export function filterToolbarProps (props: ExtractPropTypes<ReturnType<typeof makeVToolbarProps>>) {
  return pick(props, Object.keys(VToolbar?.props ?? {}) as any)
}


<template>
  <v-form>
    <v-container>
      <v-row>
        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="Regular"
            disabled
          ></v-text-field>
        </v-col>

        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="Regular"
            readonly
          ></v-text-field>
        </v-col>

        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="Contained"
            variant="contained"
            disabled
          ></v-text-field>
        </v-col>

        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="Contained"
            variant="contained"
            readonly
          ></v-text-field>
        </v-col>

        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="Outlined"
            variant="outlined"
            disabled
          ></v-text-field>
        </v-col>

        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="Outlined"
            variant="outlined"
            readonly
          ></v-text-field>
        </v-col>

        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="underlined"
            variant="underlined"
            disabled
          ></v-text-field>
        </v-col>

        <v-col
          cols="12"
          sm="6"
        >
          <v-text-field
            value="John Doe"
            label="underlined"
            variant="underlined"
            readonly
          ></v-text-field>
        </v-col>
      </v-row>
    </v-container>
  </v-form>
</template>


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

// Components
// import VSlider from '../VSlider'

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

/* eslint-disable max-statements */
describe.skip('VSlider.ts', () => {
  type Instance = InstanceType<typeof VSlider>
  let mountFunction: (options?: object) => Wrapper<Instance>
  let el

  beforeEach(() => {
    el = document.createElement('div')
    el.setAttribute('data-app', 'true')
    document.body.appendChild(el)
    mountFunction = (options = {}) => {
      return mount(VSlider, {
        // https://github.com/vuejs/vue-test-utils/issues/1130
        sync: false,
        mocks: {
          $vuetify: {
            rtl: false,
          },
        },
        ...options,
      })
    }
  })

  afterEach(() => {
    document.body.removeChild(el)
  })

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

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

  it('should render vertical slider', async () => {
    const wrapper = mountFunction({
      propsData: {
        vertical: true,
      },
    })

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

  it('should render component with ticks and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        ticks: 'yes',
        step: 25,
      },
    })

    expect('Invalid prop: custom validator check failed for prop "ticks"').toHaveBeenWarned()

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

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

    wrapper.setProps({ ticks: 'always' })
    await wrapper.vm.$nextTick()

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

  it('should render component with thumbLabel and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        thumbLabel: 'true',
      },
    })

    expect('Invalid prop: custom validator check failed for prop "thumbLabel"').toHaveBeenWarned()

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

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

    wrapper.setProps({ thumbLabel: 'always' })
    await wrapper.vm.$nextTick()

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

  it('should set tabindex in disabled component', () => {
    const wrapper = mountFunction({
      propsData: {
        disabled: true,
      },
    })

    const slider = wrapper.find('.v-slider__thumb-container')

    expect(slider.element.getAttribute('tabindex')).toBe('-1')
  })

  it('should not allow values outside of min/max', async () => {
    const wrapper = mountFunction({
      propsData: {
        min: 2,
        max: 4,
      },
    })

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

    wrapper.setProps({ value: 0 })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenCalledWith(2)

    wrapper.setProps({ value: 5 })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenCalledWith(4)
  })

  it('should adjust value if min/max props change', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: 5,
        min: 0,
        max: 10,
      },
    })

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

    wrapper.setProps({ min: 6 })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenCalledWith(6)

    wrapper.setProps({ max: 4 })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenCalledWith(4)
  })

  it('should round value with offset correct', async () => {
    const wrapper = mountFunction({
      propsData: {
        min: 3,
        max: 15,
        step: 3,
      },
    })

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

    wrapper.setProps({ value: 5 })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenLastCalledWith(6)

    wrapper.setProps({ value: 7 })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenLastCalledWith(6)
  })

  it('should react to keydown event', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: 50,
      },
    })

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

    const slider = wrapper.find('.v-slider__thumb-container')

    slider.trigger('keydown.space')
    expect(input).not.toHaveBeenCalled()
    slider.trigger('keydown.left')
    expect(input).toHaveBeenCalledWith(49)
    slider.trigger('keydown.right')
    expect(input).toHaveBeenCalledWith(50)
    slider.trigger('keydown.home')
    expect(input).toHaveBeenCalledWith(0)
    slider.trigger('keydown.left')
    slider.trigger('keydown.end')
    expect(input).toHaveBeenCalledWith(100)
    slider.trigger('keydown.pagedown')
    expect(input).toHaveBeenCalledWith(90)
    slider.trigger('keydown.pageup')
    expect(input).toHaveBeenCalledWith(100)

    wrapper.setProps({ step: 4 })
    slider.trigger('keydown.pagedown')
    expect(input).toHaveBeenCalledWith(60)
    wrapper.setProps({ step: 2 })
    slider.trigger('keydown.pageup')
    expect(input).toHaveBeenCalledWith(80)
    wrapper.setProps({ max: 1000 })
    slider.trigger('keydown.pageup')
    expect(input).toHaveBeenCalledWith(180)
    slider.trigger('keydown.pagedown')
    wrapper.setProps({ max: 100 })

    slider.trigger('keydown.left', {
      shiftKey: true,
    })
    expect(input).toHaveBeenCalledWith(74)

    slider.trigger('keydown.right', {
      ctrlKey: true,
    })
    expect(input).toHaveBeenCalledWith(78)
    expect(input).toHaveBeenCalledTimes(12)

    wrapper.setProps({ disabled: true })

    slider.trigger('keydown.left')

    expect(input).toHaveBeenCalledTimes(12)

    wrapper.setProps({ disabled: false })
    wrapper.vm.$vuetify.rtl = true

    slider.trigger('keydown.right', {
      shiftKey: true,
    })
    expect(input).toHaveBeenCalledWith(72)
    wrapper.vm.$vuetify.rtl = undefined
  })

  it('should add for to label', () => {
    const wrapper = mountFunction({
      attachToDocument: true,
      attrs: { id: 'foo' },
      propsData: {
        label: 'bar',
      },
    })

    const label = wrapper.find('.v-label')

    expect(label.element.getAttribute('for')).toBe('foo')

    const wrapper2 = mountFunction({
      attachToDocument: true,
      propsData: {
        label: 'bar',
      },
    })

    const label2 = wrapper2.find('.v-label')

    expect(label2.element.getAttribute('for')).toBe(`input-${(wrapper2.vm as any)._uid}`)
  })

  it('should deactivate', async () => {
    const wrapper = mountFunction({
      attachToDocument: true,
    })

    const container = wrapper.find('.v-slider__thumb-container')

    expect(wrapper.vm.isActive).toBe(false)

    container.trigger('mousedown')

    expect(wrapper.vm.isActive).toBe(true)
  })

  it('should react to touch', async () => {
    const wrapper = mountFunction({
      attachToDocument: true,
    })

    const container = wrapper.find('.v-slider__thumb-container')

    expect(wrapper.vm.thumbPressed).toBe(false)
    expect(wrapper.vm.isActive).toBe(false)

    container.trigger('mousedown')

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.thumbPressed).toBe(true)
    expect(wrapper.vm.isActive).toBe(true)

    el.dispatchEvent(new Event('mouseup'))

    expect(wrapper.vm.thumbPressed).toBe(false)
    expect(wrapper.vm.isActive).toBe(false)

    container.trigger('touchstart', {
      touches: [{}],
    })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.thumbPressed).toBe(true)
    expect(wrapper.vm.isActive).toBe(true)

    el.dispatchEvent(new Event('touchend'))

    expect(wrapper.vm.thumbPressed).toBe(false)
    expect(wrapper.vm.isActive).toBe(false)
  })

  it('should return a rounded value', () => {
    const wrapper = mountFunction({
      propsData: { step: 0 },
    })

    expect(wrapper.vm.roundValue(1.234)).toBe(1.234)

    wrapper.setProps({ step: 1 })

    expect(wrapper.vm.roundValue(1.234)).toBe(1)

    wrapper.setProps({ step: 4 })

    expect(wrapper.vm.roundValue(5.667)).toBe(4)

    expect(wrapper.vm.roundValue(7.667)).toBe(8)

    wrapper.setProps({ step: 2.5 })

    expect(wrapper.vm.roundValue(5.667)).toBe(5)
  })

  it('should return a rounded value with offset', async () => {
    const wrapper = mountFunction({
      propsData: { step: 0 },
    })

    expect(wrapper.vm.roundValue(1.234)).toBe(1.234)

    wrapper.setProps({ step: 1 })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.roundValue(1.234)).toBe(1)

    wrapper.setProps({ step: 4, min: 2 })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.roundValue(5.667)).toBe(6)
    expect(wrapper.vm.roundValue(7.667)).toBe(6)

    wrapper.setProps({ step: 2.5, min: 5 })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.roundValue(5.667)).toBe(5)
  })

  it('should return a rounded value bounded by min and max', async () => {
    const wrapper = mountFunction({
      propsData: {
        min: 5,
        max: 10,
      },
    })

    wrapper.setProps({ value: 1 })
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.internalValue).toBe(5)

    wrapper.setProps({ value: 15 })
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.internalValue).toBe(10)
  })

  it('should not update if value matches lazy value', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: 10,
      },
    })

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

    expect(wrapper.vm.lazyValue).toBe(10)

    wrapper.vm.internalValue = 15

    expect(input).toHaveBeenCalledTimes(1)
    expect(wrapper.vm.lazyValue).toBe(15)

    wrapper.vm.internalValue = 15

    expect(input).toHaveBeenCalledTimes(1)
  })

  it('should react to input events', async () => {
    const wrapper = mountFunction()
    const focus = jest.fn()
    const blur = jest.fn()

    wrapper.vm.$on('focus', focus)
    wrapper.vm.$on('blur', blur)

    const input = wrapper.find('.v-slider__thumb-container')

    expect(wrapper.vm.isActive).toBe(false)
    expect(wrapper.vm.isFocused).toBe(false)

    input.trigger('focus')

    expect(wrapper.vm.isFocused).toBe(true)
    expect(focus).toHaveBeenCalledTimes(1)

    input.trigger('blur')

    expect(wrapper.vm.isFocused).toBe(false)
    expect(blur).toHaveBeenCalledTimes(1)
  })

  it('should call mousemove and emit change', () => {
    const wrapper = mountFunction()

    const change = jest.fn()
    wrapper.vm.$on('change', change)
    const input = wrapper.find('.v-slider')

    input.trigger('click')

    expect(change).toHaveBeenCalledTimes(1)
  })

  it('should keep thumb-label when focused and clicked', async () => {
    const onBlur = jest.fn()
    const wrapper = mountFunction({
      propsData: {
        thumbLabel: true,
      },
    })

    const input = wrapper.find('.v-slider__thumb-container')
    const thumb = wrapper.find('.v-slider__thumb-container')

    input.trigger('focus')

    expect(wrapper.vm.showThumbLabel).toBe(true)
    expect(wrapper.vm.isActive).toBe(false)
    expect(wrapper.vm.isFocused).toBe(true)

    // Clicking thumb label triggers blur
    thumb.trigger('mousedown')
    input.trigger('blur')

    expect(wrapper.vm.isActive).toBe(true)
    expect(wrapper.vm.isFocused).toBe(false)
  })

  it('should reverse label location when inverse', async () => {
    const wrapper = mountFunction({
      propsData: { label: 'foo' },
    })

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

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

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

  it('should change track styles in rtl', async () => {
    const wrapper = mountFunction()

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

    wrapper.setProps({ value: 50 })
    await wrapper.vm.$nextTick()

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

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

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

    wrapper.vm.$vuetify.rtl = true
    wrapper.setProps({ value: 0, disabled: false })
    await wrapper.vm.$nextTick()

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

    wrapper.setProps({ value: 50 })
    await wrapper.vm.$nextTick()

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

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

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

  it('should display label and have different aria-label', () => {
    const wrapper = mountFunction({
      propsData: { label: 'foo' },
      attrs: { 'aria-label': 'bar' },
    })

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

  it('should display tick labels', () => {
    const wrapper = mountFunction({
      propsData: {
        max: 1,
        tickLabels: ['foo', 'bar'],
      },
    })

    const ticks = wrapper.findAll('.v-slider__tick')

    expect(ticks).toHaveLength(2)
    expect((ticks.at(0).element.firstChild as HTMLElement).innerHTML).toBe('foo')
    expect((ticks.at(1).element.firstChild as HTMLElement).innerHTML).toBe('bar')
  })

  it('should not react to keydown if disabled', () => {
    const parseKeyDown = jest.fn()
    const wrapper = mountFunction({
      propsData: { disabled: true },
      methods: { parseKeyDown },
    })

    const input = wrapper.find('.v-slider__thumb-container')

    input.trigger('keydown.right')

    expect(parseKeyDown).not.toHaveBeenCalled()

    wrapper.setProps({
      disabled: false,
      readonly: true,
    })

    input.trigger('keydown.right')

    expect(parseKeyDown).not.toHaveBeenCalled()

    wrapper.setProps({
      disabled: false,
      readonly: false,
    })

    input.trigger('keydown.right')

    expect(parseKeyDown).toHaveBeenCalled()
  })

  it('should set value to min value if given a NaN value', () => {
    const input = jest.fn()
    const wrapper = mountFunction({
      propsData: {
        min: -20,
        max: 20,
        value: NaN,
      },
      listeners: {
        input,
      },
    })

    expect(wrapper.vm.internalValue).toBe(-20)
  })

  it('should correctly handle initial value of zero (#7320)', () => {
    const input = jest.fn()
    const wrapper = mountFunction({
      propsData: {
        min: -20,
        max: 20,
        value: 0,
      },
      listeners: {
        input,
      },
    })

    expect(input).not.toHaveBeenCalledWith(-20)
    expect(wrapper.html()).toMatchSnapshot()
  })

  // TODO: this fails without sync, nextTick doesn't help
  // https://github.com/vuejs/vue-test-utils/issues/1130
  it.skip('should correctly handle setting value to zero (#7320)', async () => {
    const input = jest.fn()
    const wrapper = mountFunction({
      propsData: {
        min: -20,
        max: 20,
        value: 10,
      },
      listeners: {
        input,
      },
    })

    wrapper.setProps({
      value: 0,
    })
    await wrapper.vm.$nextTick()

    expect(input).not.toHaveBeenCalledWith(-20)
    expect(wrapper.html()).toMatchSnapshot()
  })

  // https://github.com/vuetifyjs/vuetify/issues/10018
  it('should not fire event if value is provided and valid', async () => {
    const input = jest.fn()
    mountFunction({
      propsData: { value: 10, min: -20 },
      listeners: { input },
    })

    expect(input).not.toHaveBeenCalled()

    // Should set to min value if invalid
    mountFunction({
      propsData: { value: NaN, min: -20 },
      listeners: { input },
    })

    expect(input).toHaveBeenCalledWith(-20)
  })

  // https://github.com/vuetifyjs/vuetify/issues/11569
  it('should not fire change event onKeyDown if value is invalid', () => {
    const change = jest.fn()
    const wrapper = mountFunction({
      propsData: { min: 1 },
      listeners: { change },
    })

    const slider = wrapper.find('.v-slider__thumb-container')

    slider.trigger('keydown.left')

    expect(change).not.toHaveBeenCalled()
  })
})


<template>
  <v-navigation-drawer
    id="settings-drawer"
    v-model="app.settings"
    :position="isRtl ? 'left' : 'right'"
    disable-route-watcher
    fixed
    hide-overlay
    temporary
    width="300"
  >
    <v-toolbar flat>
      <v-toolbar-title text="Settings" class="pl-0" />

      <template #append>
        <v-btn icon="mdi-close" @click="app.settings = false" />
      </template>
    </v-toolbar>

    <v-divider />

    <v-container>

      <app-settings-pwa v-if="!!pwa.sw.install" />

      <app-settings-theme />

      <v-divider class="mt-4 mb-3 mx-n3" />

      <app-settings-rtl />

      <v-divider class="mt-4 mb-3 mx-n3" />

      <app-settings-api />
    </v-container>
  </v-navigation-drawer>
</template>

<script>
  // Components
  import AppSettingsApi from './Api.vue'
  import AppSettingsPwa from './Pwa.vue'
  import AppSettingsRtl from './Rtl.vue'
  import AppSettingsTheme from './Theme.vue'

  // Composables
  import { useAppStore } from '@/store/app'
  import { usePwaStore } from '@/store/pwa'
  import { useRtl } from 'vuetify'

  export default {
    name: 'AppSettingsDrawer',

    components: {
      AppSettingsApi,
      AppSettingsPwa,
      AppSettingsRtl,
      AppSettingsTheme,
    },

    setup () {
      const { isRtl } = useRtl()
      const app = useAppStore()
      const pwa = usePwaStore()

      return {
        app,
        isRtl,
        pwa,
      }
    },
  }
</script>


keep keep keep keep replace keep keep keep

style={{
...layoutItemStyles.value,
height: undefined,
}}

// Styles
import './VAppBar.sass'

// Components
import { filterToolbarProps, makeVToolbarProps, VToolbar } from '@/components/VToolbar/VToolbar'

// Composables
import { makeLayoutItemProps, useLayoutItem } from '@/composables/layout'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utilities
import { computed, ref, toRef } from 'vue'
import { defineComponent } from '@/util'

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

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

  props: {
    // TODO: Implement scrolling techniques
    // hideOnScroll: Boolean
    // invertedScroll: Boolean
    // collapseOnScroll: Boolean
    // elevateOnScroll: Boolean
    // shrinkOnScroll: Boolean
    // fadeImageOnScroll: Boolean
    modelValue: {
      type: Boolean,
      default: true,
    },
    position: {
      type: String as PropType<'top' | 'bottom'>,
      default: 'top',
      validator: (value: any) => ['top', 'bottom'].includes(value),
    },

    ...makeVToolbarProps(),
    ...makeLayoutItemProps(),

    height: {
      type: [Number, String],
      default: 64,
    },
  },

  emits: {
    'update:modelValue': (value: boolean) => true,
  },

  setup (props, { slots }) {
    const vToolbarRef = ref()
    const isActive = useProxiedModel(props, 'modelValue')
    const height = computed(() => vToolbarRef.value?.contentHeight)
    const { layoutItemStyles } = useLayoutItem({
      id: props.name,
      priority: computed(() => parseInt(props.priority, 10)),
      position: toRef(props, 'position'),
      layoutSize: height,
      elementSize: height,
      active: isActive,
      absolute: toRef(props, 'absolute'),
    })

    return () => {
      const [toolbarProps] = filterToolbarProps(props)

      return (
        <VToolbar
          ref={ vToolbarRef }
          class={[
            'v-app-bar',
            {
              'v-app-bar--bottom': props.position === 'bottom',
            },
          ]}
          style={ layoutItemStyles.value }
          { ...toolbarProps }
          v-slots={ slots }
        />
      )
    }
  },
})

export type VAppBar = InstanceType<typeof VAppBar>


module.exports = {
  'v-alert': {
    slots: [
      {
        name: 'default',
        props: undefined,
      },
      {
        name: 'append',
        props: undefined,
      },
      {
        name: 'close',
        props: {
          toggle: 'Function',
        },
      },
      {
        name: 'prepend',
        props: undefined,
      },
    ],
    events: [
      {
        name: 'input',
        value: 'boolean',
      },
    ],
    functions: [
      {
        name: 'toggle',
        signature: '(): void',
      },
    ],
  },
}


@forward './variables'
@use '../../styles/tools'
@use './variables' as *

.v-bottom-navigation
  display: flex
  max-width: 100%
  overflow: hidden
  position: absolute

  @include tools.border($bottom-navigation-border...)
  @include tools.rounded($bottom-navigation-border-radius)
  @include tools.theme($bottom-navigation-theme...)

  &--active
    @include tools.elevation($bottom-navigation-elevation)

.v-bottom-navigation__content
  display: flex
  flex: none
  font-size: $bottom-navigation-content-font-size
  justify-content: center
  transition: $bottom-navigation-transition
  width: 100%

  .v-bottom-navigation &
    > .v-btn
      font-size: inherit
      height: $bottom-navigation-height
      max-width: $bottom-navigation-max-width
      min-width: $bottom-navigation-min-width
      text-transform: $bottom-navigation-text-transform
      transition: inherit
      width: auto

      @include tools.rounded(0)

      .v-btn__content,
      .v-btn__icon
        transition: inherit

      .v-btn__icon
        font-size: $bottom-navigation-icon-font-size

      &:not(.v-btn--selected):not(.v-btn--disabled)
        color: $bottom-navigation-button-color

  .v-bottom-navigation--grow &
    > .v-btn
      width: 100%

  .v-bottom-navigation--shift &
    > .v-btn
      .v-btn__icon
        top: $bottom-navigation-shift-icon-top
        transform: $bottom-navigation-shift-icon-transform

      &:not(.v-btn--selected)
        .v-btn__content
          color: $bottom-navigation-shift-content-color

        .v-btn__icon
          transform: translateY(0)


The function to invoke on window resize.

Will **not** automatically invoke the provided callback on bind.


const { borderClasses } = useBorder(props)

const { elevationClasses } = useElevation(props)

const { roundedClasses } = useRounded(props)

const { themeClasses } = provideTheme(props)

const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(toRef(props, 'color'))

(props.density === 'prominent' ? Number(props.height) : 0) -

(props.density === 'comfortable' ? 8 : 0) -

const contentHeight = computed(() => parseInt((


<template>
  <v-card>
    <v-tabs
      v-model="tab"
      background-color="primary"
    >
      <v-tab value="one">Item One</v-tab>
      <v-tab value="two">Item Two</v-tab>
      <v-tab value="three">Item Three</v-tab>
    </v-tabs>

    <v-card-text>
      <v-window v-model="tab">
        <v-window-item value="one">
          One
        </v-window-item>

        <v-window-item value="two">
          Two
        </v-window-item>

        <v-window-item value="three">
          Three
        </v-window-item>
      </v-window>
    </v-card-text>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      tab: null,
    }),
  }
</script>


<template>

      <v-btn class="ma-2" @click="all">
        All
      </v-btn>
      <v-btn class="ma-2" @click="none">
        None
      </v-btn>

v-model {{ panel }}

    <v-expansion-panels
      v-model="panel"
      multiple
    >
      <v-expansion-panel
        title="Foo"
        text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
        value="foo"
      ></v-expansion-panel>

      <v-expansion-panel
        title="Bar"
        text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
        value="bar"
      ></v-expansion-panel>

      <v-expansion-panel
        title="Baz"
        text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
        value="baz"
      ></v-expansion-panel>
    </v-expansion-panels>

</template>


<template>
  <div class="text-center">
    <v-progress-circular
      indeterminate
      color="primary"
    ></v-progress-circular>

    <v-progress-circular
      indeterminate
      color="red"
    ></v-progress-circular>

    <v-progress-circular
      indeterminate
      color="purple"
    ></v-progress-circular>

    <v-progress-circular
      indeterminate
      color="green"
    ></v-progress-circular>

    <v-progress-circular
      indeterminate
      color="amber"
    ></v-progress-circular>
  </div>
</template>

<style scoped>
.v-progress-circular {
  margin: 1rem;
}
</style>


keep keep keep keep replace keep keep keep keep keep

const extensionHeight = computed(() => parseInt((

(props.density === 'prominent' ? Number(props.extensionHeight) : 0) -

(props.density === 'comfortable' ? 4 : 0) -


.v-application
  display: flex
  background: rgb(var(--v-theme-background))
  color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity))

  a
    cursor: pointer

.v-application__wrap
    flex: 1 1 auto
    backface-visibility: hidden
    display: flex
    flex-direction: column
    max-width: 100%
    position: relative

// Firefox overrides
@-moz-document url-prefix()
  @media print
    .v-application
      display: block

      &__wrap
        display: block


<template>
  <v-container>
    <v-row justify="center">
      <v-col
        v-for="({ icon, text, ...attrs }, i) in buttons"
        :key="`home-btn-${i}`"
        cols="auto"
      >
        <v-btn
          :min-width="btnWidth"
          v-bind="attrs"
          size="x-large"
        >
          <v-icon :icon="icon" left />
          {{ t(text) }}
        </v-btn>
      </v-col>
    </v-row>
  </v-container>
</template>

<script lang="ts">
  // Utilities
  import { defineComponent } from 'vue'
  import { useI18n } from 'vue-i18n'

  export default defineComponent({
    name: 'ActionBtns',

    setup () {
      const { locale, t } = useI18n()

      // data
      const buttons: Array<Object> = [
        {
          color: 'primary',
          flat: true,
          icon: 'mdi-speedometer',
          text: 'home.get-started',
          to: {
            name: `${locale.value}-getting-started-installation`,
          },
        },
        {
          color: 'primary',
          icon: 'mdi-vuetify',
          variant: 'outlined',
          text: 'home.why-vuetify',
          to: {
            name: `${locale.value}-introduction-why-vuetify`,
          },
        },
        {
          color: '#212121',
          theme: 'dark',
          flat: true,
          href: 'https://github.com/vuetifyjs/vuetify',
          icon: 'mdi-github',
          rel: 'noopener',
          target: '_blank',
          text: 'github',
        },
      ]
      const btnWidth = 228

      return {
        buttons,
        btnWidth,
        t,
      }
    },

  })
</script>


style={{ height: convertToUnit(props.extensionHeight) }}

keep keep keep keep keep replace keep keep keep keep keep

style={{ height: convertToUnit(extensionHeight.value) }}


---
meta:
  title: Theme
  description: Setup your application's theme and supplemental colors in a flash.
  keywords: theme, themes, theming, color, colors
related:
  - /styles/colors/
  - /styles/transitions/
  - /getting-started/wireframes/
---

# Theme configuration

Customize your application's default text colors, surfaces, and more. Easily modify your theme programmatically in real time. Vuetify comes with standard support for light and dark variants.

<promoted slug="vuemastery-themes" />

## API

<api-inline />

## Setup

Vuetify comes with two themes pre-installed, `light` and `dark`. To set the default theme of your application, use the **defaultTheme** option.

```js
// src/plugins/vuetify.js
import { createApp } from 'vue'
import { createVuetify } from 'vuetify'

export default createVuetify({
  theme: {
    defaultTheme: 'dark'
  }
})
```

Adding new themes is as easy as defining a new property in the **theme** object. A theme is a collection of colors and options that change the overall look and feel of your application. One of these options designates the theme as being either a **light** or **dark** variation. This makes it possible for Vuetify to implement Material Design concepts such as elevated surfaces having a lighter overlay color the higher up they are. Find out more about dark themes on the official [Material Design](https://material.io/design/color/dark-theme.html) page.

Use the `ThemeDefinition` type to get type hints for the structure of the theme object.

```ts
// src/plugins/vuetify.ts
import { createApp } from 'vue'
import { createVuetify, ThemeDefinition } from 'vuetify'

const myCustomLightTheme: ThemeDefinition = {
  dark: false,
  colors: {
    background: '#FFFFFF',
    surface: '#FFFFFF',
    primary: '#6200EE',
    'primary-darken-1': '#3700B3',
    secondary: '#03DAC6',
    'secondary-darken-1': '#018786',
    error: '#B00020',
    info: '#2196F3',
    success: '#4CAF50',
    warning: '#FB8C00',
  }
}

export default createVuetify({
  theme: {
    defaultTheme: 'myCustomLightTheme',
    themes: {
      myCustomLightTheme,
    }
  }
})
```

## Changing theme

To dynamically change theme during run-time, add the **theme** prop to the `<v-app>` component.

```html
<template>
  <v-app :theme="theme">
    <v-btn @click="toggleTheme">toggle theme</v-btn>
    ...
  </v-app>
</template>

<script>
import { ref } from 'vue'

export default {
  setup () {
    const theme = ref('light')

    return {
      theme,
      toggleTheme: () => theme.value = theme.value === 'light' ? 'dark' : 'light'
    }
  }
}
</script>
```

Most components support the **theme** prop. When used, a new context is created for _that_ specific component and **all** of its children. In the following example, the [v-btn](/components/buttons/) uses the **dark** theme applied by its parent [v-card](/components/cards/).

```html
<template>
  <v-app>
    <v-card theme="dark">
      <!-- button uses dark theme -->
      <v-btn>foo</v-btn>
    </v-card>
  </v-app>
</template>
```

You can use the `<v-theme-provider>` component to dynamically apply different themes to larger sections of your application, without having to set the **theme** prop on each individual component. In the following example, we apply a custom theme named `high-contrast`.

```html
<template>
  <v-app>
    <!-- uses the current default theme -->
    <v-card>...</v-card>

    <v-theme-provider theme="high-contrast">
      <!-- uses the high-contrast theme -->
      <v-card>...</v-card>
      <v-btn>...</v-btn>
    </v-theme-provider>
  </v-app>
</template>
```

## Custom theme colors

The Vuetify theme system supports adding _custom_ colors. When configuring the Vuetify theme settings, add any number of custom colors to the **colors** object and Vuetify will generate a number of CSS classes and variables for you to use in your application.

```js
// src/plugins/vuetify.js
import { createApp } from 'vue'
import { createVuetify } from 'vuetify'

export default createVuetify({
  theme: {
    defaultTheme: 'myCustomTheme',
    themes: {
      myCustomTheme: {
        dark: false,
        colors: {
          ..., // We have omitted the standard color properties here to emphasize the custom one that we've added
          green: '#00ff00'
        }
      }
    }
  }
})
```

Custom properties for colors are a list of `red, green, blue`, so the `rgb()` or `rgba()` function has to be used:

```html
<template>
  <div class="bg-green on-green">background color with appropriate text color contrast</div>

  <div class="text-green">text color</div>

  <div class="border-green">border color</div>
</template>

<style>
  .custom-class {
    background: rgb(var(--v-theme-green))
    color: rgba(var(--v-theme-on-green), 0.9)
  }
</style>
```

## Color variations

The Vuetify theme system can help you generate any number of **variations** for the colors in your theme. The following example shows how to generate 1 lighten and 2 darken variants for the `primary` and `secondary` colors.

```js
// src/plugins/vuetify.js
import { createApp } from 'vue'
import { createVuetify } from 'vuetify'

export default createVuetify({
  theme: {
    defaultTheme: 'myCustomTheme',
    variations: {
      colors: ['primary', 'secondary'],
      lighten: 1,
      darken: 2,
    },
    themes: {
      ...
    }
  }
})
```

```html
<template>
  <div class="text-primary-lighten-1">text color</div>

  <div class="text-primary-darken-1">text color</div>

  <div class="text-primary-darken-2">text color</div>
</template>
```

## Disable theme

The theme functionality can be disabled by setting the **theme** configuration property to `false`. This prevents the creation of the Vuetify stylesheet, and theme classes will not be applied to components.

```js
// src/plugins/vuetify.js
import { createApp } from 'vue'
import { createVuetify } from 'vuetify'

export default createVuetify({
  theme: false,
})
```

## Implementation

Vuetify generates theme styles at run-time according to the given configuration. The generated styles are injected into the `<head>` section of the DOM in a `<style>` tag with an **id** of `vuetify-theme-stylesheet`.

<backmatter />


// Composables
import { makeTagProps } from '@/composables/tag'

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

// Types
import type { MakeSlots } from '@/util'

export const VToolbarTitle = genericComponent<new () => {
  $slots: MakeSlots<{
    default: []
    text: []
  }>
}>()({
  name: 'VToolbarTitle',

  props: {
    text: String,

    ...makeTagProps(),
  },

  setup (props, { slots }) {
    useRender(() => {
      const hasText = !!(slots.default || slots.text || props.text)

      return (
        <props.tag class="v-toolbar-title">
          { hasText && (
            <div class="v-toolbar-title__placeholder">
              { slots.text ? slots.text() : props.text }

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

    return {}
  },
})

export type VToolbarTitle = InstanceType<typeof VToolbarTitle>


import type { DirectiveBinding } from 'vue'

interface ResizeDirectiveBinding extends Omit<DirectiveBinding, 'modifiers'> {
  value: () => void
  modifiers?: {
    active?: boolean
    quiet?: boolean
  }
}

function mounted (el: HTMLElement, binding: ResizeDirectiveBinding) {
  const handler = binding.value
  const options: AddEventListenerOptions = {
    passive: !binding.modifiers?.active,
  }

  window.addEventListener('resize', handler, options)

  el._onResize = Object(el._onResize)
  el._onResize![binding.instance!.$.uid] = {
    handler,
    options,
  }

  if (!binding.modifiers?.quiet) {
    handler()
  }
}

function unmounted (el: HTMLElement, binding: ResizeDirectiveBinding) {
  if (!el._onResize?.[binding.instance!.$.uid]) return

  const { handler, options } = el._onResize[binding.instance!.$.uid]!

  window.removeEventListener('resize', handler, options)

  delete el._onResize[binding.instance!.$.uid]
}

export const Resize = {
  mounted,
  unmounted,
}

export default Resize


return useForwardRef({
contentHeight,
extensionHeight,
})


export const standardEasing = 'cubic-bezier(0.4, 0, 0.2, 1)'
export const deceleratedEasing = 'cubic-bezier(0.0, 0, 0.2, 1)' // Entering
export const acceleratedEasing = 'cubic-bezier(0.4, 0, 1, 1)' // Leaving


import './VLocaleProvider.sass'

// Composables
import { provideLocale } from '@/composables/locale'
import { provideRtl } from '@/composables/rtl'

// Utilities
import { defineComponent } from '@/util'

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

  props: {
    locale: String,
    fallbackLocale: String,
    messages: Object,
    rtl: {
      type: Boolean,
      default: undefined,
    },
  },

  setup (props, { slots }) {
    const localeInstance = provideLocale(props)
    const { rtlClasses } = provideRtl(props, localeInstance)

    return () => (
      <div
        class={[
          'v-locale-provider',
          rtlClasses.value,
        ]}
      >
        { slots.default?.() }
      </div>
    )
  },
})


<template>
  <v-container class="fill-height">
    <v-row
      align="center"
      justify="center"
    >
      <v-combobox
        hint="Comboboxes can receive custom values not present in items"
        :items="items"
        label="Add some tags"
        v-bind="$attrs"
      >
        <template
          v-if="$attrs.noData"
          v-slot:no-data
        >
          <v-list-item>
            <v-list-item-content>
              <v-list-item-title>
                No results matching "{{ search }}". Press enter to create a new one
              </v-list-item-title>
            </v-list-item-content>
          </v-list-item>
        </template>
      </v-combobox>
    </v-row>
  </v-container>
</template>


},
default: '',
description: 'Priority',
},
{
displayName: 'Update History',
name: 'updateHistory',
type: 'boolean',

{
displayName: 'Reporter',
name: 'reporter',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
default: '',
description: 'Reporter',
},

import {
	IDataObject,
} from 'n8n-workflow';

export interface IFields {
	assignee?: IDataObject;
	description?: string;
	issuetype?: IDataObject;
	labels?: string[];
	parent?: IDataObject;
	priority?: IDataObject;
	project?: IDataObject;
	summary?: string;
}

export interface IIssue {
	fields?: IFields;
	transition?: IDataObject;
}

export interface INotify {
	subject?: string;
	textBody?: string;
	htmlBody?: string;
	to?: INotificationRecipients;
	restrict?: NotificationRecipientsRestrictions;
}

export interface INotificationRecipients {
	reporter?: boolean;
	assignee?: boolean;
	watchers?: boolean;
	voters?: boolean;
	users?: IDataObject[];
	groups?: IDataObject[];
}

export interface NotificationRecipientsRestrictions {
	groups?: IDataObject[];
}


export interface IUser {
	client_gravatar?: boolean;
	include_custom_profile_fields?: boolean;
	full_name?: string;
	is_admin?: boolean;
	is_guest?: boolean;
	profile_data?: [{}];
	email?: string;
	password?: string;
	short_name?: string;
}


import {
	INode,
	INodeCredentials,
	INodeParameters,
	INodePropertyOptions,
	INodeTypes,
	IWorkflowExecuteAdditionalData,
	Workflow,
} from 'n8n-workflow';

import {
	NodeExecuteFunctions,
} from './';

const TEMP_NODE_NAME = 'Temp-Node';
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';

export class LoadNodeParameterOptions {
	workflow: Workflow;

	constructor(nodeTypeName: string, nodeTypes: INodeTypes, currentNodeParameters: INodeParameters, credentials?: INodeCredentials) {
		const nodeType = nodeTypes.getByName(nodeTypeName);

		if (nodeType === undefined) {
			throw new Error(`The node-type "${nodeTypeName}"  is not known!`);
		}

		const nodeData: INode = {
			parameters: currentNodeParameters,
			name: TEMP_NODE_NAME,
			type: nodeTypeName,
			typeVersion: 1,
			position: [
				0,
				0,
			],
		};

		if (credentials) {
			nodeData.credentials = credentials;
		}

		const workflowData = {
			nodes: [
				nodeData,
			],
			connections: {},
		};

		this.workflow = new Workflow({ nodes: workflowData.nodes, connections: workflowData.connections, active: false, nodeTypes });
	}

	/**
	 * Returns data of a fake workflow
	 *
	 * @returns
	 * @memberof LoadNodeParameterOptions
	 */
	getWorkflowData() {
		return {
			name: TEMP_WORKFLOW_NAME,
			active: false,
			connections: {},
			nodes: Object.values(this.workflow.nodes),
			createdAt: new Date(),
			updatedAt: new Date(),
		};
	}

	/**
	 * Returns the available options
	 *
	 * @param {string} methodName The name of the method of which to get the data from
	 * @param {IWorkflowExecuteAdditionalData} additionalData
	 * @returns {Promise<INodePropertyOptions[]>}
	 * @memberof LoadNodeParameterOptions
	 */
	getOptions(methodName: string, additionalData: IWorkflowExecuteAdditionalData): Promise<INodePropertyOptions[]> {
		const node = this.workflow.getNode(TEMP_NODE_NAME);

		const nodeType = this.workflow.nodeTypes.getByName(node!.type);

		if (nodeType!.methods === undefined || nodeType!.methods.loadOptions === undefined || nodeType!.methods.loadOptions[methodName] === undefined) {
			throw new Error(`The node-type "${node!.type}" does not have the method "${methodName}" defined!`);
		}

		const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions(this.workflow, node!, additionalData);

		return nodeType!.methods.loadOptions[methodName].call(thisArgs);
	}

}