RocketChat.models.Users = new class extends RocketChat.models._Base
	constructor: ->
		@model = Meteor.users

	# FIND ONE
	findOneById: (_id, options) ->
		return @findOne _id, options

	findOneByUsername: (username, options) ->
		query =
			username: username

		return @findOne query, options

	findOneByEmailAddress: (emailAddress, options) ->
		query =
			'emails.address': emailAddress

		return @findOne query, options

	findOneVerifiedFromSameDomain: (email, options) ->
		domain = s.strRight(email, '@')
		query =
			emails:
				$elemMatch:
					address:
						$regex: new RegExp "@" + domain + "$", "i"
						$ne: email
					verified: true

		return @findOne query, options

	findOneAdmin: (admin, options) ->
		query =
			admin: admin

		return @findOne query, options

	findOneByIdAndLoginToken: (_id, token, options) ->
		query =
			_id: _id
			'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)

		return @findOne query, options

	# FIND
	findUsersNotOffline: (options) ->
		query =
			username:
				$exists: 1
			status:
				$in: ['online', 'away', 'busy']

		return @find query, options

	findByUsername: (username, options) ->
		query =
			username: username

		return @find query, options

	findUsersByUsernamesWithHighlights: (username, options) ->
		query =
			username: username
			'settings.preferences.highlights':
				$exists: true

		return @find query, options

	findActiveByUsernameRegexWithExceptions: (username, exceptions = [], options = {}) ->
		if not _.isArray exceptions
			exceptions = [ exceptions ]

		usernameRegex = new RegExp username, "i"
		query =
			$and: [
				{ active: true }
				{ username: { $nin: exceptions } }
				{ username: usernameRegex }
			]

		return @find query, options

	findByActiveUsersNameOrUsername: (nameOrUsername, options) ->
		query =
			username:
				$exists: 1
			active: true

			$or: [
				{name: nameOrUsername}
				{username: nameOrUsername}
			]

		return @find query, options

	findUsersByNameOrUsername: (nameOrUsername, options) ->
		query =
			username:
				$exists: 1

			$or: [
				{name: nameOrUsername}
				{username: nameOrUsername}
			]

		return @find query, options

	findByUsernameNameOrEmailAddress: (usernameNameOrEmailAddress, options) ->
		query =
			$or: [
				{name: usernameNameOrEmailAddress}
				{username: usernameNameOrEmailAddress}
				{'emails.address': usernameNameOrEmailAddress}
			]

		return @find query, options

	findLDAPUsers: (options) ->
		query =
			ldap: true

		return @find query, options

	getLastLogin: (options = {}) ->
		query = { lastLogin: { $exists: 1 } }
		options.sort = { lastLogin: -1 }
		options.limit = 1

		return @find(query, options)?.fetch?()?[0]?.lastLogin

	findUsersByUsernames: (usernames, options) ->
		query =
			username:
				$in: usernames

		return @find query, options

	# UPDATE
	updateLastLoginById: (_id) ->
		update =
			$set:
				lastLogin: new Date

		return @update _id, update

	setServiceId: (_id, serviceName, serviceId) ->
		update =
			$set: {}

		serviceIdKey = "services.#{serviceName}.id"
		update.$set[serviceIdKey] = serviceId

		return @update _id, update

	setUsername: (_id, username) ->
		update =
			$set: username: username

		return @update _id, update

	setEmail: (_id, email) ->
		update =
			$set:
				'emails.0.address': email
				'emails.0.verified': false

		return @update _id, update

	setEmailVerified: (_id, email) ->
		query =
			_id: _id
			emails:
				$elemMatch:
					address: email
					verified: false

		update =
			$set:
				'emails.$.verified': true

		return @update query, update

	setName: (_id, name) ->
		update =
			$set:
				name: name

		return @update _id, update

	setAvatarOrigin: (_id, origin) ->
		update =
			$set:
				avatarOrigin: origin

		return @update _id, update

	unsetAvatarOrigin: (_id) ->
		update =
			$unset:
				avatarOrigin: 1

		return @update _id, update

	setUserActive: (_id, active=true) ->
		update =
			$set:
				active: active

		return @update _id, update

	setAllUsersActive: (active) ->
		update =
			$set:
				active: active

		return @update {}, update, { multi: true }

	unsetLoginTokens: (_id) ->
		update =
			$set:
				"services.resume.loginTokens" : []

		return @update _id, update

	unsetRequirePasswordChange: (_id) ->
		update =
			$unset:
				"requirePasswordChange" : true
				"requirePasswordChangeReason" : true

		return @update _id, update

	resetPasswordAndSetRequirePasswordChange: (_id, requirePasswordChange, requirePasswordChangeReason) ->
		update =
			$unset:
				"services.password": 1
			$set:
				"requirePasswordChange" : requirePasswordChange,
				"requirePasswordChangeReason": requirePasswordChangeReason

		return @update _id, update

	setLanguage: (_id, language) ->
		update =
			$set:
				language: language

		return @update _id, update

	setProfile: (_id, profile) ->
		update =
			$set:
				"settings.profile": profile

		return @update _id, update

	setPreferences: (_id, preferences) ->
		update =
			$set:
				"settings.preferences": preferences

		return @update _id, update

	setUtcOffset: (_id, utcOffset) ->
		query =
			_id: _id
			utcOffset:
				$ne: utcOffset

		update =
			$set:
				utcOffset: utcOffset

		return @update query, update

	# INSERT
	create: (data) ->
		user =
			createdAt: new Date
			avatarOrigin: 'none'

		_.extend user, data

		return @insert user

	# REMOVE
	removeById: (_id) ->
		return @remove _id

	###
	Find users to send a message by email if:
	- he is not online
	- has a verified email
	- has not disabled email notifications
	###
	getUsersToSendOfflineEmail: (usersIds) ->
		query =
			_id:
				$in: usersIds
			status: 'offline'
			statusConnection:
				$ne: 'online'
			'emails.verified': true

		return @find query, { fields: { name: 1, username: 1, emails: 1, 'settings.preferences.emailNotificationMode': 1 } }


Template.oauthApps.helpers
	hasPermission: ->
		return RocketChat.authz.hasAllPermission 'manage-oauth-apps'

	applications: ->
		return ChatOAuthApps.find()

	dateFormated: (date) ->
		return moment(date).format('L LT')


style.background = `url("${this.img}") center center / cover no-repeat`

require('../../stylus/components/_cards.styl')

import Colorable from '../../mixins/colorable'
import Routable from '../../mixins/routable'
import Themeable from '../../mixins/themeable'

export default {
  name: 'v-card',

  mixins: [Colorable, Routable, Themeable],

  props: {
    flat: Boolean,
    height: {
      type: String,
      default: 'auto'
    },
    hover: Boolean,
    img: String,
    raised: Boolean,
    tag: {
      type: String,
      default: 'div'
    },
    tile: Boolean
  },

  computed: {
    classes () {
      return this.addBackgroundColorClassChecks({
        'card': true,
        'card--flat': this.flat,
        'card--horizontal': this.horizontal,
        'card--hover': this.hover,
        'card--raised': this.raised,
        'card--tile': this.tile,
        'theme--light': this.light,
        'theme--dark': this.dark
      })
    },
    styles () {
      const style = {
        height: isNaN(this.height) ? this.height : `${this.height}px`
      }

      if (this.img) {
        style.background = `url(${this.img}) center center / cover no-repeat`
      }

      return style
    }
  },

  render (h) {
    const { tag, data } = this.generateRouteLink()

    data.style = this.styles

    return h(tag, data, this.$slots.default)
  }
}


// Component specific transitions
.bottom-sheet-transition
  &-enter
    transform: translateY(100%)

  &-leave-to
    transform: translateY(100%)

.carousel-transition
  &-enter
    transform: translate3d(100%, 0, 0)

  &-leave, &-leave-to
    position: absolute
    top: 0
    transform: translate3d(-100%, 0, 0)

.carousel-reverse-transition
  &-enter
    transform: translate3d(-100%, 0, 0)

  &-leave, &-leave-to
    position: absolute
    top: 0
    transform: translate3d(100%, 0, 0)

.dialog-transition
  &-enter, &-leave-to
    transform: scale(0.5)
    opacity: 0

  &-enter-to, &-leave
    opacity: 1

.dialog-bottom-transition
  &-enter, &-leave-to
    transform: translateY(100%)

.tab-transition
  &-enter
    transform: translate(100%, 0)

  &-enter-to
    transform: translate(0, 0)

  &-leave, &-leave-active
    position: absolute
    top: 0

  &-leave-to
    position: absolute
    transform: translate(-100%, 0)

.tab-reverse-transition
  &-enter
    transform: translate(-100%, 0)

  &-leave, &-leave-to
    top: 0
    position: absolute
    transform: translate(100%, 0)

// Generic transitions
.scale-transition
  &-enter-active, &-leave-active
    transition: $primary-transition

  &-enter, &-leave, &-leave-to
    opacity: 0
    transform: scale(0)

.slide-y-transition
  &-enter-active, &-leave-active
    transition: $primary-transition

  &-enter, &-leave-to
    opacity: 0
    transform: translateY(-15px)

.slide-y-reverse-transition
  &-enter-active, &-leave-active
    transition: $primary-transition

  &-enter, &-leave-to
    opacity: 0
    transform: translateY(15px)

.slide-x-transition
  &-enter-active, &-leave-active
    transition: $primary-transition

  &-enter, &-leave-to
    opacity: 0
    transform: translateX(-15px)

.slide-x-reverse-transition
  &-enter-active, &-leave-active
    transition: $primary-transition

  &-enter, &-leave-to
    opacity: 0
    transform: translateX(15px)

.fade-transition
  &-enter-active, &-leave-active
    transition: $primary-transition

  &-enter, &-leave-to
    opacity: 0

.fab-transition
  &-enter-active, &-leave-active
    transition: $primary-transition

  &-enter, &-leave-to
    transform: scale(0) rotate(-45deg)


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

exports[`VDivider.js should render an inset component and match snapshot 1`] = `

`;

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

`;


require('../../stylus/components/_bottom-sheets.styl')

import VDialog from '../VDialog/VDialog'

export default {
  name: 'v-bottom-sheet',

  components: {
    VDialog
  },

  props: {
    inset: Boolean,
    value: null
  },

  render (h) {
    const activator = h('template', {
      slot: 'activator'
    }, this.$slots.activator)

    const contentClass = [
      'bottom-sheet',
      this.inset ? 'bottom-sheet--inset' : ''
    ].join(' ')

    return h(VDialog, {
      attrs: {
        ...this.$attrs
      },
      on: {
        ...this.$listeners
      },
      props: {
        contentClass: contentClass,
        transition: 'bottom-sheet-transition',
        value: this.value
      }
    }, [activator, this.$slots.default])
  }
}


keep keep keep keep add keep keep keep keep keep

it('should render card with img', () => {

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

exports[`VCard.vue should render a card with custom height 1`] = `

`;

exports[`VCard.vue should render a flat card 1`] = `

`;

exports[`VCard.vue should render a raised card 1`] = `

`;

exports[`VCard.vue should render a tile card 1`] = `

`;

exports[`VCard.vue should render a tile card 2`] = `

`;

exports[`VCard.vue should render component and match snapshot 1`] = `

`;


import { test } from '~util/testing'
import { VCardTitle } from '~components/VCard'

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

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

  it('should render component with specific padding applied', () => {
    const wrapper = mount(VCardTitle, functionalContext({
      props: {
        'primary-title': true
      }
    }))

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


/**
 * Menu keyable
 *
 * @mixin
 *
 * Primarily used to support VSelect
 * Handles opening and closing of VMenu from keystrokes
 * Will conditionally highlight VListTiles for VSelect
 */
export default {
  data: () => ({
    listIndex: -1,
    tiles: []
  }),

  watch: {
    isActive (val) {
      if (!val) this.listIndex = -1
    },
    listIndex (next, prev) {
      // For infinite scroll and autocomplete, re-evaluate children
      this.getTiles()

      if (next in this.tiles) {
        this.tiles[next].classList.add('list__tile--highlighted')
        this.$refs.content.scrollTop = next * 48
      }

      prev in this.tiles &&
        this.tiles[prev].classList.remove('list__tile--highlighted')
    }
  },

  methods: {
    changeListIndex (e) {
      // Up, Down, Enter, Space
      if ([40, 38, 13].includes(e.keyCode) ||
        e.keyCode === 32 && !this.isActive
      ) {
        e.preventDefault()
      }

      // Esc, Tab
      if ([27, 9].includes(e.keyCode)) return this.isActive = false
      else if (!this.isActive &&
        // Enter, Space
        [13, 32].includes(e.keyCode) &&
        this.openOnClick
      ) {
        return this.isActive = true
      }

      // Down
      if (e.keyCode === 40 && this.listIndex < this.tiles.length - 1) {
        this.listIndex++
      // Up
      } else if (e.keyCode === 38 && this.listIndex > 0) {
        this.listIndex--
      // Enter
      } else if (e.keyCode === 13 && this.listIndex !== -1) {
        this.tiles[this.listIndex].click()
      }
    },
    getTiles () {
      this.tiles = this.$refs.content.querySelectorAll('.list__tile')
    }
  }
}


@import '../settings/_elevations'

// GENERATE HELPER CLASSES
for z in (0..24)
    .elevation-{z}
      elevation(z, true)


exports[`VCard.vue should render component and match snapshot 1`] = `

exports[`VCard.vue should render card with img 1`] = `


import VIcon from './VIcon'

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

export default VIcon


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

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

        1

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

                        check_box_outline_blank

                        check_box

                    1

                        check_box_outline_blank

                    2

      clear

`;

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

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

                        check_box

                        check_box_outline_blank

                    1

                        check_box_outline_blank

                    2

      arrow_drop_down

`;

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

        1

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

                    1

                    2

      clear

`;

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

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

                    1

                    2

      arrow_drop_down

`;


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

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

export default function install (Vue) {
  Vue.directive('click-outside', ClickOutside)
  Vue.directive('ripple', Ripple)
  Vue.directive('resize', Resize)
  Vue.directive('scroll', Scroll)
  Vue.directive('touch', Touch)
}


keep keep keep replace keep keep keep keep keep

validationValue={ props.modelValue ?? model.value }

// Styles
import './VCombobox.sass'

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

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

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

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

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

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

type Primitive = string | number | boolean | symbol

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

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

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

  props: {
    // TODO: implement post keyboard support
    // autoSelectFirst: Boolean,
    delimiters: Array as PropType<string[]>,

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

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

  setup (props, { emit, slots }) {
    const { t } = useLocale()
    const vTextFieldRef = ref()
    const isFocused = ref(false)
    const isPristine = ref(true)
    const menu = useProxiedModel(props, 'menu')
    const selectionIndex = ref(-1)
    const color = computed(() => vTextFieldRef.value?.color)
    const { items, transformIn, transformOut } = useItems(props)
    const { textColorClasses, textColorStyles } = useTextColor(color)
    const model = useProxiedModel(
      props,
      'modelValue',
      [],
      v => transformIn(wrapInArray(v || [])),
      v => {
        const transformed = transformOut(v)
        return props.multiple ? transformed : (transformed[0] ?? null)
      }
    )
    const _search = ref('')
    const search = computed<string>({
      get: () => {
        return _search.value
      },
      set: val => {
        _search.value = val
        if (!props.multiple) {
          model.value = [transformItem(props, val)]
        }

        if (val && props.multiple && props.delimiters?.length) {
          const values = val.split(new RegExp(`(?:${props.delimiters.join('|')})+`))
          if (values.length > 1) {
            values.forEach(v => {
              v = v.trim()
              if (v) select(transformItem(props, v))
            })
            _search.value = ''
          }
        }

        if (!val) selectionIndex.value = -1
        if (isFocused.value) menu.value = true

        isPristine.value = !val
      },
    })
    watch(_search, value => {
      emit('update:searchInput', value)
    })

    const { filteredItems } = useFilter(props, items, computed(() => isPristine.value ? undefined : search.value))

    const selections = computed(() => {
      return model.value.map(v => {
        return items.value.find(item => item.value === v.value) || v
      })
    })
    const selected = computed(() => selections.value.map(selection => selection.props.value))
    const selection = computed(() => selections.value[selectionIndex.value])

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

      if (props.openOnClear) {
        menu.value = true
      }
    }
    function onClickControl () {
      if (
        (props.hideNoData && !items.value.length) ||
        props.readonly
      ) return

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

      const selectionStart = vTextFieldRef.value.selectionStart
      const length = selected.value.length

      if (selectionIndex.value > -1) e.preventDefault()

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

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

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

      if (!props.multiple) return

      if (['Backspace', 'Delete'].includes(e.key)) {
        if (selectionIndex.value < 0) {
          if (e.key === 'Backspace' && !search.value) {
            selectionIndex.value = length - 1
          }

          return
        }

        select(selection.value)

        nextTick(() => !selection.value && (selectionIndex.value = length - 2))
      }

      if (e.key === 'ArrowLeft') {
        if (selectionIndex.value < 0 && selectionStart > 0) return

        const prev = selectionIndex.value > -1
          ? selectionIndex.value - 1
          : length - 1

        if (selections.value[prev]) {
          selectionIndex.value = prev
        } else {
          selectionIndex.value = -1
          vTextFieldRef.value.setSelectionRange(search.value.length, search.value.length)
        }
      }

      if (e.key === 'ArrowRight') {
        if (selectionIndex.value < 0) return

        const next = selectionIndex.value + 1

        if (selections.value[next]) {
          selectionIndex.value = next
        } else {
          selectionIndex.value = -1
          vTextFieldRef.value.setSelectionRange(0, 0)
        }
      }

      if (e.key === 'Enter') {
        select(transformItem(props, search.value))
        search.value = ''
      }
    }
    function onAfterLeave () {
      if (isFocused.value) isPristine.value = true
    }
    function select (item: InternalItem) {
      if (props.multiple) {
        const index = selected.value.findIndex(selection => selection === item.value)

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

        search.value = ''
      } else {
        search.value = item.title

        // watch for search watcher to trigger
        nextTick(() => {
          menu.value = false
          isPristine.value = true
        })
      }
    }

    watch(filteredItems, val => {
      if (!val.length && props.hideNoData) menu.value = false
    })

    watch(isFocused, val => {
      if (val) {
        selectionIndex.value = -1
      } else {
        menu.value = false

        if (!props.multiple || !search.value) return

        model.value = [...model.value, transformItem(props, search.value)]
        search.value = ''
      }
    })

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

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

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

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

                    select(item)
                  }

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

                  return (

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

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

                          )
                      ) }

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

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

export type VCombobox = InstanceType<typeof VCombobox>


const fs = require('fs')

const reset = '\x1b[0m'
const red = '\x1b[31m'
const bright = '\x1b[1m'

if (fs.existsSync('package-lock.json')) {
  console.log()
  console.log(`${red}WARNING:${reset}`)
  console.log(`This project uses ${bright}Yarn${reset}. Installing its dependencies with ${bright}npm${reset} may result in errors`)
  console.log(`Please remove ${bright}package-lock.json${reset} and try again, with yarn this time`)
  console.log(`See ${bright}https://yarnpkg.com/${reset}`)
  console.log()
  process.exit(1)
}


export { VBottomNavigation } from './VBottomNavigation'


<template>

      <v-btn
        color="deep-purple"
        outlined
        @click="active = !active"
      >
        Toggle Navigation
      </v-btn>

    <v-bottom-navigation
      v-model="value"
      :input-value="active"
      color="indigo"
    >
      <v-btn>
        Recents

        <v-icon>mdi-history</v-icon>
      </v-btn>

      <v-btn>
        Favorites

        <v-icon>mdi-heart</v-icon>
      </v-btn>

      <v-btn>
        Nearby

        <v-icon>mdi-map-marker</v-icon>
      </v-btn>
    </v-bottom-navigation>

</template>


keep keep keep keep keep replace keep keep keep keep

// Styles
import './VAutocomplete.sass'

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

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

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

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

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

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

type Primitive = string | number | boolean | symbol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    const isSelecting = ref(false)

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

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

        isSelecting.value = true

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

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

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

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

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

      if (val) menu.value = true

      isPristine.value = !val
    })

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

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

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

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

                    select(item)
                  }

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

                  return (

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

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

                          )
                      )}

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

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

export type VAutocomplete = InstanceType<typeof VAutocomplete>


import VDatePicker from './VDatePicker'
import VDatePickerTitle from './VDatePickerTitle'
import VDatePickerHeader from './VDatePickerHeader'
import VDatePickerDateTable from './VDatePickerDateTable'
import VDatePickerMonthTable from './VDatePickerMonthTable'
import VDatePickerYears from './VDatePickerYears'

export {
  VDatePicker,
  VDatePickerTitle,
  VDatePickerHeader,
  VDatePickerDateTable,
  VDatePickerMonthTable,
  VDatePickerYears,
}

export default {
  $_vuetify_subcomponents: {
    VDatePicker,
    VDatePickerTitle,
    VDatePickerHeader,
    VDatePickerDateTable,
    VDatePickerMonthTable,
    VDatePickerYears,
  },
}


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

exports[`VResponsive should force aspect ratio 1`] = `

`;

exports[`VResponsive should render content 1`] = `

      content

`;

exports[`VResponsive should set height 1`] = `

`;


<template>
  <v-row>
    <v-col cols="12">
      <v-card v-ripple>
        <v-card-text>Card with ripple</v-card-text>
        <v-card-actions>
          <v-btn>Button with ripple</v-btn>
        </v-card-actions>
      </v-card>
    </v-col>
    <v-col cols="12">
      <v-card v-ripple>
        <v-card-text>
          Outer card with ripple
          <v-card-actions>
            <v-card v-ripple.stop>
              <v-card-text>
                Inner card with ripple.stop
              </v-card-text>
            </v-card>
          </v-card-actions>
        </v-card-text>
      </v-card>
    </v-col>
  </v-row>
</template>


modelValue={ model.value.map(v => v.props.value).join(', ') }

keep keep keep keep keep replace keep keep keep


// Composables
import { makeGroupItemProps, useGroupItem } from '@/composables/group'
import { VItemGroupSymbol } from './VItemGroup'

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

// Types
import type { MakeSlots } from '@/util'
import type { GroupItemProvide } from '@/composables/group'

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

  props: makeGroupItemProps(),

  emits: {
    'group:selected': (val: { value: boolean }) => true,
  },

  setup (props, { slots }) {
    const { isSelected, select, toggle, selectedClass, value, disabled } = useGroupItem(props, VItemGroupSymbol)
    return () => slots.default?.({
      isSelected: isSelected.value,
      selectedClass: selectedClass.value,
      select,
      toggle,
      value: value.value,
      disabled: disabled.value,
    })
  },
})

export type VItem = InstanceType<typeof VItem>


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

.v-banner
  display: grid
  flex: 1 1
  font-size: $banner-font-size
  grid-template-areas: "prepend content actions"
  grid-template-columns: max-content auto max-content
  grid-template-rows: max-content max-content
  line-height: $banner-line-height
  overflow: hidden
  padding-inline-start: $banner-padding-inline-start
  padding-inline-end: $banner-padding-inline-end
  padding-top: $banner-padding * 2
  padding-bottom: $banner-padding * 2
  position: relative
  width: $banner-width

  @include tools.border($banner-border...)
  @include tools.elevation($banner-elevation)
  @include tools.position($banner-positions)
  @include tools.rounded($banner-border-radius)
  @include tools.theme($banner-theme...)

  &--rounded
    @include tools.rounded($banner-rounded-border-radius)

  &--stacked
    &:not(.v-banner--one-line)
      grid-template-areas: "prepend content" ". actions"

    .v-banner-text
      padding-inline-end: $banner-stacked-padding-inline-end

  @at-root
    @include tools.density('v-banner', $banner-density) using ($modifier)
      .v-banner-actions
        margin-bottom: -($banner-padding + $modifier)

      &.v-banner--one-line
        padding-top: $banner-padding + $modifier
        padding-bottom: $banner-padding + $modifier

        .v-banner-actions
          margin-bottom: 0

      @if ($modifier == 0px)
        &.v-banner--one-line
          padding-top: $banner-padding + $modifier + 2

      &.v-banner--two-line
        padding-top: $banner-padding * 2 + $modifier
        padding-bottom: $banner-padding * 2 + $modifier

      &.v-banner--three-line
        padding-top: $banner-padding * 3 + $modifier
        padding-bottom: $banner-padding * 2 + $modifier

      &:not(.v-banner--one-line),
      &.v-banner--two-line,
      &.v-banner--three-line
        .v-banner-actions
          margin-top: $banner-action-margin + $modifier

  &--sticky
    top: $banner-sticky-top

.v-banner__content
  grid-area: content

.v-banner__prepend
  align-self: flex-start
  grid-area: prepend
  margin-inline-end: $banner-prepend-margin-end

.v-banner-actions
  align-self: flex-end
  display: flex
  flex: 0 1
  grid-area: actions
  justify-content: flex-end

  .v-banner--two-line &,
  .v-banner--three-line &
    margin-top: $banner-actions-line-margin-top

.v-banner-text
  -webkit-box-orient: vertical
  display: -webkit-box
  padding-inline-end: $banner-text-padding-end
  overflow: hidden

  .v-banner--one-line &
    -webkit-line-clamp: 1

  .v-banner--two-line &
    -webkit-line-clamp: 2

  .v-banner--three-line &
    -webkit-line-clamp: 3

  .v-banner--two-line &,
  .v-banner--three-line &
    align-self: flex-start


// Styles
import './VSelectionControlGroup.sass'

// Composables
import { IconValue } from '@/composables/icons'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utilities
import { computed, provide, toRef } from 'vue'
import { defineComponent, getUid, useRender } from '@/util'

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

export interface VSelectionGroupContext {
  disabled: Ref<boolean>
  inline: Ref<boolean>
  name: Ref<string | undefined>
  modelValue: Ref<any>
  multiple: Ref<boolean>
  trueIcon: Ref<IconValue | undefined>
  falseIcon: Ref<IconValue | undefined>
  readonly: Ref<boolean>
  type: Ref<string | undefined>
}

export const VSelectionControlGroupSymbol: InjectionKey<VSelectionGroupContext> = Symbol.for('vuetify:selection-control-group')

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

  props: {
    disabled: Boolean,
    id: String,
    inline: Boolean,
    name: String,
    falseIcon: IconValue,
    trueIcon: IconValue,
    multiple: {
      type: Boolean as PropType<boolean | null>,
      default: null,
    },
    readonly: Boolean,
    type: String,
    modelValue: null,
  },

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

  setup (props, { slots }) {
    const modelValue = useProxiedModel(props, 'modelValue')
    const uid = getUid()
    const id = computed(() => props.id || `v-selection-control-group-${uid}`)
    const name = computed(() => props.name || id.value)

    provide(VSelectionControlGroupSymbol, {
      disabled: toRef(props, 'disabled'),
      inline: toRef(props, 'inline'),
      modelValue,
      multiple: computed(() => !!props.multiple || (props.multiple == null && Array.isArray(modelValue.value))),
      name,
      falseIcon: toRef(props, 'falseIcon'),
      trueIcon: toRef(props, 'trueIcon'),
      readonly: toRef(props, 'readonly'),
      type: toRef(props, 'type'),
    })

    useRender(() => (
      <div
        class="v-selection-control-group"
        aria-labelled-by={ props.type === 'radio' ? id.value : undefined }
        role={ props.type === 'radio' ? 'radiogroup' : undefined }
      >
        { slots.default?.() }
      </div>
    ))

    return {}
  },
})

export type VSelectionControlGroup = InstanceType<typeof VSelectionControlGroup>


// Helpers
import { VNode } from 'vue'
import mixins from '../../util/mixins'
import header, { TableHeader } from './mixins/header'
import { wrapInArray } from '../../util/helpers'

export default mixins(header).extend({
  name: 'v-data-table-header-desktop',

  methods: {
    genGroupByToggle (header: TableHeader) {
      return this.$createElement('span', {
        on: {
          click: () => this.$emit('group', header.value),
        },
      }, ['group'])
    },
    // eslint-disable-next-line max-statements
    genHeader (header: TableHeader) {
      const listeners: any = {}
      const children = []
      const attrs = {
        role: 'columnheader',
        scope: 'col',
        'aria-label': header.text || '',
        'aria-sort': 'none',
        width: header.width,
      }

      const classes = [
        `text-${header.align || 'start'}`,
        ...wrapInArray(header.class),
      ]

      if (header.value === 'data-table-select' && !this.singleSelect) {
        children.push(this.genSelectAll())
      } else {
        children.push(this.$scopedSlots[header.value]
          ? this.$scopedSlots[header.value]!({ header })
          : this.$createElement('span', [header.text])
        )

        if (!this.disableSort && (header.sortable || !header.hasOwnProperty('sortable'))) {
          listeners['click'] = () => this.$emit('sort', header.value)

          const sortIndex = this.options.sortBy.findIndex(k => k === header.value)
          const beingSorted = sortIndex >= 0
          const isDesc = this.options.sortDesc[sortIndex]

          classes.push('sortable')

          if (beingSorted) {
            classes.push('active')
            classes.push(isDesc ? 'desc' : 'asc')

            attrs['aria-sort'] = isDesc ? 'descending' : 'ascending'
            attrs['aria-label'] += isDesc
              ? this.$vuetify.lang.t('$vuetify.dataTable.ariaLabel.sortDescending')
              : this.$vuetify.lang.t('$vuetify.dataTable.ariaLabel.sortAscending')
          } else {
            attrs['aria-label'] += this.$vuetify.lang.t('$vuetify.dataTable.ariaLabel.sortNone')
          }

          if (header.align === 'end') children.unshift(this.genSortIcon())
          else children.push(this.genSortIcon())

          if (this.options.multiSort && beingSorted) {
            children.push(this.$createElement('span', { class: 'v-data-table-header__sort-badge' }, [String(sortIndex + 1)]))
          }
        }

        if (this.showGroupBy) {
          children.push(this.genGroupByToggle(header))
        }
      }

      return this.$createElement('th', {
        attrs,
        class: classes,
        on: listeners,
      }, children)
    },
  },

  render (): VNode {
    return this.$createElement('thead', {
      staticClass: 'v-data-table-header',
    }, [
      this.$createElement('tr', this.headers.map(header => this.genHeader(header))),
    ])
  },
})


header

simple/column simple/mandatory simple/multiple intermediate/toothbrush intermediate/blouse complex/filter-results


module.exports = {
  'v-time-picker-clock': {
    props: [
      {
        name: 'format',
        default: '(val: string): string',
      },
    ],
  },
}


class="text-start" > <span> </span> </th>


<template>
  <v-layout column justify-center align-center>
    <v-container>
      <v-progress-linear
        :active="active"
        :background-opacity="opacity"
        :bottom="bottom"
        :buffer-value="buffer"
        :height="height"
        :indeterminate="indeterminate"
        :query="query"
        :rounded="rounded"
        :stream="stream"
        :striped="striped"
        :top="top"
        :value="value"
        color="light-blue"
      ></v-progress-linear>
    </v-container>

    <v-layout wrap>
      <v-flex xs12 md6 lg3>
        <v-text-field
          v-model="buffer"
          class="mx-4"
          label="Buffer"
          max="100"
          min="1"
          step="1"
          style="width: 125px"
          type="number"
          @keydown="false"
        ></v-text-field>
      </v-flex>
      <v-flex xs12 md6 lg3>
        <v-text-field
          v-model="height"
          class="mx-4"
          label="Height - px"
          max="100"
          min="1"
          step="1"
          style="width: 125px"
          type="number"
          @keydown="false"
        ></v-text-field>
      </v-flex>
      <v-flex xs12 md6 lg3>
        <v-text-field
          v-model="opacity"
          class="mx-4"
          label="Opacity"
          max="1"
          min="0"
          step=".01"
          style="width: 125px"
          type="number"
          @keydown="false"
        ></v-text-field>
      </v-flex>
      <v-flex xs12 md6 lg3>
        <v-text-field
          v-model="value"
          class="mx-4"
          label="Value - %"
          max="100"
          min="1"
          step="1"
          style="width: 125px"
          type="number"
          @keydown="false"
        ></v-text-field>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="active" label="Toggle active" class="mx-4"></v-switch>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="bottom" label="Toggle bottom" class="mx-4"></v-switch>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="indeterminate" label="Toggle indeterminate" class="mx-4"></v-switch>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="query" label="Toggle query" class="mx-4"></v-switch>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="rounded" label="Toggle rounded" class="mx-4"></v-switch>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="stream" label="Toggle stream" class="mx-4"></v-switch>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="striped" label="Toggle striped" class="mx-4"></v-switch>
      </v-flex>
      <v-flex xs6 md3>
        <v-switch v-model="top" label="Toggle top" class="mx-4"></v-switch>
      </v-flex>
    </v-layout>
  </v-layout>
</template>

<script>
  export default {
    data: () => ({
      absolute: false,
      active: true,
      opacity: 0.3,
      bottom: false,
      buffer: 100,
      fixed: false,
      height: 4,
      indeterminate: false,
      query: false,
      rounded: false,
      stream: false,
      striped: false,
      top: false,
      value: 25,
    }),
  }
</script>


keep keep keep keep replace keep keep keep keep keep

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

.v-data-table
  width: 100%

.v-data-table__table
  width: 100%
  border-collapse: separate
  border-spacing: 0

.v-data-table__tr
  &--focus
    border: 1px dotted black

  &--clickable
    cursor: pointer

.v-data-table
  .v-table__wrapper
    > table
      > thead,
        tbody
        > tr
          > td,
            th
            background: rgb(var(--v-theme-surface))

            &.v-data-table-column--align-end
              text-align: end

              .v-data-table-header__content
                flex-direction: row-reverse

            &.v-data-table-column--align-center
              text-align: center

              .v-data-table-header__content
                justify-content: center

            &.v-data-table-column--no-padding
              padding: 0 8px

          > th
            align-items: center

          > th.v-data-table__th--sortable:hover
              cursor: pointer
              color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity))

          > th:not(.v-data-table__th--sorted)
              .v-data-table-header__sort-icon
                opacity: 0

              &:hover
                .v-data-table-header__sort-icon
                  opacity: 0.5

.v-data-table-column--fixed
  position: sticky
  z-index: 2
  left: 0

.v-data-table-column--last-fixed
  border-right: 1px solid rgba(var(--v-border-color), var(--v-border-opacity))

.v-data-table-group-header-row
  td
    background: lightgrey

    > span
      padding-left: 5px

.v-data-table--loading
  .v-data-table__td
    opacity: 0.3

.v-data-table__progress
  th
    position: relative
    border: none !important
    height: auto !important

.v-data-table-group-header-row__column
  padding-left: calc(var(--v-data-table-group-header-row-depth) * 16px) !important

.v-data-table-header__content
  display: flex
  align-items: center

.v-data-table-header__sort-badge
  display: inline-flex
  justify-content: center
  align-items: center
  font-size: 0.875rem
  padding: 4px
  border-radius: 50%
  background: $data-table-header-sort-badge-color
  min-width: $data-table-header-sort-badge-size
  min-height: $data-table-header-sort-badge-size
  width: $data-table-header-sort-badge-size
  height: $data-table-header-sort-badge-size

.v-data-table-headers__loader
  bottom: auto
  top: 0
  left: 0
  position: absolute
  right: 0
  width: 100%
  z-index: 1

.v-data-table-rows-loading,
.v-data-table-rows-no-data
  text-align: center


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

// Components
import { VBottomSheet } from '..'
import { VSheet } from '@/components/VSheet'

// Tests
describe('VBottomSheet', () => {
  it('reduces maximum width with the inset prop', () => {
    cy.mount(({ inset }: any) => (
      <VBottomSheet model-value inset={ inset }>
        <VSheet class="pa-4">
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Sed hic, iusto tenetur rerum eum libero numquam reprehenderit
        </VSheet>
      </VBottomSheet>
    ))
      .get('.v-bottom-sheet')
      .should('have.not.class', 'v-bottom-sheet--inset')
      .percySnapshot()
      .setProps({ inset: true })
      .get('.v-bottom-sheet')
      .should('have.class', 'v-bottom-sheet--inset')
      .should('have.css', 'max-width', '70%')
      .percySnapshot()
  })
})


<template>
  <v-window show-arrows>
    <template v-slot:prev="{ props }">
      <v-btn
        color="success"
        @click="props.onClick"
      >
        Previous slide
      </v-btn>
    </template>
    <template v-slot:next="{ props }">
      <v-btn
        color="info"
        @click="props.onClick"
      >
        Next slide
      </v-btn>
    </template>
    <v-window-item
      v-for="n in 3"
      :key="`card-${n}`"
    >
      <v-card
        elevation="2"
        height="200"
        class="d-flex align-center justify-center ma-2"
      >
        <h1
          class="text-h2"
        >
          Slide {{ n }}
        </h1>
      </v-card>
    </v-window-item>
  </v-window>
</template>


---
meta:
  nav: Text fields
  title: Text field component
  description: The text field component accepts textual input from users.
  keywords: text fields, vuetify text field component, vue text field component
related:
  - /components/textareas/
  - /components/selects/
  - /components/forms/
---

# Text fields

Text field components are used for collecting user provided information.

![Text-field Entry](https://cdn.vuetifyjs.com/docs/images/components-temp/v-text-field/v-text-field-entry.png)

----

## Usage

A simple text field with placeholder and/or label.

<usage name="v-text-field" />

<entry />

## API

| Component | Description |
| - | - |
| [v-text-field](/api/v-text-field/) | Primary Component |

<api-inline hide-links />

## Anatomy

The recommended placement of elements inside of `v-text-field` is:

* Place a `v-icon` at the start of the input or label
* Place label after prepended content

![Text-field Anatomy](https://cdn.vuetifyjs.com/docs/images/components-temp/v-text-field/v-text-field-anatomy.png)

| Element / Area | Description |
| - | - |
| 1. Container | The Text field container contains the `v-input` and `v-field` components |
| 2. Prepend icon | A custom icon that is located before `v-field` |
| 3. Prepend-inner icon | A custom icon that is located at the start of `v-field` |
| 4. Label | A content area for displaying text to users that correlates to the input |
| 5. Append-inner icon | A custom icon that is located at the end of `v-field` component |
| 6. Append icon | A custom icon that is located after `v-field` component |

## Guide

The `v-text-field` component is a versatile `<input type="text">` field which combines both the `v-input` and `v-field` components into a single offering. It is a commonly used element that provides the baseline for other form inputs; such as [v-select](/components/selects/), [v-autocomplete](/components/autocompletes/), [v-combobox](/components/combobox/). In this guide you learn the basic fundamentals of `v-text-field` and how its various properties interact with each other.

### Props

The `v-text-field` component has an massive API with numerous options to modify the display, functionality, or style of your inputs. Many of the configurable options are also available through [slots](#slots).

#### Labeling

The **label** prop displays custom text for identifying an input's purpose. The following code snippet is an example of a basic `v-text-field` component:

```html
<v-text-field label="First name"></v-text-field>
```

Using this baseline makes it easy to put together quick mock implementations of your interface without needing to hook up any functional logic.

The following code snippet is an example of a simple form for for collecting a user's **First** name:

<example file="v-text-field/prop-label" />

#### Placeholders

Sometimes a label alone doesn't convey enough information and you need to expose more. For those use-cases, use the **placeholder** property with or without the [label](#labeling) or [hint](#hint) properties.

In the following snippet, we improve the user experience of a `v-text-field` that is capturing an email address:

```html
<v-text-field
  label="Email address"
  placeholder="johndoe@gmail.com"
  type="email"
></v-text-field>
```

When the user focuses the input, the placeholder fades in as the label translates up. The added visual element improves the user experience when using multiple field inputs.

<example file="v-text-field/prop-placeholder" />

<alert type="info">

  Use the **persistent-placeholder** prop to force the **placeholder** to be visible, even when the input is not focused.

</alert>

#### Hints & messages

The **label** and **placeholder** props are useful for providing context to the input but are typically concise. For longer textual information, all Vuetify inputs contain a **details** section that is used to provide **hints**, regular **messages**, and **error-messages**. In the following example watch the custom hint message display when you focus the input:

<example file="v-text-field/prop-messages" open />

If you want to make the hint visible at all times, use the **persistent-hint** property. The following example demonstrates how to force a **hint** to show in the input's details:

```html
<v-text-field
  hint="Enter your password to access this website"
  label="Password"
  persistent-hint
  type="input"
></v-text-field>
```

In addition to **persistent-hint**, there are 3 other properties that support a persistent state:

* **persistent-clear** - always show the input clear icon when a **value** is present
* **persistent-counter** - always show input character length element
* **persistent-placeholder** - always show placeholder, causes label to automatically elevate

#### Clearable

The **clearable** prop appends an inner [v-icon](/components/icons/) that clears the `v-text-field` when clicked. When an input is cleared, it resets the current `v-text-field` value. The following example displays an interactive icon when the mouse hovers over the input:

<example file="v-text-field/prop-clearable" open />

Note that **readonly** will not remove the clear icon, to prevent readonly inputs from being cleared you should also disable **clearable**.

Sometimes you may need to perform an action when the user clears an input. By using a custom [Vue Event Handler](https://vuejs.org/guide/essentials/event-handling.html), you can bind a custom function that is invoked whenever the `v-text-field` is cleared by the user. The following example demonstrates how to use a a custom event handler to invoke the **onClear** method:

```html
<template>
  <v-text-field
    clearable
    label="Last name"
    placeholder="Doe"
    persistent-clear
    @click:clear="onClear"
  ></v-text-field>
</template>

<script>
  export default {
    methods: {
      onClear () {
        alert('User cleared the input')
      }
    }
  }
</script>
```

You can see more supported events on the `v-text-field` [API page](/api/v-text-field/#events).

#### Validation & rules

When working with inputs you often need to validate the user's input in some manner; i.e. Email, Password. Use the **rules** property to invoke custom functions based upon the `v-text-field`'s state. It accepts an array of **functions** that return either `true` or a `string`. In the following example, enter a value into the field and then clear it:

<example file="v-text-field/prop-rules" open />

#### Forms

Group multiple `v-text-field` components and other functionality within a `v-form` component; for a more detailed look at forms, please visit the [v-form](/components/forms/) page. Forms are useful for validating more than 1 input and make it easy to interact with the state of many fields at once. The following example combines multiple `v-text-field` components to create a login form:

<example file="v-text-field/misc-guide" />

### Examples

The following is a collection of `v-text-field` examples that demonstrate how different the properties work in an application.

#### Custom colors

The **color** prop provides an easy way to change the color of textual content; label, prefix, suffix, etc. This color is applied as long as `v-text-field` is focused.

<example file="v-text-field/prop-custom-colors" />

#### Density

The **density** prop decreases the height of the text field based upon 1 of 3 levels of density; **default**, **comfortable**, and **compact**.

<example file="v-text-field/prop-dense" />

#### Disabled and readonly

The state of a text field can be changed by providing the **disabled** or **readonly** props.

<example file="v-text-field/prop-disabled-and-readonly" />

#### Hide details

When **hide-details** is set to `auto` messages will be rendered only if there's a message (hint, error message, counter value etc) to display.

<example file="v-text-field/prop-hide-details" />

#### Hint

The **hint** property on text fields adds the provided string beneath the text field. Using **persistent-hint** keeps the hint visible when the text field is not focused. Hint prop is _**not**_ supported in solo mode.

<example file="v-text-field/prop-hint" />

#### Icons

You can add icons to the text field with **prepend-icon**, **append-icon** and **append-inner-icon** props.

<example file="v-text-field/prop-icon" />

#### Prefixes and suffixes

The **prefix** and **suffix** properties allows you to prepend and append inline non-modifiable text next to the text field.

<example file="v-text-field/prop-prefixes-and-suffixes" />

#### Validation

Vuetify includes simple validation through the **rules** prop. The prop accepts a mixed array of types `function`, `boolean` and `string`. When the input value changes, each element in the array will be validated. Functions pass the current v-model as an argument and must return either `true` / `false` or a `string` containing an error message.

<example file="v-text-field/prop-validation" />

#### Variant

The **variant** prop provides an easy way to customize the style of your text field. The following values are valid options: **solo**, **filled**, **outlined**, **plain**, and **underlined**.

<example file="v-text-field/prop-variant" />

### Events

#### Icon events

`click:prepend`, `click:append`, `click:append-inner`, and `click:clear` are emitted when you click on the respective icon. Note that these events will not be fired if the slot is used instead.

<example file="v-text-field/event-icons" />

### Slots

Slots allow you to customize the display of many `v-text-field` properties to modify what Vuetify does by default. The following slots are available on the `v-text-field` component:

| Slot name | Description |
| - | - |
| 1. prepend | Provided by `v-input`, positioned before the input field |
| 2. prepend-inner | Provided by `v-field`, positioned at the start of the input field |
| 3. label | The form input label |
| 4. append-inner | Provided by `v-field`, positioned at the end of the input field |
| 5. append | Provided by `v-input`, positioned after the input field |
| 6. details | Used for displaying **messages**, **hint**, **error-messages**, and more |

The following example uses the **label**, **prepend**, and **prepend-inner** slots and adds custom elements to the `v-text-field`

```html
<template>
  <v-text-field v-model="model">
    <template v-slot:label>
      <span>Type something...</span>
    </template>

    <template v-slot:prepend>
      <v-icon
        :color="model ? 'primary' : undefined"
        icon="$vuetify"
      />
    </template>

    <template v-slot:append-inner>
      <v-icon
        v-if="model"
        icon="mdi-check-circle"
      />
    </template>

    <template #details>
      <v-spacer />

      See our <a href="#">Terms and Service</a>
    </template>
  </v-text-field>
</template>

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

<vuetify />

#### Icon slots

Instead of using `prepend`/`append`/`append-inner` icons you can use slots to extend input's functionality.

<example file="v-text-field/slot-icons" />

#### Label

Text field label can be defined in `label` slot - that will allow to use HTML content

<example file="v-text-field/slot-label" />

#### Progress

You can display a progress bar instead of the bottom line. You can use the default indeterminate progress having same color as the text field or designate a custom one using the `progress` slot

<example file="v-text-field/slot-progress" />

### Misc

#### Custom validation

While the built in `v-form` or 3rd party plugin such as [vuelidate](https://github.com/monterail/vuelidate) or [vee-validation](https://github.com/logaretm/vee-validate) can help streamline your validation process, you can choose to simply control it yourself.

<example file="v-text-field/misc-custom-validation" />

#### Full width with counter

Full width text fields allow you to create boundless inputs. In this example, we use a `v-divider` to separate the fields.

<example file="v-text-field/misc-full-width-with-counter" />

#### Password input

Using the HTML input **type** [password](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/password) can be used with an appended icon and callback to control the visibility.

<example file="v-text-field/misc-password" />


padding-left: calc(var(--v-data-table-group-header-row-depth) * 16px) !important

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

// Components
import { VDataTableColumn } from './VDataTableColumn'
import { VCheckboxBtn } from '@/components/VCheckbox'
import { VIcon } from '@/components/VIcon'

// Composables
import { useHeaders } from './composables/headers'
import { useSelection } from './composables/select'
import { useSort } from './composables/sort'
import { useBackgroundColor } from '@/composables/color'
import { IconValue } from '@/composables/icons'
import { LoaderSlot, makeLoaderProps, useLoader } from '@/composables/loader'

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

// Types
import type { CSSProperties, UnwrapRef } from 'vue'
import type { provideSelection } from './composables/select'
import type { provideSort } from './composables/sort'
import type { InternalDataTableHeader } from './types'
import type { LoaderSlotProps } from '@/composables/loader'

export type HeadersSlotProps = {
  headers: InternalDataTableHeader[][]
  columns: InternalDataTableHeader[]
  sortBy: UnwrapRef<ReturnType<typeof provideSort>['sortBy']>
  someSelected: UnwrapRef<ReturnType<typeof provideSelection>['someSelected']>
  allSelected: UnwrapRef<ReturnType<typeof provideSelection>['allSelected']>
  toggleSort: ReturnType<typeof provideSort>['toggleSort']
  selectAll: ReturnType<typeof provideSelection>['selectAll']
  getSortIcon: (column: InternalDataTableHeader) => IconValue
  getFixedStyles: (column: InternalDataTableHeader, y: number) => CSSProperties | undefined
  isSorted: ReturnType<typeof provideSort>['isSorted']
}

type VDataTableHeaderCellColumnSlotProps = {
  column: InternalDataTableHeader
  selectAll: ReturnType<typeof provideSelection>['selectAll']
  isSorted: ReturnType<typeof provideSort>['isSorted']
  toggleSort: ReturnType<typeof provideSort>['toggleSort']
  sortBy: UnwrapRef<ReturnType<typeof provideSort>['sortBy']>
  someSelected: UnwrapRef<ReturnType<typeof provideSelection>['someSelected']>
  allSelected: UnwrapRef<ReturnType<typeof provideSelection>['allSelected']>
  getSortIcon: (column: InternalDataTableHeader) => IconValue
}

export type VDataTableHeadersSlots = {
  headers: HeadersSlotProps
  loader: LoaderSlotProps
  'column.data-table-select': VDataTableHeaderCellColumnSlotProps
  'column.data-table-expand': VDataTableHeaderCellColumnSlotProps
} & { [key: `column.${string}`]: VDataTableHeaderCellColumnSlotProps }

export const makeVDataTableHeadersProps = propsFactory({
  color: String,
  sticky: Boolean,
  multiSort: Boolean,
  sortAscIcon: {
    type: IconValue,
    default: '$sortAsc',
  },
  sortDescIcon: {
    type: IconValue,
    default: '$sortDesc',
  },

  ...makeLoaderProps(),
}, 'VDataTableHeaders')

export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
  name: 'VDataTableHeaders',

  props: makeVDataTableHeadersProps(),

  setup (props, { slots, emit }) {
    const { toggleSort, sortBy, isSorted } = useSort()
    const { someSelected, allSelected, selectAll, showSelectAll } = useSelection()
    const { columns, headers } = useHeaders()
    const { loaderClasses } = useLoader(props)

    const getFixedStyles = (column: InternalDataTableHeader, y: number): CSSProperties | undefined => {
      if (!props.sticky && !column.fixed) return undefined

      return {
        position: 'sticky',
        zIndex: column.fixed ? 4 : props.sticky ? 3 : undefined, // TODO: This needs to account for possible previous fixed columns.
        left: column.fixed ? convertToUnit(column.fixedOffset) : undefined, // TODO: This needs to account for possible row/colspan of previous columns
        top: props.sticky ? `calc(var(--v-table-header-height) * ${y})` : undefined,
      }
    }

    function getSortIcon (column: InternalDataTableHeader) {
      const item = sortBy.value.find(item => item.key === column.key)

      if (!item) return props.sortAscIcon

      return item.order === 'asc' ? props.sortAscIcon : props.sortDescIcon
    }

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

    const slotProps = computed(() => ({
      headers: headers.value,
      columns: columns.value,
      toggleSort,
      isSorted,
      sortBy: sortBy.value,
      someSelected: someSelected.value,
      allSelected: allSelected.value,
      selectAll,
      getSortIcon,
      getFixedStyles,
    } satisfies HeadersSlotProps))

    const VDataTableHeaderCell = ({ column, x, y }: { column: InternalDataTableHeader, x: number, y: number }) => {
      const noPadding = column.key === 'data-table-select' || column.key === 'data-table-expand'

      return (
        <VDataTableColumn
          tag="th"
          align={ column.align }
          class={[
            'v-data-table__th',
            {
              'v-data-table__th--sortable': column.sortable,
              'v-data-table__th--sorted': isSorted(column),
            },
            loaderClasses.value,
          ]}
          style={{
            width: convertToUnit(column.width),
            minWidth: convertToUnit(column.width),
            ...getFixedStyles(column, y),
          }}
          colspan={ column.colspan }
          rowspan={ column.rowspan }
          onClick={ column.sortable ? () => toggleSort(column) : undefined }
          lastFixed={ column.lastFixed }
          noPadding={ noPadding }
        >
          {{
            default: () => {
              const columnSlotName = `column.${column.key}` as const
              const columnSlotProps: VDataTableHeaderCellColumnSlotProps = {
                column,
                selectAll,
                isSorted,
                toggleSort,
                sortBy: sortBy.value,
                someSelected: someSelected.value,
                allSelected: allSelected.value,
                getSortIcon,
              }

              if (slots[columnSlotName]) return slots[columnSlotName]!(columnSlotProps)

              if (column.key === 'data-table-select') {
                return slots['column.data-table-select']?.(columnSlotProps) ?? (showSelectAll && (
                  <VCheckboxBtn
                    modelValue={ allSelected.value }
                    indeterminate={ someSelected.value && !allSelected.value }
                    onUpdate:modelValue={ selectAll }
                  />
                ))
              }

              return (

                  { column.title }
                  { column.sortable && (
                    <VIcon
                      key="icon"
                      class="v-data-table-header__sort-icon"
                      icon={ getSortIcon(column) }
                    />
                  )}
                  { props.multiSort && isSorted(column) && (

                      { sortBy.value.findIndex(x => x.key === column.key) + 1 }

                  )}

              )
            },
          }}
        </VDataTableColumn>
      )
    }

    useRender(() => {
      return (
        <>
          { slots.headers
            ? slots.headers(slotProps.value)
            : headers.value.map((row, y) => (

              { row.map((column, x) => (
                <VDataTableHeaderCell column={ column } x={ x } y={ y } />
              ))}

            ))}

          { props.loading && (

                <LoaderSlot
                  name="v-data-table-headers"
                  active
                  color={ typeof props.loading === 'boolean' ? undefined : props.loading }
                  indeterminate
                  v-slots={{ default: slots.loader }}
                />

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


export * from './anchor'
export * from './animation'
export * from './colorUtils'
export * from './console'
export * from './createSimpleFunctional'
export * from './defineComponent'
export * from './dom'
export * from './easing'
export * from './getCurrentInstance'
export * from './getScrollParent'
export * from './globals'
export * from './helpers'
export * from './injectSelf'
export * from './isFixedPosition'
export * from './propsFactory'
export * from './useRender'


---
meta:
  nav: Footers
  title: Footer component
  description: The footer component provides a container for displaying additional navigation information about a site.
  keywords: footers, vuetify footer component, vue footer component
related:
  - /components/grids
  - /components/buttons
  - /components/toolbars
---

# Footers

The `v-footer` component is used for displaying general information that a user might want to access from any page within your site.

## Usage

The `v-footer` component in its simplest form is a container.

<usage name="v-footer" />

<entry />

## API

| Component | Description |
| - | - |
| [v-footer](/api/v-footer/) | The footer component. |

<api-inline hide-links />

## Examples

### Misc

#### Company Footer

The footer component as a basic company footer with links.

<example file="v-footer/misc-company-footer" />

#### Indigo Footer

The footer component with Indigo background color and social media icons and button.

<example file="v-footer/misc-indigo-footer" />

#### Teal Footer

The footer component with a Teal color header and columns and rows of links.

<example file="v-footer/misc-teal-footer" />


<template>
  <v-card>
    <v-card-text>
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi, ratione debitis quis est labore voluptatibus! Eaque cupiditate minima, at placeat totam, magni doloremque veniam neque porro libero rerum unde voluptatem!
    </v-card-text>

    <v-card-title>Title</v-card-title>
  </v-card>
</template>


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

.v-data-table-progress
> th
border: none !important
height: auto !important
padding: 0 !important


<template>
  <usage-example
    v-model="model"
    :code="code"
    :options="options"
    :name="name"
  >
    <div>

      <v-card v-bind="props">
        <template v-slot:text>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi, ratione debitis quis est labore voluptatibus! Eaque cupiditate minima, at placeat totam, magni doloremque veniam neque porro libero rerum unde voluptatem!
        </template>

        <v-card-actions v-if="actions">
          <v-btn>Click me</v-btn>
        </v-card-actions>
      </v-card>
    </div>

    <template v-slot:configuration>
      <v-checkbox v-model="title" label="Show title"></v-checkbox>

      <v-checkbox v-model="subtitle" label="Show subtitle"></v-checkbox>

      <v-checkbox v-model="actions" label="Show actions"></v-checkbox>

      <v-checkbox v-model="loading" label="Loading"></v-checkbox>
    </template>
  </usage-example>
</template>

<script setup>
  // Utilities
  import { computed, ref } from 'vue'
  import { propsToString } from '@/util/helpers'

  const name = 'v-card'
  const model = ref('default')
  const actions = ref(false)
  const loading = ref(false)
  const subtitle = ref(false)
  const title = ref(false)
  const options = ['outlined', 'tonal']
  const props = computed(() => {
    return {
      loading: loading.value || undefined,
      title: title.value ? 'Card title' : undefined,
      subtitle: subtitle.value ? 'Subtitle' : undefined,
      text: '...',
      variant: ['outlined', 'tonal'].includes(model.value) ? model.value : undefined,
    }
  })

  const slots = computed(() => {
    let str = ''

    if (actions.value) {
      str += `
  <v-card-actions>
    <v-btn>Click me</v-btn>
  </v-card-actions>
`
    }

    return str
  })

  const code = computed(() => {
    return `<${name}${propsToString(props.value)}>${slots.value}</${name}>`
  })
</script>


<template>
  <v-card
    v-click-outside="{
      handler: onClickOutside,
      include
    }"
    :color="active ? 'primary' : undefined"
    :dark="active"
    class="mx-auto"
    height="256"
    rounded="xl"
    width="256"
    @click="active = true"
  >

      {{ active ? 'Click Outside' : 'Click Me' }}

  </v-card>

    <v-card
      rounded="lg"
      class="ma-2 included"
    >
      <v-card-text class="text-h6">
        This element is included
      </v-card-text>
    </v-card>

    <v-card
      rounded="lg"
      class="ma-2"
    >
      <v-card-text class="text-h6">
        This element is not included
      </v-card-text>
    </v-card>

</template>


<template>
  <v-treeview
    shaped
    hoverable
    activatable
    :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>


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

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

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,
        'theme-kbd': '#212529',
        'theme-on-kbd': '#FFFFFF',
        'theme-code': '#F5F5F5',
        'theme-on-code': '#000000',
      },
    },
    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': 1,
        'medium-emphasis-opacity': 0.70,
        'disabled-opacity': 0.50,
        '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,
        'theme-kbd': '#212529',
        'theme-on-kbd': '#FFFFFF',
        'theme-code': '#343434',
        'theme-on-code': '#CCCCCC',
      },
    },
  },
}

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 || key === '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 (options?: ThemeOptions): ThemeInstance & { install: (app: App) => void } {
  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]

          if (!color) continue

          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}`] = RGBtoHex(fn(parseColor(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 = parseColor(theme.colors[color]!)

        const blackContrast = Math.abs(APCAcontrast(parseColor(0), colorVal))
        const whiteContrast = Math.abs(APCAcontrast(parseColor(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'])
    }

    createCssClass(lines, ':root', genCssVariables(current.value))

    for (const [themeName, theme] of Object.entries(computedThemes.value)) {
      createCssClass(lines, `.v-theme--${themeName}`, [
        `color-scheme: ${theme.dark ? 'dark' : 'normal'}`,
        ...genCssVariables(theme),
      ])
    }

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

    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(fgLines, `.${key}`, [`color: rgb(var(--v-theme-${key})) !important`])
      } else {
        createCssClass(bgLines, `.bg-${key}`, [
          `--v-theme-overlay-multiplier: var(--v-theme-${key}-overlay-multiplier)`,
          `background-color: rgb(var(--v-theme-${key})) !important`,
          `color: rgb(var(--v-theme-on-${key})) !important`,
        ])
        createCssClass(fgLines, `.text-${key}`, [`color: rgb(var(--v-theme-${key})) !important`])
        createCssClass(fgLines, `.border-${key}`, [`--v-border-color: var(--v-theme-${key})`])
      }
    }

    lines.push(...bgLines, ...fgLines)

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

  function getHead () {
    return {
      style: [{
        children: styles.value,
        id: 'vuetify-theme-stylesheet',
        nonce: parsedOptions.cspNonce || false as never,
      }],
    }
  }

  function install (app: App) {
    const head = app._context.provides.usehead as HeadClient | undefined
    if (head) {
      if (head.push) {
        const entry = head.push(getHead)
        watch(styles, () => { entry.patch(getHead) })
      } else {
        if (IN_BROWSER) {
          head.addHeadObjs(computed(getHead))
          watchEffect(() => head.updateDOM())
        } else {
          head.addHeadObjs(getHead())
        }
      }
    } 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 {
    install,
    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 = parseColor(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}`)
    }
  }

  for (const [key, value] of Object.entries(theme.variables)) {
    const color = typeof value === 'string' && value.startsWith('#') ? parseColor(value) : undefined
    const rgb = color ? `${color.r}, ${color.g}, ${color.b}` : undefined
    variables.push(`--v-${key}: ${rgb ?? value}`)
  }

  return variables
}