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

// Styles
import './VBtn.sass'

// Components
import { VIcon } from '@/components'

// Composables
import { makeDensityProps, useDensity } from '@/composables/density'
import { makeBorderProps, useBorder } from '@/composables/border'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeDimensionProps, useDimension } from '@/composables/dimensions'
import { makeElevationProps, useElevation } from '@/composables/elevation'
import { makePositionProps, usePosition } from '@/composables/position'
import { makeTagProps } from '@/composables/tag'
import { useTheme } from '@/composables/theme'
import { useColor } from '@/composables/color'

// Directives
import { Ripple, RippleDirectiveBinding } from '@/directives/ripple'

// Utilities
import { computed, defineComponent, withDirectives } from 'vue'
import { makeProps, useDirective } from '@/util'

import { makeSizeProps, useSize } from '@/composables/size'

export default defineComponent({
  name: 'VBtn',

  props: makeProps({
    text: Boolean,
    flat: Boolean,
    plain: Boolean,
    icon: [Boolean, String],
    prependIcon: String,
    appendIcon: String,

    block: Boolean,
    stacked: Boolean,

    color: String,
    disabled: Boolean,
    ...makeBorderProps(),
    ...makeRoundedProps(),
    ...makeDensityProps(),
    ...makeDimensionProps(),
    ...makeElevationProps(),
    ...makePositionProps(),
    ...makeSizeProps(),
    ...makeTagProps({ tag: 'button' }),
  }),

  setup (props, { slots }) {
    const { themeClasses } = useTheme()
    const { borderClasses } = useBorder(props, 'v-btn')
    const { roundedClasses } = useRounded(props, 'v-btn')
    const { densityClasses } = useDensity(props, 'v-btn')
    const { dimensionStyles } = useDimension(props)
    const { elevationClasses } = useElevation(props)
    const { positionClasses, positionStyles } = usePosition(props, 'v-btn')
    const { sizeClasses } = useSize(props, 'v-btn')

    const isContained = computed(() => {
      return !(props.text || props.plain || props.outlined || props.border !== false)
    })

    const isElevated = computed(() => {
      return isContained.value && !(props.disabled || props.flat)
    })

    const { colorClasses, colorStyles } = useColor(computed(() => ({
      [isContained.value ? 'background' : 'text']: props.color,
    })))

    return () => withDirectives(
      <props.tag
        type="button"
        class={[
          'v-btn',
          {
            'v-btn--contained': isContained.value,
            'v-btn--elevated': isElevated.value,
            'v-btn--icon': !!props.icon,
            'v-btn--plain': props.plain,
            'v-btn--block': props.block,
            'v-btn--disabled': props.disabled,
            'v-btn--stacked': props.stacked,
          },
          themeClasses.value,
          borderClasses.value,
          colorClasses.value,
          densityClasses.value,
          elevationClasses.value,
          positionClasses.value,
          roundedClasses.value,
          sizeClasses.value,
        ]}
        style={[
          colorStyles.value,
          dimensionStyles.value,
          positionStyles.value,
        ]}
        disabled={ props.disabled }
      >
        <span class="v-btn__overlay" />

        { !props.icon && props.prependIcon && (
          <VIcon
            class="v-btn__icon"
            icon={ props.prependIcon }
            left={ !props.stacked }
          />
        )}

        { typeof props.icon === 'boolean'
          ? slots.default?.()
          : (
            <VIcon
              class="v-btn__icon"
              icon={ props.icon }
              size={ props.size }
            />
          )
        }

        { !props.icon && props.appendIcon && (
          <VIcon
            class="v-btn__icon"
            icon={ props.appendIcon }
            right={ !props.stacked }
          />
        )}
      </props.tag>,
      [useDirective<RippleDirectiveBinding>(Ripple, {
        value: !props.disabled,
        modifiers: { center: !!props.icon },
      })]
    )
  },
})


additional: () => [__image.value, __preloadImage.value, __placeholder.value, __error.value],

---
meta:
  title: Long-term support
  description: Vuetify provides long-term support to the last major release for 12 months for critical bugs and security vulnerabilities.
  keywords: lts, long-term support
related:
  - /introduction/enterprise/
  - /getting-started/contributing/
  - /about/meet-the-team/
---

# Long-term support

As a component framework, we understand that many projects that utilize Vuetify have development cycles that prevent upgrading to the latest version. In order to provide developers and business peace of mind, we offer **Long-term Support** for the latest _minor_ of the last _major_ release. This means with each _major_ release you can have peace of mind in knowing that for **12 months** after a new major release, your existing version will be updated for critical bugs and security vulnerabilities.

<promoted-ad slug="business-support" />

## Vuetify 2.0

The latest version of Vuetify, based on the Material Design 2 specification. It includes multiple advancements over v1.5 and is the recommended version for the most up-to-date features and bug fixes. This version is actively maintained and has a [roadmap](/introduction/roadmap/) of upcoming features.

- 7 new components
- Complete upgrade to the MD2 Specification
- Full Typescript Support
- Improved accessibility and RTL support
- Numerous bug fixes, QOL changes and performance improvements

> Upgrading from v1.5? Check out the available [upgrade guide](https://github.com/vuetifyjs/vuetify/releases/tag/v2.0.0#user-content-upgrade-guide).

## Vuetify 1.5 LTS

<alert type="error">

  v1.5 reached end of life on **July 31st, 2020** and is no longer actively maintained. It is recommended to update to the latest stable version of Vuetify using our [Upgrade guide](/getting-started/upgrade-guide/).

</alert>

The latest LTS version of Vuetify, based on the Material Design 1 specification. LTS stands for long-term support—which means 12 months, until July 2020, of free security and critical bug maintenance updates, guaranteed.

Upgrading to v1.5? Check out the available upgrade guides:
1.5 Versions:

- [Vuetify v1.5](https://github.com/vuetifyjs/vuetify/releases/tag/v1.5.0)
- [Vuetify v1.4](https://github.com/vuetifyjs/vuetify/releases/tag/v1.4.0)
- [Vuetify v1.3](https://github.com/vuetifyjs/vuetify/releases/tag/v1.3.0)
- [Vuetify v1.2](https://github.com/vuetifyjs/vuetify/releases/tag/v1.2.0)
- [Vuetify v1.1](https://github.com/vuetifyjs/vuetify/releases/tag/v1.1.0)

If you have any additional questions, please join our [Discord community](https://community.vuetifyjs.com).

<backmatter />


<template>
  <v-container class="grey lighten-5">
    <v-row
      class="mb-6"
      no-gutters
    >
      <v-col
        v-for="n in 3"
        :key="n"
        :cols="n === 2 ? 6 : undefined"
      >
        <v-card
          class="pa-2"
          outlined
          tile
        >
          {{ n }} of 3 {{ n === 2 ? '(wider)' : '' }}
        </v-card>
      </v-col>
    </v-row>
    <v-row no-gutters>
      <v-col
        v-for="n in 3"
        :key="n"
        :cols="n === 2 ? 5 : undefined"
      >
        <v-card
          class="pa-2"
          outlined
          tile
        >
          {{ n }} of 3 {{ n === 2 ? '(wider)' : '' }}
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>


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

exports[`VRadio.ts should be render colored radio 1`] = `

      $radioOff

    <input aria-checked="false"
           id="input-22"
           role="radio"
           type="radio"
           value
    >

`;

exports[`VRadio.ts should not render aria-label attribute with no label value on input group 1`] = `

      $radioOff

    <input aria-checked="false"
           id="input-4"
           role="radio"
           type="radio"
           value
    >

`;

exports[`VRadio.ts should render proper input name 1`] = `

      $radioOff

    <input aria-checked="false"
           id="input-7"
           role="radio"
           type="radio"
           name="name"
           value
    >

`;

exports[`VRadio.ts should render role and aria-checked attributes on input group 1`] = `

      $radioOn

    <input aria-checked="true"
           id="input-1"
           role="radio"
           type="radio"
           value
    >

`;

exports[`VRadio.ts should use custom icons 1`] = `

      bar

    <input aria-checked="false"
           id="input-13"
           role="radio"
           type="radio"
           value
    >

`;

exports[`VRadio.ts should use custom icons 2`] = `

      foo

    <input aria-checked="true"
           id="input-13"
           role="radio"
           type="radio"
           value
    >

`;


import type { Directive, DirectiveArguments, DirectiveBinding } from 'vue'

interface Binding extends Omit<DirectiveBinding, 'value' | 'modifiers'> {

modifiers: Dictionary<boolean | undefined>

import type { DirectiveArguments, DirectiveBinding } from 'vue'


// Imports
const { md } = require('../../../build/markdown-it')
const Mode = require('frontmatter-markdown-loader/mode')
const path = require('path')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin

// Globals
const IS_PROD = process.env.NODE_ENV === 'production'

module.exports = config => {
  config
    .plugin('api-plugin')
    .use(path.resolve('./build/api-plugin.js'))

  config
    .plugin('pages-plugin')
    .use(path.resolve('./build/pages-plugin.js'))

  config.plugins.delete('html')
  config.plugins.delete('preload')
  config.plugins.delete('prefetch')

  config.module
    .rule('markdown')
    .test(/\.md$/)
    .use('toc-loader')
    .loader(path.resolve('./build/toc-loader.js'))
    .end()
    .use('frontmatter-markdown-loader')
    .loader('frontmatter-markdown-loader')
    .tap(() => ({
      markdown: body => md.render(body),
      mode: [Mode.VUE_COMPONENT, Mode.BODY],
      vue: { root: 'markdown-body' },
    }))

  config.resolve.alias
    .set('vue$', 'vue/dist/vue.runtime.common.js')

  config.optimization
    .removeAvailableModules(IS_PROD)
    .removeEmptyChunks(IS_PROD)

  if (IS_PROD) {
    config.plugin('sitemap')
      .use(path.resolve('./build/sitemap.js'))
  }

  if (process.env.ANALYZE === 'true') {
    config.plugin('webpack-bundle-analyzer')
      .use(BundleAnalyzerPlugin)
  }
}


<template>
  <v-treeview
    open-all
    :items="items"
  ></v-treeview>
</template>

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


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

// import Vue from 'vue'
// import VTimePickerTitle from '../VTimePickerTitle'
// import { SelectingTimes } from '../VTimePicker'
// import { Lang } from '../../../services/lang'
// import { preset } from '../../../presets/default'
import {
  mount,
  Wrapper,
  MountOptions,
} from '@vue/test-utils'

// Vue.prototype.$vuetify = {
//   lang: new Lang(preset),
// }

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

  [true, false].forEach(useSecondsValue => {
    const useSecondsDesc = (useSecondsValue ? '. with useSeconds' : '')
    it('should render component in 24hr' + useSecondsDesc, () => {
      const wrapper = mountFunction({
        propsData: {
          hour: 14,
          minute: 13,
          second: 25,
          period: 'pm',
          ampm: false,
          useSeconds: useSecondsValue,
        },
      })

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

    it('should render disabled component' + useSecondsDesc, () => {
      const wrapper = mountFunction({
        propsData: {
          disabled: true,
          hour: 14,
          minute: 13,
          period: 'pm',
          ampm: true,
          useSeconds: useSecondsValue,
        },
      })

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

    it('should render component in 12hr' + useSecondsDesc, () => {
      const wrapper = mountFunction({
        propsData: {
          hour: 14,
          minute: 13,
          second: 25,
          period: 'pm',
          ampm: true,
          useSeconds: useSecondsValue,
        },
      })

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

    it('should render component when selecting hour' + useSecondsDesc, () => {
      const wrapper = mountFunction({
        propsData: {
          hour: 14,
          minute: 13,
          second: 25,
          period: 'pm',
          selecting: SelectingTimes.Hour,
          useSeconds: useSecondsValue,
        },
      })

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

    it('should emit event when clicked on am/pm' + useSecondsDesc, async () => {
      const wrapper = mountFunction({
        propsData: {
          hour: 14,
          minute: 13,
          second: 25,
          period: 'pm',
          ampm: true,
          useSeconds: useSecondsValue,
        },
      })

      const period = jest.fn()
      wrapper.vm.$on('update:period', period)

      wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn--active').trigger('click')
      expect(period).not.toHaveBeenCalled()
      wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn:not(.v-picker__title__btn--active)').trigger('click')
      expect(period).toHaveBeenCalledWith('am')

      wrapper.setProps({
        hour: 2,
        minute: 13,
        second: 35,
        period: 'am',
      })
      wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn:not(.v-picker__title__btn--active)').trigger('click')
      expect(period).toHaveBeenCalledWith('pm')
    })

    it('should not emit event when clicked on readonly am/pm' + useSecondsDesc, async () => {
      const wrapper = mountFunction({
        propsData: {
          hour: 14,
          minute: 13,
          second: 25,
          period: 'pm',
          ampm: true,
          readonly: true,
          useSeconds: useSecondsValue,
        },
      })

      const period = jest.fn()
      wrapper.vm.$on('update:period', period)

      wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn:not(.v-picker__title__btn--active)').trigger('click')
      expect(period).not.toHaveBeenCalled()
    })

    it('should emit event when clicked on hours/minutes/seconds' + useSecondsDesc, async () => {
      const wrapper = mountFunction({
        propsData: {
          hour: 14,
          minute: 13,
          second: 25,
          period: 'pm',
          useSeconds: useSecondsValue,
        },
      })

      const selecting = jest.fn()
      wrapper.vm.$on('update:selecting', selecting)

      wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(1).trigger('click')
      expect(selecting).toHaveBeenCalledWith(SelectingTimes.Minute)
      wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(0).trigger('click')
      expect(selecting).toHaveBeenCalledWith(SelectingTimes.Hour)
      if (useSecondsValue) {
        wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(2).trigger('click')
        expect(selecting).toHaveBeenCalledWith(SelectingTimes.Second)
      }
      wrapper.setProps({ selecting: SelectingTimes.Hour })
      await wrapper.vm.$nextTick()
      wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(1).trigger('click')
      expect(selecting).toHaveBeenCalledWith(SelectingTimes.Minute)
    })

    it('should emit event when clicked on readonly hours/minutes' + useSecondsDesc, async () => {
      const wrapper = mountFunction({
        propsData: {
          hour: 14,
          minute: 13,
          period: 'pm',
          readonly: true,
          useSeconds: useSecondsValue,
        },
      })

      const selecting = jest.fn()
      wrapper.vm.$on('update:selecting', selecting)

      wrapper.find('.v-time-picker-title__time .v-picker__title__btn').trigger('click')
      expect(selecting).toHaveBeenCalledWith(SelectingTimes.Hour)
    })
  })
})


type Tail<T extends any[]> = T extends [infer A, ...infer R] ? R : never

import type { Directive, DirectiveArguments, DirectiveBinding } from 'vue'

interface Binding extends Omit<DirectiveBinding, 'value' | 'modifiers'> {
  value?: unknown
  modifiers: Dictionary<boolean | undefined>
}

/**
 * Transform DirectiveBinding to DirectiveArguments with strict typing
 */
export function useDirective <T extends Binding> (directive: Directive, binding: Partial<T>): DirectiveArguments[number] {
  return [
    directive,
    binding.value!,
    binding.arg!,
    binding.modifiers! as any,
  ]
}


<template>
  <div class="text-center">
    <v-menu transition="fade-transition">
      <template v-slot:activator="{ on, attrs }">
        <v-btn
          dark
          color="primary"
          v-bind="attrs"
          v-on="on"
        >
          Fade Transition
        </v-btn>
      </template>
      <v-list>
        <v-list-item
          v-for="n in 5"
          :key="n"
        >
          <v-list-item-title v-text="'Item ' + n"></v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>
  </div>
</template>


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

// Theme
+theme(v-data-table) using ($material)
  tbody
    tr
      &.v-data-table__selected
        background: map-deep-get($material, 'table', 'active')

  .v-row-group
    &__header, &__summary
      background: map-deep-get($material, 'table', 'group')

  .v-data-footer
    border-top: thin solid map-get($material, 'dividers')

  .v-data-table__empty-wrapper
    color: map-deep-get($material, 'text', 'disabled')

.v-data-table
  border-radius: $data-table-border-radius

  > .v-data-table__wrapper
    tbody
      tr
        &.v-data-table__expanded
          border-bottom: 0

        &.v-data-table__expanded__content
          box-shadow: $data-table-expanded-content-box-shadow

        &:first-child
          &:hover
            td
              &:first-child
                border-top-left-radius: $data-table-border-radius

              &:last-child
                border-top-right-radius: $data-table-border-radius

        &:last-child
          &:hover
            td
              &:first-child
                border-bottom-left-radius: $data-table-border-radius

              &:last-child
                border-bottom-right-radius: $data-table-border-radius

    .v-data-table__mobile-table-row
      display: initial

    .v-data-table__mobile-row
      height: initial
      min-height: $data-table-mobile-row-min-height

.v-data-table__empty-wrapper
  text-align: center

.v-data-table__mobile-row
  align-items: center
  display: flex
  justify-content: space-between

  &__header
    font-weight: $data-table-mobile-row-header-font-weight

    +ltr()
      padding-right: 16px

    +rtl()
      padding-left: 16px

  &__cell
    +ltr()
      text-align: right

    +rtl()
      text-align: left

.v-row-group
  &__header, &__summary

    td
      height: $data-table-row-group-children-td-height

.v-data-table__expand-icon
  user-select: none
  cursor: pointer

  &--active
    transform: rotate(-180deg)


<template>
  <Playground />
</template>

<script>
  import Playground from './Playground'

  export default {
    name: 'App',
    components: {
      Playground,
    },
  }
</script>


export function useDirective <T extends Binding> (directive: Directive, binding: Partial<T>): DirectiveArguments[number] {

export function useDirective <T extends Binding> (binding: Partial<T>): Tail<DirectiveArguments[number]> {


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

import './VTimePickerTitle.sass'

// Mixins
import PickerButton from '../../mixins/picker-button'

// Utils
import { pad } from '../VDatePicker/util'
import mixins from '../../util/mixins'

import { SelectingTimes } from './SelectingTimes'
import { VNode, PropType } from 'vue'

export default mixins(
  PickerButton
/* @vue/component */
).extend({
  name: 'v-time-picker-title',

  props: {
    ampm: Boolean,
    ampmReadonly: Boolean,
    disabled: Boolean,
    hour: Number,
    minute: Number,
    second: Number,
    period: {
      type: String as PropType<'am' | 'pm'>,
      validator: period => period === 'am' || period === 'pm',
    },
    readonly: Boolean,
    useSeconds: Boolean,
    selecting: Number,
  },

  methods: {
    genTime () {
      let hour = this.hour
      if (this.ampm) {
        hour = hour ? ((hour - 1) % 12 + 1) : 12
      }

      const displayedHour = this.hour == null ? '--' : this.ampm ? String(hour) : pad(hour)
      const displayedMinute = this.minute == null ? '--' : pad(this.minute)
      const titleContent = [
        this.genPickerButton('selecting', SelectingTimes.Hour, displayedHour, this.disabled),
        this.$createElement('span', ':'),
        this.genPickerButton('selecting', SelectingTimes.Minute, displayedMinute, this.disabled),
      ]

      if (this.useSeconds) {
        const displayedSecond = this.second == null ? '--' : pad(this.second)
        titleContent.push(this.$createElement('span', ':'))
        titleContent.push(this.genPickerButton('selecting', SelectingTimes.Second, displayedSecond, this.disabled))
      }
      return this.$createElement('div', {
        class: 'v-time-picker-title__time',
      }, titleContent)
    },
    genAmPm () {
      return this.$createElement('div', {
        staticClass: 'v-time-picker-title__ampm',
        class: {
          'v-time-picker-title__ampm--readonly': this.ampmReadonly,
        },
      }, [
        (!this.ampmReadonly || this.period === 'am') ? this.genPickerButton('period', 'am', this.$vuetify.lang.t('$vuetify.timePicker.am'), this.disabled || this.readonly) : null,
        (!this.ampmReadonly || this.period === 'pm') ? this.genPickerButton('period', 'pm', this.$vuetify.lang.t('$vuetify.timePicker.pm'), this.disabled || this.readonly) : null,
      ])
    },
  },

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

    this.ampm && children.push(this.genAmPm())

    return h('div', {
      staticClass: 'v-time-picker-title',
    }, children)
  },
})


<template>
  <v-divider
    v-bind="$attrs"
    v-on="$listeners"
  ></v-divider>
</template>

<script>
  export default {
    name: 'Usage',

    inheritAttrs: false,

    data: () => ({
      defaults: {
        inset: false,
        vertical: false,
      },
      options: {
        booleans: ['inset', 'vertical'],
      },
      tabs: [],
    }),
  }
</script>


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

exports[`VOverflowBtn.js should use default autocomplete selections 1`] = `

            foo

          <input id="input-1"
                 readonly="readonly"
                 type="text"
          >

              $dropdown

        <input type="hidden"
               value="foo"
        >

`;

exports[`VOverflowBtn.js should use default autocomplete selections 2`] = `

            foo

          <input id="input-1"
                 readonly="readonly"
                 type="text"
          >

              $dropdown

        <input type="hidden"
               value="foo"
        >

`;


return [
directive,
binding.value!,
binding.arg!,
binding.modifiers! as any,
]


import VParallax from './VParallax'

export { VParallax }
export default VParallax


export { default as VNavigationDrawer } from './VNavigationDrawer'


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

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

// Utilities
import { consoleWarn } from '../../util/console'

// Globals
import { SUPPORTS_INTERSECTION } from '@/util'

// Types
import Vue from 'vue'

export default function intersectable (options: { onVisible: string[] }) {
  if (!SUPPORTS_INTERSECTION) {
    // do nothing because intersection observer is not available
    return Vue.extend({ name: 'intersectable' })
  }

  return Vue.extend({
    name: 'intersectable',

    mounted () {
      Intersect.inserted(this.$el as HTMLElement, {
        name: 'intersect',
        value: this.onObserve,
      })
    },

    destroyed () {
      Intersect.unbind(this.$el as HTMLElement)
    },

    methods: {
      onObserve (entries: IntersectionObserverEntry[], observer: IntersectionObserver, isIntersecting: boolean) {
        if (!isIntersecting) return

        for (let i = 0, length = options.onVisible.length; i < length; i++) {
          const callback = (this as any)[options.onVisible[i]]

          if (typeof callback === 'function') {
            callback()
            continue
          }

          consoleWarn(options.onVisible[i] + ' method is not available on the instance but referenced in intersectable mixin options')
        }
      },
    },
  })
}


@use "sass:map";
@use "../../styles/settings/variables";
@use "../../styles/tools/functions";

$toolbar-background: rgb(var(--v-theme-surface)) !default;
$toolbar-color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity)) !default;
$toolbar-border-radius: 0 !default;
$toolbar-btn-icon-size: 48px !default;
$toolbar-collapsed-border-radius: 24px !default;
$toolbar-collapsed-max-width: 112px !default;
$toolbar-content-padding-x: 16px !default;
$toolbar-content-padding-y: 4px !default;
$toolbar-density: ( 'default': 0, 'comfortable': -1, 'compact': -2, 'prominent': -3 ) !default;
$toolbar-elevation: 4 !default;
$toolbar-flat-elevation: 0 !default;
$toolbar-padding-end: 16px !default;
$toolbar-padding-start: 16px !default;
$toolbar-padding: 16px !default;
$toolbar-prepend-btn-margin-start: -12px !default;
$toolbar-append-btn-margin-end: -12px !default;
$toolbar-rounded-border-radius: variables.$border-radius-root !default;
$toolbar-transition: .2s variables.$standard-easing !default;

$toolbar-title-padding: 20px !default;
$toolbar-title-extended-padding: 56px !default;
$toolbar-title-font-size: 1.25rem !default;
$toolbar-title-font-weight: 400 !default;
$toolbar-title-letter-spacing: 0 !default;
$toolbar-title-line-height: 1.75rem !default;
$toolbar-title-text-transform: none !default;
$toolbar-prominent-title-font-size: 1.5rem !default;
$toolbar-prominent-title-font-weight: 400 !default;
$toolbar-prominent-title-letter-spacing: 0 !default;
$toolbar-prominent-title-line-height: 2.25rem !default;
$toolbar-prominent-title-text-transform: none !default;

$toolbar-title-typography: (
  $toolbar-title-font-size,
  $toolbar-title-font-weight,
  $toolbar-title-letter-spacing,
  $toolbar-title-line-height,
  $toolbar-title-text-transform
);

$toolbar-prominent-title-typography: (
  $toolbar-prominent-title-font-size,
  $toolbar-prominent-title-font-weight,
  $toolbar-prominent-title-letter-spacing,
  $toolbar-prominent-title-line-height,
  $toolbar-prominent-title-text-transform
);

$toolbar-theme: (
  $toolbar-background,
  $toolbar-color
) !default;


const DataOptions = {
  page: 'number',
  itemsPerPage: 'number',
  sortBy: 'string[]',
  sortDesc: 'boolean[]',
  groupBy: 'string[]',
  groupDesc: 'boolean[]',
  multiSort: 'boolean',
  mustSort: 'boolean',
}

const DataPagination = {
  page: 'number',
  itemsPerPage: 'number',
  pageStart: 'number',
  pageStop: 'number',
  pageCount: 'number',
  itemsLength: 'number',
}

const DataProps = [
  { name: 'items', source: 'v-data' },
  { name: 'options', source: 'v-data', type: 'DataOptions', example: DataOptions },
  { name: 'sortBy', source: 'v-data' },
  { name: 'sortDesc', source: 'v-data' },
  {
    name: 'customSort',
    source: 'v-data',
    default: 'gh:sortItems',
    example: '(items: any[], sortBy: string[], sortDesc: boolean[], locale: string, customSorters?: Record<string, compareFn>) => any[]',
  },
  { name: 'mustSort', source: 'v-data' },
  { name: 'multiSort', source: 'v-data' },
  { name: 'page', source: 'v-data' },
  { name: 'itemsPerPage', source: 'v-data' },
  { name: 'groupBy', source: 'v-data' },
  { name: 'groupDesc', source: 'v-data' },
  { name: 'checkboxColor', source: 'v-data', type: 'string' },
  {
    name: 'customGroup',
    source: 'v-data',
    default: 'gh:groupItems',
    example: '(items: any[], groupBy: string[], groupDesc: boolean[]) => Record<string, any[]>',
  },
  { name: 'locale', source: 'v-data' },
  { name: 'disableSort', source: 'v-data' },
  { name: 'disablePagination', source: 'v-data' },
  { name: 'disableFiltering', source: 'v-data' },
  { name: 'search', source: 'v-data' },
  { name: 'customFilter', source: 'v-data', default: 'gh:searchItems', example: '(items: any[], search: string) => any[]' },
  { name: 'serverItemsLength', source: 'v-data' },
]

const DataDefaultScopedSlotProps = {
  items: 'any[]',
  originalItemsLength: 'number',
  pagination: DataPagination,
  options: DataOptions,
  groupedItems: 'Array<{ name: string, items: any[] }>',
  updateOptions: '(obj: any) => void',
  sort: '(value: string) => void',
  sortArray: '(sortBy: string[]) => void',
  group: '(value: string) => void',
}

const DataEvents = [
  { name: 'current-items', source: 'v-data', value: 'any[]' },
  { name: 'page-count', source: 'v-data', value: 'number' },
  { name: 'pagination', source: 'v-data', value: DataPagination },
  { name: 'update:options', source: 'v-data', value: DataOptions },
  { name: 'update:page', source: 'v-data', value: 'number' },
  { name: 'update:items-per-page', source: 'v-data', value: 'number' },
  { name: 'update:sort-by', source: 'v-data', value: 'string | string[]' },
  { name: 'update:sort-desc', source: 'v-data', value: 'boolean | boolean[]' },
  { name: 'update:group-by', source: 'v-data', value: 'string | string[]' },
  { name: 'update:group-desc', source: 'v-data', value: 'boolean | boolean[]' },
  { name: 'update:multi-sort', source: 'v-data', value: 'boolean' },
  { name: 'update:must-sort', source: 'v-data', value: 'boolean' },
]

module.exports = {
  'v-data': {
    props: DataProps,
    events: DataEvents,
    slots: [{
      name: 'default',
      props: DataDefaultScopedSlotProps,
    }],
  },
  DataOptions,
  DataPagination,
  DataProps,
  DataDefaultScopedSlotProps,
  DataEvents,
}


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

exports[`VSlideGroup.ts should match snapshot in rtl 1`] = `

    <fade-transition-stub origin="top center 0">
      <vuecomponent-stub>
        $next
      </vuecomponent-stub>
    </fade-transition-stub>

    <fade-transition-stub origin="top center 0">
      <vuecomponent-stub>
        $prev
      </vuecomponent-stub>
    </fade-transition-stub>

`;

exports[`VSlideGroup.ts should match snapshot in rtl 2`] = `

    <fade-transition-stub origin="top center 0">
      <vuecomponent-stub>
        $prev
      </vuecomponent-stub>
    </fade-transition-stub>

    <fade-transition-stub origin="top center 0">
      <vuecomponent-stub>
        $next
      </vuecomponent-stub>
    </fade-transition-stub>

`;


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

// Components
// import VSparkline from '../VSparkline'

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

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

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

  it('should render component and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
      },
    })

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

  it('should render component with padding and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        padding: 20,
      },
    })

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

  it('should render smooth component and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        smooth: 20,
      },
    })

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

  it('should render component with line width and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        lineWidth: 42,
      },
    })

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

  it('should render component with gradient and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        gradient: ['#000', 'red', 'rgba(80, 160, 240, 0.5)'],
      },
    })

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

  it('should render component with string labels and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        showLabels: true,
        value: [1, 7, 42],
      },
    })

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

    wrapper.setProps({
      value: [
        {
          value: 2,
        },
        {
          value: 8,
        },
        {
          value: 43,
        },
      ],
    })

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

    wrapper.setProps({
      labels: ['foo', 'bar', 'baz'],
    })

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

  it('should render component with bars and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
      },
    })

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

  it('should render component with bars and negative and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [-1, 1, 7, 42],
        type: 'bar',
      },
    })

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

  it('should render component with bars and gradient and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        gradient: ['#000', 'red', 'rgba(80, 160, 240, 0.5)'],
        type: 'bar',
      },
    })

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

  it('should render component with bars and labels and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        labels: ['Value 1', 'Value 2', 'Value 3'],
        type: 'bar',
      },
    })

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

  it('should render component with bars and auto-line-width and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        autoLineWidth: true,
      },
    })

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

  it('should render component with bars and line width and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        lineWidth: 8,
      },
    })

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

  it('should render component with bars and custom padding and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        padding: 12,
      },
    })

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

  it('should render component with bars and auto-line-width with custom padding and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        autoLineWidth: true,
        padding: 12,
      },
    })

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

  it('should render component with bars and custom label size and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        labels: ['Value 1', 'Value 2', 'Value 3'],
        labelSize: 15,
        type: 'bar',
      },
    })

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

  it('should render component with trend and equal values and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 1, 1],
        type: 'trend',
      },
    })

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

  it('should render component with label size and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        showLabels: true,
        labelSize: 14,
      },
    })

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

  it('should position labels correctly', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        showLabels: true,
        lineWidth: 20,
      },
    })

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

  it('should render component with bars and correct bar lengths', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 2],
        type: 'bar',
      },
    })

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

    wrapper.setProps({
      value: [-1, -2],
    })
    await wrapper.vm.$nextTick()

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

  it('should render bar component with all values 0', () => {
    const wrapper = mountFunction({
      propsData: {
        value: [0, 0, 0],
        type: 'bar',
      },
    })

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


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

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


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

// Styles
// import '../../stylus/components/_calendar-daily.styl'

// Types
import { VNode, Component } from 'vue'

// Mixins
import CalendarWithEvents from './mixins/calendar-with-events'

// Util
import props from './util/props'
import {
  DAYS_IN_MONTH_MAX,
  DAY_MIN,
  DAYS_IN_WEEK,
  parseTimestamp,
  validateTimestamp,
  relativeDays,
  nextDay,
  prevDay,
  copyTimestamp,
  updateFormatted,
  updateWeekday,
  updateRelative,
  getStartOfMonth,
  getEndOfMonth,
  VTime,
  VTimestampInput,
  timestampToDate,
} from './util/timestamp'

// Calendars
import VCalendarMonthly from './VCalendarMonthly'
import VCalendarDaily from './VCalendarDaily'
import VCalendarWeekly from './VCalendarWeekly'
import VCalendarCategory from './VCalendarCategory'
import { CalendarTimestamp, CalendarFormatter, CalendarCategory } from 'vuetify/types'
import { getParsedCategories } from './util/parser'

// Types
interface VCalendarRenderProps {
  start: CalendarTimestamp
  end: CalendarTimestamp
  component: string | Component
  maxDays: number
  weekdays: number[]
  categories: CalendarCategory[]
}

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

  props: {
    ...props.calendar,
    ...props.weeks,
    ...props.intervals,
    ...props.category,
  },

  data: () => ({
    lastStart: null as CalendarTimestamp | null,
    lastEnd: null as CalendarTimestamp | null,
  }),

  computed: {
    parsedValue (): CalendarTimestamp {
      return (validateTimestamp(this.value)
        ? parseTimestamp(this.value, true)
        : (this.parsedStart || this.times.today))
    },
    parsedCategoryDays (): number {
      return parseInt(this.categoryDays) || 1
    },
    renderProps (): VCalendarRenderProps {
      const around = this.parsedValue
      let component: any = null
      let maxDays = this.maxDays
      let weekdays = this.parsedWeekdays
      let categories = this.parsedCategories
      let start = around
      let end = around
      switch (this.type) {
        case 'month':
          component = VCalendarMonthly
          start = getStartOfMonth(around)
          end = getEndOfMonth(around)
          break
        case 'week':
          component = VCalendarDaily
          start = this.getStartOfWeek(around)
          end = this.getEndOfWeek(around)
          maxDays = 7
          break
        case 'day':
          component = VCalendarDaily
          maxDays = 1
          weekdays = [start.weekday]
          break
        case '4day':
          component = VCalendarDaily
          end = relativeDays(copyTimestamp(end), nextDay, 3)
          updateFormatted(end)
          maxDays = 4
          weekdays = [
            start.weekday,
            (start.weekday + 1) % 7,
            (start.weekday + 2) % 7,
            (start.weekday + 3) % 7,
          ]
          break
        case 'custom-weekly':
          component = VCalendarWeekly
          start = this.parsedStart || around
          end = this.parsedEnd
          break
        case 'custom-daily':
          component = VCalendarDaily
          start = this.parsedStart || around
          end = this.parsedEnd
          break
        case 'category':
          const days = this.parsedCategoryDays

          component = VCalendarCategory
          end = relativeDays(copyTimestamp(end), nextDay, days)
          updateFormatted(end)
          maxDays = days
          weekdays = []

          for (let i = 0; i < days; i++) {
            weekdays.push((start.weekday + i) % 7)
          }

          categories = this.getCategoryList(categories)
          break
        default:
          throw new Error(this.type + ' is not a valid Calendar type')
      }

      return { component, start, end, maxDays, weekdays, categories }
    },
    eventWeekdays (): number[] {
      return this.renderProps.weekdays
    },
    categoryMode (): boolean {
      return this.type === 'category'
    },
    title (): string {
      const { start, end } = this.renderProps
      const spanYears = start.year !== end.year
      const spanMonths = spanYears || start.month !== end.month

      if (spanYears) {
        return this.monthShortFormatter(start, true) + ' ' + start.year + ' - ' + this.monthShortFormatter(end, true) + ' ' + end.year
      }

      if (spanMonths) {
        return this.monthShortFormatter(start, true) + ' - ' + this.monthShortFormatter(end, true) + ' ' + end.year
      } else {
        return this.monthLongFormatter(start, false) + ' ' + start.year
      }
    },
    monthLongFormatter (): CalendarFormatter {
      return this.getFormatter({
        timeZone: 'UTC', month: 'long',
      })
    },
    monthShortFormatter (): CalendarFormatter {
      return this.getFormatter({
        timeZone: 'UTC', month: 'short',
      })
    },
    parsedCategories (): CalendarCategory[] {
      return getParsedCategories(this.categories, this.categoryText)
    },
  },

  watch: {
    renderProps: 'checkChange',
  },

  mounted () {
    this.updateEventVisibility()
    this.checkChange()
  },

  updated () {
    window.requestAnimationFrame(this.updateEventVisibility)
  },

  methods: {
    checkChange (): void {
      const { lastStart, lastEnd } = this
      const { start, end } = this.renderProps
      if (!lastStart || !lastEnd ||
        start.date !== lastStart.date ||
        end.date !== lastEnd.date) {
        this.lastStart = start
        this.lastEnd = end
        this.$emit('change', { start, end })
      }
    },
    move (amount = 1): void {
      const moved = copyTimestamp(this.parsedValue)
      const forward = amount > 0
      const mover = forward ? nextDay : prevDay
      const limit = forward ? DAYS_IN_MONTH_MAX : DAY_MIN
      let times = forward ? amount : -amount

      while (--times >= 0) {
        switch (this.type) {
          case 'month':
            moved.day = limit
            mover(moved)
            break
          case 'week':
            relativeDays(moved, mover, DAYS_IN_WEEK)
            break
          case 'day':
            relativeDays(moved, mover, 1)
            break
          case '4day':
            relativeDays(moved, mover, 4)
            break
          case 'category':
            relativeDays(moved, mover, this.parsedCategoryDays)
            break
        }
      }

      updateWeekday(moved)
      updateFormatted(moved)
      updateRelative(moved, this.times.now)

      if (this.value instanceof Date) {
        this.$emit('input', timestampToDate(moved))
      } else if (typeof this.value === 'number') {
        this.$emit('input', timestampToDate(moved).getTime())
      } else {
        this.$emit('input', moved.date)
      }

      this.$emit('moved', moved)
    },
    next (amount = 1): void {
      this.move(amount)
    },
    prev (amount = 1): void {
      this.move(-amount)
    },
    timeToY (time: VTime, clamp = true): number | false {
      const c = this.$children[0] as any

      if (c && c.timeToY) {
        return c.timeToY(time, clamp)
      } else {
        return false
      }
    },
    timeDelta (time: VTime): number | false {
      const c = this.$children[0] as any

      if (c && c.timeDelta) {
        return c.timeDelta(time)
      } else {
        return false
      }
    },
    minutesToPixels (minutes: number): number {
      const c = this.$children[0] as any

      if (c && c.minutesToPixels) {
        return c.minutesToPixels(minutes)
      } else {
        return -1
      }
    },
    scrollToTime (time: VTime): boolean {
      const c = this.$children[0] as any

      if (c && c.scrollToTime) {
        return c.scrollToTime(time)
      } else {
        return false
      }
    },
    parseTimestamp (input: VTimestampInput, required?: false): CalendarTimestamp | null {
      return parseTimestamp(input, required, this.times.now)
    },
    timestampToDate (timestamp: CalendarTimestamp): Date {
      return timestampToDate(timestamp)
    },
    getCategoryList (categories: CalendarCategory[]): CalendarCategory[] {
      if (!this.noEvents) {
        const categoryMap: any = categories.reduce((map: any, category, index) => {
          if (typeof category === 'object' && category.categoryName) map[category.categoryName] = { index, count: 0 }
          else if (typeof category === 'string') map[category] = { index, count: 0 }
          return map
        }, {})

        if (!this.categoryHideDynamic || !this.categoryShowAll) {
          let categoryLength = categories.length

          this.parsedEvents.forEach(ev => {
            let category = ev.category

            if (typeof category !== 'string') {
              category = this.categoryForInvalid
            }

            if (!category) {
              return
            }

            if (category in categoryMap) {
              categoryMap[category].count++
            } else if (!this.categoryHideDynamic) {
              categoryMap[category] = {
                index: categoryLength++,
                count: 1,
              }
            }
          })
        }

        if (!this.categoryShowAll) {
          for (const category in categoryMap) {
            if (categoryMap[category].count === 0) {
              delete categoryMap[category]
            }
          }
        }

        categories = categories.filter((category: CalendarCategory) => {
          if (typeof category === 'object' && category.categoryName) {
            return categoryMap.hasOwnProperty(category.categoryName)
          } else if (typeof category === 'string') {
            return categoryMap.hasOwnProperty(category)
          }
          return false
        })
      }
      return categories
    },
  },

  render (h): VNode {
    const { start, end, maxDays, component, weekdays, categories } = this.renderProps

    return h(component, {
      staticClass: 'v-calendar',
      class: {
        'v-calendar-events': !this.noEvents,
      },
      props: {
        ...this.$props,
        start: start.date,
        end: end.date,
        maxDays,
        weekdays,
        categories,
      },
      directives: [{
        modifiers: { quiet: true },
        name: 'resize',
        value: this.updateEventVisibility,
      }],
      on: {
        ...this.$listeners,
        'click:date': (day: CalendarTimestamp, e?: MouseEvent) => {
          if (this.$listeners.input) {
            this.$emit('input', day.date)
          }
          if (this.$listeners['click:date']) {
            this.$emit('click:date', day, e)
          }
        },
      },
      scopedSlots: this.getScopedSlots(),
    })
  },
})


<template>
  <v-container>
    <v-overflow-btn
      class="my-2"
      :items="items"
      label="Overflow Btn - Dense"
      dense
    ></v-overflow-btn>
  </v-container>
</template>

<script>
  export default {
    data: () => ({
      items: ['Arial', 'Calibri', 'Courier', 'Verdana'],
    }),
  }
</script>


<template>
  <v-input
    :success-messages="['Success']"
    success
    disabled
  >
    Input
  </v-input>
</template>


flat: Boolean,
floating: Boolean,
height: {
type: [Number, String],
default: 64,
},
image: String,
title: String,

keep keep keep keep replace keep keep keep keep


<template>
  <v-row
    align="center"
    justify="center"
  >
    <v-btn
      v-bind="$attrs"
    >
      <v-icon v-if="$attrs.fab || $attrs.icon">
        $mdiVuetify
      </v-icon>

      <span v-else>Click Me</span>
    </v-btn>
  </v-row>
</template>

<script>
  export default {
    name: 'Usage',

    inheritAttrs: false,

    data: () => ({
      defaults: {
        block: false,
        color: null,
        depressed: false,
        disabled: false,
        elevation: 2,
        fab: false,
        icon: false,
        large: false,
        loading: false,
        outlined: false,
        plain: false,
        rounded: false,
        small: false,
        text: false,
        tile: false,
        'x-large': false,
        'x-small': false,
      },
      options: {
        booleans: [
          'block',
          'disabled',
          'large',
          'loading',
          'small',
          'x-large',
          'x-small',
        ],
        sliders: {
          elevation: [0, 24],
        },
        selects: {
          color: [
            'accent',
            'primary',
            'secondary',
          ],
        },
      },
      tabs: [
        'depressed',
        'icon',
        'outlined',
        'plain',
        'raised',
        'rounded',
        'fab',
        'text',
        'tile',
      ],
    }),
  }
</script>


<template>
  <div class="text-center">
    <v-rating v-model="rating">
      <template v-slot:item="props">
        <v-icon
          :color="props.isFilled ? colors[props.index] : 'grey-lighten-1'"
          large
          @click="props.onClick"
        >
          {{ props.isFilled ? 'mdi-star-circle' : 'mdi-star-circle-outline' }}
        </v-icon>
      </template>
    </v-rating>
  </div>
</template>

<script>
  export default {
    data: () => ({
      colors: ['green', 'purple', 'orange', 'indigo', 'red'],
      rating: 4.5,
    }),
  }
</script>


export default {
  badge: 'Lencana',
  close: 'Tutup',
  dataIterator: {
    noResultsText: 'Tidak ditemukan catatan yang cocok',
    loadingText: 'Memuat data...',
  },
  dataTable: {
    itemsPerPageText: 'Baris per halaman:',
    ariaLabel: {
      sortDescending: 'Diurutkan kebawah.',
      sortAscending: 'Diurutkan keatas.',
      sortNone: 'Tidak diurutkan.',
      activateNone: 'Aktifkan untuk menghapus penyortiran.',
      activateDescending: 'Aktifkan untuk mengurutkan kebawah.',
      activateAscending: 'Aktifkan untuk mengurutkan keatas.',
    },
    sortBy: 'Urutkan berdasar',
  },
  dataFooter: {
    itemsPerPageText: 'Item per halaman:',
    itemsPerPageAll: 'Semua',
    nextPage: 'Halaman selanjutnya',
    prevPage: 'Halaman sebelumnya',
    firstPage: 'Halaman pertama',
    lastPage: 'Halaman terakhir',
    pageText: '{0}-{1} dari {2}',
  },
  datePicker: {
    itemsSelected: '{0} dipilih',
    nextMonthAriaLabel: 'Bulan depan',
    nextYearAriaLabel: 'Tahun depan',
    prevMonthAriaLabel: 'Bulan sebelumnya',
    prevYearAriaLabel: 'Tahun sebelumnya',
  },
  noDataText: 'Tidak ada data tersedia',
  carousel: {
    prev: 'Visual sebelumnya',
    next: 'Visual selanjutnya',
    ariaLabel: {
      delimiter: 'Carousel slide {0} of {1}',
    },
  },
  calendar: {
    moreEvents: '{0} lagi',
  },
  fileInput: {
    counter: '{0} berkas',
    counterSize: '{0} berkas (dari total {1})',
  },
  timePicker: {
    am: 'AM',
    pm: 'PM',
  },
  pagination: {
    ariaLabel: {
      root: 'Navigasi Pagination',
      next: 'Halaman selanjutnya',
      previous: 'Halaman sebelumnya',
      page: 'Buka halaman {0}',
      currentPage: 'Halaman Saat Ini, Halaman {0}',
      first: 'First page',
      last: 'Last page',
    },
  },
  rating: {
    ariaLabel: {
      item: 'Rating {0} of {1}',
    },
  },
}


$toolbar-color: rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity)) !default;

keep keep keep keep replace replace keep keep keep keep

$toolbar-color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)) !default;


<template>
  <v-container fluid>
    <v-data-iterator
      v-model:items-per-page="itemsPerPage"
      :items="items"
      hide-default-footer
    >
      <template v-slot:header>
        <v-toolbar
          class="mb-2"
          color="indigo darken-5"
          dark
          flat
        >
          <v-toolbar-title>This is a header</v-toolbar-title>
        </v-toolbar>
      </template>

      <template v-slot:default="props">
        <v-row>
          <v-col
            v-for="item in props.items"
            :key="item.name"
            cols="12"
            sm="6"
            md="4"
            lg="3"
          >
            <v-card>
              <v-card-title class="subheading font-weight-bold">
                {{ item.name }}
              </v-card-title>

              <v-divider></v-divider>

              <v-list dense>
                <v-list-item>
                  <v-list-item-content>Calories:</v-list-item-content>
                  <v-list-item-content class="align-end">
                    {{ item.calories }}
                  </v-list-item-content>
                </v-list-item>

                <v-list-item>
                  <v-list-item-content>Fat:</v-list-item-content>
                  <v-list-item-content class="align-end">
                    {{ item.fat }}
                  </v-list-item-content>
                </v-list-item>

                <v-list-item>
                  <v-list-item-content>Carbs:</v-list-item-content>
                  <v-list-item-content class="align-end">
                    {{ item.carbs }}
                  </v-list-item-content>
                </v-list-item>

                <v-list-item>
                  <v-list-item-content>Protein:</v-list-item-content>
                  <v-list-item-content class="align-end">
                    {{ item.protein }}
                  </v-list-item-content>
                </v-list-item>

                <v-list-item>
                  <v-list-item-content>Sodium:</v-list-item-content>
                  <v-list-item-content class="align-end">
                    {{ item.sodium }}
                  </v-list-item-content>
                </v-list-item>

                <v-list-item>
                  <v-list-item-content>Calcium:</v-list-item-content>
                  <v-list-item-content class="align-end">
                    {{ item.calcium }}
                  </v-list-item-content>
                </v-list-item>

                <v-list-item>
                  <v-list-item-content>Iron:</v-list-item-content>
                  <v-list-item-content class="align-end">
                    {{ item.iron }}
                  </v-list-item-content>
                </v-list-item>
              </v-list>
            </v-card>
          </v-col>
        </v-row>
      </template>

      <template v-slot:footer>
        <v-toolbar
          class="mt-2"
          color="indigo"
          dark
          flat
        >
          <v-toolbar-title class="subheading">
            This is a footer
          </v-toolbar-title>
        </v-toolbar>
      </template>
    </v-data-iterator>
  </v-container>
</template>

<script>
  export default {
    data: () => ({
      itemsPerPage: 4,
      items: [
        {
          name: 'Frozen Yogurt',
          calories: 159,
          fat: 6.0,
          carbs: 24,
          protein: 4.0,
          sodium: 87,
          calcium: '14%',
          iron: '1%',
        },
        {
          name: 'Ice cream sandwich',
          calories: 237,
          fat: 9.0,
          carbs: 37,
          protein: 4.3,
          sodium: 129,
          calcium: '8%',
          iron: '1%',
        },
        {
          name: 'Eclair',
          calories: 262,
          fat: 16.0,
          carbs: 23,
          protein: 6.0,
          sodium: 337,
          calcium: '6%',
          iron: '7%',
        },
        {
          name: 'Cupcake',
          calories: 305,
          fat: 3.7,
          carbs: 67,
          protein: 4.3,
          sodium: 413,
          calcium: '3%',
          iron: '8%',
        },
      ],
    }),
  }
</script>


<template>

    <v-menu>
      <template v-slot:activator="{ props: menu }">
        <v-tooltip bottom>
          <template v-slot:activator="{ props: tooltip }">
            <v-btn
              color="primary"
              dark
              v-bind="mergeProps(menu, tooltip)"
            >
              Dropdown w/ Tooltip
            </v-btn>
          </template>
          I'm A Tooltip
        </v-tooltip>
      </template>
      <v-list>
        <v-list-item
          v-for="(item, index) in items"
          :key="index"
        >
          <v-list-item-title>{{ item.title }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>

</template>

<codepen-additional>
const { mergeProps } = Vue
</codepen-additional>


<template>
  <div>
    <v-card title="Title" subtitle="Subtitle" class="ma-10"></v-card>
    <v-defaults-provider :defaults="defaults">
      <v-card title="Title" subtitle="Subtitle" class="ma-10"></v-card>
    </v-defaults-provider>
  </div>
</template>

<script>
  export default {
    data: () => ({
      defaults: {
        global: {
          elevation: 10,
        },
        VCard: {
          color: 'secondary',
        },
      },
    }),
  }
</script>


$toolbar-density: ( 'default': 0, 'comfortable': -1, 'compact': -2, 'prominent': -3 ) !default;

keep keep keep keep keep replace keep keep keep

// 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(),
  },

  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>


import { createSimpleFunctional } from '@/util'

export const VCardImg = createSimpleFunctional('v-card-img')


@use '../../styles/settings';

$expansion-panel-background-color: rgb(var(--v-theme-surface)) !default;
$expansion-panel-color: rgb(var(--v-theme-on-surface)) !default;
$expansion-panel-border-color: rgba(var(--v-border-color), var(--v-border-opacity)) !default;
$expansion-panel-border-radius: settings.$border-radius-root !default;
$expansion-panel-active-margin: 16px !default;
$expansion-panel-title-font-size: 0.9375rem !default;
$expansion-panel-title-min-height: 48px !default;
$expansion-panel-active-title-min-height: 64px !default;
$expansion-panel-title-padding: 16px 24px !default;
$expansion-panel-text-padding: 8px 24px 16px !default;
$expansion-panel-popout-max-width: calc(100% - #{$expansion-panel-active-margin * 2}) !default;
$expansion-panel-popout-active-max-width: calc(100% + #{$expansion-panel-active-margin}) !default;
$expansion-panel-inset-max-width: 100% !default;
$expansion-panel-inset-active-max-width: calc(100% - #{$expansion-panel-active-margin * 2}) !default;
$expansion-panel-disabled-opacity: 0.26 !default;
$expansion-panel-disabled-overlay: 0.12 !default;
$expansion-panel-disabled-color: rgba(var(--v-theme-on-surface), $expansion-panel-disabled-opacity) !default;


---
meta:
  title: Select component
  description: The select component provides a list of options that a user can make selections from.
  keywords: selects, vuetify select component, vue select component
related:
  - /components/autocompletes/
  - /components/combobox/
  - /components/forms/
---

# Selects

Select fields components are used for collecting user provided information from a list of options.

<entry />

## Usage

<example file="v-select/usage" />

## API

<api-inline />

## Caveats

<alert type="info">

  Browser autocomplete is set to off by default, may vary by browser and may be ignored. [MDN](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion)

</alert>

<alert type="warning">

  The **auto** property of **menu-props** is only supported for the default input style.

</alert>

<alert type="error">

  When using objects for the **items** prop, you must associate **item-text** and **item-value** with existing properties on your objects. These values are defaulted to **text** and **value** and can be changed.

</alert>

## Examples

### Props

#### Custom text and value

You can specify the specific properties within your items array correspond to the text and value fields. By default, this is **text** and **value**. In this example we also use the **return-object** prop which will return the entire object of the selected item on selection.

<example file="v-select/prop-custom-text-and-value" />

#### Dense

You can use **dense** prop to reduce the field height and lower max height of list items.

<example file="v-select/prop-dense" />

#### Disabled

Applying the **disabled** prop to a `v-select` will prevent a user from interacting with the component.

<example file="v-select/prop-disabled" />

#### Icons

Use a custom prepended or appended icon.

<example file="v-select/prop-icons" />

#### Light

A standard single select has a multitude of configuration options.

<example file="v-select/prop-light" />

#### Menu props

Custom props can be passed directly to `v-menu` using **menuProps** prop. In this example menu is force directed to top and shifted to top.

<example file="v-select/prop-menu-props" />

#### Multiple

A multi-select can utilize `v-chip` as the display for selected items.

<example file="v-select/prop-multiple" />

#### Readonly

You can use the **read-only** prop on `v-select` which will prevent a user from changing its value.

<example file="v-select/prop-readonly" />

### Slots

#### Append and prepend item

The `v-select` components can be optionally expanded with prepended and appended items. This is perfect for customized **select-all** functionality.

<example file="v-select/slot-append-and-prepend-item" />

#### Selection

The **selection** slot can be used to customize the way selected values are shown in the input. This is great when you want something like `foo (+20 others)` or don't want the selection to occupy multiple lines.

<example file="v-select/slot-selection" />

<backmatter />


const colors = new Set(Object.values(computedThemes.value).flatMap(theme => Object.keys(theme.colors)))

const bgLines: string[] = []
const fgLines: string[] = []

@use 'sass:map';
@use '../settings';
@use '../settings/colors';
@use '../tools/functions' as *;

@mixin background-color($color_value) {
  background-color: $color_value !important;
}
@mixin text-color($color_value) {
  color: $color_value !important;
  caret-color: $color_value !important;
}
@mixin background-text-color($color_name, $color_type) {
  $map_value: map-deep-get(colors.$text-on-colors, $color_name, $color_type);

  color: $map_value !important;
}

@if (settings.$color-pack) {
  @each $color_name, $color_value in colors.$shades {
    .bg-#{$color_name} {
      @include background-color($color_value);

      @if (map.has-key(colors.$text-on-colors, 'shades')) {
        @include background-text-color('shades', $color_name);
      }
    }

    .text-#{$color_name} {
      @include text-color($color_value);
    }
  }

  @each $color_name, $color_color in colors.$colors {
    @each $color_type, $color_value in $color_color {
      @if ($color_type == 'base') {
        .bg-#{$color_name} {
          @include background-color($color_value);

          @if (map.has-key(colors.$text-on-colors, $color_name)) {
            @include background-text-color($color_name, $color_type);
          }
        }

        .text-#{$color_name} {
          @include text-color($color_value);
        }
      }
      @else if ($color_type != 'shades') {
        .bg-#{$color_name}-#{$color_type} {
          @include background-color($color_value);

          @if (map.has-key(colors.$text-on-colors, $color_name)) {
            @include background-text-color($color_name, $color_type);
          }
        }

        .text-#{$color_name}-#{$color_type} {
          @include text-color($color_value);
        }
      }
    }
  }
}


<template>
  <v-row justify="center">
    <v-btn
      color="primary"
      dark
      @click.stop="dialog = true"
    >
      Open Dialog
    </v-btn>

    <v-dialog
      v-model="dialog"
      max-width="290"
    >
      <v-card>
        <v-card-title class="text-h5">
          Use Google's location service?
        </v-card-title>

        <v-card-text>
          Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.
        </v-card-text>

        <v-card-actions>
          <v-spacer></v-spacer>

          <v-btn
            color="green darken-1"
            text
            @click="dialog = false"
          >
            Disagree
          </v-btn>

          <v-btn
            color="green darken-1"
            text
            @click="dialog = false"
          >
            Agree
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>

<script>
  export default {
    data () {
      return {
        dialog: false,
      }
    },
  }
</script>


export default {
  badge: 'Placa',
  close: 'Cerrar',
  dataIterator: {
    noResultsText: 'Ningún elemento coincide con la búsqueda',
    loadingText: 'Cargando...',
  },
  dataTable: {
    itemsPerPageText: 'Filas por página:',
    ariaLabel: {
      sortDescending: 'Orden descendente.',
      sortAscending: 'Orden ascendente.',
      sortNone: 'Sin ordenar.',
      activateNone: 'Pulse para quitar orden.',
      activateDescending: 'Pulse para ordenar descendente.',
      activateAscending: 'Pulse para ordenar ascendente.',
    },
    sortBy: 'Ordenado por',
  },
  dataFooter: {
    itemsPerPageText: 'Elementos por página:',
    itemsPerPageAll: 'Todos',
    nextPage: 'Página siguiente',
    prevPage: 'Página anterior',
    firstPage: 'Primer página',
    lastPage: 'Última página',
    pageText: '{0}-{1} de {2}',
  },
  datePicker: {
    itemsSelected: '{0} seleccionados',
    nextMonthAriaLabel: 'Próximo mes',
    nextYearAriaLabel: 'Próximo año',
    prevMonthAriaLabel: 'Mes anterior',
    prevYearAriaLabel: 'Año anterior',
  },
  noDataText: 'No hay datos disponibles',
  carousel: {
    prev: 'Visual anterior',
    next: 'Visual siguiente',
    ariaLabel: {
      delimiter: 'Visual {0} de {1}',
    },
  },
  calendar: {
    moreEvents: '{0} más',
  },
  fileInput: {
    counter: '{0} archivos',
    counterSize: '{0} archivos ({1} en total)',
  },
  timePicker: {
    am: 'AM',
    pm: 'PM',
  },
  pagination: {
    ariaLabel: {
      root: 'Navegación de paginación',
      next: 'Página siguiente',
      previous: 'Página anterior',
      page: 'Ir a la página {0}',
      currentPage: 'Página actual, página {0}',
      first: 'First page',
      last: 'Last page',
    },
  },
  rating: {
    ariaLabel: {
      item: 'Puntuación {0} de {1}',
    },
  },
}


Array of expanded items. Can be used with `.sync` modifier

Changes which item property should be used for grouping items. Currently only supports a single grouping in the format: `group` or `['group']`. When using an array, only the first element is considered. Can be used with `.sync` modifier

Changes which direction grouping is done. Can be used with `.sync` modifier

The property on each item that is used as a unique key

Changes how many items per page should be visible. Can be used with `.sync` modifier. Setting this prop to `-1` will display all items on the page

If `true` and no items are provided, then a loading text will be shown

Text shown when `loading` is true and no items are provided

Sets the locale used for sorting. This is passed into [`Intl.Collator()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator) in the default `customSort` function

Used to set when to toggle between regular table and mobile view

If `true` then one can sort on multiple properties

If `true` then one can not disable sorting, it will always switch between ascending and descending

Text shown when no items are provided to the component

Text shown when `search` prop is used and there are no results

The property on each item that is used to determine if it is selectable or not

Used only when data is provided by a server. Should be set to the total amount of items available on server so that pagination works correctly

Changes which item property (or properties) should be used for sort order. Can be used with `.sync` modifier

Changes which direction sorting is done. Can be used with `.sync` modifier

The default slot. Use this to render your items

This slot is forwarded to the default footer. See the [`v-data-footer`](/api/v-data-footer) API for more information

Defines a header above the items

Defines content for when `loading` is true and no items are provided

Defines content for when no items are provided

Defines content for when `search` is provided but no results are found

Input: Array of selected items

Event emitted when an item is expanded or closed

Event emitted when an item is selected or deselected


createCssClass(fgLines, `.${key}`, [`color: rgb(var(--v-theme-${key})) !important`])

// Utilities
import {
  computed,
  inject,
  provide,
  reactive,
  ref,
  watch,
  watchEffect,
} from 'vue'
import {
  colorToInt,
  colorToRGB,
  createRange,
  darken,
  getCurrentInstance,
  getLuma,
  IN_BROWSER,
  intToHex,
  lighten,
  mergeDeep,
  propsFactory,
} from '@/util'
import { APCAcontrast } from '@/util/color/APCA'

// Types
import type { App, DeepReadonly, InjectionKey, Ref } from 'vue'
import type { HeadAttrs, HeadClient } from '@vueuse/head'

type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T

export type ThemeOptions = false | {
  cspNonce?: string
  defaultTheme?: string
  variations?: false | VariationsOptions
  themes?: Record<string, ThemeDefinition>
}
export type ThemeDefinition = DeepPartial<InternalThemeDefinition>

interface InternalThemeOptions {
  cspNonce?: string
  isDisabled: boolean
  defaultTheme: string
  variations: false | VariationsOptions
  themes: Record<string, InternalThemeDefinition>
}

interface VariationsOptions {
  colors: string[]
  lighten: number
  darken: number
}

interface InternalThemeDefinition {
  dark: boolean
  colors: Colors
  variables: Record<string, string | number>
}

export interface Colors extends BaseColors, OnColors {
  [key: string]: string
}

interface BaseColors {
  background: string
  surface: string
  primary: string
  secondary: string
  success: string
  warning: string
  error: string
  info: string
}

interface OnColors {
  'on-background': string
  'on-surface': string
  'on-primary': string
  'on-secondary': string
  'on-success': string
  'on-warning': string
  'on-error': string
  'on-info': string
}

export interface ThemeInstance {
  readonly isDisabled: boolean
  readonly themes: Ref<Record<string, InternalThemeDefinition>>

  readonly name: Readonly<Ref<string>>
  readonly current: DeepReadonly<Ref<InternalThemeDefinition>>
  readonly computedThemes: DeepReadonly<Ref<Record<string, InternalThemeDefinition>>>

  readonly themeClasses: Readonly<Ref<string | undefined>>
  readonly styles: Readonly<Ref<string>>

  readonly global: {
    readonly name: Ref<string>
    readonly current: DeepReadonly<Ref<InternalThemeDefinition>>
  }
}

export const ThemeSymbol: InjectionKey<ThemeInstance> = Symbol.for('vuetify:theme')

export const makeThemeProps = propsFactory({
  theme: String,
}, 'theme')

const defaultThemeOptions: Exclude<ThemeOptions, false> = {
  defaultTheme: 'light',
  variations: { colors: [], lighten: 0, darken: 0 },
  themes: {
    light: {
      dark: false,
      colors: {
        background: '#FFFFFF',
        surface: '#FFFFFF',
        'surface-variant': '#424242',
        'on-surface-variant': '#EEEEEE',
        primary: '#6200EE',
        'primary-darken-1': '#3700B3',
        secondary: '#03DAC6',
        'secondary-darken-1': '#018786',
        error: '#B00020',
        info: '#2196F3',
        success: '#4CAF50',
        warning: '#FB8C00',
      },
      variables: {
        'border-color': '#000000',
        'border-opacity': 0.12,
        'high-emphasis-opacity': 0.87,
        'medium-emphasis-opacity': 0.60,
        'disabled-opacity': 0.38,
        'idle-opacity': 0.04,
        'hover-opacity': 0.04,
        'focus-opacity': 0.12,
        'selected-opacity': 0.08,
        'activated-opacity': 0.12,
        'pressed-opacity': 0.12,
        'dragged-opacity': 0.08,
        'kbd-background-color': '#212529',
        'kbd-color': '#FFFFFF',
        'code-background-color': '#C2C2C2',
      },
    },
    dark: {
      dark: true,
      colors: {
        background: '#121212',
        surface: '#212121',
        'surface-variant': '#BDBDBD',
        'on-surface-variant': '#424242',
        primary: '#BB86FC',
        'primary-darken-1': '#3700B3',
        secondary: '#03DAC5',
        'secondary-darken-1': '#03DAC5',
        error: '#CF6679',
        info: '#2196F3',
        success: '#4CAF50',
        warning: '#FB8C00',
      },
      variables: {
        'border-color': '#FFFFFF',
        'border-opacity': 0.12,
        'high-emphasis-opacity': 0.87,
        'medium-emphasis-opacity': 0.60,
        'disabled-opacity': 0.38,
        'idle-opacity': 0.10,
        'hover-opacity': 0.04,
        'focus-opacity': 0.12,
        'selected-opacity': 0.08,
        'activated-opacity': 0.12,
        'pressed-opacity': 0.16,
        'dragged-opacity': 0.08,
        'kbd-background-color': '#212529',
        'kbd-color': '#FFFFFF',
        'code-background-color': '#B7B7B7',
      },
    },
  },
}

function parseThemeOptions (options: ThemeOptions = defaultThemeOptions): InternalThemeOptions {
  if (!options) return { ...defaultThemeOptions, isDisabled: true } as InternalThemeOptions

  const themes: Record<string, InternalThemeDefinition> = {}
  for (const [key, theme] of Object.entries(options.themes ?? {})) {
    const defaultTheme = theme.dark
      ? defaultThemeOptions.themes?.dark
      : defaultThemeOptions.themes?.light
    themes[key] = mergeDeep(defaultTheme, theme) as InternalThemeDefinition
  }

  return mergeDeep(
    defaultThemeOptions,
    { ...options, themes },
  ) as InternalThemeOptions
}

// Composables
export function createTheme (app: App, options?: ThemeOptions): ThemeInstance {
  const head = app._context.provides.usehead as HeadClient | undefined
  const parsedOptions = reactive(parseThemeOptions(options))
  const name = ref(parsedOptions.defaultTheme)
  const themes = ref(parsedOptions.themes)

  const computedThemes = computed(() => {
    const acc: Record<string, InternalThemeDefinition> = {}
    for (const [name, original] of Object.entries(themes.value)) {
      const theme: InternalThemeDefinition = acc[name] = {
        ...original,
        colors: {
          ...original.colors,
        },
      }

      if (parsedOptions.variations) {
        for (const name of parsedOptions.variations.colors) {
          const color = theme.colors[name]
          for (const variation of (['lighten', 'darken'] as const)) {
            const fn = variation === 'lighten' ? lighten : darken
            for (const amount of createRange(parsedOptions.variations[variation], 1)) {
              theme.colors[`${name}-${variation}-${amount}`] = intToHex(fn(colorToInt(color), amount))
            }
          }
        }
      }

      for (const color of Object.keys(theme.colors)) {
        if (/on-[a-z]/.test(color) || theme.colors[`on-${color}`]) continue

        const onColor = `on-${color}` as keyof OnColors
        const colorVal = colorToInt(theme.colors[color]!)

        const blackContrast = Math.abs(APCAcontrast(0, colorVal))
        const whiteContrast = Math.abs(APCAcontrast(0xffffff, colorVal))

        // TODO: warn about poor color selections
        // const contrastAsText = Math.abs(APCAcontrast(colorVal, colorToInt(theme.colors.background)))
        // const minContrast = Math.max(blackContrast, whiteContrast)
        // if (minContrast < 60) {
        //   consoleInfo(`${key} theme color ${color} has poor contrast (${minContrast.toFixed()}%)`)
        // } else if (contrastAsText < 60 && !['background', 'surface'].includes(color)) {
        //   consoleInfo(`${key} theme color ${color} has poor contrast as text (${contrastAsText.toFixed()}%)`)
        // }

        // Prefer white text if both have an acceptable contrast ratio
        theme.colors[onColor] = whiteContrast > Math.min(blackContrast, 50) ? '#fff' : '#000'
      }
    }

    return acc
  })
  const current = computed(() => computedThemes.value[name.value])

  const styles = computed(() => {
    const lines: string[] = []

    if (current.value.dark) {
      createCssClass(lines, ':root', ['color-scheme: dark'])
    }

    for (const [themeName, theme] of Object.entries(computedThemes.value)) {
      const { variables, dark } = theme

      createCssClass(lines, `.v-theme--${themeName}`, [
        `color-scheme: ${dark ? 'dark' : 'normal'}`,
        ...genCssVariables(theme),
        ...Object.keys(variables).map(key => {
          const value = variables[key]
          const color = typeof value === 'string' && value.startsWith('#') ? colorToRGB(value) : undefined
          const rgb = color ? `${color.r}, ${color.g}, ${color.b}` : undefined

          return `--v-${key}: ${rgb ?? value}`
        }),
      ])
    }

    const colors = new Set(Object.values(computedThemes.value).flatMap(theme => Object.keys(theme.colors)))
    for (const key of colors) {
      if (/on-[a-z]/.test(key)) {
        createCssClass(lines, `.${key}`, [`color: rgb(var(--v-theme-${key})) !important`])
      } else {
        createCssClass(lines, `.bg-${key}`, [
          `--v-theme-overlay-multiplier: var(--v-theme-${key}-overlay-multiplier)`,
          `background: rgb(var(--v-theme-${key})) !important`,
          `color: rgb(var(--v-theme-on-${key})) !important`,
        ])
        createCssClass(lines, `.text-${key}`, [`color: rgb(var(--v-theme-${key})) !important`])
        createCssClass(lines, `.border-${key}`, [`--v-border-color: var(--v-theme-${key})`])
      }
    }

    return lines.map((str, i) => i === 0 ? str : `    ${str}`).join('')
  })

  if (head) {
    head.addHeadObjs(computed(() => {
      const style: HeadAttrs = {
        children: styles.value,
        type: 'text/css',
        id: 'vuetify-theme-stylesheet',
      }
      if (parsedOptions.cspNonce) style.nonce = parsedOptions.cspNonce

      return { style: [style] }
    }))

    if (IN_BROWSER) {
      watchEffect(() => head.updateDOM())
    }
  } else {
    let styleEl = IN_BROWSER
      ? document.getElementById('vuetify-theme-stylesheet')
      : null

    watch(styles, updateStyles, { immediate: true })
    function updateStyles () {
      if (parsedOptions.isDisabled) return

      if (typeof document !== 'undefined' && !styleEl) {
        const el = document.createElement('style')
        el.type = 'text/css'
        el.id = 'vuetify-theme-stylesheet'
        if (parsedOptions.cspNonce) el.setAttribute('nonce', parsedOptions.cspNonce)

        styleEl = el
        document.head.appendChild(styleEl)
      }

      if (styleEl) styleEl.innerHTML = styles.value
    }
  }

  const themeClasses = computed(() => parsedOptions.isDisabled ? undefined : `v-theme--${name.value}`)

  return {
    isDisabled: parsedOptions.isDisabled,
    name,
    themes,
    current,
    computedThemes,
    themeClasses,
    styles,
    global: {
      name,
      current,
    },
  }
}

export function provideTheme (props: { theme?: string }) {
  getCurrentInstance('provideTheme')

  const theme = inject(ThemeSymbol, null)

  if (!theme) throw new Error('Could not find Vuetify theme injection')

  const name = computed<string>(() => {
    return props.theme ?? theme?.name.value
  })

  const themeClasses = computed(() => theme.isDisabled ? undefined : `v-theme--${name.value}`)

  const newTheme: ThemeInstance = {
    ...theme,
    name,
    themeClasses,
  }

  provide(ThemeSymbol, newTheme)

  return newTheme
}

export function useTheme () {
  getCurrentInstance('useTheme')

  const theme = inject(ThemeSymbol, null)

  if (!theme) throw new Error('Could not find Vuetify theme injection')

  return theme
}

function createCssClass (lines: string[], selector: string, content: string[]) {
  lines.push(
    `${selector} {\n`,
    ...content.map(line => `  ${line};\n`),
    '}\n',
  )
}

function genCssVariables (theme: InternalThemeDefinition) {
  const lightOverlay = theme.dark ? 2 : 1
  const darkOverlay = theme.dark ? 1 : 2

  const variables: string[] = []
  for (const [key, value] of Object.entries(theme.colors)) {
    const rgb = colorToRGB(value)
    variables.push(`--v-theme-${key}: ${rgb.r},${rgb.g},${rgb.b}`)
    if (!key.startsWith('on-')) {
      variables.push(`--v-theme-${key}-overlay-multiplier: ${getLuma(value) > 0.18 ? lightOverlay : darkOverlay}`)
    }
  }

  return variables
}


<template>
  <v-card>
    <v-layout>
      <v-navigation-drawer
        class="bg-deep-purple"
        theme="dark"
        permanent
      >
        <v-list color="transparent">
          <v-list-item prepend-icon="mdi-view-dashboard" title="Dashboard"></v-list-item>
          <v-list-item prepend-icon="mdi-account-box" title="Account"></v-list-item>
          <v-list-item prepend-icon="mdi-gavel" title="Admin"></v-list-item>
        </v-list>

        <template v-slot:append>
          <div class="pa-2">
            <v-btn block>
              Logout
            </v-btn>
          </div>
        </template>
      </v-navigation-drawer>
      <v-main style="height: 400px"></v-main>
    </v-layout>
  </v-card>
</template>


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

.v-kbd
  background: rgb(var(--v-kbd-background-color))
  border-radius: $kbd-border-radius
  color: rgb(var(--v-kbd-color))
  font-size: $kbd-font-size
  font-weight: $kbd-font-weight
  padding: $kbd-padding

  @include tools.elevation($kbd-elevation)


<template>
  <v-combobox
    v-model="chips"
    :items="items"
    chips
    clearable
    label="Your favorite hobbies"
    multiple
    prepend-icon="mdi-filter-variant"
    solo
  >
    <template v-slot:selection="{ attrs, item, select, selected }">
      <v-chip
        v-bind="attrs"
        :input-value="selected"
        close
        @click="select"
        @click:close="remove(item)"
      >
        {{ item }}
        (interest)
      </v-chip>
    </template>
  </v-combobox>
</template>


export default {
  badge: 'Huy hiệu',
  close: 'Đóng',
  dataIterator: {
    noResultsText: 'Không tìm thấy kết quả nào',
    loadingText: 'Đang tải...',
  },
  dataTable: {
    itemsPerPageText: 'Số hàng mỗi trang:',
    ariaLabel: {
      sortDescending: 'Sắp xếp giảm dần.',
      sortAscending: 'Sắp xếp tăng dần.',
      sortNone: 'Không sắp xếp.',
      activateNone: 'Kích hoạt để bỏ sắp xếp.',
      activateDescending: 'Kích hoạt để sắp xếp giảm dần.',
      activateAscending: 'Kích hoạt để sắp xếp tăng dần.',
    },
    sortBy: 'Sắp xếp',
  },
  dataFooter: {
    itemsPerPageText: 'Số mục mỗi trang:',
    itemsPerPageAll: 'Toàn bộ',
    nextPage: 'Trang tiếp theo',
    prevPage: 'Trang trước',
    firstPage: 'Trang đầu',
    lastPage: 'Trang cuối',
    pageText: '{0}-{1} trên {2}',
  },
  datePicker: {
    itemsSelected: '{0} được chọn',
    nextMonthAriaLabel: 'Tháng sau',
    nextYearAriaLabel: 'Năm sau',
    prevMonthAriaLabel: 'Tháng trước',
    prevYearAriaLabel: 'Năm trước',
  },
  noDataText: 'Không có dữ liệu',
  carousel: {
    prev: 'Ảnh tiếp theo',
    next: 'Ảnh trước',
    ariaLabel: {
      delimiter: 'Carousel slide {0} trên {1}',
    },
  },
  calendar: {
    moreEvents: '{0} nữa',
  },
  fileInput: {
    counter: '{0} tệp',
    counterSize: '{0} tệp (tổng cộng {1})',
  },
  timePicker: {
    am: 'SA',
    pm: 'CH',
  },
  pagination: {
    ariaLabel: {
      root: 'Điều hướng phân trang',
      next: 'Trang tiếp theo',
      previous: 'Trang trước',
      page: 'Đến trang {0}',
      currentPage: 'Trang hiện tại, Trang {0}',
      first: 'First page',
      last: 'Last page',
    },
  },
  rating: {
    ariaLabel: {
      item: 'Đánh giá {0} trên {1}',
    },
  },
}


<template>
  <v-card
    class="overflow-auto mx-auto"
    max-height="300"
    width="448"
  >
    <v-toolbar color="primary">
      <v-toolbar-title>My Document</v-toolbar-title>

      <v-spacer></v-spacer>

      <div>
        <v-switch
          v-model="sticky"
          label="Sticky Banner"
          hide-details
        ></v-switch>
      </div>
    </v-toolbar>

    <v-banner
      :sticky="sticky"
      lines="one"
    >
      <template v-slot:text>
        We can't save your edits while you are in offline mode.
      </template>

      <template v-slot:actions>
        <v-btn color="deep-purple accent-4">
          Go Online
        </v-btn>
      </template>
    </v-banner>

    <v-card-text class="bg-grey-lighten-4">
      <v-sheet
        class="mx-auto"
        height="300"
      ></v-sheet>
    </v-card-text>

    <v-footer
      class="justify-center"
      color="primary"
    >
      End of Content
    </v-footer>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      sticky: false,
    }),
  }
</script>


---
nav: Parallax
meta:
  title: Parallax component
  description: The parallax component creates a 3d effect that makes an image appear to scroll slower than the window.
  keywords: parallax, vuetify parallax component, vue parallax component
related:
  - /components/aspect-ratios/
  - /components/cards/
  - /components/images/
---

# Parallax

The `v-parallax` component creates a 3d effect that makes an image appear to scroll slower than the window.

<entry />

## Usage

A parallax causes a shift in a background image when the user scrolls the page.

<example file="v-parallax/usage" />

## API

<api-inline />

## Examples

### Misc

#### Content

You can also place any content inside of the parallax. This allows you to use the parallax as a hero image.

<example file="v-parallax/misc-content" />

#### Custom height

You can specify a custom height on a parallax. Keep in mind this can break the parallax if your image is not sized properly

<example file="v-parallax/misc-custom-height" />

<backmatter />


createCssClass(lines, `.text-${key}`, [`color: rgb(var(--v-theme-${key})) !important`])

createCssClass(lines, `.border-${key}`, [`--v-border-color: var(--v-theme-${key})`])

keep keep keep replace replace keep keep keep

createCssClass(fgLines, `.text-${key}`, [`color: rgb(var(--v-theme-${key})) !important`])

createCssClass(fgLines, `.border-${key}`, [`--v-border-color: var(--v-theme-${key})`])


@use 'sass:math';
@use 'sass:map';
@use '../../styles/settings';
@use '../../styles/tools';

// Defaults
$chip-background: rgb(var(--v-theme-surface-variant)) !default;
$chip-border-color: settings.$border-color-root !default;
$chip-border-radius: map.get(settings.$rounded, "pill") !default;
$chip-border-style: settings.$border-style-root !default;
$chip-border-thin-width: thin !default;
$chip-border-width: 0 !default;
$chip-close-size: 18px !default;
$chip-color: rgb(var(--v-theme-on-surface-variant)) !default;
$chip-density: ("default": 0, "comfortable": -2, "compact": -3) !default;
$chip-disabled-opacity: 0.3 !default;
$chip-elevation: 1 !default;
$chip-font-size: tools.map-deep-get(settings.$typography, "button", "size") !default;
$chip-font-weight: 400 !default;
$chip-height: 32px !default;
$chip-icon-font-size: 1.25rem !default;
$chip-label-border-radius: settings.$border-radius-root !default;
$chip-max-width: 100% !default;
$chip-overflow: hidden !default;
$chip-padding-ratio: 2 + math.div(2, 3) !default;
$chip-plain-opacity: 0.26 !default;
$chip-filter-transition: .15s settings.$standard-easing !default;
$chip-white-space: nowrap !default;

$chip-sizes: (
  "height": $chip-height,
  "font-size": $chip-font-size,
) !default;

$chip-border: (
  $chip-border-color,
  $chip-border-style,
  $chip-border-width
) !default;

$chip-variants: (
  $chip-background,
  $chip-color,
  $chip-elevation,
  $chip-plain-opacity,
  'v-chip'
) !default;


import type { TouchStoredHandlers } from './directives/touch'
import type { Events, VNode } from 'vue'

declare global {
  interface HTMLCollection {
    [Symbol.iterator] (): IterableIterator<Element>
  }

  interface Element {
    _clickOutside?: Record<number, {
      onClick: EventListener
      onMousedown: EventListener
    } | undefined> & { lastMousedownWasOutside: boolean }
    _onResize?: Record<number, {
      handler: () => void
      options: AddEventListenerOptions
    } | undefined>
    _ripple?: {
      enabled?: boolean
      centered?: boolean
      class?: string
      circle?: boolean
      touched?: boolean
      isTouch?: boolean
      showTimer?: number
      showTimerCommit?: (() => void) | null
    }
    _observe?: Record<number, {
      init: boolean
      observer: IntersectionObserver
    } | undefined>
    _mutate?: Record<number, {
      observer: MutationObserver
    } | undefined>
    _onScroll?: Record<number, {
      handler: EventListenerOrEventListenerObject
      options: AddEventListenerOptions
      target?: EventTarget
    } | undefined>
    _touchHandlers?: {
      [_uid: number]: TouchStoredHandlers
    }
    _transitionInitialStyles?: {
      position: string
      top: string
      left: string
      width: string
      height: string
    }

    getElementsByClassName(classNames: string): NodeListOf<HTMLElement>
  }

  interface WheelEvent {
    path?: EventTarget[]
  }

  interface UIEvent {
    initUIEvent (
      typeArg: string,
      canBubbleArg: boolean,
      cancelableArg: boolean,
      viewArg: Window,
      detailArg: number,
    ): void
  }

  function parseInt(s: string | number, radix?: number): number
  function parseFloat(string: string | number): number

  export const __VUETIFY_VERSION__: string
  export const __REQUIRED_VUE__: string

  namespace JSX {
    interface Element extends VNode {}
    interface IntrinsicAttributes {
      [name: string]: any
    }
  }
}

declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    _: ComponentInternalInstance
  }

  export interface ComponentInternalInstance {
    ctx: Record<string, unknown>
    provides: Record<string, unknown>
    setupState: any
  }
}

declare module '@vue/runtime-dom' {
  type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

  type Combine<T extends string> = T | {
    [K in T]: {
      [L in Exclude<T, K>]: `${K}${Exclude<T, K>}` | `${K}${L}${Exclude<T, K | L>}`
    }[Exclude<T, K>]
  }[T]

  type Modifiers = Combine<'Passive' | 'Capture' | 'Once'>

  type ModifiedEvents = UnionToIntersection<{
    [K in keyof Events]: { [L in `${K}${Modifiers}`]: Events[K] }
  }[keyof Events]>

  type EventHandlers<E> = {
    [K in keyof E]?: E[K] extends Function ? E[K] : (payload: E[K]) => void
  }

  export interface HTMLAttributes extends EventHandlers<ModifiedEvents> {}

  type CustomProperties = {
    [k in `--${string}`]: any
  }

  export interface CSSProperties extends CustomProperties {}
}


export const IN_BROWSER = typeof window !== 'undefined'
export const SUPPORTS_INTERSECTION = IN_BROWSER && 'IntersectionObserver' in window
export const SUPPORTS_TOUCH = IN_BROWSER && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0)
export const SUPPORTS_FOCUS_VISIBLE = IN_BROWSER && CSS.supports('selector(:focus-visible)')


return lines.map((str, i) => i === 0 ? str : `    ${str}`).join('')


module.exports = {
  'v-pagination': {
    events: [
      {
        name: 'update:modelValue',
        value: 'number',
      },
      {
        name: 'next',
        value: 'number',
      },
      {
        name: 'previous',
        value: 'number',
      },
      {
        name: 'first',
        value: 'number',
      },
      {
        name: 'last',
        value: 'number',
      },
    ],
  },
}


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

.v-app-bar
  display: flex

  &.v-toolbar
    @include tools.theme($app-bar-theme...)

    &:not(.v-toolbar--flat)
      @include tools.elevation($app-bar-elevation)


// Styles
import './VBtnToggle.sass'

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

// Composables
import { makeGroupProps, useGroup } from '@/composables/group'

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

// Types
import type { GroupProvide } from '@/composables/group'
import type { InjectionKey } from 'vue'
import type { MakeSlots } from '@/util'

export type BtnToggleSlotProps = 'isSelected' | 'select' | 'selected' | 'next' | 'prev'
export interface DefaultBtnToggleSlot extends Pick<GroupProvide, BtnToggleSlotProps> {}

export const VBtnToggleSymbol: InjectionKey<GroupProvide> = Symbol.for('vuetify:v-btn-toggle')

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

  props: makeGroupProps({ selectedClass: 'v-btn--selected' }),

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

  setup (props, { slots }) {
    const { isSelected, next, prev, select, selected } = useGroup(props, VBtnToggleSymbol)

    useRender(() => (
      <VBtnGroup class="v-btn-toggle">
        { slots.default?.({
          isSelected,
          next,
          prev,
          select,
          selected,
        } as DefaultBtnToggleSlot) }
      </VBtnGroup>
    ))

    return {
      next,
      prev,
      select,
    }
  },
})


@mixin background-text-color($color_name, $color_type) {

$map_value: map-deep-get(colors.$text-on-colors, $color_name, $color_type);


const IN_BROWSER = typeof window !== 'undefined'
const IS_DEBUG = import.meta.env.DEBUG === 'true'
const IS_PROD = import.meta.env.NODE_ENV === 'production'
const IS_SERVER = import.meta.env.SSR

export {
  IN_BROWSER,
  IS_DEBUG,
  IS_PROD,
  IS_SERVER,
}