---
meta:
  title: Contributing
  description: Contributing to open source helps developers access amazing tools for free. Learn how you can help develop the Vuetify framework.
  keywords: contribute, contributing, feature request
related:
  - /getting-started/unit-testing/
  - /about/code-of-conduct/
  - /getting-started/frequently-asked-questions/
---

# Contributing

Vuetify is made possible by an amazing community that submits issues, creates pull requests, and provides invaluable feedback. It is our job to enable you to create amazing applications. A lot of the time, you come across something that can be made better. Maybe you find a bug, or you have an idea for additional functionality. That's great! It's as easy as cloning the Vuetify repository to get started working in a development environment.

<promoted-ad slug="vuetify-discord" />

## Reporting Issues

The issue list of this repo is exclusively for bug reports and feature requests. Non-conforming issues will be closed immediately. Before reporting an issue:

- Search for similar [issues](https://github.com/vuetifyjs/vuetify/issues), it may have been answered already.
- Try to reproduce with the [latest](https://github.com/vuetifyjs/vuetify/releases/latest) version in a [codepen](https://template.vuetifyjs.com/) or repository that can be cloned to produce the expected behavior.
- Make sure that the reproduction is **MINIMAL** and concise

These steps ensure that we have all the information necessary to quickly triage and resolve your issue. Once your reproduction is complete, submit a new issue using the [Vuetify Issue Creator](https://issues.vuetifyjs.com/).

When writing an issue please provide as much detail as possible. Note that "reproduction steps" should be a series of actions another developer should take after clicking your reproduction link, not a recollection of how you discovered the bug.

Issues that are convoluted and lacking a proper reproduction may be closed by a member of the [Core Team]. For additional questions regarding reporting issues and creating reproductions, join the official Vuetify Discord [community].

<alert type="success">

  **TIP**

  When you create a reproduction, exclude all **elements, properties, and data variables** that are not needed for the reproduction. This helps drastically reduce the time it takes to triage the issue and ultimately resolve it.

</alert>

In the next section you will learn step-by-step how to set up your local environment and how to configure Vuetify for development.

## Local development

The Vuetify repository is a [lerna](https://github.com/lerna/lerna) monorepo that connects the vuetify library, docs, api generator, and reduces the friction of working with multiple projects at once. The following guide is designed to get you up and running in no time.

### Setting up your environment

Required software:

- [Git](https://git-scm.com/) >v2.20
- [Node.js](https://nodejs.org/) LTS
- [Yarn](https://classic.yarnpkg.com/)

Some of our dependencies use [node-gyp](https://github.com/nodejs/node-gyp#installation) to build themselves. You don't need to install node-gyp itself but may require additional tools, especially on windows. See the node-gyp documentation for more details.

Once you have everything installed, clone the repository:

```bash
# Using HTTPS
git clone https://github.com/vuetifyjs/vuetify.git

# Using SSH
git clone git@github.com:vuetifyjs/vuetify.git
```

<alert type="info">

[Which remote URL should I use?](https://docs.github.com/en/free-pro-team@latest/github/using-git/which-remote-url-should-i-use)

</alert>

Then install dependencies and perform an initial build to link all the packages together:

```bash
# Navigate to the vuetify folder
cd vuetify

# Install all project dependencies
yarn

# Build the packages
yarn build
```

The build process compiles all the Vuetify packages for development and may take a while (grab some ☕). Once the packages are built, you can start developing.

### Vuetify

The Vuetify library is located in `packages/vuetify`. In `packages/vuetify/dev` you will find a `Playground.vue` file; running `yarn dev` from the project root will start a dev server on [localhost:8080](http://localhost:8080/) with this file loaded. You can test your changes in the playground then copy its contents into your pull request when you're ready.

You can also test vuetify in your own project using [`yarn link`](https://classic.yarnpkg.com/en/docs/cli/link/):

- Navigate to `packages/vuetify`
- Run `yarn link`
- Navigate to your project's directory
- Run `yarn link vuetify`

If your project is using vuetify-loader you will have to run `yarn build:lib` in the vuetify package to see changes, otherwise you can use `yarn watch` for incremental builds.

#### Playground.vue

The **Playground** file is a cleanroom used for Vuetify development and is the recommended way to iterate on changes within the framework.

```html
<template>
  <v-container>
    <!--  -->
  </v-container>
</template>

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

The **App.vue** file used for Vuetify development is located in `packages/vuetify/dev`. It contains a [v-app](/api/v-app/) and [v-main](/api/v-main/) component and the local Playground.vue file.

### Documentation

The documentation is located in `packages/docs` but also uses some files from `packages/api-generator`. A dev server for the documentation can be started by running `yarn dev docs` from the project root and will be available on [localhost:8080](http://localhost:8080/) by default.

If you want to see changes from Vuetify in the documentation you need to run `yarn build:lib` in the vuetify package before starting the documentation server.

### Submitting Changes / Pull Requests

First you should create a fork of the vuetify repository to push your changes to. Information on forking repositories can be found in the [GitHub documentation](https://help.github.com/en/github/getting-started-with-github/fork-a-repo).

Then add your fork as a remote in git:

```bash
# Using HTTPS
git remote add fork https://github.com/YOUR_USERNAME/vuetify.git

# Using SSH
git remote add fork git@github.com:YOUR_USERNAME/vuetify.git
```

#### Choosing a base branch

Before starting development you should know which branch to base your changes on. If in doubt use master as changes to master can usually be merged into a different branch without rebasing.

| Type of change | Branch |
| --- | --- |
| Documentation | `master` |
| Bug fixes | `master` |
| New features | `dev` |
| Features with breaking changes | `next` |

```bash
# Switch to the desired branch
git switch master

# Pull down any upstream changes
git pull

# Create a new branch to work on
git switch --create fix/1234-some-issue
```

<alert type="warning">Never commit directly to the base branches, always create a feature branch to work on</alert>

Commit your changes following [our guidelines](#commit-guidelines), then push the branch to your fork with `git push -u fork` and open a pull request on the Vuetify repository following the provided template.

<alert type="warning">

  Pull requests that include unrelated commits or your local merges may be **closed** without notice.

</alert>

## Working with GitHub

Vuetify's repository lives on [GitHub](https://github.com/vuetifyjs/vuetify) and is the primary location for all development related information. In addition, we have a public GitHub project with a detailed overview of what we are currently working on in [Vuetify 3](https://titan.vuetifyjs.com).

Some of the more notable links within these services include:

**GitHub**

- [Issues]
- [Discussions](https://github.com/vuetifyjs/vuetify/discussions)
- [Projects](https://github.com/vuetifyjs/vuetify/projects)
- [Coding Guidelines](https://github.com/vuetifyjs/vuetify/wiki/coding-guidelines)

---

The following sections are designed to familiarize you with our standard operating procedures for Vuetify development.

<promoted-ad slug="vue-jobs" />

### Issue triage

With the size and popularity of Vuetify has come a constant influx of new issues, questions, and feature requests. To organize these requests the [Core Team] developed tools to aid not only the triaging of issues, but creating them as well.

The [Issues] board makes heavy use of GitHub's label system with some light automation, such as adding the `triage` label to new issues.

#### For Docs - Language

We **do not** accept PRs for any documentation changes pertaining to languages other than `en`. All changes for languages other than `en` are to be submitted through our [Crowdin project](https://crowdin.com/project/vuetify). You can help translate in one of 2 ways:

- Using in-context translation service directly through the documentation site. To get started simply select `Help Translate` in the language drop down in the docs.
- Directly through the [Crowdin project](https://crowdin.com/project/vuetify).

**Note**: Languages will not be added to the language drop down on the docs site until they have at least 50% of their translations completed.

### Requesting new features

Vuetify uses the **RFC** (request for comments) process for new feature suggestions. It is intended to provide a consistent and controlled path for new features to enter the framework.

Many changes, including bug fixes and documentation improvements can be implemented and reviewed via the normal GitHub pull request workflow.

Some changes though are _substantial_, and we ask that these be put through a bit of a design process and produce a consensus among the Vuetify [Core Team](/about/meet-the-team/) and the [community](https://discord.gg/eXubxyJ).

#### Getting started

In order to get a major feature added to Vuetify you must get your RFC merged into this repository as a `.md` file. The following is a guide on how to get started:

- Fork the Vuetify RFC repo <https://github.com/vuetifyjs/rfcs>

- Copy `0000-template.md` to `active-rfcs/0000-my-feature.md` (where **my-feature** is descriptive. **do not** assign an RFC number yet).

- Fill in the RFC. Be detailed and put care into the details.

  <alert type="error">

    RFCs that do not present convincing motivation, demonstrate understanding of the impact of the design, or are disingenuous about the drawbacks or alternatives tend to be poorly-received

  </alert>

- Submit a pull request. As a pull request the RFC will receive design feedback from the larger community, and the author should be prepared to revise it in response. New RFC pull requests start in the **Pending** stage.

- Build consensus and integrate feedback. RFCs that have broad support are much more likely to make progress than those that don't receive any comments.

- Eventually, the [Core Team] will decide whether the RFC is a candidate for inclusion in Vuetify.

- An RFC can be modified based upon feedback from the [Core Team] and [community]. Significant modifications may trigger a new _final comment_ period.

- An RFC may be rejected after public discussion has settled and comments have been made summarizing the rationale for rejection. A [Core Team] member will close the RFCs associated pull request, at which point the RFC will enter the **Rejected** stage.

- An RFC may be accepted at the close of its _final comment_ period. A [Core Team] member will merge the RFCs associated pull request, at which point the RFC will enter the **Active** stage.

Once an RFC is merged and the corresponding functionality implemented within the Vuetify repository, it will be part of the next _major_ or _minor_ release. Once released, the RFC will enter the **Released** stage and be locked.

For more information regarding RFCs, see the official repository: <https://github.com/vuetifyjs/rfcs>

### Commit guidelines

All commit messages are required to follow the [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) standard using the _angular_ preset. This standard format consists of 2 types of commits:

- With scope: `<type>(scope): <subject>`

  ```bash
  fix(VSelect): don't close when a detachable child is clicked

  fixes #12354
  ```

- Without scope: `<type>: <subject>`

  ```bash
  docs: restructure nav components

  Moved duplicated functionality in drawer to reduce
  scope of responsibility
  ```

#### General Rules

- Commit messages must have a subject line and may have body copy. These must be separated by a blank line.
- The subject line must not exceed 60 characters
- The subject line must be written in imperative mood (fix, not fixed / fixes etc.)
- The body copy must include a reference all issues resolved:

  ```bash
  docs(sass-variables): fix broken link to api

  resolves #3219
  resolves #3254
  ```

- The body copy must be wrapped at 72 characters
- The body copy must only contain explanations as to what and why, never how. The latter belongs in documentation and implementation.

#### Commit types

The following is a list of **commit types** used in the _angular_ preset:

- **feat:** Commits that result in new features or functionalities. Backwards compatible features will release with the next **MINOR** whereas breaking changes will be in the next **MAJOR**. The body of a commit with breaking changes must begin with `BREAKING CHANGE`, followed by a description of how the API has changed.
- **fix:** Commits that provide fixes for bugs within vuetify's codebase.
- **docs:** Commits that provide updates to the docs.
- **style:** Commits that do not affect how the code runs, these are simply changes to formatting.
- **refactor:** Commits that neither fixes a bug nor adds a feature.
- **perf:** Commits that improve performance.
- **test:** Commits that add missing or correct existing tests.
- **chore:** Other commits that don't modify src or test files.
- **revert:** Commits that revert previous commits.

<promoted-ad slug="vuetify-reddit" />

#### Commitizen

The [Vuetify team](/about/meet-the-team/) uses [commitizen] for all repository commits. This allows for easy to read and organized commits with minimal change to normal commit functions. Commitizen provides a fluid interface for handling semantic versioning and makes it easier to write [release notes](https://github.com/vuetifyjs/vuetify/releases).

To get started, [globally install the commitizen package](https://github.com/commitizen/cz-cli#conventional-commit-messages-as-a-global-utility) using [yarn](https://yarnpkg.com/) by running the following commands in your terminal:

```bash
# Install commitizen and the conventional changelog adapter
yarn global add commitizen cz-conventional-changelog

# Then create a .czrc file that tells commitizen
# which adapter to use globally.
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc
```

<alert type="warning">

  Sometimes creating a `.czrc` file does not work using the **command prompt**. If you get unexpected results, create the file in your user folder located in the home directory. This is typically located on your primary harddrive in the `Users` folder.

</alert>

Once complete, instead of using git commit you will run the command git cz in your terminal. From here, you are presented with a series of prompts used to build the commit message. For additional information, please review our guidlines on [commits](#commit-guidelines).

<backmatter />

[commitizen]: https://github.com/commitizen/cz-cli
[community]: https://community.vuetifyjs.com/
[Core Team]: /about/team/
[pull request]: https://github.com/vuetifyjs/vuetify/pulls
[Issues]: https://github.com/vuetifyjs/vuetify/issues


describe('click-outside.js within the Shadow DOM', () => {

it('should register and unregister handler outside of the shadow DOM', () => {

const { outsideClickHandler, shadowEl } = bootstrap()

// Directives
import ClickOutside from '../'
import { wait } from '../../../../test'

function bootstrap (args?: object) {
  const el = document.createElement('div')

  const binding = {
    value: {
      handler: jest.fn(),
      ...args,
    },
  }

  let clickHandler
  let mousedownHandler
  jest.spyOn(window.document.body, 'addEventListener').mockImplementation((eventName, eventHandler, options) => {
    if (eventName === 'click') clickHandler = eventHandler
    if (eventName === 'mousedown') mousedownHandler = eventHandler
  })
  jest.spyOn(window.document.body, 'removeEventListener')

  ClickOutside.inserted(el as HTMLElement, binding as any)

  return {
    callback: binding.value.handler,
    el: el as HTMLElement,
    clickHandler,
    mousedownHandler,
  }
}

describe('click-outside', () => {
  it('should register and unregister handler', () => {
    const { clickHandler, el } = bootstrap()
    expect(window.document.body.addEventListener).toHaveBeenCalledWith('click', clickHandler, true)

    ClickOutside.unbind(el)
    expect(window.document.body.removeEventListener).toHaveBeenCalledWith('click', clickHandler, true)
  })

  it('should call the callback when closeConditional returns true', async () => {
    const { clickHandler, callback } = bootstrap({ closeConditional: () => true })
    const event = { target: document.createElement('div') }

    clickHandler(event)
    await wait()
    expect(callback).toHaveBeenCalledWith(event)
  })

  it('should not call the callback when closeConditional returns false', async () => {
    const { clickHandler, callback, el } = bootstrap({ closeConditional: () => false })

    clickHandler({ target: el })
    await wait()
    expect(callback).not.toHaveBeenCalled()
  })

  it('should not call the callback when closeConditional is not provided', async () => {
    const { clickHandler, callback, el } = bootstrap()

    clickHandler({ target: el })
    await wait()
    expect(callback).not.toHaveBeenCalled()
  })

  it('should not call the callback when clicked in element', async () => {
    const { clickHandler, callback, el } = bootstrap({ closeConditional: () => true })

    clickHandler({ target: el })
    await wait()
    expect(callback).not.toHaveBeenCalledWith()
  })

  it('should not call the callback when clicked in elements', async () => {
    const { clickHandler, callback, el } = bootstrap({
      closeConditional: () => true,
      include: () => [el],
    })

    clickHandler({ target: document.createElement('div') })
    await wait()
    expect(callback).not.toHaveBeenCalledWith()
  })

  it('should not call the callback when event is not fired by user action', async () => {
    const { clickHandler, callback } = bootstrap({ closeConditional: () => true })

    clickHandler({ isTrusted: false })
    await wait()
    expect(callback).not.toHaveBeenCalledWith()

    clickHandler({ pointerType: false })
    await wait()
    expect(callback).not.toHaveBeenCalledWith()
  })

  it('should not call the callback when mousedown was on the element', async () => {
    const { clickHandler, mousedownHandler, callback, el } = bootstrap({ closeConditional: () => true })
    const mousedownEvent = { target: el }
    const clickEvent = { target: document.createElement('div') }

    mousedownHandler(mousedownEvent)
    clickHandler(clickEvent)
    await wait()
    expect(callback).not.toHaveBeenCalledWith(clickEvent)
  })
})


// Components
import Overlayable from '../index'

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

describe('Overlayable.ts', () => {
  const Mock = Overlayable.extend({
    data: () => ({
      isActive: false,
    }),

    render: h => h('div'),
  })

  beforeEach(() => {
    document.body.setAttribute('data-app', 'true')
  })

  type Instance = InstanceType<typeof Mock>
  let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>

  beforeEach(() => {
    mountFunction = (options = {}) => {
      return mount(Mock, {
        mocks: {
          $vuetify: { breakpoint: {} },
        },
        ...options,
      })
    }
  })

  it('should avoid removing overlay', async () => {
    const wrapper = mountFunction()

    wrapper.vm.genOverlay()

    await waitAnimationFrame()

    expect(wrapper.vm.overlay).toBeTruthy()

    wrapper.vm.removeOverlay()
    // Simular overlay being rapidly opened/closed
    wrapper.vm.overlay.value = true

    const event = new Event('transitionend')

    wrapper.vm.overlay.$el.dispatchEvent(event)
    expect(wrapper.vm.overlay).toBeTruthy()

    wrapper.vm.removeOverlay()

    wrapper.vm.overlay.$el.dispatchEvent(event)
    expect(wrapper.vm.overlay).toBeFalsy()
  })

  it('should be removed', async () => {
    const wrapper = mountFunction()

    wrapper.vm.genOverlay()
    wrapper.vm.removeOverlay()

    await waitAnimationFrame()
    expect(wrapper.vm.overlay.value).toBeFalsy()

    const event = new Event('transitionend')
    wrapper.vm.overlay.$el.dispatchEvent(event)
    expect(wrapper.vm.overlay).toBeFalsy()
  })

  // https://github.com/vuetifyjs/vuetify/issues/8473
  it('should get root element z-index if activeIndex is not available', async () => {
    const wrapper = mountFunction()

    wrapper.vm.$el.style.zIndex = 8

    wrapper.vm.genOverlay()

    await waitAnimationFrame()

    expect(wrapper.vm.overlay.zIndex).toBe(8)
  })

  // https://github.com/vuetifyjs/vuetify/issues/8142
  it('should not update overlay state if not active', () => {
    const cb = jest.fn()
    const wrapper = mountFunction({
      methods: {
        removeOverlay: cb,
        genOVerlay: cb,
      },
    })

    wrapper.setProps({ hideOverlay: true })
    wrapper.setProps({ hideOverlay: false })

    expect(cb).not.toHaveBeenCalled()

    wrapper.setData({ isActive: true })
    wrapper.setProps({ hideOverlay: true })
    wrapper.setProps({ hideOverlay: false })

    expect(cb).toHaveBeenCalledTimes(1)
  })
})


<template>
  <v-container fluid>
    <v-switch
      v-model="switch1"
      :label="`Switch 1: ${switch1.toString()}`"
    ></v-switch>
    <v-switch
      v-model="switch2"
      :label="`Switch 2: ${switch2.toString()}`"
    ></v-switch>
  </v-container>
</template>

<script>
  export default {
    data () {
      return {
        switch1: true,
        switch2: false,
      }
    },
  }
</script>


import './_grid.sass'
import { createSimpleFunctional } from '../../util/helpers'

export default createSimpleFunctional('spacer', 'div', 'v-spacer')


it('should register and unregister handler within the shadow DOM', () => {


@import './_variables.scss'

+theme(v-subheader) using ($material)
  color: map-deep-get($material, 'text', 'secondary')

.v-subheader
  align-items: center
  display: flex
  height: $subheader-item-single-height
  font-size: map-deep-get($headings, 'body-2', 'size')
  font-weight: map-deep-get($headings, 'body-2', 'weight')
  padding: 0 $subheader-right-padding 0 $subheader-left-padding

  &--inset
    margin-left: $subheader-inset-margin


chore(release): publish %s


<template>

    <v-avatar
      v-bind="$attrs"
      v-on="$listeners"
    >
      VJ
    </v-avatar>

</template>


if (eventName === 'click') clickHandler = eventHandler

if (eventName === 'mousedown') mousedownHandler = eventHandler

keep keep keep keep replace keep keep keep

jest.spyOn(window.document, 'addEventListener').mockImplementation((eventName, eventHandler, options) => {

// Directives
import ClickOutside from '../'
import { wait } from '../../../../test'

function bootstrap (args?: object) {
  const outsideEl = document.createElement('div')
  const shadowHost = document.createElement('div')
  const shadowRoot = shadowHost.attachShadow({ mode: 'open' })
  const shadowEl = document.createElement('div')

  const binding = {
    value: {
      handler: jest.fn(),
      ...args,
    },
  }

  let shadowClickHandler
  let outsideClickHandler

  let shadowMousedownHandler
  let outsideMousedownHandler

  document.body.appendChild(shadowHost)
  shadowRoot.appendChild(shadowEl)

  jest.spyOn(window.document.body, 'addEventListener').mockImplementation((eventName, eventHandler, options) => {
    if (eventName === 'click') outsideClickHandler = eventHandler
    if (eventName === 'mousedown') outsideMousedownHandler = eventHandler
  })

  jest.spyOn(shadowRoot, 'addEventListener').mockImplementation((eventName, eventHandler, options) => {
    if (eventName === 'click') shadowClickHandler = eventHandler
    if (eventName === 'mousedown') shadowMousedownHandler = eventHandler
  })

  jest.spyOn(window.document.body, 'removeEventListener')
  jest.spyOn(shadowRoot, 'removeEventListener')

  ClickOutside.inserted(shadowEl as HTMLElement, binding as any)

  return {
    callback: binding.value.handler,
    shadowEl: shadowEl as HTMLElement,
    outsideEl: outsideEl as HTMLElement,
    shadowRoot: shadowRoot as Node,
    shadowClickHandler,
    outsideClickHandler,
    shadowMousedownHandler,
    outsideMousedownHandler,
  }
}

describe('click-outside.js within the Shadow DOM', () => {
  it('should register and unregister handler outside of the shadow DOM', () => {
    const { outsideClickHandler, shadowEl } = bootstrap()
    expect(window.document.body.addEventListener).toHaveBeenCalledWith('click', outsideClickHandler, true)

    ClickOutside.unbind(shadowEl)
    expect(window.document.body.removeEventListener).toHaveBeenCalledWith('click', outsideClickHandler, true)
  })

  it('should register and unregister handler within the shadow DOM', () => {
    const { shadowClickHandler, shadowRoot, shadowEl } = bootstrap()
    expect(shadowRoot.addEventListener).toHaveBeenCalledWith('click', shadowClickHandler, true)

    ClickOutside.unbind(shadowEl)
    expect(shadowRoot.removeEventListener).toHaveBeenCalledWith('click', shadowClickHandler, true)
  })

  it('should call the callback when closeConditional returns true and event target is outside the shadow DOM', async () => {
    const { outsideClickHandler, callback, outsideEl } = bootstrap({ closeConditional: () => true })
    const event = { target: outsideEl }

    outsideClickHandler(event)
    await wait()
    expect(callback).toHaveBeenCalledWith(event)
  })

  it('should call the callback when closeConditional returns true and event target is within the shadow DOM', async () => {
    const { shadowClickHandler, callback, shadowRoot } = bootstrap({ closeConditional: () => true })
    const event = { target: shadowRoot }

    shadowClickHandler(event)
    await wait()
    expect(callback).toHaveBeenCalledWith(event)
  })

  it('should not call the callback when closeConditional is not provided', async () => {
    const { shadowClickHandler, callback, shadowEl } = bootstrap()

    shadowClickHandler({ target: shadowEl })
    await wait()
    expect(callback).not.toHaveBeenCalled()
  })

  it('should not call the callback when clicked in element within the shadow DOM', async () => {
    const { shadowClickHandler, callback, shadowEl } = bootstrap({ closeConditional: () => true })

    shadowClickHandler({ target: shadowEl })
    await wait()
    expect(callback).not.toHaveBeenCalledWith()
  })

  it('should not call the callback when mousedown was on the element within the shadow DOM', async () => {
    const { shadowClickHandler, shadowMousedownHandler, callback, shadowEl } = bootstrap({ closeConditional: () => true })
    const mousedownEvent = { target: shadowEl }
    const clickEvent = { target: document.createElement('div') }

    shadowMousedownHandler(mousedownEvent)
    shadowClickHandler(clickEvent)
    await wait()
    expect(callback).not.toHaveBeenCalledWith(clickEvent)
  })
})


module.exports = {
  'v-divider': {
    props: [
      {
        name: 'dark',
        source: 'themeable',
      },
      {
        name: 'light',
        source: 'themeable',
      },
    ],
  },
}


// Components
import { VExpandTransition } from '../transitions'
import { VIcon } from '../VIcon'
import VTreeview from './VTreeview'

// Mixins
import { inject as RegistrableInject } from '../../mixins/registrable'
import Colorable from '../../mixins/colorable'

// Utils
import mixins, { ExtractVue } from '../../util/mixins'
import { getObjectValueByPath, createRange } from '../../util/helpers'

// Types
import { VNode, VNodeChildren, PropType } from 'vue'
import { PropValidator } from 'vue/types/options'

type VTreeViewInstance = InstanceType<typeof VTreeview>

const baseMixins = mixins(
  Colorable,
  RegistrableInject('treeview')
)

interface options extends ExtractVue<typeof baseMixins> {
  treeview: VTreeViewInstance
}

export const VTreeviewNodeProps = {
  activatable: Boolean,
  activeClass: {
    type: String,
    default: 'v-treeview-node--active',
  },
  color: {
    type: String,
    default: 'primary',
  },
  expandIcon: {
    type: String,
    default: '$subgroup',
  },
  indeterminateIcon: {
    type: String,
    default: '$checkboxIndeterminate',
  },
  itemChildren: {
    type: String,
    default: 'children',
  },
  itemDisabled: {
    type: String,
    default: 'disabled',
  },
  itemKey: {
    type: String,
    default: 'id',
  },
  itemText: {
    type: String,
    default: 'name',
  },
  loadChildren: Function as PropType<(item: any) => Promise<void>>,
  loadingIcon: {
    type: String,
    default: '$loading',
  },
  offIcon: {
    type: String,
    default: '$checkboxOff',
  },
  onIcon: {
    type: String,
    default: '$checkboxOn',
  },
  openOnClick: Boolean,
  rounded: Boolean,
  selectable: Boolean,
  selectedColor: {
    type: String,
    default: 'accent',
  },
  shaped: Boolean,
  transition: Boolean,
  selectionType: {
    type: String as PropType<'leaf' | 'independent'>,
    default: 'leaf',
    validator: (v: string) => ['leaf', 'independent'].includes(v),
  },
}

/* @vue/component */
const VTreeviewNode = baseMixins.extend<options>().extend({
  name: 'v-treeview-node',

  inject: {
    treeview: {
      default: null,
    },
  },

  props: {
    level: Number,
    item: {
      type: Object,
      default: () => null,
    } as PropValidator<Record<string, unknown> | null>,
    parentIsDisabled: Boolean,
    ...VTreeviewNodeProps,
  },

  data: () => ({
    hasLoaded: false,
    isActive: false, // Node is selected (row)
    isIndeterminate: false, // Node has at least one selected child
    isLoading: false,
    isOpen: false, // Node is open/expanded
    isSelected: false, // Node is selected (checkbox)
  }),

  computed: {
    disabled (): boolean {
      return (
        getObjectValueByPath(this.item, this.itemDisabled) ||
        (this.parentIsDisabled && this.selectionType === 'leaf')
      )
    },
    key (): string {
      return getObjectValueByPath(this.item, this.itemKey)
    },
    children (): any[] | null {
      const children = getObjectValueByPath(this.item, this.itemChildren)
      return children && children.filter((child: any) => !this.treeview.isExcluded(getObjectValueByPath(child, this.itemKey)))
    },
    text (): string {
      return getObjectValueByPath(this.item, this.itemText)
    },
    scopedProps (): object {
      return {
        item: this.item,
        leaf: !this.children,
        selected: this.isSelected,
        indeterminate: this.isIndeterminate,
        active: this.isActive,
        open: this.isOpen,
      }
    },
    computedIcon (): string {
      if (this.isIndeterminate) return this.indeterminateIcon
      else if (this.isSelected) return this.onIcon
      else return this.offIcon
    },
    hasChildren (): boolean {
      return !!this.children && (!!this.children.length || !!this.loadChildren)
    },
  },

  created () {
    this.treeview.register(this)
  },

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

  methods: {
    checkChildren (): Promise<void> {
      return new Promise<void>(resolve => {
        // TODO: Potential issue with always trying
        // to load children if response is empty?
        if (!this.children || this.children.length || !this.loadChildren || this.hasLoaded) return resolve()

        this.isLoading = true
        resolve(this.loadChildren(this.item))
      }).then(() => {
        this.isLoading = false
        this.hasLoaded = true
      })
    },
    open () {
      this.isOpen = !this.isOpen
      this.treeview.updateOpen(this.key, this.isOpen)
      this.treeview.emitOpen()
    },
    genLabel () {
      const children = []

      if (this.$scopedSlots.label) children.push(this.$scopedSlots.label(this.scopedProps))
      else children.push(this.text)

      return this.$createElement('div', {
        slot: 'label',
        staticClass: 'v-treeview-node__label',
      }, children)
    },
    genPrependSlot () {
      if (!this.$scopedSlots.prepend) return null

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__prepend',
      }, this.$scopedSlots.prepend(this.scopedProps))
    },
    genAppendSlot () {
      if (!this.$scopedSlots.append) return null

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__append',
      }, this.$scopedSlots.append(this.scopedProps))
    },
    genContent () {
      const children = [
        this.genPrependSlot(),
        this.genLabel(),
        this.genAppendSlot(),
      ]

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__content',
      }, children)
    },
    genToggle () {
      return this.$createElement(VIcon, {
        staticClass: 'v-treeview-node__toggle',
        class: {
          'v-treeview-node__toggle--open': this.isOpen,
          'v-treeview-node__toggle--loading': this.isLoading,
        },
        slot: 'prepend',
        on: {
          click: (e: MouseEvent) => {
            e.stopPropagation()

            if (this.isLoading) return

            this.checkChildren().then(() => this.open())
          },
        },
      }, [this.isLoading ? this.loadingIcon : this.expandIcon])
    },
    genCheckbox () {
      return this.$createElement(VIcon, {
        staticClass: 'v-treeview-node__checkbox',
        props: {
          color: this.isSelected || this.isIndeterminate ? this.selectedColor : undefined,
          disabled: this.disabled,
        },
        on: {
          click: (e: MouseEvent) => {
            e.stopPropagation()

            if (this.isLoading) return

            this.checkChildren().then(() => {
              // We nextTick here so that items watch in VTreeview has a chance to run first
              this.$nextTick(() => {
                this.isSelected = !this.isSelected
                this.isIndeterminate = false

                this.treeview.updateSelected(this.key, this.isSelected)
                this.treeview.emitSelected()
              })
            })
          },
        },
      }, [this.computedIcon])
    },
    genLevel (level: number) {
      return createRange(level).map(() => this.$createElement('div', {
        staticClass: 'v-treeview-node__level',
      }))
    },
    genNode () {
      const children = [this.genContent()]

      if (this.selectable) children.unshift(this.genCheckbox())

      if (this.hasChildren) {
        children.unshift(this.genToggle())
      } else {
        children.unshift(...this.genLevel(1))
      }

      children.unshift(...this.genLevel(this.level))

      return this.$createElement('div', this.setTextColor(this.isActive && this.color, {
        staticClass: 'v-treeview-node__root',
        class: {
          [this.activeClass]: this.isActive,
        },
        on: {
          click: () => {
            if (this.openOnClick && this.hasChildren) {
              this.checkChildren().then(this.open)
            } else if (this.activatable && !this.disabled) {
              this.isActive = !this.isActive
              this.treeview.updateActive(this.key, this.isActive)
              this.treeview.emitActive()
            }
          },
        },
      }), children)
    },
    genChild (item: any, parentIsDisabled: boolean) {
      return this.$createElement(VTreeviewNode, {
        key: getObjectValueByPath(item, this.itemKey),
        props: {
          activatable: this.activatable,
          activeClass: this.activeClass,
          item,
          selectable: this.selectable,
          selectedColor: this.selectedColor,
          color: this.color,
          expandIcon: this.expandIcon,
          indeterminateIcon: this.indeterminateIcon,
          offIcon: this.offIcon,
          onIcon: this.onIcon,
          loadingIcon: this.loadingIcon,
          itemKey: this.itemKey,
          itemText: this.itemText,
          itemDisabled: this.itemDisabled,
          itemChildren: this.itemChildren,
          loadChildren: this.loadChildren,
          transition: this.transition,
          openOnClick: this.openOnClick,
          rounded: this.rounded,
          shaped: this.shaped,
          level: this.level + 1,
          selectionType: this.selectionType,
          parentIsDisabled,
        },
        scopedSlots: this.$scopedSlots,
      })
    },
    genChildrenWrapper () {
      if (!this.isOpen || !this.children) return null

      const children = [this.children.map(c => this.genChild(c, this.disabled))]

      return this.$createElement('div', {
        staticClass: 'v-treeview-node__children',
      }, children)
    },
    genTransition () {
      return this.$createElement(VExpandTransition, [this.genChildrenWrapper()])
    },
  },

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

    if (this.transition) children.push(this.genTransition())
    else children.push(this.genChildrenWrapper())

    return h('div', {
      staticClass: 'v-treeview-node',
      class: {
        'v-treeview-node--leaf': !this.hasChildren,
        'v-treeview-node--click': this.openOnClick,
        'v-treeview-node--disabled': this.disabled,
        'v-treeview-node--rounded': this.rounded,
        'v-treeview-node--shaped': this.shaped,
        'v-treeview-node--selected': this.isSelected,
      },
      attrs: {
        'aria-expanded': String(this.isOpen),
      },
    }, children)
  },
})

export default VTreeviewNode


<template>
  <v-fab-transition>
    <v-btn
      v-show="fab"
      v-scroll="onScroll"
      :aria-label="$t('scroll-to-top')"
      :style="{
        marginBottom: `${$vuetify.application.snackbar}px`,
      }"
      :title="$t('scroll-to-top')"
      bottom
      class="transition-swing"
      color="primary"
      fab
      fixed
      large
      right
      style="z-index: 6"
      @click="toTop"
    >
      <v-icon>$mdiChevronUp</v-icon>
    </v-btn>
  </v-fab-transition>
</template>

<script>
  // Globals
  import { IN_BROWSER } from '@/util/globals'

  export default {
    name: 'DefaultFabToTop',

    data: () => ({ fab: false }),

    methods: {
      onScroll () {
        if (!IN_BROWSER) return

        const top = (
          window.pageYOffset ||
          document.documentElement.offsetTop ||
          0
        )

        this.fab = top > 300
      },
      toTop () {
        if (this.$route.hash) {
          this.$router.push({ hash: '' })
        }

        this.$vuetify.goTo(0)
      },
    },
  }
</script>


ClickOutside.inserted(el as HTMLElement, binding as any)

keep keep keep replace keep keep keep keep


<template>
  <div>
    <v-alert
      border="top"
      color="red lighten-2"
      dark
    >
      I'm an alert with a top border and red color
    </v-alert>
    <v-alert
      border="right"
      color="blue-grey"
      dark
    >
      I'm an alert with a right border and blue-grey color
    </v-alert>
    <v-alert
      border="bottom"
      color="pink darken-1"
      dark
    >
      I'm an alert with a bottom border and pink color
    </v-alert>
    <v-alert
      border="left"
      color="indigo"
      dark
    >
      I'm an alert with a border left type info
    </v-alert>
  </div>
</template>


@import './_variables.scss'

/* Theme */
+theme(v-system-bar) using ($material)
  background-color: map-deep-get($material, 'status-bar', 'regular')
  color: map-deep-get($material, 'text', 'secondary')

  .v-icon
    color: map-deep-get($material, 'text', 'secondary')

  &--lights-out
    background-color: map-deep-get($material, 'status-bar', 'lights-out') !important

.v-system-bar
  align-items: center
  display: flex
  font-size: $system-bar-font-size
  font-weight: $system-bar-font-weight
  padding: $system-bar-padding

  .v-icon
    font-size: $system-bar-icon-font-size
    margin-right: $system-bar-icon-margin-right

  &--fixed, &--absolute
    left: 0
    top: 0
    width: 100%
    z-index: 3

  &--fixed
    position: fixed

  &--absolute
    position: absolute

  &--window
    .v-icon
      font-size: $system-bar-window-icon-font-size
      margin-right: $system-bar-window-icon-margin-right


import dependent from '../'
import toggleable from '../../toggleable'
import { mount } from '@vue/test-utils'

function genDependentMixin () {
  return {
    mixins: [dependent, toggleable],

    props: {
      value: Boolean,
    },

    render (h) {
      return h('div', [
        h('div', {
          ref: 'content',
        }, 'foobar'),
        this.$slots.default,
      ])
    },
  }
}

describe('dependent.ts', () => {
  beforeEach(() => {
    document.body.setAttribute('data-app', 'true')
  })

  it('should set open dependents value to false when deactivated', async () => {
    const mock = { isActive: true }
    const getOpenDependents = jest.fn(() => [mock])
    const wrapper = mount(genDependentMixin(), {
      methods: { getOpenDependents },
    })

    wrapper.setProps({ value: true })

    await wrapper.vm.$nextTick()

    expect(getOpenDependents).not.toHaveBeenCalled()

    wrapper.setProps({ value: false })

    await wrapper.vm.$nextTick()

    expect(getOpenDependents).toHaveBeenCalled()
    expect(mock.isActive).toBe(false)
  })

  it('should conditionally get open dependents', async () => {
    const wrapper = mount(genDependentMixin(), {
      slots: {
        default: [{
          ...genDependentMixin(),
          data: () => ({
            isActive: true,
          }),
        }],
      },
    })

    const openDependents = wrapper.vm.getOpenDependents()

    expect(openDependents).toEqual(wrapper.vm.$children)

    wrapper.setData({ closeDependents: false })

    expect(wrapper.vm.getOpenDependents()).toEqual([])
  })

  it('should get open dependent elements', async () => {
    const wrapper = mount(genDependentMixin(), {
      slots: {
        default: [
          {
            ...genDependentMixin(),
            data: () => ({
              isActive: true,
            }),
          },
          {
            ...genDependentMixin(),
            data: () => ({
              isActive: true,
            }),
            render: h => h('div', 'fizzbuzz'),
          },
          { render: h => h('div') },
        ],
      },
    })

    const openDependentElements = wrapper.vm.getOpenDependentElements()

    expect(openDependentElements).toHaveLength(3)
  })
})


it('should register and unregister handler', () => {

const { clickHandler, el } = bootstrap()


<template>
  <v-list class="pa-0" outlined>
    <v-list-item class="included" @click="() => null">
      <v-list-item-title>Included</v-list-item-title>
    </v-list-item>

    <v-divider></v-divider>

    <v-list-item @click="() => null">
      <v-list-item-title>Not included</v-list-item-title>
    </v-list-item>

    <v-divider></v-divider>

    <v-list-item
      v-click-outside="{
        handler: onClickOutsideStandard,
        include: include,
      }"
      @click="models.base = true"
    >
      <v-list-item-title>Default Click Outside</v-list-item-title>

      <v-list-item-action>
        <v-icon :color="models.base ? 'green' : 'red'">
          mdi-record
        </v-icon>
      </v-list-item-action>
    </v-list-item>
  </v-list>
</template>

<script>
  export default {
    data: () => ({
      models: {
        base: false,
      },
    }),
    methods: {
      onClickOutsideStandard () {
        this.models.base = false
      },
      include () {
        return [document.querySelector('.included')]
      },
    },
  }
</script>


<template>
  <v-row
    justify="center"
    align="center"
  >
    <v-chip draggable>
      Default
    </v-chip>
  </v-row>
</template>


<template>
  <v-row justify="space-around">
    <v-col cols="12">
      <v-slider
        v-model="slider"
        label="Slider"
        v-bind="$attrs"
        v-on="$listeners"
      ></v-slider>
    </v-col>
  </v-row>
</template>

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

    inheritAttrs: false,

    data: () => ({
      slider: 0,
      defaults: {
        dense: false,
        disabled: false,
        'hide-details': false,
        hint: 'Im a hint',
        'inverse-label': false,
        max: 50,
        min: -50,
        'persistent-hint': false,
        readonly: false,
        vertical: false,
      },
      options: {
        booleans: [
          'disabled',
          'hide-details',
          'inverse-label',
          'persistent-hint',
          'readonly',
        ],
        sliders: {
          min: [-100, 100],
          max: [-100, 100],
        },
      },
      tabs: ['dense', 'vertical'],
    }),
  }
</script>


it('should call the callback when closeConditional returns true', async () => {

const { clickHandler, callback } = bootstrap({ closeConditional: () => true })

const event = { target: document.createElement('div') }

keep keep replace keep keep keep keep keep


<template>
  <v-container
    id="input-usage"
    fluid
  >
    <v-row>
      <v-col cols="12">
        <v-input
          :messages="['Messages']"
          append-icon="mdi-close"
          prepend-icon="mdi-phone"
          @click:append="appendIconCallback"
          @click:prepend="prependIconCallback"
        >
          Default Slot
        </v-input>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
  export default {
    data () {
      return {
        text: '',
      }
    },
    methods: {
      appendIconCallback () {
        alert('click:append')
      },
      prependIconCallback () {
        alert('click:prepend')
      },
    },
  }
</script>

<style>
  #input-usage .v-input__prepend-outer,
  #input-usage .v-input__append-outer,
  #input-usage .v-input__slot,
  #input-usage .v-messages {
    border: 1px dashed rgba(0,0,0, .4);
  }
</style>


// Components
import VSimpleCheckbox from '../VCheckbox/VSimpleCheckbox'
import VDivider from '../VDivider'
import VSubheader from '../VSubheader'
import {
  VList,
  VListItem,
  VListItemAction,
  VListItemContent,
  VListItemTitle,
} from '../VList'

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

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

// Helpers
import {
  escapeHTML,
  getPropertyFromItem,
} from '../../util/helpers'

// Types
import mixins from '../../util/mixins'
import { VNode, PropType, VNodeChildren } from 'vue'
import { PropValidator } from 'vue/types/options'
import { SelectItemKey } from 'vuetify/types'

type ListTile = { item: any, disabled?: null | boolean, value?: boolean, index: number };

/* @vue/component */
export default mixins(Colorable, Themeable).extend({
  name: 'v-select-list',

  // https://github.com/vuejs/vue/issues/6872
  directives: {
    ripple,
  },

  props: {
    action: Boolean,
    dense: Boolean,
    hideSelected: Boolean,
    items: {
      type: Array,
      default: () => [],
    } as PropValidator<any[]>,
    itemDisabled: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'disabled',
    },
    itemText: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'text',
    },
    itemValue: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'value',
    },
    noDataText: String,
    noFilter: Boolean,
    searchInput: null as unknown as PropType<any>,
    selectedItems: {
      type: Array,
      default: () => [],
    } as PropValidator<any[]>,
  },

  computed: {
    parsedItems (): any[] {
      return this.selectedItems.map(item => this.getValue(item))
    },
    tileActiveClass (): string {
      return Object.keys(this.setTextColor(this.color).class || {}).join(' ')
    },
    staticNoDataTile (): VNode {
      const tile = {
        attrs: {
          role: undefined,
        },
        on: {
          mousedown: (e: Event) => e.preventDefault(), // Prevent onBlur from being called
        },
      }

      return this.$createElement(VListItem, tile, [
        this.genTileContent(this.noDataText),
      ])
    },
  },

  methods: {
    genAction (item: object, inputValue: any): VNode {
      return this.$createElement(VListItemAction, [
        this.$createElement(VSimpleCheckbox, {
          props: {
            color: this.color,
            value: inputValue,
            ripple: false,
          },
          on: {
            input: () => this.$emit('select', item),
          },
        }),
      ])
    },
    genDivider (props: { [key: string]: any }) {
      return this.$createElement(VDivider, { props })
    },
    genFilteredText (text: string) {
      text = text || ''

      if (!this.searchInput || this.noFilter) return escapeHTML(text)

      const { start, middle, end } = this.getMaskedCharacters(text)

      return `${escapeHTML(start)}${this.genHighlight(middle)}${escapeHTML(end)}`
    },
    genHeader (props: { [key: string]: any }): VNode {
      return this.$createElement(VSubheader, { props }, props.header)
    },
    genHighlight (text: string): string {
      return `<span class="v-list-item__mask">${escapeHTML(text)}</span>`
    },
    getMaskedCharacters (text: string): {
      start: string
      middle: string
      end: string
    } {
      const searchInput = (this.searchInput || '').toString().toLocaleLowerCase()
      const index = text.toLocaleLowerCase().indexOf(searchInput)

      if (index < 0) return { start: text, middle: '', end: '' }

      const start = text.slice(0, index)
      const middle = text.slice(index, index + searchInput.length)
      const end = text.slice(index + searchInput.length)
      return { start, middle, end }
    },
    genTile ({
      item,
      index,
      disabled = null,
      value = false,
    }: ListTile): VNode | VNode[] | undefined {
      if (!value) value = this.hasItem(item)

      if (item === Object(item)) {
        disabled = disabled !== null
          ? disabled
          : this.getDisabled(item)
      }

      const tile = {
        attrs: {
          // Default behavior in list does not
          // contain aria-selected by default
          'aria-selected': String(value),
          id: `list-item-${this._uid}-${index}`,
          role: 'option',
        },
        on: {
          mousedown: (e: Event) => {
            // Prevent onBlur from being called
            e.preventDefault()
          },
          click: () => disabled || this.$emit('select', item),
        },
        props: {
          activeClass: this.tileActiveClass,
          disabled,
          ripple: true,
          inputValue: value,
        },
      }

      if (!this.$scopedSlots.item) {
        return this.$createElement(VListItem, tile, [
          this.action && !this.hideSelected && this.items.length > 0
            ? this.genAction(item, value)
            : null,
          this.genTileContent(item, index),
        ])
      }

      const parent = this
      const scopedSlot = this.$scopedSlots.item({
        parent,
        item,
        attrs: {
          ...tile.attrs,
          ...tile.props,
        },
        on: tile.on,
      })

      return this.needsTile(scopedSlot)
        ? this.$createElement(VListItem, tile, scopedSlot)
        : scopedSlot
    },
    genTileContent (item: any, index = 0): VNode {
      const innerHTML = this.genFilteredText(this.getText(item))

      return this.$createElement(VListItemContent,
        [this.$createElement(VListItemTitle, {
          domProps: { innerHTML },
        })]
      )
    },
    hasItem (item: object) {
      return this.parsedItems.indexOf(this.getValue(item)) > -1
    },
    needsTile (slot: VNode[] | undefined) {
      return slot!.length !== 1 ||
        slot![0].componentOptions == null ||
        slot![0].componentOptions.Ctor.options.name !== 'v-list-item'
    },
    getDisabled (item: object) {
      return Boolean(getPropertyFromItem(item, this.itemDisabled, false))
    },
    getText (item: object) {
      return String(getPropertyFromItem(item, this.itemText, item))
    },
    getValue (item: object) {
      return getPropertyFromItem(item, this.itemValue, this.getText(item))
    },
  },

  render (): VNode {
    const children: VNodeChildren = []
    const itemsLength = this.items.length
    for (let index = 0; index < itemsLength; index++) {
      const item = this.items[index]

      if (this.hideSelected &&
        this.hasItem(item)
      ) continue

      if (item == null) children.push(this.genTile({ item, index }))
      else if (item.header) children.push(this.genHeader(item))
      else if (item.divider) children.push(this.genDivider(item))
      else children.push(this.genTile({ item, index }))
    }

    children.length || children.push(this.$slots['no-data'] || this.staticNoDataTile)

    this.$slots['prepend-item'] && children.unshift(this.$slots['prepend-item'])

    this.$slots['append-item'] && children.push(this.$slots['append-item'])

    return this.$createElement(VList, {
      staticClass: 'v-select-list',
      class: this.themeClasses,
      attrs: {
        role: 'listbox',
        tabindex: -1,
      },
      props: { dense: this.dense },
    }, children)
  },
})


<template>
  <v-date-picker
    v-model="picker"
    show-adjacent-months
  ></v-date-picker>
</template>

<script>
  export default {
    data () {
      return {
        picker: null,
      }
    },
  }
</script>


function handleShadow (el: HTMLElement, callback: Function): void {

keep keep keep replace keep keep keep keep keep


<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="auto">
        <v-btn
          :min-width="btnWidth"
          :to="{
            name: 'Documentation',
            params: {
              category: 'getting-started',
              page: 'installation'
            }
          }"
          color="primary"
          depressed
          x-large
        >
          <v-icon left>
            $mdiSpeedometer
          </v-icon>

          {{ $t('home.get-started') }}
        </v-btn>
      </v-col>

      <v-col cols="auto">
        <v-btn
          :min-width="btnWidth"
          :to="{
            name: 'Documentation',
            params: {
              category: 'introduction',
              page: 'why-vuetify'
            }
          }"
          color="primary"
          outlined
          x-large
        >
          <v-icon left>
            $mdiVuetify
          </v-icon>

          {{ $t('home.why-vuetify') }}
        </v-btn>
      </v-col>

      <v-col cols="auto">
        <v-btn
          :min-width="btnWidth"
          color="#212121"
          dark
          depressed
          href="https://github.com/vuetifyjs/vuetify"
          rel="noopener"
          target="_blank"
          x-large
        >
          <v-icon left>
            $mdiGithub
          </v-icon>

          GitHub
        </v-btn>
      </v-col>
    </v-row>
  </v-container>
</template>

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

    data: () => ({
      btnWidth: 228,
    }),
  }
</script>


@import './_variables.scss'

.v-item-group
  flex: 0 1 auto
  position: relative
  max-width: 100%
  transition: $item-group-transition


@mixin theme ($component)
  .theme--light.#{$component}
    @content($material-light)
  .theme--dark.#{$component}
    @content($material-dark)


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

import Themeable from '../../mixins/themeable'

import { VNode, PropType, VNodeChildren } from 'vue'

import { PropValidator } from 'vue/types/options'

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

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

import Vue from 'vue'
import { VNode, VNodeDirective } from 'vue/types'
import { VuetifyIcon } from 'vuetify/types/services/icons'
import { DataTableCompareFunction, SelectItemKey, ItemGroup } from 'vuetify/types'

export function createSimpleFunctional (
  c: string,
  el = 'div',
  name?: string
) {
  return Vue.extend({
    name: name || c.replace(/__/g, '-'),

    functional: true,

    props: {
      tag: {
        type: String,
        default: el,
      },
    },

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

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

export type BindingConfig = Pick<VNodeDirective, 'arg' | 'modifiers' | 'value'>
export function directiveConfig (binding: BindingConfig, defaults = {}): VNodeDirective {
  return {
    ...defaults,
    ...binding.modifiers,
    value: binding.arg,
    ...(binding.value || {}),
  }
}

export function addOnceEventListener (
  el: EventTarget,
  eventName: string,
  cb: (event: Event) => void,
  options: boolean | AddEventListenerOptions = false
): void {
  const once = (event: Event) => {
    cb(event)
    el.removeEventListener(eventName, once, options)
  }

  el.addEventListener(eventName, once, options)
}

let passiveSupported = false
try {
  if (typeof window !== 'undefined') {
    const testListenerOpts = Object.defineProperty({}, 'passive', {
      get: () => {
        passiveSupported = true
      },
    }) as EventListener & EventListenerOptions

    window.addEventListener('testListener', testListenerOpts, testListenerOpts)
    window.removeEventListener('testListener', testListenerOpts, testListenerOpts)
  }
} catch (e) { console.warn(e) } /* eslint-disable-line no-console */
export { passiveSupported }

export function addPassiveEventListener (
  el: EventTarget,
  event: string,
  cb: (event: any) => void,
  options: {}
): void {
  el.addEventListener(event, cb, passiveSupported ? options : false)
}

export function getNestedValue (obj: any, path: (string | number)[], fallback?: any): any {
  const last = path.length - 1

  if (last < 0) return obj === undefined ? fallback : obj

  for (let i = 0; i < last; i++) {
    if (obj == null) {
      return fallback
    }
    obj = obj[path[i]]
  }

  if (obj == null) return fallback

  return obj[path[last]] === undefined ? fallback : obj[path[last]]
}

export function deepEqual (a: any, b: any): boolean {
  if (a === b) return true

  if (
    a instanceof Date &&
    b instanceof Date &&
    a.getTime() !== b.getTime()
  ) {
    // If the values are Date, compare them as timestamps
    return false
  }

  if (a !== Object(a) || b !== Object(b)) {
    // If the values aren't objects, they were already checked for equality
    return false
  }

  const props = Object.keys(a)

  if (props.length !== Object.keys(b).length) {
    // Different number of props, don't bother to check
    return false
  }

  return props.every(p => deepEqual(a[p], b[p]))
}

export function getObjectValueByPath (obj: any, path: string, fallback?: any): any {
  // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621
  if (obj == null || !path || typeof path !== 'string') return fallback
  if (obj[path] !== undefined) return obj[path]
  path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
  path = path.replace(/^\./, '') // strip a leading dot
  return getNestedValue(obj, path.split('.'), fallback)
}

export function getPropertyFromItem (
  item: object,
  property: SelectItemKey,
  fallback?: any
): any {
  if (property == null) return item === undefined ? fallback : item

  if (item !== Object(item)) return fallback === undefined ? item : fallback

  if (typeof property === 'string') return getObjectValueByPath(item, property, fallback)

  if (Array.isArray(property)) return getNestedValue(item, property, fallback)

  if (typeof property !== 'function') return fallback

  const value = property(item, fallback)

  return typeof value === 'undefined' ? fallback : value
}

export function createRange (length: number): number[] {
  return Array.from({ length }, (v, k) => k)
}

export function getZIndex (el?: Element | null): number {
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return 0

  const index = +window.getComputedStyle(el).getPropertyValue('z-index')

  if (!index) return getZIndex(el.parentNode as Element)
  return index
}

const tagsToReplace = {
  '&': '&',
  '<': '<',
  '>': '>',
} as any

export function escapeHTML (str: string): string {
  return str.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag)
}

export function filterObjectOnKeys<T, K extends keyof T> (obj: T, keys: K[]): { [N in K]: T[N] } {
  const filtered = {} as { [N in K]: T[N] }

  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if (typeof obj[key] !== 'undefined') {
      filtered[key] = obj[key]
    }
  }

  return filtered
}

export function convertToUnit (str: string | number | null | undefined, unit = 'px'): string | undefined {
  if (str == null || str === '') {
    return undefined
  } else if (isNaN(+str!)) {
    return String(str)
  } else {
    return `${Number(str)}${unit}`
  }
}

export function kebabCase (str: string): string {
  return (str || '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}

export function isObject (obj: any): obj is object {
  return obj !== null && typeof obj === 'object'
}

// KeyboardEvent.keyCode aliases
export const keyCodes = Object.freeze({
  enter: 13,
  tab: 9,
  delete: 46,
  esc: 27,
  space: 32,
  up: 38,
  down: 40,
  left: 37,
  right: 39,
  end: 35,
  home: 36,
  del: 46,
  backspace: 8,
  insert: 45,
  pageup: 33,
  pagedown: 34,
  shift: 16,
})

/**
 * This remaps internal names like '$cancel' or '$vuetify.icons.cancel'
 * to the current name or component for that icon.
 */
export function remapInternalIcon (vm: Vue, iconName: string): VuetifyIcon {
  // Look for custom component in the configuration
  const component = vm.$vuetify.icons.component

  // Look for overrides
  if (iconName.startsWith('$')) {
    // Get the target icon name
    const iconPath = `$vuetify.icons.values.${iconName.split('$').pop()!.split('.').pop()}`

    // Now look up icon indirection name,
    // e.g. '$vuetify.icons.values.cancel'
    const override = getObjectValueByPath(vm, iconPath, iconName)

    if (typeof override === 'string') iconName = override
    else return override
  }

  if (component == null) {
    return iconName
  }

  return {
    component,
    props: {
      icon: iconName,
    },
  }
}

export function keys<O> (o: O) {
  return Object.keys(o) as (keyof O)[]
}

/**
 * Camelize a hyphen-delimited string.
 */
const camelizeRE = /-(\w)/g
export const camelize = (str: string): string => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
}

/**
 * Returns the set difference of B and A, i.e. the set of elements in B but not in A
 */
export function arrayDiff (a: any[], b: any[]): any[] {
  const diff: any[] = []
  for (let i = 0; i < b.length; i++) {
    if (a.indexOf(b[i]) < 0) diff.push(b[i])
  }
  return diff
}

/**
 * Makes the first character of a string uppercase
 */
export function upperFirst (str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

export function groupItems<T extends any = any> (
  items: T[],
  groupBy: string[],
  groupDesc: boolean[]
): ItemGroup<T>[] {
  const key = groupBy[0]
  const groups: ItemGroup<T>[] = []
  let current
  for (let i = 0; i < items.length; i++) {
    const item = items[i]
    const val = getObjectValueByPath(item, key, null)
    if (current !== val) {
      current = val
      groups.push({
        name: val ?? '',
        items: [],
      })
    }
    groups[groups.length - 1].items.push(item)
  }
  return groups
}

export function wrapInArray<T> (v: T | T[] | null | undefined): T[] { return v != null ? Array.isArray(v) ? v : [v] : [] }

export function sortItems<T extends any = any> (
  items: T[],
  sortBy: string[],
  sortDesc: boolean[],
  locale: string,
  customSorters?: Record<string, DataTableCompareFunction<T>>
): T[] {
  if (sortBy === null || !sortBy.length) return items
  const stringCollator = new Intl.Collator(locale, { sensitivity: 'accent', usage: 'sort' })

  return items.sort((a, b) => {
    for (let i = 0; i < sortBy.length; i++) {
      const sortKey = sortBy[i]

      let sortA = getObjectValueByPath(a, sortKey)
      let sortB = getObjectValueByPath(b, sortKey)

      if (sortDesc[i]) {
        [sortA, sortB] = [sortB, sortA]
      }

      if (customSorters && customSorters[sortKey]) {
        const customResult = customSorters[sortKey](sortA, sortB)

        if (!customResult) continue

        return customResult
      }

      // Check if both cannot be evaluated
      if (sortA === null && sortB === null) {
        continue
      }

      // Dates should be compared numerically
      if (sortA instanceof Date && sortB instanceof Date) {
        return sortA.getTime() - sortB.getTime()
      }

      [sortA, sortB] = [sortA, sortB].map(s => (s || '').toString().toLocaleLowerCase())

      if (sortA !== sortB) {
        if (!isNaN(sortA) && !isNaN(sortB)) return Number(sortA) - Number(sortB)
        return stringCollator.compare(sortA, sortB)
      }
    }

    return 0
  })
}

export function defaultFilter (value: any, search: string | null, item: any) {
  return value != null &&
    search != null &&
    typeof value !== 'boolean' &&
    value.toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1
}

export function searchItems<T extends any = any> (items: T[], search: string): T[] {
  if (!search) return items
  search = search.toString().toLowerCase()
  if (search.trim() === '') return items

  return items.filter((item: any) => Object.keys(item).some(key => defaultFilter(getObjectValueByPath(item, key), search, item)))
}

/**
 * Returns:
 *  - 'normal' for old style slots - `<template slot="default">`
 *  - 'scoped' for old style scoped slots (`<template slot="default" slot-scope="data">`) or bound v-slot (`#default="data"`)
 *  - 'v-slot' for unbound v-slot (`#default`) - only if the third param is true, otherwise counts as scoped
 */
export function getSlotType<T extends boolean = false> (vm: Vue, name: string, split?: T): (T extends true ? 'v-slot' : never) | 'normal' | 'scoped' | void {
  if (vm.$slots.hasOwnProperty(name) && vm.$scopedSlots.hasOwnProperty(name) && (vm.$scopedSlots[name] as any).name) {
    return split ? 'v-slot' as any : 'scoped'
  }
  if (vm.$slots.hasOwnProperty(name)) return 'normal'
  if (vm.$scopedSlots.hasOwnProperty(name)) return 'scoped'
}

export function debounce (fn: Function, delay: number) {
  let timeoutId = 0 as any
  return (...args: any[]) => {
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => fn(...args), delay)
  }
}

export function throttle<T extends (...args: any[]) => any> (fn: T, limit: number) {
  let throttling = false
  return (...args: Parameters<T>): void | ReturnType<T> => {
    if (!throttling) {
      throttling = true
      setTimeout(() => throttling = false, limit)
      return fn(...args)
    }
  }
}

export function getPrefixedScopedSlots (prefix: string, scopedSlots: any) {
  return Object.keys(scopedSlots).filter(k => k.startsWith(prefix)).reduce((obj: any, k: string) => {
    obj[k.replace(prefix, '')] = scopedSlots[k]
    return obj
  }, {})
}

export function getSlot (vm: Vue, name = 'default', data?: object | (() => object), optional = false) {
  if (vm.$scopedSlots.hasOwnProperty(name)) {
    return vm.$scopedSlots[name]!(data instanceof Function ? data() : data)
  } else if (vm.$slots.hasOwnProperty(name) && (!data || optional)) {
    return vm.$slots[name]
  }
  return undefined
}

export function clamp (value: number, min = 0, max = 1) {
  return Math.max(min, Math.min(max, value))
}

export function padEnd (str: string, length: number, char = '0') {
  return str + char.repeat(Math.max(0, length - str.length))
}

export function chunk (str: string, size = 1) {
  const chunked: string[] = []
  let index = 0
  while (index < str.length) {
    chunked.push(str.substr(index, size))
    index += size
  }
  return chunked
}

export function humanReadableFileSize (bytes: number, binary = false): string {
  const base = binary ? 1024 : 1000
  if (bytes < base) {
    return `${bytes} B`
  }

  const prefix = binary ? ['Ki', 'Mi', 'Gi'] : ['k', 'M', 'G']
  let unit = -1
  while (Math.abs(bytes) >= base && unit < prefix.length - 1) {
    bytes /= base
    ++unit
  }
  return `${bytes.toFixed(1)} ${prefix[unit]}B`
}

export function camelizeObjectKeys (obj: Record<string, any> | null | undefined) {
  if (!obj) return {}

  return Object.keys(obj).reduce((o: any, key: string) => {
    o[camelize(key)] = obj[key]
    return o
  }, {})
}

export function mergeDeep (
  source: Dictionary<any> = {},
  target: Dictionary<any> = {}
) {
  for (const key in target) {
    const sourceProperty = source[key]
    const targetProperty = target[key]

    // Only continue deep merging if
    // both properties are objects
    if (
      isObject(sourceProperty) &&
      isObject(targetProperty)
    ) {
      source[key] = mergeDeep(sourceProperty, targetProperty)

      continue
    }

    source[key] = targetProperty
  }

  return source
}

export function fillArray<T> (length: number, obj: T) {
  return Array(length).fill(obj)
}

/**  Polyfill for Event.prototype.composedPath */
export function composedPath (e: Event): EventTarget[] {
  if (e.composedPath) return e.composedPath()

  const path = []
  let el = e.target as Element

  while (el) {
    path.push(el)

    if (el.tagName === 'HTML') {
      path.push(document)
      path.push(window)

      return path
    }

    el = el.parentElement!
  }
  return path
}


<template>
  <v-card
    class="mx-auto"
    max-width="400"
  >
    <v-list>
      <v-list-item-group
        v-model="model"
        active-class="border"
        color="indigo"
      >
        <v-list-item
          v-for="(item, i) in items"
          :key="i"
        >
          <v-list-item-icon>
            <v-icon v-text="item.icon"></v-icon>
          </v-list-item-icon>

          <v-list-item-content>
            <v-list-item-title v-text="item.text"></v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list-item-group>
    </v-list>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      items: [
        {
          icon: 'mdi-wifi',
          text: 'Wifi',
        },
        {
          icon: 'mdi-bluetooth',
          text: 'Bluetooth',
        },
        {
          icon: 'mdi-chart-donut',
          text: 'Data Usage',
        },
      ],
      model: 1,
    }),
  }
</script>

<style scoped>
.border {
  border: 2px dashed orange;
}
</style>


# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

yarn-path ".yarn/releases/yarn-1.19.0.cjs"


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

+theme(v-tabs) using ($material)
  > .v-tabs-bar
    background-color: map-get($material, $tabs-bar-background-color)

    .v-tab:not(.v-tab--active),
    .v-tab:not(.v-tab--active) > .v-icon,
    .v-tab:not(.v-tab--active) > .v-btn,
    .v-tab--disabled
      color: map-get($material, 'tabs')

  .v-tab
    +states($material)

+theme(v-tabs-items) using ($material)
  background-color: map-get($material, 'cards')

.v-tabs-bar
  &.primary,
  &.secondary,
  &.accent,
  &.success,
  &.error,
  &.warning,
  &.info
    .v-tab,
    .v-tabs-slider
      color: map-deep-get($material-dark, 'text', 'primary')

// Block
.v-tabs
  flex: 1 1 auto
  width: 100%

  .v-menu__activator
    height: 100%

  &.v.tabs--vertical.v-tabs--right
    flex-direction: row-reverse

  &:not(.v-tabs--vertical)
    .v-tab
      white-space: normal

    // https://github.com/vuetifyjs/vuetify/issues/8294
    &.v-tabs--right
      > .v-slide-group--is-overflowing.v-tabs-bar--is-mobile:not(.v-slide-group--has-affixes)
        .v-slide-group__next
          display: initial
          visibility: hidden

    // https://github.com/vuetifyjs/vuetify/issues/6932
    &:not(.v-tabs--right)
      > .v-slide-group--is-overflowing.v-tabs-bar--is-mobile:not(.v-slide-group--has-affixes)
        .v-slide-group__prev
          display: initial
          visibility: hidden

// Element
.v-tabs-bar
  border-radius: inherit
  height: $tabs-bar-height

  &.v-item-group > *
    cursor: initial

.v-tab
  align-items: center
  cursor: pointer
  display: flex
  flex: 0 1 auto
  font-size: $tab-font-size
  font-weight: $tab-font-weight
  justify-content: center
  letter-spacing: $tabs-item-letter-spacing
  line-height: $tab-line-height
  min-width: $tabs-item-min-width
  max-width: $tabs-item-max-width
  outline: none
  padding: $tabs-item-padding
  position: relative
  text-align: center
  text-decoration: none
  text-transform: uppercase
  transition: none
  user-select: none

  // Needs increased specificity
  &.v-tab
    color: inherit

  &:before
    background-color: currentColor
    bottom: 0
    content: ''
    left: 0
    opacity: 0
    pointer-events: none
    position: absolute
    right: 0
    top: 0
    transition: $primary-transition

.v-tabs-slider
  background-color: currentColor
  height: 100%
  width: 100%

  &-wrapper
    bottom: 0
    margin: 0 !important
    position: absolute
    transition: $primary-transition
    z-index: 1

// Modifier
.v-tabs--align-with-title > .v-tabs-bar:not(.v-tabs-bar--show-arrows):not(.v-slide-group--is-overflowing) > .v-slide-group__wrapper > .v-tabs-bar__content
  & > .v-tab:first-child,
  & > .v-tabs-slider-wrapper + .v-tab
    +ltr()
      margin-left: $tabs-item-align-with-title-margin

    +rtl()
      margin-right: $tabs-item-align-with-title-margin

.v-tabs--fixed-tabs > .v-tabs-bar,
.v-tabs--centered > .v-tabs-bar
  .v-tabs-bar__content > *:last-child
    +ltr()
      margin-right: auto

    +rtl()
      margin-left: auto

  .v-tabs-bar__content > *:first-child:not(.v-tabs-slider-wrapper),
  .v-tabs-slider-wrapper + *
    +ltr()
      margin-left: auto

    +rtl()
      margin-right: auto

.v-tabs--fixed-tabs > .v-tabs-bar
  .v-tab
    flex: 1 1 auto
    width: 100%

.v-tabs--grow > .v-tabs-bar
  .v-tab
    flex: 1 0 auto
    max-width: none

.v-tabs--icons-and-text > .v-tabs-bar
  height: $tabs-icons-and-text-bar-height

  .v-tab
    flex-direction: column-reverse

    > *:first-child
      margin-bottom: $tabs-icons-and-text-first-tab-margin-bottom

.v-tabs--overflow > .v-tabs-bar
  .v-tab
    flex: 1 0 auto

.v-tabs--right > .v-tabs-bar
  .v-tab:first-child,
  .v-tabs-slider-wrapper + .v-tab
    +ltr()
      margin-left: auto

    +rtl()
      margin-right: auto

  .v-tab:last-child
    +ltr()
      margin-right: 0

    +rtl()
      margin-left: 0

.v-tabs--vertical
  display: flex

  & > .v-tabs-bar
    flex: 1 0 auto
    height: auto

    .v-slide-group__next,
    .v-slide-group__prev
      display: none

    .v-tabs-bar__content
      flex-direction: column

    .v-tab
      height: $tabs-item-vertical-height

    .v-tabs-slider
      height: 100%

  & > .v-window
    flex: 0 1 100%

  &.v-tabs--icons-and-text
    & > .v-tabs-bar
      .v-tab
        height: $tabs-item-vertical-icons-and-text-height

.v-tab--active
  color: inherit

  &.v-tab:not(:focus)::before
    opacity: 0

  .v-icon,
  .v-btn.v-btn--flat
    color: inherit

.v-tab--disabled
  opacity: $tab-disabled-opacity

  &,
  & *
    pointer-events: none


if (!this.searchInput || this.noFilter) return escapeHTML(text)

const { start, middle, end } = this.getMaskedCharacters(text)

if (!this.searchInput || this.noFilter) return text


// Extensions
import { Service } from '../service'

// Types
import { TargetPropValues, TargetProp, Application as IApplication } from 'vuetify/types/services/application'

export class Application extends Service implements IApplication {
  static property: 'application' = 'application'

  bar = 0

  top = 0

  left = 0

  insetFooter = 0

  right = 0

  bottom = 0

  footer = 0

  application: Dictionary<TargetPropValues> = {
    bar: {},
    top: {},
    left: {},
    insetFooter: {},
    right: {},
    bottom: {},
    footer: {},
  }

  register (
    uid: number,
    location: TargetProp,
    size: number
  ) {
    this.application[location][uid] = size

    this.update(location)
  }

  unregister (uid: number, location: TargetProp) {
    if (this.application[location][uid] == null) return

    delete this.application[location][uid]
    this.update(location)
  }

  update (location: TargetProp) {
    this[location] = Object.values(this.application[location])
      .reduce((acc: number, cur: number): number => (acc + cur), 0)
  }
}


Puts all children inputs into a disabled state.

If enabled, **value** will always be _true_ unless there are visible validation errors. You can still call `validate()` to manually trigger validation

Puts all children inputs into a readonly state.

A boolean value representing the validity of the form.

Resets the state of all registered inputs (inside the form) to **{}** for arrays and **null** for all other values. Resets errors bag when using the **lazy-validation** prop.

Resets validation of all registered inputs without modifying their state

Validates all registered inputs. Returns **true** if successful and **false** if not


<template>
  <v-card
    class="mx-auto"
    max-width="500"
  >
    <v-system-bar
      color="indigo darken-2"
      dark
    >
      <v-spacer></v-spacer>

      <v-icon>mdi-window-minimize</v-icon>

      <v-icon>mdi-window-maximize</v-icon>

      <v-icon>mdi-close</v-icon>
    </v-system-bar>

    <v-toolbar
      color="indigo"
      dark
    >
      <v-app-bar-nav-icon></v-app-bar-nav-icon>

      <v-toolbar-title>Discover</v-toolbar-title>

      <v-spacer></v-spacer>

      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>
    </v-toolbar>

    <v-container fluid>
      <v-row dense>
        <v-col
          v-for="card in cards"
          :key="card.title"
          :cols="card.flex"
        >
          <v-card>
            <v-img
              :src="card.src"
              class="white--text align-end"
              gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)"
              height="200px"
            >
              <v-card-title v-text="card.title"></v-card-title>
            </v-img>

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

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

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

              <v-btn icon>
                <v-icon>mdi-share-variant</v-icon>
              </v-btn>
            </v-card-actions>
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      cards: [
        { title: 'Pre-fab homes', src: 'https://cdn.vuetifyjs.com/images/cards/house.jpg', flex: 12 },
        { title: 'Favorite road trips', src: 'https://cdn.vuetifyjs.com/images/cards/road.jpg', flex: 6 },
        { title: 'Best airlines', src: 'https://cdn.vuetifyjs.com/images/cards/plane.jpg', flex: 6 },
      ],
    }),
  }
</script>


genHeader (props: { [key: string]: any }): VNode {

return this.$createElement(VSubheader, { props }, props.header)

return [start, this.genHighlight(middle), end]


// Types
import Vue from 'vue'
import Framework from '../'
import { VuetifyPreset } from 'vuetify/types/services/presets'

export interface VuetifyServiceContract {
  framework: Record<string, VuetifyServiceContract>
  init: (root: Vue, ssrContext?: object) => void
}

export interface VuetifyService {
  property: string
  new (
    preset: VuetifyPreset,
    parent: InstanceType<typeof Framework>
  ): VuetifyServiceContract
}


<template>
  <v-container>
    <v-row>
      <v-slider
        v-model="value"
        vertical
        label="Regular"
      ></v-slider>
    </v-row>
  </v-container>
</template>

<script>
  export default {
    data () {
      return {
        value: 10,
      }
    },
  }
</script>


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

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

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        2
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        3
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        4
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        5
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should only render end of range if value is equals "right" 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

        ...

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        98
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        99
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

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

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        41
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        42
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        43
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        44
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        45
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        46
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        47
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        48
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        49
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        50
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        51
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        52
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        53
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        54
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        55
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        56
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        57
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        58
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

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

      <button disabled="disabled"
              type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation v-pagination__navigation--disabled"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        2
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        3
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        4
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        5
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        6
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        7
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        8
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        9
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        10
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        11
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        91
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        92
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        93
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        94
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        95
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        96
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        97
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        98
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        99
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should only render start of range if value is equals "left" 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        2
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        3
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should render component in RTL mode and match snapshot 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        2
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        3
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        4
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        5
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should render disabled buttons with length equals to 0 1`] = `

      <button disabled="disabled"
              type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation v-pagination__navigation--disabled"
      >

      </button>

      <button disabled="disabled"
              type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation v-pagination__navigation--disabled"
      >

      </button>

`;

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

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        47
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        48
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        49
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        50
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        51
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        52
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;


keep keep keep keep replace replace keep keep keep

return this.$createElement('span', { staticClass: 'v-list__tile__mask' }, text)


// Pathify
import { make } from 'vuex-pathify'

// Data
import pageToApi from '@/data/page-to-api'

const state = {
  frontmatter: {},
  pages: {},
  toc: [],
  md: undefined,
  pageToApi,
}

const mutations = make.mutations(state)

const actions = {}

const getters = make.getters(state)

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters,
}


<template>
  <v-card
    class="mx-auto"
    max-width="500"
  >
    <v-list shaped>
      <v-list-item-group
        v-model="model"
        multiple
      >
        <template v-for="(item, i) in items">
          <v-divider
            v-if="!item"
            :key="`divider-${i}`"
          ></v-divider>

          <v-list-item
            v-else
            :key="`item-${i}`"
            :value="item"
            active-class="deep-purple--text text--accent-4"
          >
            <template v-slot:default="{ active }">
              <v-list-item-content>
                <v-list-item-title v-text="item"></v-list-item-title>
              </v-list-item-content>

              <v-list-item-action>
                <v-checkbox
                  :input-value="active"
                  color="deep-purple accent-4"
                ></v-checkbox>
              </v-list-item-action>
            </template>
          </v-list-item>
        </template>
      </v-list-item-group>
    </v-list>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      items: [
        'Dog Photos',
        'Cat Photos',
        '',
        'Potatoes',
        'Carrots',
      ],
      model: ['Carrots'],
    }),
  }
</script>


module.exports = {
  'v-item-group': {
    events: [
      {
        name: 'change',
        value: 'any[] | any',
      },
    ],
  },
}