_   = require('lodash')
DOM = require('./dom')

Utils =
  findDeepestNode: (node, offset) ->
    while node.firstChild?
      [node, offset] = Utils.getChildAtOffset(node, offset)
    return [node, offset]

  getChildAtOffset: (node, offset) ->
    child = node.firstChild
    length = Utils.getNodeLength(child)
    while child?
      break if offset < length
      offset -= length
      child = child.nextSibling
      length = Utils.getNodeLength(child)
    unless child?
      child = node.lastChild
      offset = Utils.getNodeLength(child)
    return [child, offset]

  getNodeLength: (node) ->
    return 0 unless node?
    length = DOM.getText(node).length
    if DOM.isElement(node)
      length += node.querySelectorAll(_.keys(DOM.EMBED_TAGS).join(',')).length
    return length

  isIE: (maxVersion) ->
    version = document.documentMode
    return version and maxVersion >= version

  # refNode is node after split point, root is parent of eldest node we want split (root will not be split)
  splitAncestors: (refNode, root) ->
    return refNode if refNode == root or refNode.parentNode == root
    parentNode = refNode.parentNode
    parentClone = parentNode.cloneNode(false)
    parentNode.parentNode.insertBefore(parentClone, parentNode.nextSibling)
    while refNode?
      nextNode = refNode.nextSibling
      parentClone.appendChild(refNode)
      refNode = nextNode
    return Utils.splitAncestors(parentClone, root)

  splitNode: (node, offset, force = false) ->
    # Check if split necessary
    nodeLength = Utils.getNodeLength(node)
    offset = Math.max(0, offset)
    offset = Math.min(offset, nodeLength)
    return [node.previousSibling, node, false] unless force or offset != 0
    return [node, node.nextSibling, false] unless force or offset != nodeLength
    if node.nodeType == DOM.TEXT_NODE
      after = node.splitText(offset)
      return [node, after, true]
    else
      left = node
      right = node.cloneNode(false)
      node.parentNode.insertBefore(right, left.nextSibling)
      [child, offset] = Utils.getChildAtOffset(node, offset)
      [childLeft, childRight] = Utils.splitNode(child, offset)
      while childRight != null
        nextRight = childRight.nextSibling
        right.appendChild(childRight)
        childRight = nextRight
      return [left, right, true]

module.exports = Utils


format: new Quill.Format(document, Quill.Format.FORMATS.align)

format: new Quill.Format(document, { class: 'author-' })

module.exports = (grunt) ->
  grunt.config('browserify',
    options:
      alias: [
        'node_modules/eventemitter2/lib/eventemitter2.js:eventemitter2'
        'node_modules/lodash/lodash.js:lodash'
        'node_modules/tandem-core/build/tandem-core.js:tandem-core'
        'node_modules/underscore.string/lib/underscore.string.js:underscore.string'
      ]
      browserifyOptions:
        extensions: ['.js', '.coffee']
      transform: ['coffeeify']
    'quill':
      options:
        bundleOptions:
          standalone: 'Quill'
      files: [
        { 'build/quill.js': ['index.coffee'] }
        { 'build/quill.exposed.js': ['test/quill.coffee'] }
      ]
    'tandem':
      options:
        bundleOptions:
          standalone: 'Tandem'
      files: [{ 'build/tandem-core.js': ['node_modules/tandem-core/index.js'] }]
    'quill-watchify':
      options:
        bundleOptions:
          standalone: 'Quill'
        keepAlive: true
        watch: true
      files: [{ 'build/quill.js': ['index.coffee'] }]
    'quill-exposed-watchify':
      options:
        bundleOptions:
          standalone: 'Quill'
        keepAlive: true
        watch: true
      files: [{ 'build/quill.exposed.js': ['test/quill.coffee'] }]
  )

  grunt.config('clean',
    all: ['build']
    coverage: ['src/**/*.js']
  )

  grunt.config('coffee',
    all:
      expand: true
      dest: 'build/'
      src: ['demo/scripts/*.coffee', 'test/**/*.coffee']
      ext: '.js'
    coverage:
      expand: true
      dest: 'build/'
      src: ['src/**/*.coffee']
      ext: '.js'
  )

  grunt.config('concat',
    options:
      banner:
        '/*! Stypi Editor - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' +
        ' *  https://quilljs.com/\n' +
        ' *  Copyright (c) <%= grunt.template.today("yyyy") %>\n' +
        ' *  Jason Chen, Salesforce.com\n' +
        ' */\n\n'
    quill:
      'build/quill.js': ['build/quill.js']
      'build/quill.min.js': ['build/quill.min.js']
  )

  grunt.config('copy'
    build:
      expand: true
      dest: 'build/'
      src: ['lib/*.js', 'demo/images/*.png']
  )

  grunt.config('jade',
    all:
      options:
        pretty: true
      dest: 'build/'
      expand: true
      ext: '.html'
      src: ['demo/*.jade', 'test/fixtures/*.jade', '!demo/content.jade']
  )

  grunt.config('stylus',
    options:
      compress: false
    themes:
      options:
        urlfunc: 'url'
      files: [{
        expand: true
        ext: '.css'
        flatten: true
        src: 'src/themes/**/*.styl'
        rename: (dest, src) ->
          return "build/themes/quill.#{src}"
      }]
    demo:
      expand: true
      ext: '.css'
      dest: 'build/'
      src: ['demo/styles/*.styl']
  )

  grunt.config('uglify',
    quill:
      files: { 'build/quill.min.js': ['build/quill.js'] }
  )


class DefaultTheme
  @OPTIONS: {}

  constructor: (@quill) ->
    @editor = @quill.editor
    @editorContainer = @editor.root

module.exports = DefaultTheme


color: rgba($material.fg-color, $material.secondary-text-percent) !important

keep keep keep keep keep replace replace keep keep

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

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

theme(v-toolbar, "v-toolbar")

.v-toolbar
  bootable()
  elevation(4)

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

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

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

    label
      top: auto

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

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

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

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

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

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

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

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

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

    .v-btn
      align-items: stretch

    .v-tooltip,
    .v-tooltip > span
      height: inherit

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

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

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

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

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

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

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

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

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

  &--absolute
    position: absolute
    z-index 2

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

  &--clipped
    z-index: 3

  &--prominent
    v-toolbar-content(15px)

  &--dense
    v-toolbar-content(7px)

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

    > *:last-child
      margin-right: left


export default function Grid (name) {
  return {
    name: `v-${name}`,

    functional: true,

    props: {
      id: String,
      tag: {
        type: String,
        default: 'div'
      }
    },

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

      if (data.attrs) {
        const classes = Object.keys(data.attrs).filter(key => {
          // TODO: Remove once resolved
          // https://github.com/vuejs/vue/issues/7841
          if (key === 'slot') return false

          const value = data.attrs[key]
          return value || typeof value === 'string'
        })

        if (classes.length) data.staticClass += ` ${classes.join(' ')}`
        delete data.attrs
      }

      if (props.id) {
        data.domProps = data.domProps || {}
        data.domProps.id = props.id
      }

      return h(props.tag, data, children)
    }
  }
}


import '../../stylus/components/_time-picker-title.styl'

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

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

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

  mixins: [PickerButton],

  props: {
    ampm: Boolean,
    hour: Number,
    minute: Number,
    period: {
      type: String,
      validator: period => period === 'am' || period === 'pm'
    },
    selectingHour: Boolean
  },

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

      const displayedHour = this.hour == null ? '--' : this.ampm ? hour : pad(hour)
      const displayedMinute = this.minute == null ? '--' : pad(this.minute)

      return this.$createElement('div', {
        'class': 'v-time-picker-title__time'
      }, [
        this.genPickerButton('selectingHour', true, displayedHour),
        this.$createElement('span', ':'),
        this.genPickerButton('selectingHour', false, displayedMinute)
      ])
    },
    genAmPm () {
      return this.$createElement('div', {
        staticClass: 'v-time-picker-title__ampm'
      }, [
        this.genPickerButton('period', 'am', 'am'),
        this.genPickerButton('period', 'pm', 'pm')
      ])
    }
  },

  render (h) {
    return h('div', {
      staticClass: 'v-time-picker-title'
    }, [
      this.genTime(),
      this.ampm ? this.genAmPm() : null
    ])
  }
}


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

exports[`VBtnToggle.vue should allow deselecting a value when mandatory prop is used with multiple prop 1`] = `

  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--active v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--active v-btn--flat"
  >

        add

  </button>

`;

exports[`VBtnToggle.vue should allow new value when mandatory prop is used 1`] = `

  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--active v-btn--flat"
          data-only-child="true"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>

`;

exports[`VBtnToggle.vue should allow new value when mandatory prop is used with multiple prop 1`] = `

  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--active v-btn--flat"
          data-only-child="true"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>

`;

exports[`VBtnToggle.vue should not allow empty value when mandatory prop is used 1`] = `

  <button type="button"
          class="v-btn v-btn--active v-btn--flat"
          data-only-child="true"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>

`;

exports[`VBtnToggle.vue should not allow empty value when mandatory prop is used with multiple prop 1`] = `

  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--active v-btn--flat"
          data-only-child="true"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>

`;

exports[`VBtnToggle.vue should toggle values of any type 1`] = `

  <button type="button"
          value="true"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          value="6"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          value="foo"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          value="{"key":"value"}"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          value="["arrayyy"]"
          class="v-btn v-btn--flat"
  >

        add

  </button>

`;

exports[`VBtnToggle.vue should use button value attribute if available 1`] = `

  <button type="button"
          value="left"
          class="v-btn v-btn--flat"
  >

        add

  </button>
  <button type="button"
          value="center"
          class="v-btn v-btn--active v-btn--flat"
          data-only-child="true"
  >

        add

  </button>
  <button type="button"
          value="right"
          class="v-btn v-btn--flat"
  >

        add

  </button>

`;


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

v-dataiterator($material)
  .v-data-iterator__actions
    color: rgba($material.fg-color, $material.secondary-text-percent)

    &__select
      .input-group--select
        .input-group__selections__comma
          color: rgba($material.fg-color, $material.secondary-text-percent) !important

        .input-group__append-icon
          color: rgba($material.fg-color, $material.secondary-text-percent) !important

theme(v-dataiterator, "v-data-iterator")

/** Actions */
.v-data-iterator__actions
  display: flex
  justify-content: flex-end
  align-items: center
  font-size: 12px
  flex-wrap: wrap-reverse

  .v-btn
    color: inherit

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

  &__range-controls
    display: flex
    align-items: center
    min-height: 48px

  &__pagination
    display: block
    text-align: center
    margin: 0 32px 0 24px

  &__select
    display: flex
    align-items: center
    justify-content: center
    margin-right: 14px

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

      .input-group__selections__comma
        font-size: 12px


for $size, $media_query in $display-breakpoints
  .hidden
    &-{$size}
      @media $media_query
        display: none !important

.overflow-hidden
  overflow: hidden

.overflow-x-hidden
  overflow-x: hidden

.overflow-y-hidden
  overflow-y: hidden


// Mixins
import Routable from '../../mixins/routable'
import {
  inject as RegistrableInject
} from '../../mixins/registrable'

// Utilities
import { getObjectValueByPath } from '../../util/helpers'

export default {
  name: 'v-tab',

  mixins: [
    RegistrableInject('tabs', 'v-tab', 'v-tabs'),
    Routable
  ],

  inject: ['tabClick'],

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

  props: {
    activeClass: {
      type: String,
      default: 'v-tabs__item--active'
    },
    ripple: {
      type: [Boolean, Object],
      default: true
    }
  },

  computed: {
    classes () {
      return {
        'v-tabs__item': true,
        'v-tabs__item--disabled': this.disabled,
        [this.activeClass]: !this.to && this.isActive
      }
    },
    action () {
      let to = this.to || this.href

      if (this.$router &&
        this.to === Object(this.to)
      ) {
        const resolve = this.$router.resolve(
          this.to,
          this.$route,
          this.append
        )

        to = resolve.href
      }

      return typeof to === 'string'
        ? to.replace('#', '')
        : this
    }
  },

  watch: {
    $route: 'onRouteChange'
  },

  mounted () {
    this.tabs.register(this)
    this.onRouteChange()
  },

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

  methods: {
    click (e) {
      // If user provides an
      // actual link, do not
      // prevent default
      if (this.href &&
        this.href.indexOf('#') > -1
      ) e.preventDefault()

      this.$emit('click', e)

      this.to || this.tabClick(this)
    },
    onRouteChange () {
      if (!this.to || !this.$refs.link) return

      const path = `_vnode.data.class.${this.activeClass}`

      this.$nextTick(() => {
        if (getObjectValueByPath(this.$refs.link, path)) {
          this.tabClick(this)
        }
      })
    },
    toggle (action) {
      this.isActive = (action === this) || (action === this.action)
    }
  },

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

    // If disabled, use div as anchor tags do not support
    // being disabled
    const tag = this.disabled ? 'div' : link.tag

    data.ref = 'link'

    return h('div', {
      staticClass: 'v-tabs__div'
    }, [h(tag, data, this.$slots.default)])
  }
}


import VBtnToggle from './VBtnToggle'

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

export default VBtnToggle


justify-content: center
margin-right: 14px
.input-group--select
margin: 13px 0 13px 34px
padding: 0
position: initial

keep keep keep replace keep keep keep keep

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

/* Theme */
v-datatable($material)
  thead
    th.column.sortable
      i
        color: rgba($material.fg-color, $material.disabledORhints-text-percent)

      &:hover
        color: rgba($material.fg-color, $material.primary-text-percent)

      &.active
        color: rgba($material.fg-color, $material.primary-text-percent)

        i
          color: rgba($material.fg-color, $material.primary-text-percent)

  .v-datatable__actions
    background-color: $material.cards
    color: rgba($material.fg-color, $material.secondary-text-percent)
    border-top: 1px solid rgba($material.fg-color, $material.divider-percent)

    &__select
      .input-group--select
        .input-group__selections__comma
          color: rgba($material.fg-color, $material.secondary-text-percent) !important

        .input-group__append-icon
          color: rgba($material.fg-color, $material.secondary-text-percent) !important

theme(v-datatable, "v-datatable")

.v-datatable
  .v-input
    margin: 0

  thead
    th.column.sortable
      cursor: pointer
      outline: 0

      i
        font-size: 16px
        vertical-align: sub
        display: inline-block
        opacity: 0
        transition: .3s $transition.swing

      &:focus,
      &:hover
        i
          opacity: .6

      &.active
        transform: none

        i
          opacity: 1

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

/** Actions */
.v-datatable__actions
  display: flex
  justify-content: flex-end
  align-items: center
  font-size: 12px
  flex-wrap: wrap-reverse

  .v-btn
    color: inherit

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

  &__range-controls
    display: flex
    align-items: center
    min-height: 48px

  &__pagination
    display: block
    text-align: center
    margin: 0 32px 0 24px

  &__select
    display: flex
    align-items: center
    justify-content: flex-end
    margin-right: 14px
    white-space: nowrap

    .v-select
      flex: 0 1 0
      margin: 13px 0 13px 34px
      padding: 0
      position: initial

      .v-select__selection--comma
        font-size: 12px

.v-datatable__progress
  height: auto !important

  tr, td, th
    height: auto !important

  th
    padding: 0 !important

    .v-progress-linear
      top: -2px
      margin: 0 0 -2px

.v-datatable__expand
  &-row
    border: none !important

  &-col
    padding: 0 !important
    height: 0px !important

    &--expanded
      border-bottom: 1px solid rgba(0,0,0,0.12)

  &-content
    transition: height $primary-transition

    > .card
      border-radius: 0
      box-shadow: none


// Styles
import '../../stylus/components/_labels.styl'

// Mixins
import Colorable from '../../mixins/colorable'

// Helpers
import { convertToUnit } from '../../util/helpers'

export default {
  functional: true,

  name: 'v-label',

  props: {
    absolute: Boolean,
    color: {
      type: [Boolean, String],
      default: 'primary'
    },
    disabled: Boolean,
    focused: Boolean,
    for: String,
    left: {
      type: [Number, String],
      default: 0
    },
    right: {
      type: [Number, String],
      default: 'auto'
    },
    value: Boolean
  },

  render (h, { children, listeners, props }) {
    const data = {
      staticClass: 'v-label',
      'class': {
        'v-label--active': props.value,
        'v-label--is-disabled': props.disabled
      },
      attrs: {
        for: props.for,
        'aria-hidden': !props.for
      },
      on: listeners,
      style: {
        left: convertToUnit(props.left),
        right: convertToUnit(props.right),
        position: props.absolute ? 'absolute' : 'relative'
      }
    }

    if (props.focused) {
      data.class = Colorable.options.methods.addTextColorClassChecks(data.class, props.color)
    }

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


import { test } from '@/test'
import VApp from '@/components/VApp'
import Detachable from '@/mixins/detachable'

const Mock = () => ({
  name: 'mock',

  mixins: [Detachable],

  render (h) {
    const content = h('div', {
      staticClass: 'content',
      ref: 'content'
    })

    return h('div', {
      staticClass: 'mock'
    }, [this.$slots.default, content])
  }
})

test('detachable.js', ({ mount }) => {
  it('should detach to app', async () => {
    const localMock = Mock()
    const wrapper = mount(VApp, {
      attachToDocument: true,
      slots: {
        default: [{
          render: h => h(localMock)
        }]
      }
    })

    const detach = wrapper.find(localMock)[0]

    expect(detach.vm.hasDetached).toBe(true)

    wrapper.destroy()
  })

  it('should not detach when lazy', async () => {
    const localMock = Mock()
    const wrapper = mount(VApp, {
      attachToDocument: true,
      slots: {
        default: [{
          render: h => h(localMock, {
            props: { lazy: true }
          })
        }]
      }
    })

    const detach = wrapper.find(localMock)[0]

    expect(detach.vm.hasDetached).toBe(false)

    wrapper.destroy()
  })

  it('should attach and detach', () => {
    const localMock = Mock()
    const elementMock = mount(Mock())
    const wrapper = mount(localMock, {
      attachToDocument: true,
      propsData: {
        attach: ''
      },
      slots: {
        default: [{
          render: h => h('div', { staticClass: 'foo' })
        }]
      }
    })

    expect('[Vuetify] Unable to locate target [data-app] in "mock"').toHaveBeenTipped()

    expect(wrapper.vm.initDetach()).toBe(undefined)

    wrapper.setProps({ attach: true })

    expect(wrapper.vm.initDetach()).toBe(undefined)

    wrapper.setProps({ attach: 'attach' })

    expect(wrapper.vm.initDetach()).toBe(undefined)

    wrapper.setProps({ attach: elementMock.vm.$el })

    wrapper.vm.initDetach()

    expect(wrapper.vm.hasDetached).toBe(true)

    wrapper.setData({ hasDetached: false })

    wrapper.setProps({ attach: '.foo' })

    wrapper.vm.initDetach()

    expect(wrapper.vm.hasDetached).toBe(true)

    wrapper.setData({ hasDetached: false })

    wrapper.setProps({ attach: '.bar' })

    wrapper.vm.initDetach()

    expect('[Vuetify] Unable to locate target .bar in "mock"').toHaveBeenTipped()

    delete wrapper.vm.$refs.content
    wrapper.vm.$destroy()
  })

  it('should validate attach prop', () => {
    const validator = Detachable.props.attach.validator

    expect(validator(true)).toBe(true)
    expect(validator(false)).toBe(true)
    expect(validator('foo')).toBe(true)
    expect(validator({ nodeType: Node.ELEMENT_NODE })).toBe(true)
    expect(validator({})).toBe(false)
  })
})


import VTimePickerClock from '@/components/VTimePicker/VTimePickerClock'
import { test, touch } from '@/test'

test('VTimePickerClock.js', ({ mount }) => {
  it('should render component', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        allowedValues: n => n % 2,
        max: 59,
        min: 0,
        size: 280,
        step: 5,
        value: 10
      }
    })

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

  it('should render component with double prop', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        allowedValues: n => n % 2,
        double: true,
        max: 59,
        min: 0,
        size: 280,
        step: 5,
        value: 10
      }
    })

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

  it('should emit input event on wheel if scrollable', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        max: 59,
        min: 3,
        value: 59,
        scrollable: true
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)
    wrapper.trigger('wheel')
    expect(input).toBeCalledWith(3)
  })

  it('should not emit input event on wheel if not scrollable', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        max: 59,
        min: 3,
        value: 59,
        scrollable: false
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)
    wrapper.trigger('wheel')
    expect(input).not.toBeCalled()
  })

  it('should emit change event on mouseup/touchend', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        value: 59,
        min: 0,
        max: 60
      }
    })

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

    wrapper.trigger('mouseup')
    expect(change).toBeCalledWith(59)

    wrapper.setProps({ value: 55 })
    wrapper.trigger('touchend')
    expect(change).toBeCalledWith(55)
  })

  it('should emit change event on mouseleave', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        value: 59,
        min: 0,
        max: 60
      }
    })

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

    wrapper.trigger('mouseleave')
    expect(change).not.toBeCalled()

    wrapper.vm.isDragging = true
    wrapper.trigger('mouseleave')
    expect(change).toBeCalledWith(59)
  })

  it('should calculate angle', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        value: 59,
        min: 0,
        max: 60
      }
    })

    const center = { x: 1, y: 1 }
    const angle = p => Math.round(wrapper.vm.angle(center, p))
    expect(angle({ x: 2, y: 1 })).toBe(90)
    expect(angle({ x: 2, y: 2 })).toBe(45)
    expect(angle({ x: 0, y: 2 })).toBe(315)
    expect(angle({ x: 0, y: 0 })).toBe(225)
    expect(angle({ x: 2, y: 0 })).toBe(135)
  })

  it('should change with touch move', () => {
    const wrapper = mount(VTimePickerClock, {
      propsData: {
        min: 0,
        max: 7,
        value: 0,
        size: 300,
        double: true
      }
    })

    wrapper.vm.$refs.clock.getBoundingClientRect = () => {
      return {
        width: 300,
        height: 300,
        top: 0,
        left: 0,
        right: 300,
        bottom: 300,
        x: 0,
        y: 0
      }
    }

    const input = jest.fn()
    const finger = touch(wrapper).start(0, 0)
    wrapper.vm.$on('input', input)

    finger.move(300, 150)
    expect(input).toBeCalledWith(1)
    finger.move(150, 300)
    expect(input).toBeCalledWith(2)
    finger.move(150, 200)
    expect(input).toBeCalledWith(6)

    // edge case
    finger.move(120, 0)
    expect(input).toBeCalledWith(0)
    finger.move(135, 90)
    expect(input).toBeCalledWith(7)
  })
})


padding: 0
position: initial
.input-group__selections__comma
font-size: 12px


import '../../stylus/components/_date-picker-years.styl'

// Mixins
import Colorable from '../../mixins/colorable'

// Utils
import { createNativeLocaleFormatter } from './util'

export default {
  name: 'v-date-picker-years',

  mixins: [Colorable],

  data () {
    return {
      defaultColor: 'primary'
    }
  },

  props: {
    format: {
      type: Function,
      default: null
    },
    locale: {
      type: String,
      default: 'en-us'
    },
    min: [Number, String],
    max: [Number, String],
    value: [Number, String]
  },

  computed: {
    formatter () {
      return this.format || createNativeLocaleFormatter(this.locale, { year: 'numeric', timeZone: 'UTC' }, { length: 4 })
    }
  },

  mounted () {
    this.$el.scrollTop = this.$el.scrollHeight / 2 - this.$el.offsetHeight / 2
  },

  methods: {
    genYearItem (year) {
      const formatted = this.formatter(`${year}`)

      return this.$createElement('li', {
        key: year,
        'class': parseInt(this.value, 10) === year
          ? this.addTextColorClassChecks({ active: true })
          : {},
        on: {
          click: () => this.$emit('input', year)
        }
      }, formatted)
    },
    genYearItems () {
      const children = []
      const selectedYear = this.value ? parseInt(this.value, 10) : new Date().getFullYear()
      const maxYear = this.max ? parseInt(this.max, 10) : (selectedYear + 100)
      const minYear = Math.min(maxYear, this.min ? parseInt(this.min, 10) : (selectedYear - 100))

      for (let year = maxYear; year >= minYear; year--) {
        children.push(this.genYearItem(year))
      }

      return children
    }
  },

  render () {
    return this.$createElement('ul', {
      staticClass: 'v-date-picker-years',
      ref: 'years'
    }, this.genYearItems())
  }
}


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

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

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="22.857142857142858 22.857142857142858 45.714285714285715 45.714285714285715"
  >
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="0"
            class="v-progress-circular__underlay"
    >
    </circle>
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="84.19468311620646px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

      content

`;

exports[`VProgressCircular.js should render component with button prop and match snapshot 1`] = `

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="22.857142857142858 22.857142857142858 45.714285714285715 45.714285714285715"
  >
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="0"
            class="v-progress-circular__underlay"
    >
    </circle>
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="84.19468311620646px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

`;

exports[`VProgressCircular.js should render component with color prop and match snapshot 1`] = `

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="22.857142857142858 22.857142857142858 45.714285714285715 45.714285714285715"
  >
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="0"
            class="v-progress-circular__underlay"
    >
    </circle>
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="84.19468311620646px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

`;

exports[`VProgressCircular.js should render component with fill prop and match snapshot 1`] = `

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="22.857142857142858 22.857142857142858 45.714285714285715 45.714285714285715"
  >
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="0"
            class="v-progress-circular__underlay"
    >
    </circle>
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="84.19468311620646px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

`;

exports[`VProgressCircular.js should render component with indeterminate prop and match snapshot 1`] = `

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="22.857142857142858 22.857142857142858 45.714285714285715 45.714285714285715"
  >
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="125.66370614359172px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

`;

exports[`VProgressCircular.js should render component with rotate prop and match snapshot 1`] = `

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="22.857142857142858 22.857142857142858 45.714285714285715 45.714285714285715"
  >
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="0"
            class="v-progress-circular__underlay"
    >
    </circle>
    <circle fill="transparent"
            cx="45.714285714285715"
            cy="45.714285714285715"
            r="20"
            stroke-width="5.714285714285714"
            stroke-dasharray="125.664"
            stroke-dashoffset="84.19468311620646px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

`;

exports[`VProgressCircular.js should render component with size prop and match snapshot 1`] = `

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="26.153846153846157 26.153846153846157 52.307692307692314 52.307692307692314"
  >
    <circle fill="transparent"
            cx="52.307692307692314"
            cy="52.307692307692314"
            r="20"
            stroke-width="12.307692307692308"
            stroke-dasharray="125.664"
            stroke-dashoffset="0"
            class="v-progress-circular__underlay"
    >
    </circle>
    <circle fill="transparent"
            cx="52.307692307692314"
            cy="52.307692307692314"
            r="20"
            stroke-width="12.307692307692308"
            stroke-dasharray="125.664"
            stroke-dashoffset="84.19468311620646px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

`;

exports[`VProgressCircular.js should render component with width prop and match snapshot 1`] = `

  <svg xmlns="http://www.w3.org/2000/svg"
       viewbox="33.68421052631579 33.68421052631579 67.36842105263158 67.36842105263158"
  >
    <circle fill="transparent"
            cx="67.36842105263158"
            cy="67.36842105263158"
            r="20"
            stroke-width="27.36842105263158"
            stroke-dasharray="125.664"
            stroke-dashoffset="0"
            class="v-progress-circular__underlay"
    >
    </circle>
    <circle fill="transparent"
            cx="67.36842105263158"
            cy="67.36842105263158"
            r="20"
            stroke-width="27.36842105263158"
            stroke-dasharray="125.664"
            stroke-dashoffset="84.19468311620646px"
            class="v-progress-circular__overlay"
    >
    </circle>
  </svg>

`;


import { test } from '@/test'
import { VRadioGroup, VRadio } from '@/components/VRadioGroup'

test('VRadioGroup.vue', ({ mount }) => {
  it('should render role on radio group', () => {
    const wrapper = mount(VRadioGroup)

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

    const radioGroup = wrapper.find('.v-input--radio-group__input')[0]
    expect(radioGroup.getAttribute('role')).toBe('radiogroup')
  })

  // TODO: Test ability to toggle multiple data types

  it('should be in error when v-radio-group is', async () => {
    const wrapper = mount(VRadioGroup, {
      propsData: {
        error: false
      },
      slots: {
        default: [VRadio]
      }
    })

    const radio = wrapper.vm.radios[0]

    wrapper.setProps({ error: true })
    await wrapper.vm.$nextTick()
    expect(radio.parentError).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({ error: false })
    await wrapper.vm.$nextTick()
    expect(radio.parentError).toBe(false)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should provide isMandatory', () => {
    const stub = {
      name: 'stub',
      inject: ['isMandatory'],
      render: h => null
    }
    const wrapper = mount(VRadioGroup, {
      slots: {
        default: [stub]
      }
    })

    const find = wrapper.first(stub)

    expect(find.vm.isMandatory()).toBe(true)

    wrapper.setProps({ mandatory: false })

    expect(find.vm.isMandatory()).toBe(false)
  })

  it('should toggle radio', async () => {
    const stub = value => ({
      extends: VRadio,
      props: {
        value: {
          default: value
        }
      }
    })
    const wrapper = mount(VRadioGroup, {
      attachToDocument: true,
      slots: {
        default: [stub('foo'), stub('bar')]
      }
    })

    expect(wrapper.vm.radios.length).toBe(2)

    const radios = wrapper.find(VRadio)
    const one = radios[0]
    const two = radios[1]
    const inputOne = one.first('input')
    const inputTwo = two.first('input')

    inputOne.trigger('focus')
    inputOne.trigger('keydown.enter')

    expect(one.vm.isActive).toBe(true)

    wrapper.setProps({ disabled: true })

    inputTwo.trigger('focus')
    inputTwo.trigger('keydown.enter')

    await wrapper.vm.$nextTick()

    expect(one.vm.isActive).toBe(true)
    expect(two.vm.isActive).toBe(false)
  })

  it('should toggle radio - objects', async () => {
    const stub = value => ({
      extends: VRadio,
      props: {
        value: {
          default: value
        }
      }
    })
    const wrapper = mount(VRadioGroup, {
      attachToDocument: true,
      slots: {
        default: [stub(() => ({a: 1})), stub(() => ({b: 2}))]
      }
    })

    expect(wrapper.vm.radios.length).toBe(2)

    const radios = wrapper.find(VRadio)
    const one = radios[0]
    const two = radios[1]

    expect(wrapper.vm.internalValue).toBe(null)
    expect(one.vm.isActive).toBe(false)
    expect(two.vm.isActive).toBe(false)

    wrapper.setProps({ value: {a: 1} })
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.internalValue).toEqual({a: 1})
    expect(one.vm.isActive).toBe(true)
    expect(two.vm.isActive).toBe(false)

    wrapper.setProps({ value: {b: 2} })
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.internalValue).toEqual({b: 2})
    expect(one.vm.isActive).toBe(false)
    expect(two.vm.isActive).toBe(true)

    wrapper.setProps({ value: {a: 1, b: 2} })
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.internalValue).toEqual({a: 1, b: 2})
    expect(one.vm.isActive).toBe(false)
    expect(two.vm.isActive).toBe(false)
  })

  it('should change selected radio', async () => {
    const wrapper = mount(VRadioGroup, {
      attachToDocument: true,
      slots: {
        default: [{
          functional: true,

          render: h => h(VRadio, {
            props: { value: 'foo' }
          })
        }]
      }
    })

    const radio = wrapper.first(VRadio)

    expect(wrapper.vm.internalValue).toBe(null)
    expect(radio.vm.isActive).toBe(false)

    wrapper.setProps({ value: 'foo' })

    await wrapper.vm.$nextTick()

    expect(wrapper.vm.internalValue).toBe('foo')
    expect(radio.vm.isActive).toBe(true)
  })

  it('should have custom tabindex', () => {
    const wrapper = mount(VRadioGroup, {
      slots: {
        default: [{
          functional: true,

          render: h => h(VRadio, {
            attrs: {
              tabindex: 2
            }
          })
        }]
      }
    })

    const input = wrapper.first('input')

    expect(input.element.tabIndex).toBe(2)
  })

  it('should unregister radios that are removed', () => {
    const wrapper = mount(VRadioGroup, {
      slots: {
        default: [{
          functional: true,
          render: h => h(VRadio)
        }]
      }
    })

    const radio = wrapper.first(VRadio)

    expect(wrapper.vm.radios.length).toBe(1)

    radio.vm.$destroy()

    expect(wrapper.vm.radios.length).toBe(0)
  })

  it('should validate on blur', async () => {
    const wrapper = mount(VRadioGroup, {
      propsData: { error: true },
      slots: {
        default: [{
          extends: VRadio
        }]
      }
    })

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

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

    input.trigger('focus')
    input.trigger('blur')

    expect(blur).toHaveBeenCalledTimes(1)
    expect(wrapper.vm.shouldValidate).toBe(true)

    wrapper.setData({ hasInput: false })

    wrapper.vm.onRadioBlur({
      relatedTarget: {
        classList: {
          contains: () => false
        }
      }
    })

    expect(blur).toHaveBeenCalledTimes(2)
    expect(wrapper.vm.shouldValidate).toBe(true)

    wrapper.setData({ hasInput: false })

    wrapper.vm.onRadioBlur({
      relatedTarget: {
        classList: {
          contains: () => true
        }
      }
    })

    expect(blur).toHaveBeenCalledTimes(2)
    expect(wrapper.vm.shouldValidate).toBe(false)
  })
})


border-top: 1px solid rgba($material.fg-color, $material.divider-percent)

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


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

v-text-field($material)
  &:not(.v-input--has-state)
    .v-input__slot:before
      background-color: $material.input-bottom-line

    &:hover
      .v-input__slot:before
        background-color: $material.text.primary

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

  &__prefix,
  &__suffix
    color: $material.text.secondary

  &--full-width
    margin: 0

    .v-input__slot
      padding: 16px

    .v-input__append-inner
      align-self: center
      margin-top: 0

  &--solo
    background: $material.cards

  &--solo-inverted.v-text-field--solo
    background: rgba($material.inputs.solo-inverted)

    &.v-input--is-focused
      background: rgba($material.inputs.solo-inverted-focused)
      .v-label,
      input
        color: $material.inputs.solo-inverted-focused-text

  &--box
    .v-input__slot
      background: $material.text-fields.box

      &:hover
        background: $material.text-fields.box-hover

  &--outline
    border: 2px solid $material.text.secondary
    background: transparent !important

v-text-field-rtl()
  .v-label
    transform-origin: top right

  &--enclosed
    .v-input__append-outer
      margin-left: 0
      margin-right: 16px

    .v-input__prepend-outer
      margin-left: 16px
      margin-right: 0

  &--reverse
    input
      text-align: left

    .v-label
      transform-origin: top left

  &__prefix
    padding-right: 0
    padding-left: 4px

  &__suffix
    padding-left: 0
    padding-right: 4px

theme(v-text-field, "v-text-field")
rtl(v-text-field-rtl, "v-text-field")

.v-text-field
  input
    flex: 1 1 auto
    line-height: 20px
    padding: 8px 0 8px
    max-width: 100%
    width: 100%

  .v-input__append-inner
    align-self: flex-start
    display: inline-flex
    margin-left: auto
    margin-top: 4px
    line-height: 1
    padding-left: 4px
    user-select: none

  .v-counter
    white-space: nowrap

  .v-label
    cursor: auto
    height: 20px
    line-height: 20px
    max-width: 90%
    overflow: hidden
    text-overflow: ellipsis
    top: 6px // equal to the margin top of icons
    transform-origin: top left
    white-space: nowrap

    &--active
      max-width: 100%
      transform: translateY(-18px) scale(.75)

  .v-input__slot
    &:before,
    &:after
      bottom: -1px
      content: ''
      left: 0
      position: absolute
      transition: $primary-transition
      width: 100%

    &:before
      background-color: transparent
      background-position: bottom
      background-size: 3px 1px
      background-repeat: repeat-x
      height: 1px

    &:after
      background-color: currentColor
      height: 2px
      transform: scaleX(0)

  &__details
    display: flex
    flex: 1 0 auto
    max-width: 100%

  &__prefix,
  &__suffix
    align-self: center

  &__prefix
    padding-right: 4px

  &__suffix
    overflow: hidden
    padding-left: 4px
    text-overflow: ellipsis
    white-space: nowrap

  &__slot
    display: flex
    flex: 1 1 auto
    height: 100%
    position: relative

  &--box,
  &--outline
    position: relative

    .v-input__slot
      align-items: flex-start

    input
      cursor: pointer
      margin-top: 24px

    &.v-text-field--single-line
      input
        margin-top: 12px

    .v-label
      top: 18px

      &--active
        transform: translateY(-6px) scale(.75)

    .v-input__slot
      min-height: 56px

  &--box,
  &--enclosed
    .v-input__prepend-outer,
    .v-input__append-inner,
    .v-input__append-outer
      margin-top: 16px

    &:not(.v-text-field--solo)
      .v-label
        cursor: pointer

  &--box
    .v-input__slot,
    input
      cursor: pointer

    .v-input__slot
      border-radius: 4px
      margin-bottom: 4px

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

      &:before
        height: 2px

    .v-input__slot,
    .v-text-field__details
      padding: 0 12px

  &--enclosed
    margin-top: 16px

    .v-input__slot
      margin-bottom: 0 // normally for messages

    .v-text-field__details
      bottom: -22px
      pointer-events: none
      position: absolute
      width: 100%

    .v-input__prepend-outer
      margin-left: 16px

    .v-input__append-outer
      margin-right: 16px

    &:not(.v-text-field--box)
      .v-input__slot
        &:before,
        &:after
          display: none

    .v-input__slot,
    .v-text-field__details
      padding: 0 12px

  &--reverse
    input
      text-align: right

    .v-label
      transform-origin: top right

    .v-input__slot,
    .v-text-field__slot
      flex-direction: row-reverse

  &--outline
    border-radius: 4px
    cursor: pointer

    &.v-input--is-focused
      border: 2px solid currentColor
      transition: border $primary-transition

  &.v-text-field--solo,
  &.v-text-field--outline
    .v-input__prepend-outer
      margin-right: 0

    .v-input__append-outer
      margin-left: 0

  &.v-text-field--solo
    align-items: center
    margin: 0
    elevation(2)

    .v-label
      top: calc(50% - 10px)

    .v-input__control
      min-height: 48px
      padding: 0

    .v-text-field__slot
      align-items: center

    .v-input__append-inner
      align-self: center
      margin-top: 0

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 8px
      margin-bottom: 8px

  &.v-input--is-focused
    .v-input__slot:after
      transform: scaleX(1)

  &.v-input--has-state
    .v-input__slot:before
      background-color: currentColor


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

test('VDatePickerMonthTable.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

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

  it('should watch tableDate value and run transition', async () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

    wrapper.setProps({
      tableDate: '2006'
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.find('table')[0].element.className).toBe('tab-transition-enter tab-transition-enter-active')
  })

  it('should watch tableDate value and run reverse transition', async () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

    wrapper.setProps({
      tableDate: '2004'
    })
    await wrapper.vm.$nextTick()
    expect(wrapper.find('table')[0].element.className).toBe('tab-reverse-transition-enter tab-reverse-transition-enter-active')
  })

  it('should emit event when month button is clicked', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11'
      }
    })

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

    wrapper.find('tbody button')[0].trigger('click')
    expect(input).toBeCalledWith('2005-01')
  })

  it('should not emit event when disabled month button is clicked', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        current: '2005-05',
        value: '2005-11',
        allowedDates: () => false
      }
    })

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

    wrapper.find('tbody button')[0].trigger('click')
    expect(input).not.toBeCalled()
  })

  it('should emit tableDate event when scrolled and scrollable', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005',
        scrollable: true
      }
    })

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

    wrapper.trigger('wheel')
    expect(tableDate).toBeCalledWith('2006')
  })

  it('should not emit tableDate event when scrolled and not scrollable', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005'
      }
    })

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

    wrapper.trigger('wheel')
    expect(tableDate).not.toBeCalled()
  })

  // TODO
  it.skip('should emit tableDate event when swiped', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005'
      }
    })

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

    wrapper.trigger('touchstart')
    wrapper.trigger('touchend')
    expect(tableDate).toBeCalledWith(2006)
  })

  it('should change tableDate when touch is called', () => {
    const wrapper = mount(VDatePickerMonthTable, {
      propsData: {
        tableDate: '2005'
      }
    })

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

    wrapper.vm.touch(1)
    expect(tableDate).toBeCalledWith('2006')
    wrapper.vm.touch(-1)
    expect(tableDate).toBeCalledWith('2004')
  })
})


import '../../stylus/components/_chips.styl'

import VIcon from '../VIcon'
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'
import Toggleable from '../../mixins/toggleable'

export default {
  name: 'v-chip',

  mixins: [Colorable, Themeable, Toggleable],

  props: {
    close: Boolean,
    disabled: Boolean,
    label: Boolean,
    outline: Boolean,
    // Used for selects/tagging
    selected: Boolean,
    small: Boolean,
    textColor: String,
    value: {
      type: Boolean,
      default: true
    }
  },

  computed: {
    classes () {
      const classes = this.addBackgroundColorClassChecks({
        'v-chip--disabled': this.disabled,
        'v-chip--selected': this.selected,
        'v-chip--label': this.label,
        'v-chip--outline': this.outline,
        'v-chip--small': this.small,
        'v-chip--removable': this.close,
        'theme--light': this.light,
        'theme--dark': this.dark
      })

      return (this.textColor || this.outline)
        ? this.addTextColorClassChecks(classes, this.textColor || this.color)
        : classes
    }
  },

  methods: {
    genClose (h) {
      const data = {
        staticClass: 'v-chip__close',
        on: {
          click: e => {
            e.stopPropagation()

            this.$emit('input', false)
          }
        }
      }

      return h('div', data, [
        h(VIcon, '$vuetify.icons.delete')
      ])
    },
    genContent (h) {
      const children = [this.$slots.default]

      this.close && children.push(this.genClose(h))

      return h('span', {
        staticClass: 'v-chip__content'
      }, children)
    }
  },

  render (h) {
    const data = {
      staticClass: 'v-chip',
      'class': this.classes,
      attrs: { tabindex: this.disabled ? -1 : 0 },
      directives: [{
        name: 'show',
        value: this.isActive
      }],
      on: this.$listeners
    }

    return h('span', data, [this.genContent(h)])
  }
}


import Vue from 'vue'
import { VListTileAction } from '@/components/VList'
import { test } from '@/test'

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

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

  it('should render component with static class and match snapshot', () => {
    const wrapper = mount(VListTileAction, functionalContext({
      staticClass: 'static-class'
    }))

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

  it('should render component with many children and match snapshot', () => {
    const content1 = mount(Vue.component('content1', {
      render: h => h('div')
    })).vNode
    const content2 = mount(Vue.component('content2', {
      render: h => h('span')
    })).vNode
    const wrapper = mount(VListTileAction, functionalContext({}, [content1, content2]))

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


// Mixins
import Colorable from '../../mixins/colorable'
import Routable from '../../mixins/routable'
import Toggleable from '../../mixins/toggleable'

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

export default {
  name: 'v-list-tile',

  mixins: [
    Colorable,
    Routable,
    Toggleable
  ],

  directives: {
    Ripple
  },

  inheritAttrs: false,

  data: () => ({
    proxyClass: 'v-list__tile--active'
  }),

  props: {
    activeClass: {
      type: String,
      default: 'primary--text'
    },
    avatar: Boolean,
    inactive: Boolean,
    tag: String
  },

  computed: {
    listClasses () {
      return this.disabled
        ? 'v-list--disabled'
        : this.color
          ? this.addTextColorClassChecks()
          : this.defaultColor
    },
    classes () {
      return {
        'v-list__tile': true,
        'v-list__tile--link': this.isLink && !this.inactive,
        'v-list__tile--avatar': this.avatar,
        'v-list__tile--disabled': this.disabled,
        'v-list__tile--active': !this.to && this.isActive,
        [this.activeClass]: this.isActive
      }
    },
    isLink () {
      return this.href || this.to ||
        (this.$listeners && (this.$listeners.click || this.$listeners['!click']))
    }
  },

  render (h) {
    const isRouteLink = !this.inactive && this.isLink
    const { tag, data } = isRouteLink ? this.generateRouteLink() : {
      tag: this.tag || 'div',
      data: {
        class: this.classes
      }
    }

    data.attrs = Object.assign({}, data.attrs, this.$attrs)

    return h('div', {
      'class': this.listClasses,
      attrs: {
        disabled: this.disabled
      },
      on: {
        ...this.$listeners
      }
    }, [h(tag, data, this.$slots.default)])
  }
}


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

radiogroup($material)
  .input-group--selection-controls
    //

theme(radiogroup, "radio-group")

.v-input--radio-group
  &__input
    display: flex
    width: 100%

  &--row
    .v-input--radio-group__input
      flex-direction: row
      flex-wrap: wrap

  &--column
    .v-input--radio-group__input
      flex-direction: column

  .v-radio
    margin-bottom: 8px


keep keep keep replace replace replace replace replace replace keep

.v-text-field--enclosed,
.v-text-field--box
margin: 0

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

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

theme(v-toolbar, "v-toolbar")

.v-toolbar
  bootable()
  elevation(4)

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

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

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

    label
      top: auto

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

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

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

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

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

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

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

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

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

    .v-btn
      align-items: stretch

    .v-tooltip,
    .v-tooltip > span
      height: inherit

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

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

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

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

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

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

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

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

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

  &--absolute
    position: absolute
    z-index 2

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

  &--clipped
    z-index: 3

  &--prominent
    v-toolbar-content(15px)

  &--dense
    v-toolbar-content(7px)

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

    > *:last-child
      margin-right: left


import { VList, VListGroup } from '@/components/VList'
import { test } from '@/test'

const warning = '[Vuetify] The v-list-group component must be used inside a v-list'

// TODO: Test actual behaviour instead of classes
test('VListGroup.js', ({ mount }) => {
  it('should render component and match snapshot', () => {
    const wrapper = mount(VList, {
      slots: {
        default: [VListGroup]
      }
    })

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

  it('should render a lazy component and match snapshot', () => {
    const wrapper = mount(VList, {
      propsData: {
        lazy: true
      },
      slots: {
        default: [VListGroup]
      }
    })

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

  it('should render a component with no padding for action icon and match snapshot', () => {
    const wrapper = mount(VList, {
      propsData: {
        noAction: true
      },
      slots: {
        default: [VListGroup]
      }
    })

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

  it('should render a component with route namespace and match snapshot', () => {
    const $route = { path: '' }
    const wrapper = mount(VList, {
      propsData: {
        group: 'listGroup'
      },
      slots: {
        default: [VListGroup]
      },
      globals: {
        $route
      }
    })

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

  it('should toggle based upon matching uid', () => {
    const listClick = jest.fn()
    const wrapper = mount(VListGroup, {
      provide: {
        listClick: listClick,
        list: {
          register: () => {},
          unregister: () => {}
        }
      }
    })

    expect(wrapper.vm.isActive).toBe(false)
    wrapper.vm.toggle(wrapper.vm._uid)
    expect(wrapper.vm.isActive).toBe(true)
    wrapper.vm.toggle(null)
    expect(wrapper.vm.isActive).toBe(false)
  })

  it('should accept a custom active class', () => {
    const wrapper = mount(VListGroup, {
      attachToDocument: true,
      propsData: {
        activeClass: 'foo',
        prependIcon: 'list',
        value: true
      }
    })

    const header = wrapper.find('.v-list__group__header__prepend-icon')[0]

    expect(header.hasClass('foo')).toBe(true)
    wrapper.setProps({ activeClass: 'bar' })
    expect(header.hasClass('bar')).toBe(true)

    expect('Injection "listClick" not found').toHaveBeenWarned()
    expect(warning).toHaveBeenTipped()
  })

  it('should open if no value provided and group matches route', async () => {
    const $route = { path: '/foo' }
    const listClick = jest.fn()
    const wrapper = mount(VListGroup, {
      attachToDocument: true,
      propsData: {
        group: 'foo'
      },
      provide: {
        listClick
      },
      globals: {
        $route
      }
    })

    await wrapper.vm.$nextTick()
    expect(listClick).toBeCalledWith(wrapper.vm._uid)

    expect(warning).toHaveBeenTipped()
  })

  it('should toggle when clicked', async () => {
    const wrapper = mount(VListGroup, {
      attachToDocument: true,
      provide: {
        listClick: () => {}
      }
    })

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

    expect(warning).toHaveBeenTipped()
  })

  it('should unregister when destroyed', async () => {
    const unregister = jest.fn()
    const wrapper = mount(VListGroup, {
      attachToDocument: true,
      provide: {
        listClick: () => {},
        list: {
          register: () => {},
          unregister
        }
      }
    })

    wrapper.destroy()
    await wrapper.vm.$nextTick()
    expect(unregister).toBeCalledWith(wrapper.vm._uid)
  })

  it('should render a custom append icon', async () => {
    const wrapper = mount(VListGroup, {
      slots: {
        appendIcon: {
          render: h => h('span', 'bar')
        }
      }
    })

    const icon = wrapper.find('span')[0]
    expect(icon.html()).toBe('bar')

    expect('Injection "listClick" not found').toHaveBeenWarned()
    expect(warning).toHaveBeenTipped()
  })

  it('should only render custom prepend icon', async () => {
    const wrapper = mount(VListGroup, {
      slots: {
        prependIcon: {
          render: h => h('span', 'bar')
        }
      }
    })

    const icon = wrapper.find('span')[0]
    expect(icon.html()).toBe('bar')

    expect('Injection "listClick" not found').toHaveBeenWarned()
    expect(warning).toHaveBeenTipped()
  })

  it('should render a default prepended icon', async () => {
    const wrapper = mount(VListGroup, {
      propsData: {
        subGroup: true
      }
    })

    const icon = wrapper.find('.v-icon')[0]

    expect(icon.text()).toBe('arrow_drop_down')

    expect('Injection "listClick" not found').toHaveBeenWarned()
    expect(warning).toHaveBeenTipped()
  })

  it('should return proper content from icon methods', () => {
    const wrapper = mount(VListGroup)

    expect(wrapper.vm.genPrependIcon()).toBe(null)

    wrapper.setProps({ prependIcon: 'list' })

    expect(wrapper.vm.genPrependIcon()).toBeTruthy()
    wrapper.setProps({ prependIcon: undefined })

    const icon = wrapper.find('.v-icon')[0]

    expect(icon.text()).toBe('keyboard_arrow_down')
    wrapper.setProps({ appendIcon: 'list' })

    expect(icon.text()).toBe('list')
    expect('Injection "listClick" not found').toHaveBeenWarned()
    expect(warning).toHaveBeenTipped()
  })
})


.v-ripple
  &__container
    color: inherit
    border-radius: inherit
    position: absolute
    width: 100%
    height: 100%
    left: 0
    top: 0
    overflow: hidden
    z-index: 0
    pointer-events: none
    contain: strict

  &__animation
    color: inherit
    position: absolute
    top: 0
    left: 0
    border-radius: 50%
    background: currentColor
    opacity: 0
    transition: $ripple-animation-transition
    pointer-events: none
    overflow: hidden
    will-change: transform, opacity

    &--enter
      transition: none

    &--visible
      opacity: $ripple-animation-visible-opacity


import { colorToInt, intToHex } from './colorUtils'
import * as sRGB from './color/transformSRGB'
import * as LAB from './color/transformCIELAB'

/**
 * @param {object} theme
 * @returns {object}
 */
export function parse (theme) {
  const colors = Object.keys(theme)
  const parsedTheme = {}

  for (let i = 0; i < colors.length; ++i) {
    const name = colors[i]
    const value = theme[name]

    parsedTheme[name] = colorToInt(value)
  }

  return parsedTheme
}

export function genVariations (name, value) {
  const values = Array(10)
  values[0] = genBaseColor(name, value)

  for (let i = 1, n = 5; i <= 5; ++i, --n) {
    values[i] = genVariantColor(name, lighten(value, n), 'lighten', n)
  }

  for (let i = 1; i <= 4; ++i) {
    values[i + 5] = genVariantColor(name, darken(value, i), 'darken', i)
  }

  return values
}

function lighten (value, amount) {
  const lab = LAB.fromXYZ(sRGB.toXYZ(value))
  lab[0] = lab[0] + amount * 10
  return sRGB.fromXYZ(LAB.toXYZ(lab))
}

function darken (value, amount) {
  const lab = LAB.fromXYZ(sRGB.toXYZ(value))
  lab[0] = lab[0] - amount * 10
  return sRGB.fromXYZ(LAB.toXYZ(lab))
}

/**
 * Generate the CSS for a base color (.primary)
 *
 * @param {string} name - The color name
 * @param {string|number} value - The color value
 * @returns {string}
 */
export const genBaseColor = (name, value) => {
  value = intToHex(value)
  return `
.${name} {
  background-color: ${value} !important;
  border-color: ${value} !important;
}
.${name}--text {
  color: ${value} !important;
}
.${name}--text input,
.${name}--text textarea {
  caret-color: ${value} !important;
}`
}

/**
 * Generate the CSS for a variant color (.primary.darken-2)
 *
 * @param {string} name - The color name
 * @param {string|number} value - The color value
 * @param {string} type - The variant type (darken/lighten)
 * @param {number} n - The darken/lighten step number
 * @returns {string}
 */
export const genVariantColor = (name, value, type, n) => {
  value = intToHex(value)
  return `
.${name}.${type}-${n} {
  background-color: ${value} !important;
  border-color: ${value} !important;
}
.${name}--text.text--${type}-${n} {
  color: ${value} !important;
}
.${name}--text.text--${type}-${n} input,
.${name}--text.text--${type}-${n} textarea {
  caret-color: ${value} !important;
}`
}


label
top: auto
/** Children */
.v-toolbar
.v-tabs

keep replace replace keep keep keep keep

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

/* Themes */
v-icon($material)
  color: $material.icons.active

  &.v-icon--disabled:not(.input-group__append-icon)
    color: $material.icons.inactive !important

theme(v-icon, "v-icon")

.v-icon
  align-items: center
  display: inline-flex
  font-feature-settings: 'liga'
  font-size: 24px
  justify-content: center
  line-height: 1
  transition: $primary-transition
  vertical-align: middle

  &--link
    cursor: pointer

  &.v-icon--large
    font-size: 2.5rem

  &.v-icon--medium
    font-size: 2rem

  &.v-icon--x-large
    font-size: 3rem

  &.v-icon--disabled
    pointer-events: none


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

export { VExpansionPanel, VExpansionPanelContent }

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

export default VExpansionPanel


import VTabs from './VTabs'
import VTab from './VTab'
import VTabsItems from './VTabsItems'
import VTabItem from './VTabItem'
import VTabsSlider from './VTabsSlider'

export { VTabs, VTabItem, VTab, VTabsItems, VTabsSlider }

/* istanbul ignore next */
VTabs.install = function install (Vue) {
  Vue.component(VTabs.name, VTabs)
  Vue.component(VTab.name, VTab)
  Vue.component(VTabsItems.name, VTabsItems)
  Vue.component(VTabItem.name, VTabItem)
  Vue.component(VTabsSlider.name, VTabsSlider)
}

export default VTabs


// Styles
import '../../stylus/components/_radios.styl'

// Components
import VIcon from '../VIcon'
import VLabel from '../VLabel'

// Mixins
import Colorable from '../../mixins/colorable'
import Rippleable from '../../mixins/rippleable'
import Themeable from '../../mixins/themeable'
import {
  inject as RegistrableInject
} from '../../mixins/registrable'

// Utils
import { keyCodes } from '../../util/helpers'

export default {
  name: 'v-radio',

  inheritAttrs: false,

  inject: {
    name: {
      default: false
    },
    isMandatory: {
      default: false
    },
    validationState: {
      default: false
    }
  },

  mixins: [
    Colorable,
    Rippleable,
    RegistrableInject('radio', 'v-radio', 'v-radio-group'),
    Themeable
  ],

  data: () => ({
    isActive: false,
    isFocused: false,
    parentError: false
  }),

  props: {
    color: {
      type: [Boolean, String],
      default: 'accent'
    },
    disabled: Boolean,
    label: String,
    onIcon: {
      type: String,
      default: '$vuetify.icons.radioOn'
    },
    offIcon: {
      type: String,
      default: '$vuetify.icons.radioOff'
    },
    readonly: Boolean,
    value: null
  },

  computed: {
    classes () {
      const classes = {
        'v-radio--is-disabled': this.isDisabled,
        'v-radio--is-focused': this.isFocused,
        'theme--dark': this.dark,
        'theme--light': this.light
      }

      if (!this.parentError && this.isActive) {
        return this.addTextColorClassChecks(classes)
      }

      return classes
    },
    classesSelectable () {
      return this.addTextColorClassChecks(
        {},
        this.isActive ? this.color : this.validationStateProxy
      )
    },
    computedIcon () {
      return this.isActive
        ? this.onIcon
        : this.offIcon
    },
    hasState () {
      return this.isActive || !!this.validationStateProxy
    },
    isDisabled () {
      return this.disabled || this.readonly
    },
    validationStateProxy () {
      return this.validationState && this.validationState()
    }
  },

  mounted () {
    this.radio.register(this)
  },

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

  methods: {
    genInput (type, attrs) {
      return this.$createElement('input', {
        attrs: Object.assign({}, attrs, {
          'aria-label': this.label,
          name: this.name && this.name(),
          role: type,
          type,
          value: this.inputValue
        }),
        on: {
          blur: this.onBlur,
          change: this.onChange,
          focus: this.onFocus,
          keydown: e => {
            if ([keyCodes.enter, keyCodes.space].includes(e.keyCode)) {
              e.preventDefault()
              this.onChange()
            }
          }
        },
        ref: 'input'
      })
    },
    genLabel () {
      return this.$createElement(VLabel, {
        on: { click: this.onChange },
        attrs: {
          for: this.id
        },
        props: {
          color: this.validationStateProxy,
          focused: this.hasState
        }
      }, this.$slots.label || this.label)
    },
    genRadio () {
      return this.$createElement('div', {
        staticClass: 'v-input--selection-controls__input'
      }, [
        this.genInput('radio', {
          'aria-checked': this.isActive.toString(),
          ...this.$attrs
        }),
        this.genRipple({
          'class': this.classesSelectable
        }),
        this.$createElement(VIcon, {
          'class': this.classesSelectable
        }, this.computedIcon)
      ])
    },
    onFocus () {
      this.isFocused = true
    },
    onBlur (e) {
      this.isFocused = false
      this.$emit('blur', e)
    },
    onChange () {
      const mandatory = !!this.isMandatory && this.isMandatory()

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

  render (h) {
    return h('div', {
      staticClass: 'v-radio',
      class: this.classes
    }, [
      this.genRadio(),
      this.genLabel()
    ])
  }
}


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

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

/* Theme */
v-datatable($material)
  thead
    th.column.sortable
      i
        color: rgba($material.fg-color, $material.disabledORhints-text-percent)

      &:hover
        color: rgba($material.fg-color, $material.primary-text-percent)

      &.active
        color: rgba($material.fg-color, $material.primary-text-percent)

        i
          color: rgba($material.fg-color, $material.primary-text-percent)

  .v-datatable__actions
    background-color: $material.cards
    color: rgba($material.fg-color, $material.secondary-text-percent)
    border-top: 1px solid rgba($material.fg-color, $material.divider-percent)

    &__select
      .input-group--select
        .input-group__selections__comma
          color: rgba($material.fg-color, $material.secondary-text-percent) !important

        .input-group__append-icon
          color: rgba($material.fg-color, $material.secondary-text-percent) !important

theme(v-datatable, "v-datatable")

.v-datatable
  .v-input
    margin: 0

  thead
    th.column.sortable
      cursor: pointer
      outline: 0

      i
        font-size: 16px
        vertical-align: sub
        display: inline-block
        opacity: 0
        transition: .3s $transition.swing

      &:focus,
      &:hover
        i
          opacity: .6

      &.active
        transform: none

        i
          opacity: 1

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

/** Actions */
.v-datatable__actions
  display: flex
  justify-content: flex-end
  align-items: center
  font-size: 12px
  flex-wrap: wrap-reverse

  .v-btn
    color: inherit

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

  &__range-controls
    display: flex
    align-items: center
    min-height: 48px

  &__pagination
    display: block
    text-align: center
    margin: 0 32px 0 24px

  &__select
    display: flex
    align-items: center
    justify-content: flex-end
    margin-right: 14px
    white-space: nowrap

    .v-select
      flex: 0 1 0
      margin: 13px 0 13px 34px
      padding: 0
      position: initial

      .v-select__selection--comma
        font-size: 12px

.v-datatable__progress
  height: auto !important

  tr, td, th
    height: auto !important

  th
    padding: 0 !important

    .v-progress-linear
      top: -2px
      margin: 0 0 -2px

.v-datatable__expand
  &-row
    border: none !important

  &-col
    padding: 0 !important
    height: 0px !important

    &--expanded
      border-bottom: 1px solid rgba(0,0,0,0.12)

  &-content
    transition: height $primary-transition

    > .card
      border-radius: 0
      box-shadow: none


import Vue from 'vue'
import { test } from '@/test'
import VDataIterator from '@/components/VDataIterator'
import VBtn from '@/components/VBtn'

test('VDataIterator.js', ({ mount, compileToFunctions }) => {
  function dataIteratorTestData () {
    return {
      propsData: {
        pagination: {
          descending: false,
          sortBy: 'col1',
          rowsPerPage: 5,
          page: 1
        },
        items: [
          { other: 1, col1: 'foo', col2: 'a', col3: 1 },
          { other: 2, col1: null, col2: 'b', col3: 2 },
          { other: 3, col1: undefined, col2: 'c', col3: 3 }
        ]
      }
    }
  }

  it('should match a snapshot - no matching records', () => {
    const data = dataIteratorTestData()
    data.propsData.search = "asdf"
    const wrapper = mount(VDataIterator, data)

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

    const content = wrapper.find('.v-data-iterator div div')[0]
    expect(content.element.textContent).toBe('No matching records found')

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

  it('should match a snapshot - hideActions and no footer slot', () => {
    const data = dataIteratorTestData()
    data.propsData.hideActions = true
    const wrapper = mount(VDataIterator, data)

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

  it('should match a snapshot - footer slot', () => {
    const data = dataIteratorTestData()
    data.slots = {
      footer: [compileToFunctions('footer')],
    }
    const wrapper = mount(VDataIterator, data)

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

  it('should match a snapshot - no data', () => {
    const data = dataIteratorTestData()
    data.propsData.items = []
    const wrapper = mount(VDataIterator, data)

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

    const content = wrapper.find('.v-data-iterator div div')[0]
    expect(content.element.textContent).toBe('No data available')

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

  it('should match a snapshot - with data', () => {
    const data = dataIteratorTestData()

    const vm = new Vue()
    const item = props => vm.$createElement('div', [props.item.col2])
    const component = Vue.component('test', {
      render (h) {
        return h(VDataIterator, {
          props: {
            'content-tag': 'span',
            ...data.propsData
          },
          scopedSlots: {
            item
          }
        })
      }
    })

    const wrapper = mount(component)

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

  it('should pass attrs, class and props to content', () => {
    const data = dataIteratorTestData()

    const vm = new Vue()
    const item = props => vm.$createElement('div', [props.item.col2])
    const component = Vue.component('test', {
      render (h) {
        return h(VDataIterator, {
          props: {
            'content-tag': 'button', // TODO: use v-btn so we can test content-props
            ...data.propsData,
            // 'content-props': { block: true }, // TODO: enable when migrating to vue-test-utils
            'content-class': 'test__class'
          },
          attrs: {
            id: "testButtonId"
          },
          scopedSlots: {
            item
          }
        })
      }
    })

    const wrapper = mount(component)

    const mainDiv = wrapper.find('.v-data-iterator')[0]
    expect(mainDiv.hasAttribute('id')).toBe(false)

    var button = mainDiv.find('button')[0]
    expect(button.getAttribute('id')).toBe('testButtonId')
    // expect(button.hasClass('v-btn--block')).toBe(true) // TODO: enable when migrating to vue-test-utils
    expect(button.hasClass('test__class')).toBe(true)

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

  it('should not filter items if search is empty', async () => {
    const data = dataIteratorTestData()
    data.propsData.search = '    '
    const wrapper = mount(VDataIterator, data)

    expect(wrapper.instance().filteredItems).toHaveLength(data.propsData.items.length)

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

  it('should render header slot', async () => {
    const data = dataIteratorTestData()
    data.slots = {
      footer: [compileToFunctions('header')],
    }

    const wrapper = mount(VDataIterator, data)

    expect(wrapper.find('span.header').length).toBe(1)
    expect('Unable to locate target [data-app]').toHaveBeenTipped()
  })
})


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

exports[`VTimePickerClock.js should render component 1`] = `

      0

      5

      10

      15

      20

      25

      30

      35

      40

      45

      50

      55

`;

exports[`VTimePickerClock.js should render component with double prop 1`] = `

      0

      5

      10

      15

      20

      25

      30

      35

      40

      45

      50

      55

`;


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

v-date-picker-header($material)

  .v-date-picker-header__value:not(.v-date-picker-header__value--disabled)
    strong:not(:hover)
      color: $material.text.primary !important

  .v-date-picker-header__value--disabled strong
    color: $material.text.disabled

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

.v-date-picker-header
  padding: 4px 16px

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

  .v-btn
    margin: 0
    z-index: auto

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

.v-date-picker-header__value
  flex: 1
  text-align: center
  position: relative
  overflow: hidden

  strong
    cursor: pointer
    transition: $primary-transition
    display: block
    width: 100%


import { ToggleSwitch, RadioButton, OptionTitle } from '@rocket.chat/fuselage';
import { useUserPreference, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useCallback } from 'react';

import ListItem from '../Sidebar/ListItem';

function ViewModeList(): ReactElement {
	const t = useTranslation();

	const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');

	const useHandleChange = (value: 'medium' | 'extended' | 'condensed'): (() => void) =>
		useCallback(() => saveUserPreferences({ data: { sidebarViewMode: value } }), [value]);

	const sidebarViewMode = useUserPreference<'medium' | 'extended' | 'condensed'>('sidebarViewMode', 'extended');
	const sidebarDisplayAvatar = useUserPreference('sidebarDisplayAvatar', false);

	const setToExtended = useHandleChange('extended');
	const setToMedium = useHandleChange('medium');
	const setToCondensed = useHandleChange('condensed');

	const handleChangeSidebarDisplayAvatar = useCallback(
		() => saveUserPreferences({ data: { sidebarDisplayAvatar: !sidebarDisplayAvatar } }),
		[saveUserPreferences, sidebarDisplayAvatar],
	);

	return (
		<>
			<OptionTitle>{t('Display')}</OptionTitle>
			<ul aria-description='displayList'>
				<ListItem
					is='label'
					role='listitem'
					icon='extended-view'
					text={t('Extended')}
					input={<RadioButton pis='x24' onChange={setToExtended} checked={sidebarViewMode === 'extended'} />}
				/>
				<ListItem
					is='label'
					role='listitem'
					icon='medium-view'
					text={t('Medium')}
					input={<RadioButton pis='x24' onChange={setToMedium} checked={sidebarViewMode === 'medium'} />}
				/>
				<ListItem
					is='label'
					role='listitem'
					icon='condensed-view'
					text={t('Condensed')}
					input={<RadioButton pis='x24' onChange={setToCondensed} checked={sidebarViewMode === 'condensed'} />}
				/>
				<ListItem
					is='label'
					role='listitem'
					icon='user-rounded'
					text={t('Avatars')}
					input={<ToggleSwitch pis='x24' onChange={handleChangeSidebarDisplayAvatar} checked={sidebarDisplayAvatar} />}
				/>
			</ul>
		</>
	);
}

export default ViewModeList;


import { Skeleton, Table } from '@rocket.chat/fuselage';
import React from 'react';

function ImportOperationSummarySkeleton({ small }) {
	return (
		<Table.Row>
			<Table.Cell>
				<Skeleton />
			</Table.Cell>
			<Table.Cell>
				<Skeleton />
			</Table.Cell>
			{!small && (
				<>
					<Table.Cell>
						<Skeleton />
					</Table.Cell>
					<Table.Cell>
						<Skeleton />
					</Table.Cell>
					<Table.Cell>
						<Skeleton />
					</Table.Cell>
					<Table.Cell>
						<Skeleton />
					</Table.Cell>
					<Table.Cell>
						<Skeleton />
					</Table.Cell>
					<Table.Cell>
						<Skeleton />
					</Table.Cell>
				</>
			)}
		</Table.Row>
	);
}

export default ImportOperationSummarySkeleton;


import { useMemo } from 'react';

export function useDirectoryQuery(
	{ text, itemsPerPage, current }: { text: string; current: number; itemsPerPage: number },
	[column, direction]: [string, 'asc' | 'desc'],
	type: string,
	workspace = 'local',
): { offset?: number | undefined; count?: number; query: string; sort: string } {
	return useMemo(
		() => ({
			query: JSON.stringify({
				type,
				text,
				workspace,
			}),
			sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }),
			...(itemsPerPage && { count: itemsPerPage }),
			...(current && { offset: current }),
		}),
		[itemsPerPage, current, column, direction, type, workspace, text],
	);
}


import { Divider } from '@rocket.chat/fuselage';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import React, { useRef } from 'react';

import TextEditor from '.';

export default {
	title: 'Enterprise/Omnichannel/TextEditor',
	component: TextEditor,
} as ComponentMeta<typeof TextEditor>;

export const Default: ComponentStory<typeof TextEditor> = () => {
	// eslint-disable-next-line react-hooks/rules-of-hooks
	const textAreaRef = useRef<HTMLTextAreaElement>(null);

	const action = () => {
		if (!textAreaRef.current) {
			return;
		}

		const text = textAreaRef.current.value;
		const startPos = textAreaRef.current.selectionStart;
		const endPos = textAreaRef.current.selectionEnd;
		textAreaRef.current.value = `${text.slice(0, startPos)}*${text.slice(startPos, endPos)}*${text.slice(endPos)}`;
		textAreaRef.current.focus();
		textAreaRef.current.setSelectionRange(startPos + 1, endPos + 1);
	};

	return (
		<TextEditor>
			<TextEditor.Toolbox>
				<TextEditor.Toolbox.IconButton name='bold' action={action} />
				<TextEditor.Toolbox.TextButton text='Insert_Placeholder' action={action} />
			</TextEditor.Toolbox>
			<Divider w='full' mbe='16px' />
			<TextEditor.Textarea rows={10} ref={textAreaRef} />
		</TextEditor>
	);
};
Default.storyName = 'TextEditor';


keep keep keep keep replace keep keep keep


// We use cryptographically strong PRNGs (window.crypto.getRandomValues())
// when available. If these PRNGs fail, we fall back to the Alea PRNG, which is
// not cryptographically strong, and we seed it with various sources
// such as the date, Math.random, and window size on the client.
// When using window.crypto.getRandomValues() or alea, the primitive is fraction
// and we use that to construct hex string.

import { BrowserRandomGenerator } from './BrowserRandomGenerator';
import { createAleaGeneratorWithGeneratedSeed } from './createAleaGenerator';
import type { RandomGenerator } from './RandomGenerator';

let generator: RandomGenerator;
if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {
	generator = new BrowserRandomGenerator();
} else {
	generator = createAleaGeneratorWithGeneratedSeed();
}

export const Random = generator;


import { Component } from 'preact';
import { withTranslation } from 'react-i18next';

import CloseIcon from '../../icons/close.svg';
import { createClassName } from '../helpers';
import styles from './styles.scss';

class Alert extends Component {
	static defaultProps = {
		timeout: 3000,
		hideCloseButton: false,
	};

	handleDismiss = () => {
		const { onDismiss, id } = this.props;
		onDismiss && onDismiss(id);
	};

	componentDidMount() {
		const { timeout } = this.props;
		if (Number.isFinite(timeout) && timeout > 0) {
			this.dismissTimeout = setTimeout(this.handleDismiss, timeout);
		}
	}

	componentWillUnmount() {
		clearTimeout(this.dismissTimeout);
	}

	render = ({ success, warning, error, color, hideCloseButton, className, style = {}, children, t }) => (

{children}

			{!hideCloseButton && (
				<button onClick={this.handleDismiss} className={createClassName(styles, 'alert__close')} aria-label={t('dismiss_this_alert')}>
					<CloseIcon width={20} height={20} />
				</button>
			)}

	);
}

export default withTranslation()(Alert);


/* eslint-env mocha */

import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import { expect } from 'chai';

import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { saveBusinessHour } from '../../../data/livechat/business-hours';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
import { IS_EE } from '../../../e2e/config/constants';

describe('[CE] LIVECHAT - business hours', function () {
	this.retries(0);

	before((done) => getCredentials(done));

	before(async () => {
		await updateSetting('Livechat_enabled', true);
	});

	describe('livechat/business-hour', () => {
		it('should fail when user doesnt have view-livechat-business-hours permission', async () => {
			await updatePermission('view-livechat-business-hours', []);
			const response = await request.get(api('livechat/business-hour')).set(credentials).expect(403);
			expect(response.body.success).to.be.false;
		});
		it('should fail when business hour type is not a valid BH type', async () => {
			await updatePermission('view-livechat-business-hours', ['admin', 'livechat-manager']);
			const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: 'invalid' }).expect(200);
			expect(response.body.success).to.be.true;
			expect(response.body.businessHour).to.be.null;
		});
		it('should return a business hour of type default', async () => {
			const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: 'default' }).expect(200);
			expect(response.body.success).to.be.true;
			expect(response.body.businessHour).to.be.an('object');
			expect(response.body.businessHour._id).to.be.a('string');
			expect(response.body.businessHour.workHours).to.be.an('array');
			expect(response.body.businessHour.workHours[0]).to.be.an('object');
			expect(response.body.businessHour.workHours[0].day)
				.to.be.an('string')
				.that.is.oneOf(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
			expect(response.body.businessHour.workHours[0].start).to.be.an('object');
			expect(response.body.businessHour.workHours[0].finish).to.be.an('object');
			expect(response.body.businessHour.workHours[0].open).to.be.a('boolean');
			expect(response.body.businessHour.timezone).to.be.an('object').that.has.property('name').that.is.an('string');
		});
	});

	(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - business hours', () => {
		it('should fail if user doesnt have view-livechat-business-hours permission', async () => {
			await updatePermission('view-livechat-business-hours', []);
			const response = await request.get(api('livechat/business-hours')).set(credentials).expect(403);
			expect(response.body.success).to.be.false;
		});
		it('should return a list of business hours', async () => {
			await updatePermission('view-livechat-business-hours', ['admin', 'livechat-manager']);
			const response = await request.get(api('livechat/business-hours')).set(credentials).expect(200);
			expect(response.body.success).to.be.true;
			expect(response.body.businessHours).to.be.an('array').with.lengthOf.greaterThan(0);
			expect(response.body.businessHours[0]).to.be.an('object');
			expect(response.body.businessHours[0]._id).to.be.a('string');
			expect(response.body.businessHours[0].workHours).to.be.an('array');
			expect(response.body.businessHours[0].workHours[0]).to.be.an('object');
			expect(response.body.businessHours[0].workHours[0].day)
				.to.be.an('string')
				.that.is.oneOf(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
			expect(response.body.businessHours[0].workHours[0].start).to.be.an('object');
			expect(response.body.businessHours[0].workHours[0].finish).to.be.an('object');
			expect(response.body.businessHours[0].workHours[0].open).to.be.a('boolean');
			expect(response.body.businessHours[0].timezone).to.be.an('object').that.has.property('name').that.is.an('string');
			expect(response.body.businessHours[0].active).to.be.a('boolean');
		});
		it('should return a just created custom business hour', async () => {
			const name = `business-hour-${Date.now()}`;
			await updateSetting('Livechat_business_hour_type', 'multiple');
			await saveBusinessHour({
				name,
				active: true,
				type: LivechatBusinessHourTypes.CUSTOM,
				workHours: [
					{
						day: 'Monday',
						open: true,
						// @ts-expect-error - this is valid for endpoint, actual type converts this into an object
						start: '08:00',
						// @ts-expect-error - same as previous one
						finish: '18:00',
					},
				],
				timezone: {
					name: 'America/Sao_Paulo',
					utc: '-03:00',
				},
				departmentsToApplyBusinessHour: '',
				timezoneName: 'America/Sao_Paulo',
			});

			const { body } = await request.get(api('livechat/business-hours')).set(credentials).query({ name }).expect(200);
			expect(body.success).to.be.true;
			expect(body.businessHours).to.be.an('array').with.lengthOf(1);
			expect(body.businessHours[0]).to.be.an('object');
			expect(body.businessHours[0]._id).to.be.a('string');
			expect(body.businessHours[0]).to.have.property('name', name);
			expect(body.businessHours[0]).to.have.property('active', true);
			expect(body.businessHours[0]).to.have.property('type', LivechatBusinessHourTypes.CUSTOM);
			expect(body.businessHours[0]).to.have.property('workHours').that.is.an('array').with.lengthOf(1);
			expect(body.businessHours[0].workHours[0]).to.be.an('object').with.property('day', 'Monday');
			expect(body.businessHours[0].workHours[0]).to.have.property('start').that.is.an('object');
			expect(body.businessHours[0].workHours[0]).to.have.property('finish').that.is.an('object');
			expect(body.businessHours[0]).to.have.property('timezone').that.is.an('object').with.property('name', 'America/Sao_Paulo');
		});
	});
});


keep keep keep replace keep keep keep keep keep

import { OptionTitle } from '@rocket.chat/fuselage';
import { useSetting, useAtLeastOnePermission, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, MouseEvent } from 'react';
import React from 'react';

import CreateDiscussion from '../../../components/CreateDiscussion';
import ListItem from '../../../components/Sidebar/ListItem';
import CreateChannelWithData from '../CreateChannel';
import CreateDirectMessage from '../CreateDirectMessage';
import CreateTeam from '../CreateTeam';
import { useCreateRoomModal } from '../hooks/useCreateRoomModal';

const CREATE_CHANNEL_PERMISSIONS = ['create-c', 'create-p'];
const CREATE_TEAM_PERMISSIONS = ['create-team'];
const CREATE_DIRECT_PERMISSIONS = ['create-d'];
const CREATE_DISCUSSION_PERMISSIONS = ['start-discussion', 'start-discussion-other-user'];

type CreateRoomListProps = {
	closeList: () => void;
};

const CreateRoomList = ({ closeList }: CreateRoomListProps): ReactElement => {
	const t = useTranslation();
	const discussionEnabled = useSetting('Discussion_enabled');

	const canCreateChannel = useAtLeastOnePermission(CREATE_CHANNEL_PERMISSIONS);
	const canCreateTeam = useAtLeastOnePermission(CREATE_TEAM_PERMISSIONS);
	const canCreateDirectMessages = useAtLeastOnePermission(CREATE_DIRECT_PERMISSIONS);
	const canCreateDiscussion = useAtLeastOnePermission(CREATE_DISCUSSION_PERMISSIONS);

	const createChannel = useCreateRoomModal(CreateChannelWithData);
	const createTeam = useCreateRoomModal(CreateTeam);
	const createDiscussion = useCreateRoomModal(CreateDiscussion);
	const createDirectMessage = useCreateRoomModal(CreateDirectMessage);

	return (
		<>
			<OptionTitle>{t('Create_new')}</OptionTitle>
			<ul aria-description='createRoomList'>
				{canCreateChannel && (
					<ListItem
						role='listitem'
						icon='hashtag'
						text={t('Channel')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createChannel(e);
							closeList();
						}}
					/>
				)}
				{canCreateTeam && (
					<ListItem
						role='listitem'
						icon='team'
						text={t('Team')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createTeam(e);
							closeList();
						}}
					/>
				)}
				{canCreateDirectMessages && (
					<ListItem
						role='listitem'
						icon='balloon'
						text={t('Direct_Messages')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createDirectMessage(e);
							closeList();
						}}
					/>
				)}
				{discussionEnabled && canCreateDiscussion && (
					<ListItem
						role='listitem'
						icon='discussion'
						text={t('Discussion')}
						onClick={(e: MouseEvent<HTMLElement>): void => {
							createDiscussion(e);
							closeList();
						}}
					/>
				)}
			</ul>
		</>
	);
};

export default CreateRoomList;


@rocket.chat/models

tsc --watch --preserveWatchOutput -p tsconfig.json


import { Meteor } from 'meteor/meteor';
import { LivechatCustomField } from '@rocket.chat/models';

import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';

Meteor.methods({
	async 'livechat:getCustomFields'() {
		methodDeprecationLogger.warn('livechat:getCustomFields will be deprecated in future versions of Rocket.Chat');
		return LivechatCustomField.find().toArray();
	},
});