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

.v-list-item
  align-items: center
  display: grid
  flex: none
  grid-template-areas: "prepend content append"
  grid-template-columns: max-content 1fr auto
  outline: none
  max-width: 100%
  padding: $list-item-padding
  position: relative
  text-decoration: none

  @include tools.border($list-item-border...)
  @include tools.states('.v-list-item__overlay')
  @include tools.rounded($list-item-border-radius)
  @include tools.variant($list-item-variants...)

  @supports selector(:focus-visible)
    &::after
      @include tools.absolute(true)
      pointer-events: none
      border: 2px solid currentColor
      border-radius: 4px
      opacity: 0
      transition: opacity .2s ease-in-out

    &:focus-visible::after
      opacity: calc(.15 * var(--v-theme-overlay-multiplier))

  &__prepend,
  &__append
    > .v-badge .v-icon,
    > .v-icon
      opacity: #{$list-item-icon-opacity}

  &--active
    .v-list-item__prepend,
    .v-list-item__append
      > .v-badge .v-icon,
      > .v-icon
        opacity: #{$list-item-icon-active-opacity}

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

  &--disabled
    pointer-events: none
    user-select: none
    opacity: $list-disabled-opacity

  &--link
    cursor: pointer

  .v-navigation-drawer--rail:not(.v-navigation-drawer--expand-on-hover) &,
  .v-navigation-drawer--rail.v-navigation-drawer--expand-on-hover:not(.v-navigation-drawer--is-hovering) &
    .v-avatar
      --v-avatar-height: 24px

.v-list-item__prepend
  align-items: center
  align-self: center
  display: flex
  grid-area: prepend

  > .v-badge,
  > .v-icon,
  > .v-tooltip
    ~ .v-list-item__spacer
      width: $list-item-icon-margin-start

  > .v-avatar ~ .v-list-item__spacer
    width: $list-item-avatar-margin-start

  .v-list-item--slim &
    > .v-badge,
    > .v-icon,
    > .v-tooltip
      ~ .v-list-item__spacer
        width: $list-item-slim-spacer-width

    > .v-avatar ~ .v-list-item__spacer
      width: $list-item-slim-avatar-spacer-width

  .v-list-item--three-line &
    align-self: start

.v-list-item__append
  align-self: center
  display: flex
  align-items: center
  grid-area: append

  .v-list-item__spacer
    order: -1
    transition: 150ms width settings.$standard-easing

  > .v-badge,
  > .v-icon,
  > .v-tooltip
    ~ .v-list-item__spacer
      width: $list-item-icon-margin-end

  > .v-avatar ~ .v-list-item__spacer
    width: $list-item-avatar-margin-end

  .v-list-item--slim &
    > .v-badge,
    > .v-icon,
    > .v-tooltip
      ~ .v-list-item__spacer
        width: $list-item-slim-spacer-width

    > .v-avatar ~ .v-list-item__spacer
      width: $list-item-slim-avatar-spacer-width

  .v-list-item--three-line &
    align-self: start

.v-list-item__content
  align-self: center
  grid-area: content
  overflow: hidden

.v-list-item-action
  align-self: center
  display: flex
  align-items: center
  grid-area: prepend
  flex: none
  transition: inherit
  transition-property: height, width

  &--start
    margin-inline-end: $list-item-action-margin-start

  &--end
    margin-inline-start: $list-item-action-margin-end

.v-list-item-media
  margin-top: $list-item-media-margin-top
  margin-bottom: $list-item-media-margin-bottom

  &--start
    margin-inline-end: $list-item-media-margin-end

  &--end
    margin-inline-start: $list-item-media-margin-start

  .v-list-item--two-line &
    margin-top: $list-item-media-two-line-margin-top
    margin-bottom: $list-item-media-two-line-margin-bottom

  .v-list-item--three-line &
    margin-top: $list-item-media-three-line-margin-top
    margin-bottom: $list-item-media-three-line-margin-bottom

.v-list-item-subtitle
  -webkit-box-orient: vertical
  display: -webkit-box
  opacity: $list-item-subtitle-opacity
  overflow: hidden
  padding: $list-item-subtitle-padding
  text-overflow: ellipsis

  .v-list-item--one-line &
    -webkit-line-clamp: 1

  .v-list-item--two-line &
    -webkit-line-clamp: 2

  .v-list-item--three-line &
    -webkit-line-clamp: 3

  @include tools.typography($list-item-subtitle-typography...)

  .v-list-item--nav &
    @include tools.typography($list-item-nav-subtitle-typography...)

.v-list-item-title
  hyphens: $list-item-title-hyphens
  overflow-wrap: $list-item-title-overflow-wrap
  overflow: hidden
  padding: $list-item-title-padding
  white-space: nowrap
  text-overflow: ellipsis
  word-break: $list-item-title-word-break
  word-wrap: $list-item-title-word-wrap

  @include tools.typography($list-item-title-typography...)

  .v-list-item--nav &
    @include tools.typography($list-item-nav-title-typography...)

.v-list-item
  @at-root
    @include tools.density('v-list-item', $list-density) using ($modifier)
      min-height: $list-item-min-height + $modifier

      &.v-list-item--one-line
        $one-line-padding: (list.nth($list-item-padding, 1) + $modifier)

        min-height: $list-item-one-line-min-height + $modifier

        @if ($one-line-padding > 0)
          padding-top: $one-line-padding
          padding-bottom: $one-line-padding

      &.v-list-item--two-line
        $two-line-padding: (list.nth($list-item-two-line-padding, 1) + $modifier)

        min-height: $list-item-two-line-min-height + $modifier

        @if ($two-line-padding > 0)
          padding-top: $two-line-padding
          padding-bottom: $two-line-padding

      &.v-list-item--three-line
        $three-line-padding: (list.nth($list-item-three-line-padding, 1) + $modifier)

        min-height: $list-item-three-line-min-height + $modifier

        @if ($three-line-padding > 0)
          padding-top: $three-line-padding
          padding-bottom: $three-line-padding

          .v-list-item__prepend,
          .v-list-item__append
            padding-top: math.div($three-line-padding, 2)

      &:not(.v-list-item--nav)
        &.v-list-item--one-line
          padding-inline: list.nth($list-item-padding, 2)

        &.v-list-item--two-line
          padding-inline: list.nth($list-item-two-line-padding, 2)

        &.v-list-item--three-line
          padding-inline: list.nth($list-item-three-line-padding, 2)

  &--nav
    padding-inline: $list-nav-padding

    .v-list &
      &:not(:only-child)
        margin-bottom: $list-item-nav-margin-top

.v-list-item__underlay
  position: absolute

.v-list-item__overlay
  background-color: currentColor
  border-radius: inherit
  bottom: 0
  left: 0
  opacity: 0
  pointer-events: none
  position: absolute
  right: 0
  top: 0
  transition: opacity 0.2s ease-in-out

  .v-list-item--active.v-list-item--variant-elevated &
    --v-theme-overlay-multiplier: 0

$base-padding: list.nth($list-item-padding, 2)
.v-list
  --indent-padding: 0px

  &--nav
    --indent-padding: -#{$list-nav-padding}

.v-list-group
  --list-indent-size: #{$list-indent-size}
  --parent-padding: var(--indent-padding)
  --prepend-width: #{$list-item-prepend-size}

  &--fluid
    --list-indent-size: 0px

  &--prepend
    --parent-padding: calc(var(--indent-padding) + var(--prepend-width))

  &--fluid.v-list-group--prepend
    --parent-padding: var(--indent-padding)

.v-list-group__items
  --indent-padding: calc(var(--parent-padding) + var(--list-indent-size))

.v-list-group__items .v-list-item
  padding-inline-start: calc(#{$base-padding} + var(--indent-padding)) !important

.v-list-group__header.v-list-item--active
  &:not(:focus-visible)
    .v-list-item__overlay
      opacity: 0

  &:hover
    .v-list-item__overlay
      opacity: calc(#{map.get(settings.$states, 'hover')} * var(--v-theme-overlay-multiplier))


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

exports[`VList should match a snapshot 1`] = `
<div class="v-list v-theme--light v-list--density-default v-list--one-line"
     tabindex="0"
     role="listbox"
>
</div>
`;


---
meta:
  title: Icon Fonts
  description: Vuetify supports Material Design Icons, Font awesome and other icon sets through prefixes and global options.
  keywords: vue icon component, iconfont, icon libraries, vuetify icons
related:
  - /components/icons
  - /components/buttons
  - /components/avatars
---

<script setup>
  import { defineAsyncComponent } from 'vue'

  const IconList = defineAsyncComponent(() => import('@/components/doc/IconList.vue'))
  const IconTable = defineAsyncComponent(() => import('@/components/doc/IconTable.vue'))
</script>

# Icon Fonts

Out of the box, Vuetify supports 4 popular icon font libraries—[Material Design Icons](https://materialdesignicons.com/), [Material Icons](https://fonts.google.com/icons), [Font Awesome 4](https://fontawesome.com/v4.7.0/) and [Font Awesome 5](https://fontawesome.com/).

<entry />

## Usage

To change your font library, import one of the pre-defined icon sets or provide your own.

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { aliases, mdi } from 'vuetify/iconsets/mdi'

export default createVuetify({
  icons: {
    defaultSet: 'mdi',
    aliases,
    sets: {
      mdi,
    },
  },
})
```

```html
<template>
  <v-icon icon="mdi-home" />
</template>
```

In the above examples we import the default `mdi` icon set and its corresponding aliases. These aliases reference commonly used types of icons that are utilized by Vuetify components.

::: info

While it is still possible to supply the icon value through the default slot in Vuetify 3.0 (`<v-icon>mdi-home</v-icon>`), we recommend using the `icon` prop instead.

:::

## Installing icon fonts

You are required to include the specified icon library (even when using the default icons from [Material Design Icons](https://materialdesignicons.com/)). This can be done by including a CDN link or importing the icon library into your application.

::: info

In this page "Material Icons" is used to refer to the [official google icons](https://fonts.google.com/icons) and "Material Design Icons" refers to the [extended third-party library](https://materialdesignicons.com/)

:::

### Material Design Icons

This is the default icon set used by Vuetify. It supports local installation with a build process or a CDN link. The following shows how to add the CDN link to your `index.html`:

#### MDI - CSS

```html
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
```

Or as a local dependency:

::: tabs

```bash [yarn]
yarn add @mdi/font -D
```

``` bash [npm]
npm install @mdi/font -D
```

```bash [pnpm]
pnpm add @mdi/font -D
```

```bash [bun]
bun add @mdi/font -D
```

:::

```js { resource="src/plugins/vuetify.js" }
import '@mdi/font/css/materialdesignicons.css' // Ensure you are using css-loader
import { createVuetify } from 'vuetify'

export default createVuetify({
  icons: {
    defaultSet: 'mdi', // This is already the default value - only for display purposes
  },
})
```

::: error

**DO NOT** use a CDN link without specifying a package *version*. Failure to do so can result in unexpected changes to your application with new releases.

:::

#### MDI - JS SVG

This is the recommended installation when optimizing your application for production, as only icons used for Vuetify components internally will be imported into your application bundle. You will need to provide your own icons for the rest of the app.

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { aliases, mdi } from 'vuetify/iconsets/mdi-svg'

export default createVuetify({
  icons: {
    defaultSet: 'mdi',
    aliases,
    sets: {
      mdi,
    },
  },
})
```

`@mdi/js` or [unplugin-icons](https://github.com/antfu/unplugin-icons) are two alternatives to get the rest of the icons that you will need in your application.

If you want to stick with `@mdi/js`, use the SVG paths as designated in [@mdi/js](https://www.npmjs.com/package/@mdi/js) and
only import the icons that you need.

The following example shows how to use an imported icon within a `.vue` SFC template:

::: tabs

```bash [yarn]
yarn add @mdi/js -D
```

```bash [npm]
npm install @mdi/js -D
```

```bash [pnpm]
pnpm add @mdi/js -D
```

```bash [bun]
bun add @mdi/js -D
```

:::

```html
<template>
  <v-icon :icon="mdiAccount" />
</template>

<script setup>
  import { mdiAccount } from '@mdi/js'
</script>
```

Or the icons you want to use can be added as aliases to simplify reuse:

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { aliases, mdi } from 'vuetify/iconsets/mdi-svg'
import { mdiAccount } from '@mdi/js'

export default createVuetify({
  icons: {
    defaultSet: 'mdi',
    aliases: {
      ...aliases,
      account: mdiAccount,
    },
    sets: {
      mdi,
    },
  },
})
```

```html
<template>
  <v-icon icon="$account" />
</template>
```

#### MDI - Icon search

Use this tool to search for any Material Design Icons and copy them to your clipboard by clicking the item.

<icon-list />

### Material Icons

For projects without a build process, it is recommended to import the icons from CDN.

#### Material Icons - CSS

```html
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet">
```

Some Material Icons are missing by default. For example, `person` and `person_outline` are available, but `visibility_outline` isn't, while `visibility` is. To use the missing icons, replace the existing `<link>` with the following:

```html
<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp"
/>
```

Alternatively, it is possible to install locally using yarn or npm. Keep in mind that this is not an official google repository and may not contain all icons.

::: tabs

```bash [yarn]
yarn add material-design-icons-iconfont -D
```

```bash [npm]
npm install material-design-icons-iconfont -D
```

```bash [pnpm]
pnpm add material-design-icons-iconfont -D
```

```bash [bun]
bun add material-design-icons-iconfont -D
```

:::

```js { resource="src/plugins/vuetify.js" }
import 'material-design-icons-iconfont/dist/material-design-icons.css' // Ensure your project is capable of handling css files
import { createVuetify } from 'vuetify'
import { aliases, md } from 'vuetify/iconsets/md'

export default createVuetify({
  icons: {
    defaultSet: 'md',
    aliases,
    sets: {
      md,
    },
  },
})
```

```html
<template>
  <v-icon icon="home" />
</template>
```

### Font Awesome

The easiest way to get started with **FontAwesome** is to use a CDN.

#### FA 5 - CSS

```html
<link href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" rel="stylesheet">
```

To install locally you can pull in the [free](https://fontawesome.com/) version of **FontAwesome** using your preferred package manager:

::: tabs

```bash [yarn]
yarn add @fortawesome/fontawesome-free -D
```

```bash [npm]
npm install @fortawesome/fontawesome-free -D
```

```bash [pnpm]
pnpm add @fortawesome/fontawesome-free -D
```

```bash [bun]
bun add @fortawesome/fontawesome-free -D
```

:::

```js { resource="src/plugins/vuetify.js" }
import '@fortawesome/fontawesome-free/css/all.css' // Ensure your project is capable of handling css files
import { createVuetify } from 'vuetify'
import { aliases, fa } from 'vuetify/iconsets/fa'

export default createVuetify({
  icons: {
    defaultSet: 'fa',
    aliases,
    sets: {
      fa,
    },
  },
})
```

```html
<template>
  <v-icon icon="fas fa-home" />
</template>
```

::: error

The JavaScript version (`all.js`) of the FontAwesome icons will **NOT** work with Vue

:::

#### FA 4 - CSS

The easiest way to get started with **FontAwesome** is to use a CDN.

```html
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.x/css/font-awesome.min.css" rel="stylesheet">
```

To install FontAwesome **4** locally is the same as its newer version, just from a different package. You will be using the `font-awesome` package as opposed to `@fortawesome`.

::: tabs

```bash [yarn]
yarn add font-awesome@4.7.0 -D
```

```bash [npm]
npm install font-awesome@4.7.0 -D
```

```bash [pnpm]
pnpm add font-awesome@4.7.0 -D
```

```bash [bun]
bun add font-awesome@4.7.0 -D
```

:::

```js { resource="src/plugins/vuetify.js" }
import 'font-awesome/css/font-awesome.min.css' // Ensure your project is capable of handling css files
import { createVuetify } from 'vuetify'
import { aliases, fa } from 'vuetify/iconsets/fa4'

export default createVuetify({
  icons: {
    defaultSet: 'fa',
    aliases,
    sets: {
      fa,
    },
  },
})
```

```html
<template>
  <v-icon icon="fa-check" />
</template>
```

#### FA 5 - SVG

Install the following packages.

::: tabs

```bash [yarn]
yarn add @fortawesome/fontawesome-svg-core @fortawesome/vue-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/free-regular-svg-icons -D
```

```bash [npm]
npm install @fortawesome/fontawesome-svg-core @fortawesome/vue-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/free-regular-svg-icons -D
```

```bash [pnpm]
pnpm add @fortawesome/fontawesome-svg-core @fortawesome/vue-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/free-regular-svg-icons -D
```

```bash [bun]
bun add @fortawesome/fontawesome-svg-core @fortawesome/vue-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/free-regular-svg-icons -D
```

:::

Then register the global `font-awesome-icon` component and use the pre-defined `fa-svg` icon set. If you have access to Font Awesome Pro icons they can be added to the library in the same way.

```js { resource="src/main.js" }
import { createApp } from 'vue'
import { createVuetify } from 'vuetify'
import { aliases, fa } from 'vuetify/iconsets/fa-svg'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { far } from '@fortawesome/free-regular-svg-icons'

const app = createApp()

app.component('font-awesome-icon', FontAwesomeIcon) // Register component globally
library.add(fas) // Include needed solid icons
library.add(far) // Include needed regular icons

const vuetify = createVuetify({
  icons: {
    defaultSet: 'fa',
    aliases,
    sets: {
      fa,
    },
  },
})

app.use(vuetify)

app.mount('#app')
```

```html
<template>
  <v-icon icon="fas fa-home" />
</template>
```

## Built-in aliases

The following icons are available as aliases for use in Vuetify components:

<icon-table />

## Multiple icon sets

Out of the box, Vuetify supports the use of multiple *different* icon sets at the same time. The following example demonstrates how to change the default icon font to Font Awesome (`fa`) while still maintaining access to the original Material Design Icons (`mdi`) through the use of a prefix:

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { aliases, fa } from 'vuetify/iconsets/fa'
import { mdi } from 'vuetify/iconsets/mdi'

export default createVuetify({
  icons: {
    defaultSet: 'fa',
    aliases,
    sets: {
      fa,
      mdi,
    },
  },
})
```

```html
<template>
  <v-icon icon="fas fa-plus" /> // This renders a FontAwesome icon
  <v-icon icon="mdi:mdi-minus" /> // This renders a MDI icon
</template>
```

::: info

It is not necessary to provide a prefix (such as `mdi:`) for icons from the default icon set

:::

## Creating a custom icon set

An icon set consists of an object with one property `component` which should be a functional component that receives props of type `IconsProps`, and renders an icon.

In order to use a custom set as the default icon set, you must also add the necessary *aliases* that correspond to values used by Vuetify components.

```ts { resource="src/iconsets/custom.ts" }
import { h } from 'vue'
import type { IconSet, IconAliases, IconProps } from 'vuetify'

const aliases: IconAliases = {
  collapse: '...',
  complete: '...',
  cancel: '...',
  close: '...',
  delete: '...',
  clear: '...',
  success: '...',
  info: '...',
  warning: '...',
  error: '...',
  prev: '...',
  next: '...',
  checkboxOn: '...',
  checkboxOff: '...',
  checkboxIndeterminate: '...',
  delimiter: '...',
  sort: '...',
  expand: '...',
  menu: '...',
  subgroup: '...',
  dropdown: '...',
  radioOn: '...',
  radioOff: '...',
  edit: '...',
  ratingEmpty: '...',
  ratingFull: '...',
  ratingHalf: '...',
  loading: '...',
  first: '...',
  last: '...',
  unfold: '...',
  file: '...',
  plus: '...',
  minus: '...',
}

const custom: IconSet = {
  component: (props: IconProps) => h(...),
}

export { aliases, custom }
```

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { aliases, custom } from '../iconsets/custom'

export default createVuetify({
  icons: {
    defaultSet: 'custom',
    aliases,
    sets: {
      custom,
    },
  },
})
```

## Extending available aliases

If you are developing custom Vuetify components, you can extend the `aliases` object to utilize the same functionality that internal Vuetify components use. Icon aliases are referenced with an initial `$` followed by the name of the alias, e.g. `$product`.

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { aliases, mdi } from 'vuetify/iconsets/mdi'

export default createVuetify({
  icons: {
    aliases: {
      ...aliases,
      product: 'mdi-dropbox',
      support: 'mdi-lifebuoy',
    },
  },
})
```

```html
<template>
  <v-icon icon="$product" />
  <v-icon icon="$support" />
</template>
```


<template>
  <v-card
    class="mx-auto"
    max-width="434"
    rounded="0"
  >
    <v-img
      height="100%"
      cover
      src="https://cdn.vuetifyjs.com/images/cards/server-room.jpg"
    >
      <v-avatar
        color="grey"
        size="150"
        rounded="0"
      >
        <v-img cover src="https://cdn.vuetifyjs.com/images/profiles/marcus.jpg"></v-img>
      </v-avatar>
      <v-list-item
        class="text-white"
        title="Marcus Obrien"
        subtitle="Network Engineer"
      ></v-list-item>
    </v-img>
  </v-card>
</template>


--parent-padding: calc(var(--indent-padding) + var(--prepend-width))

keep keep keep keep add keep keep keep keep keep

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

// VList
$list-background: rgba(var(--v-theme-surface)) !default;
$list-border-color: settings.$border-color-root !default;
$list-border-radius: 0 !default;
$list-border-style: settings.$border-style-root !default;
$list-border-thin-width: thin !default;
$list-border-width: 0 !default;
$list-color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)) !default;
$list-disabled-opacity: 0.6 !default;
$list-elevation: 0 !default;
$list-padding: 8px 0 !default;
$list-rounded-border-radius: map.get(settings.$rounded, null) !default;
$list-indent-size: 16px !default;

$list-nav-padding: 8px !default;
$list-nav-subheader-font-size: .75rem !default;

// VListSubheader
$list-subheader-color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)) !default;
$list-subheader-font-size: .875rem !default;
$list-subheader-font-weight: 400 !default;
$list-subheader-inset-margin: 56px !default;
$list-subheader-inset-padding-start: 56px !default;
$list-subheader-line-height: 1.375rem !default;
$list-subheader-min-height: 40px !default;
$list-subheader-padding-end: 16px !default;
$list-subheader-padding-start: 16px !default;
$list-subheader-padding-top: 0 !default;
$list-subheader-min-height-multiplier: 1 !default;
$list-subheader-text-opacity: var(--v-medium-emphasis-opacity) !default;
$list-subheader-transition: 0.2s min-height settings.$standard-easing !default;

// VListItem
$list-item-border-color: settings.$border-color-root !default;
$list-item-border-radius: 0 !default;
$list-item-border-style: settings.$border-style-root !default;
$list-item-border-width: 0 !default;
$list-item-border-thin-width: thin !default;
$list-item-elevation: 1 !default;
$list-item-icon-opacity: var(--v-medium-emphasis-opacity) !default;
$list-item-icon-active-opacity: 1 !default;
$list-item-min-height: 40px !default;
$list-item-padding: 4px 16px !default;
$list-item-prepend-size: 40px !default;
$list-item-plain-opacity: .62 !default;
$list-item-rounded-border-radius: map.get(settings.$rounded, null) !default;
$list-item-one-line-min-height: 48px !default;
$list-item-one-line-padding: 8px 16px !default;
$list-item-two-line-min-height: 64px !default;
$list-item-two-line-padding: 12px 16px !default;
$list-item-three-line-min-height: 88px !default;
$list-item-three-line-padding: 16px 16px !default;

$list-item-avatar-align-self: flex-start !default;
$list-item-avatar-margin-end: 16px !default;
$list-item-avatar-margin-start: 16px !default;
$list-item-avatar-size: 40px !default;
$list-item-avatar-margin-y: 4px !default;
$list-item-slim-spacer-width: 8px !default;
$list-item-slim-avatar-spacer-width: 4px !default;

$list-item-action-margin-end: 12px !default;
$list-item-action-margin-start: 12px !default;

$list-item-icon-margin-end: 32px !default;
$list-item-icon-margin-start: 32px !default;
$list-item-slim-icon-margin: 8px !default;
$list-item-icon-size: 16px !default;

$list-item-media-margin-bottom: 0 !default;
$list-item-media-margin-end: 16px !default;
$list-item-media-margin-start: 16px !default;
$list-item-media-margin-top: 0 !default;
$list-item-media-two-line-margin-bottom: -4px !default;
$list-item-media-two-line-margin-top: -4px !default;
$list-item-media-three-line-margin-bottom: 0 !default;
$list-item-media-three-line-margin-top: 0 !default;

$list-item-nav-margin-top: 4px !default;
$list-item-nav-title-font-size: .8125rem !default;
$list-item-nav-title-font-weight: 500 !default;
$list-item-nav-title-letter-spacing: normal !default;
$list-item-nav-title-line-height: 1rem !default;
$list-item-nav-subtitle-font-size: .75rem !default;
$list-item-nav-subtitle-font-weight: tools.map-deep-get(settings.$typography, 'body-2', 'weight') !default;
$list-item-nav-subtitle-letter-spacing: tools.map-deep-get(settings.$typography, 'body-2', 'letter-spacing') !default;
$list-item-nav-subtitle-line-height: 1rem !default;

$list-item-subtitle-opacity: var(--v-medium-emphasis-opacity) !default;
$list-item-subtitle-font-size: tools.map-deep-get(settings.$typography, 'body-2', 'size') !default;
$list-item-subtitle-font-weight: tools.map-deep-get(settings.$typography, 'body-2', 'weight') !default;
$list-item-subtitle-letter-spacing: tools.map-deep-get(settings.$typography, 'body-2', 'letter-spacing') !default;
$list-item-subtitle-line-height: 1rem !default;
$list-item-subtitle-padding: 0 !default;
$list-item-subtitle-text-transform: none !default;

$list-item-title-font-size: tools.map-deep-get(settings.$typography, 'body-1', 'size') !default;
$list-item-title-font-weight: tools.map-deep-get(settings.$typography, 'body-1', 'weight') !default;
$list-item-title-header-padding: 0 !default;
$list-item-title-hyphens: auto !default;
$list-item-title-letter-spacing: tools.map-deep-get(settings.$typography, 'subtitle-1', 'letter-spacing') !default;
$list-item-title-line-height: tools.map-deep-get(settings.$typography, 'body-1', 'line-height') !default;
$list-item-title-overflow-wrap: normal !default;
$list-item-title-padding: 0 !default;
$list-item-title-text-overflow: ellipsis !default;
$list-item-title-text-transform: none !default;
$list-item-title-word-break: normal !default;
$list-item-title-word-wrap: break-word !default;

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

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

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

$list-item-border: (
  $list-item-border-color,
  $list-item-border-style,
  $list-item-border-width,
  $list-item-border-thin-width
) !default;

$list-item-title-typography: (
  $list-item-title-font-size,
  $list-item-title-font-weight,
  $list-item-title-letter-spacing,
  $list-item-title-line-height,
  $list-item-title-text-transform
) !default;

$list-item-subtitle-typography: (
  $list-item-subtitle-font-size,
  $list-item-subtitle-font-weight,
  $list-item-subtitle-letter-spacing,
  $list-item-subtitle-line-height,
  $list-item-subtitle-text-transform
) !default;

$list-item-nav-title-typography: (
  $list-item-nav-title-font-size,
  $list-item-nav-title-font-weight,
  $list-item-nav-title-letter-spacing,
  $list-item-nav-title-line-height,
  null
) !default;

$list-item-nav-subtitle-typography: (
  $list-item-nav-subtitle-font-size,
  $list-item-nav-subtitle-font-weight,
  $list-item-nav-subtitle-letter-spacing,
  $list-item-nav-subtitle-line-height,
  null
) !default;

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


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

$picker-border-radius: settings.$border-radius-root !default;
$picker-elevation: 0 !default;
$picker-title-padding: 16px !default;
$picker-inactive-btn-opacity: .6 !default;
$picker-active-btn-opacity: 1 !default;
$picker-landscape-title-width: 170px !default;
$picker-font-size: 1rem !default;


@use 'sass:math';
@use 'sass:map';
@use 'sass:meta';
@use '../tools/functions' as *;

$color-pack: true !default;
$reset: true !default;

$body-font-family: 'Roboto', sans-serif !default;
$font-size-root: 1rem !default;
$line-height-root: 1.5 !default;
$border-color-root: rgba(var(--v-border-color), var(--v-border-opacity)) !default;
$border-radius-root: 4px !default;
$border-style-root: solid !default;

$borders: () !default;
$borders: map-deep-merge(
  (
    0: 0,
    null: thin,
    sm: 1px,
    md: 2px,
    lg: 4px,
    xl: 8px
  ),
  $borders
);

@each $key, $border in $borders {
  $borders: map-deep-merge(
    $borders,
    ( $key: $border $border-style-root $border-color-root )
  )
}

$border-opacities: () !default;
$border-opacities: map-deep-merge(
  (
    0: 0,
    null: .12,
    25: .25,
    50: .50,
    75: .75,
    100: 1
  ),
  $border-opacities
);

$states: () !default;
$states: map-deep-merge(
    (
    'hover': var(--v-hover-opacity),
    'focus': var(--v-focus-opacity),
    'selected': var(--v-selected-opacity),
    'activated': var(--v-activated-opacity),
    'pressed': var(--v-pressed-opacity),
    'dragged': var(--v-dragged-opacity)
  ),
  $states
);

$rounded: () !default;
$rounded: map-deep-merge(
  (
    0: 0,
    'sm': $border-radius-root * .5,
    null: $border-radius-root,
    'lg': $border-radius-root * 2,
    'xl': $border-radius-root * 6,
    'pill': 9999px,
    'circle': 50%,
    'shaped': $border-radius-root * 6 0
  ),
  $rounded
);

$spacer: 4px !default;
$spacers-steps: 16 !default;

$spacers: () !default;
@if (meta.type-of($spacers) == list) {
  @for $i from 0 through $spacers-steps {
    $spacers: map.merge($spacers, ($i: $spacer * $i))
  }
}

$negative-spacers: () !default;
@if (meta.type-of($negative-spacers) == list) {
  @for $i from 1 through $spacers-steps {
    $negative-spacers: map.merge($negative-spacers, ("n" + $i: -$spacer * $i))
  }
}

$grid-breakpoints: () !default;
$grid-breakpoints: map-deep-merge(
  (
    'xs': 0,
    'sm': 600px,
    'md': 960px,
    'lg': 1280px,
    'xl': 1920px,
    'xxl': 2560px,
  ),
  $grid-breakpoints
);

$grid-gutter: $spacer * 6 !default;
$form-grid-gutter: $spacer * 2 !default;
$grid-columns: 12 !default;
$container-padding-x: $spacer * 4 !default;

$grid-gutters: () !default;
$grid-gutters: map-deep-merge(
  (
    'xs': math.div($grid-gutter, 12),
    'sm': math.div($grid-gutter, 6),
    'md': math.div($grid-gutter, 3),
    'lg': math.div($grid-gutter * 2, 3),
    'xl': $grid-gutter,
  ),
  $grid-gutters
);

$container-max-widths: () !default;
$container-max-widths: map-deep-merge(
  (
    'xs': null,
    'sm': null,
    'md': map.get($grid-breakpoints, 'md') * 0.9375,
    'lg': map.get($grid-breakpoints, 'lg') * 0.9375,
    'xl': map.get($grid-breakpoints, 'xl') * 0.9375,
    'xxl': map.get($grid-breakpoints, 'xxl') * 0.9375,
  ),
  $container-max-widths
);

// Avoid using *-and-down where possible
$display-breakpoints: () !default;
$display-breakpoints: map-deep-merge(
  (
    'print-only': 'only print',
    'screen-only': 'only screen',
    'xs': '(max-width: #{map.get($grid-breakpoints, 'sm') - 0.02})',
    'sm': '(min-width: #{map.get($grid-breakpoints, 'sm')}) and (max-width: #{map.get($grid-breakpoints, 'md') - 0.02})',
    'md': '(min-width: #{map.get($grid-breakpoints, 'md')}) and (max-width: #{map.get($grid-breakpoints, 'lg') - 0.02})',
    'lg': '(min-width: #{map.get($grid-breakpoints, 'lg')}) and (max-width: #{map.get($grid-breakpoints, 'xl') - 0.02})',
    'xl': '(min-width: #{map.get($grid-breakpoints, 'xl')}) and (max-width: #{map.get($grid-breakpoints, 'xxl') - 0.02})',
    'xxl': '(min-width: #{map.get($grid-breakpoints, 'xxl')})',
    'sm-and-up': '(min-width: #{map.get($grid-breakpoints, 'sm')})',
    'md-and-up': '(min-width: #{map.get($grid-breakpoints, 'md')})',
    'lg-and-up': '(min-width: #{map.get($grid-breakpoints, 'lg')})',
    'xl-and-up': '(min-width: #{map.get($grid-breakpoints, 'xl')})',
    'sm-and-down': '(max-width: #{map.get($grid-breakpoints, 'md') - 0.02})',
    'md-and-down': '(max-width: #{map.get($grid-breakpoints, 'lg') - 0.02})',
    'lg-and-down': '(max-width: #{map.get($grid-breakpoints, 'xl') - 0.02})',
    'xl-and-down': '(max-width: #{map.get($grid-breakpoints, 'xxl') - 0.02})',
  ),
  $display-breakpoints
);

$font-weights: () !default;
$font-weights: map-deep-merge(
  (
    'thin': 100,
    'light': 300,
    'regular': 400,
    'medium': 500,
    'bold': 700,
    'black': 900
  ),
  $font-weights
);

$heading-font-family: $body-font-family !default;

$typography: () !default;
$typography: map-deep-merge(
  (
    'h1': (
      'size': 6rem,
      'weight': 300,
      'line-height': 6rem,
      'letter-spacing': -.015625em,
      'font-family': $heading-font-family,
      'text-transform': none
    ),
    'h2': (
      'size': 3.75rem,
      'weight': 300,
      'line-height': 3.75rem,
      'letter-spacing': -.0083333333em,
      'font-family': $heading-font-family,
      'text-transform': none
    ),
    'h3': (
      'size': 3rem,
      'weight': 400,
      'line-height': 3.125rem,
      'letter-spacing': normal,
      'font-family': $heading-font-family,
      'text-transform': none
    ),
    'h4': (
      'size': 2.125rem,
      'weight': 400,
      'line-height': 2.5rem,
      'letter-spacing': .0073529412em,
      'font-family': $heading-font-family,
      'text-transform': none
    ),
    'h5': (
      'size': 1.5rem,
      'weight': 400,
      'line-height': 2rem,
      'letter-spacing': normal,
      'font-family': $heading-font-family,
      'text-transform': none
    ),
    'h6': (
      'size': 1.25rem,
      'weight': 500,
      'line-height': 2rem,
      'letter-spacing': .0125em,
      'font-family': $heading-font-family,
      'text-transform': none
    ),
    'subtitle-1': (
      'size': 1rem,
      'weight': normal,
      'line-height': 1.75rem,
      'letter-spacing': .009375em,
      'font-family': $body-font-family,
      'text-transform': none
    ),
    'subtitle-2': (
      'size': .875rem,
      'weight': 500,
      'line-height': 1.375rem,
      'letter-spacing': .0071428571em,
      'font-family': $body-font-family,
      'text-transform': none
    ),
    'body-1': (
      'size': 1rem,
      'weight': 400,
      'line-height': 1.5rem,
      'letter-spacing': .03125em,
      'font-family': $body-font-family,
      'text-transform': none
    ),
    'body-2': (
      'size': .875rem,
      'weight': 400,
      'line-height': 1.25rem,
      'letter-spacing': .0178571429em,
      'font-family': $body-font-family,
      'text-transform': none
    ),
    'button': (
      'size': .875rem,
      'weight': 500,
      'line-height': 2.25rem,
      'letter-spacing': .0892857143em,
      'font-family': $body-font-family,
      'text-transform': uppercase
    ),
    'caption': (
      'size': .75rem,
      'weight': 400,
      'line-height': 1.25rem,
      'letter-spacing': .0333333333em,
      'font-family': $body-font-family,
      'text-transform': none
    ),
    'overline': (
      'size': .75rem,
      'weight': 500,
      'line-height': 2rem,
      'letter-spacing': .1666666667em,
      'font-family': $body-font-family,
      'text-transform': uppercase
    )
  ),
  $typography
);

$flat-typography: () !default;
@each $type, $values in $typography {
  $flat-typography: map-deep-merge(
    $flat-typography,
    (#{$type}: map.values($values))
  );
}

// Mapping from transition to easings:
// fast-out-slow-in -> standard
// linear-out-slow-in -> decelerated
// fast-out-linear-in -> accelerated
// ease-in-out -> standard or accelerated depending on usage
// fast-in-fast-out -> accelerated
// swing -> standard

$standard-easing: cubic-bezier(0.4, 0, 0.2, 1) !default;
$decelerated-easing: cubic-bezier(0.0, 0, 0.2, 1) !default; // Entering
$accelerated-easing: cubic-bezier(0.4, 0, 1, 1) !default; // Leaving

// Elements
$bootable-transition: 0.2s $decelerated-easing !default;
$blockquote-font-size: 18px !default;
$blockquote-font-weight: 300 !default;

$sizes: (
  'x-small',
  'small',
  'default',
  'large',
  'x-large'
) !default;

$size-scale: $spacer * 2 !default;
$size-scales: (
  'x-small': -2,
  'small': -1,
  'default': 0,
  'large': 1,
  'x-large': 2
) !default;


<template>
  <div class="d-flex align-center flex-column pa-6">
    <v-btn-toggle
      v-model="toggle"
      divided
      variant="outlined"
    >
      <v-btn icon="mdi-format-align-left"></v-btn>
      <v-btn icon="mdi-format-align-center"></v-btn>
      <v-btn icon="mdi-format-align-right"></v-btn>
      <v-btn icon="mdi-format-align-justify"></v-btn>
    </v-btn-toggle>
  </div>
</template>

<script setup>
  import { ref } from 'vue'

  const toggle = ref(null)
</script>

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


$list-item-rounded-border-radius: map.get(settings.$rounded, null) !default;

$list-item-one-line-padding: 8px 16px !default;


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

export const VStepperHeader = createSimpleFunctional('v-stepper-header')

export type VStepperHeader = InstanceType<typeof VStepperHeader>


Function that returns position and size information about a specific layout item.

Position and size information for the v-main area.


}, please provide the paths via environment variables, see https://pris.ly/d/custom-engines`,

} else if (['freebsd11', 'freebsd12', 'freebsd13', 'openbsd', 'netbsd'].includes(binaryTarget)) {

)} Precompiled engine files are not available for ${binaryTarget}. Read more about building your own engines at https://pris.ly/d/build-engines`,

keep keep keep replace keep keep keep keep keep

} else if (['freebsd11', 'freebsd12', 'freebsd13', 'freebsd14', 'openbsd', 'netbsd'].includes(binaryTarget)) {

export type BinaryTarget =
  | 'native'
  | 'darwin'
  | 'darwin-arm64'
  | 'debian-openssl-1.0.x'
  | 'debian-openssl-1.1.x'
  | 'debian-openssl-3.0.x'
  | 'rhel-openssl-1.0.x'
  | 'rhel-openssl-1.1.x'
  | 'rhel-openssl-3.0.x'
  | 'linux-arm64-openssl-1.1.x'
  | 'linux-arm64-openssl-1.0.x'
  | 'linux-arm64-openssl-3.0.x'
  | 'linux-arm-openssl-1.1.x'
  | 'linux-arm-openssl-1.0.x'
  | 'linux-arm-openssl-3.0.x'
  | 'linux-musl'
  | 'linux-musl-openssl-3.0.x'
  | 'linux-musl-arm64-openssl-1.1.x'
  | 'linux-musl-arm64-openssl-3.0.x'
  | 'linux-nixos'
  | 'linux-static-x64'
  | 'linux-static-arm64'
  | 'windows'
  | 'freebsd11'
  | 'freebsd12'
  | 'freebsd13'
  | 'openbsd'
  | 'netbsd'
  | 'arm'

export const binaryTargets: BinaryTarget[] = [
  'darwin',
  'darwin-arm64',
  'debian-openssl-1.0.x',
  'debian-openssl-1.1.x',
  'debian-openssl-3.0.x',
  'rhel-openssl-1.0.x',
  'rhel-openssl-1.1.x',
  'rhel-openssl-3.0.x',
  'linux-arm64-openssl-1.1.x',
  'linux-arm64-openssl-1.0.x',
  'linux-arm64-openssl-3.0.x',
  'linux-arm-openssl-1.1.x',
  'linux-arm-openssl-1.0.x',
  'linux-arm-openssl-3.0.x',
  'linux-musl',
  'linux-musl-openssl-3.0.x',
  'linux-musl-arm64-openssl-1.1.x',
  'linux-musl-arm64-openssl-3.0.x',
  'linux-nixos',
  'linux-static-x64',
  'linux-static-arm64',
  'windows',
  'freebsd11',
  'freebsd12',
  'freebsd13',
  'openbsd',
  'netbsd',
  'arm',
]


import { defineMatrix } from '../_utils/defineMatrix'
import { Providers } from '../_utils/providers'

export default defineMatrix(() => [
  [
    {
      provider: Providers.SQLITE,
    },
  ],
])


import { PrismaClient } from '../prisma/client'

export { PrismaClient }


import testMatrix from './_matrix'
// @ts-ignore
import type { PrismaClient } from './node_modules/@prisma/client'

declare let prisma: PrismaClient

testMatrix.setupTestSuite(
  () => {
    test('query model with multiple fields', async () => {
      await prisma.testModel.create({
        data: {
          id: 1,
          json: { a: 'b' },
          string_list: ['1', 'a', '2', ''],
          bInt_list: [BigInt('-1234'), BigInt('1234')],
          date: new Date('2022-05-04T14:40:06.617Z'),
          time: new Date('2022-05-04T14:40:06.617Z'),
        },
      })

      const testModel = await prisma.$queryRaw`SELECT * FROM "TestModel"`

      expect(testModel).toEqual([
        {
          id: 1,
          json: { a: 'b' },
          string_list: ['1', 'a', '2', ''],
          // TODO: replace with exact values and remove next 2 asserts after
          // https://github.com/facebook/jest/issues/11617 is fixed
          bInt_list: [expect.anything(), expect.anything()],
          date: new Date('2022-05-04T00:00:00.000Z'),
          time: new Date('1970-01-01T14:40:06.617Z'),
        },
      ])

      expect(testModel![0].bInt_list[0] === BigInt('-1234')).toBe(true)
      expect(testModel![0].bInt_list[1] === BigInt('1234')).toBe(true)
    })
  },
  {
    optOut: {
      from: ['mongodb', 'mysql', 'sqlite', 'sqlserver'],
      reason: `
        $queryRaw only works on SQL based providers
        mySql: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '"TestModel"'
        sqlite: The current connector does not support lists of primitive types
        sqlserver: The current connector does not support the Json type.
      `,
    },
  },
)


| 'linux-static-arm64'
| 'windows'
| 'freebsd11'
| 'freebsd12'
| 'freebsd13'
| 'openbsd'
| 'netbsd'
| 'arm'

keep keep keep keep add keep keep keep keep


import { idForProvider } from '../../_utils/idForProvider'
import { Providers } from '../../_utils/providers'
import testMatrix from '../_matrix'

export default testMatrix.setupSchema(({ provider }) => {
  return /* Prisma */ `
    generator client {
      provider = "prisma-client-js"
      previewFeatures = ["views"]
    }

    datasource db {
      provider = "${provider}"
      url      = env("DATABASE_URI_${provider}")
    }

    model User {
      id ${idForProvider(provider)}
      email   String   @unique
      name    String?
      profile Profile?
    }

    model Profile {
      id ${idForProvider(provider)}
      bio       String
      user      User      @relation(fields: [userId], references: [id])
      userId    ${provider === Providers.MONGODB ? 'String @db.ObjectId' : 'String'} @unique
    }

    view UserInfo {
      id    ${idForProvider(provider)}
      email String
      name  String
      bio   String
    }
  `
})


import { defineMatrix } from '../../_utils/defineMatrix'
import { sqlProviders } from '../../_utils/providers'

export default defineMatrix(() => [sqlProviders])


import testMatrix from '../_matrix'

export default testMatrix.setupSchema(({ provider }) => {
  return /* Prisma */ `
    generator client {
      provider = "prisma-client-js"
    }

    datasource db {
      provider = "${provider}"
      url      = env("DATABASE_URI_${provider}")
    }

    model TestModel {
      id     Int       @id
      string String?
      int    Int?
      bInt   BigInt?
      float  Float?
      bytes  Bytes?
      bool   Boolean?
      dt     DateTime?
      dec    Decimal?
    }
  `
})


import Debug from '@prisma/debug'
import {
  assertNodeAPISupported,
  BinaryTarget,
  binaryTargets,
  getNodeAPIName,
  getPlatformInfo,
} from '@prisma/get-platform'
import execa from 'execa'
import fs from 'fs'
import { ensureDir } from 'fs-extra'
import { bold, yellow } from 'kleur/colors'
import pFilter from 'p-filter'
import path from 'path'
import tempDir from 'temp-dir'
import { promisify } from 'util'

import { BinaryType } from './BinaryType'
import { chmodPlusX } from './chmodPlusX'
import { cleanupCache } from './cleanupCache'
import { downloadZip } from './downloadZip'
import { allEngineEnvVarsSet, getBinaryEnvVarPath } from './env'
import { getHash } from './getHash'
import { getBar } from './log'
import { getCacheDir, getDownloadUrl, overwriteFile } from './utils'

const { enginesOverride } = require('../package.json')

const debug = Debug('prisma:fetch-engine:download')
const exists = promisify(fs.exists)

const channel = 'master'

// matches `/snapshot/` or `C:\\snapshot\\` or `C:/snapshot/` for vercel's pkg apps
export const vercelPkgPathRegex = /^((\w:[\\\/])|\/)snapshot[\/\\]/

export type BinaryDownloadConfiguration = {
  [binary in BinaryType]?: string // that is a path to the binary download location
}

export type BinaryPaths = {
  [binary in BinaryType]?: { [binaryTarget in BinaryTarget]: string } // key: target, value: path
}

export interface DownloadOptions {
  binaries: BinaryDownloadConfiguration
  binaryTargets?: BinaryTarget[]
  showProgress?: boolean
  progressCb?: (progress: number) => void
  version?: string
  skipDownload?: boolean
  failSilent?: boolean
  printVersion?: boolean
  skipCacheIntegrityCheck?: boolean
}

type BinaryDownloadJob = {
  binaryName: string
  targetFolder: string
  binaryTarget: BinaryTarget
  fileName: string
  targetFilePath: string
  envVarPath: string | undefined
  skipCacheIntegrityCheck: boolean
}

export async function download(options: DownloadOptions): Promise<BinaryPaths> {
  if (enginesOverride?.['branch'] || enginesOverride?.['folder']) {
    // if this is true the engines have been fetched before and already cached
    // into .cache/prisma/master/_local_ for us to be able to use this version
    options.version = '_local_'
    options.skipCacheIntegrityCheck = true
  }

  // get platform
  const { binaryTarget, ...os } = await getPlatformInfo()

  if (os.targetDistro && ['nixos'].includes(os.targetDistro) && !allEngineEnvVarsSet(Object.keys(options.binaries))) {
    console.error(
      `${yellow('Warning')} Precompiled engine files are not available for ${
        os.targetDistro
      }, please provide the paths via environment variables, see https://pris.ly/d/custom-engines`,
    )
  } else if (['freebsd11', 'freebsd12', 'freebsd13', 'openbsd', 'netbsd'].includes(binaryTarget)) {
    console.error(
      `${yellow(
        'Warning',
      )} Precompiled engine files are not available for ${binaryTarget}. Read more about building your own engines at https://pris.ly/d/build-engines`,
    )
  } else if (BinaryType.QueryEngineLibrary in options.binaries) {
    assertNodeAPISupported()
  }

  // no need to do anything, if there are no binaries
  if (!options.binaries || Object.values(options.binaries).length === 0) {
    return {} // we don't download anything if nothing is provided
  }

  // merge options
  const opts = {
    ...options,
    binaryTargets: options.binaryTargets ?? [binaryTarget],
    version: options.version ?? 'latest',
    binaries: options.binaries,
  }

  // creates a matrix of binaries x binary targets
  const binaryJobs = Object.entries(opts.binaries).flatMap(([binaryName, targetFolder]) =>
    opts.binaryTargets.map((binaryTarget) => {
      const fileName = getBinaryName(binaryName as BinaryType, binaryTarget)
      const targetFilePath = path.join(targetFolder, fileName)
      return {
        binaryName,
        targetFolder,
        binaryTarget,
        fileName,
        targetFilePath,
        envVarPath: getBinaryEnvVarPath(binaryName as BinaryType)?.path,
        skipCacheIntegrityCheck: !!opts.skipCacheIntegrityCheck,
      }
    }),
  )

  if (process.env.BINARY_DOWNLOAD_VERSION) {
    debug(`process.env.BINARY_DOWNLOAD_VERSION is set to "${process.env.BINARY_DOWNLOAD_VERSION}"`)
    opts.version = process.env.BINARY_DOWNLOAD_VERSION
  }

  if (opts.printVersion) {
    console.log(`version: ${opts.version}`)
  }

  // filter out files, which don't yet exist or have to be created
  const binariesToDownload = await pFilter(binaryJobs, async (job) => {
    const needsToBeDownloaded = await binaryNeedsToBeDownloaded(job, binaryTarget, opts.version)
    const isSupported = binaryTargets.includes(job.binaryTarget as BinaryTarget)
    const shouldDownload =
      isSupported &&
      !job.envVarPath && // this is for custom binaries
      needsToBeDownloaded
    if (needsToBeDownloaded && !isSupported) {
      throw new Error(`Unknown binaryTarget ${job.binaryTarget} and no custom engine files were provided`)
    }
    return shouldDownload
  })

  if (binariesToDownload.length > 0) {
    const cleanupPromise = cleanupCache() // already start cleaning up while we download

    let finishBar: undefined | (() => void)
    let setProgress: undefined | ((sourcePath: string) => (progress: number) => void)

    if (opts.showProgress) {
      const collectiveBar = getCollectiveBar(opts)
      finishBar = collectiveBar.finishBar
      setProgress = collectiveBar.setProgress
    }

    const promises = binariesToDownload.map((job) => {
      const downloadUrl = getDownloadUrl({
        channel: 'all_commits',
        version: opts.version,
        binaryTarget: job.binaryTarget,
        binaryName: job.binaryName,
      })

      debug(`${downloadUrl} will be downloaded to ${job.targetFilePath}`)

      return downloadBinary({
        ...job,
        downloadUrl,
        version: opts.version,
        failSilent: opts.failSilent,
        progressCb: setProgress ? setProgress(job.targetFilePath) : undefined,
      })
    })

    await Promise.all(promises)

    await cleanupPromise // make sure, that cleanup finished
    if (finishBar) {
      finishBar()
    }
  }

  const binaryPaths = binaryJobsToBinaryPaths(binaryJobs)
  const dir = eval('__dirname')

  // this is necessary for pkg
  if (dir.match(vercelPkgPathRegex)) {
    for (const engineType in binaryPaths) {
      const binaryTargets = binaryPaths[engineType]
      for (const binaryTarget in binaryTargets) {
        const binaryPath = binaryTargets[binaryTarget]
        binaryTargets[binaryTarget] = await maybeCopyToTmp(binaryPath)
      }
    }
  }

  return binaryPaths
}

function getCollectiveBar(options: DownloadOptions): {
  finishBar: () => void
  setProgress: (sourcePath: string) => (progress: number) => void
} {
  const hasNodeAPI = 'libquery-engine' in options.binaries
  const bar = getBar(
    `Downloading Prisma engines${hasNodeAPI ? ' for Node-API' : ''} for ${options.binaryTargets
      ?.map((p) => bold(p))
      .join(' and ')}`,
  )

  const progressMap: { [key: string]: number } = {}
  // Object.values is faster than Object.keys
  const numDownloads = Object.values(options.binaries).length * Object.values(options?.binaryTargets ?? []).length
  const setProgress =
    (sourcePath: string) =>
    (progress): void => {
      progressMap[sourcePath] = progress
      const progressValues = Object.values(progressMap)
      const totalProgress =
        progressValues.reduce((acc, curr) => {
          return acc + curr
        }, 0) / numDownloads
      if (options.progressCb) {
        options.progressCb(totalProgress)
      }
      if (bar) {
        bar.update(totalProgress)
      }
    }

  return {
    setProgress,
    finishBar: (): void => {
      bar.update(1)
      bar.terminate()
    },
  }
}

function binaryJobsToBinaryPaths(jobs: BinaryDownloadJob[]): BinaryPaths {
  return jobs.reduce<BinaryPaths>((acc, job) => {
    if (!acc[job.binaryName]) {
      acc[job.binaryName] = {}
    }

    // if an env var path has been provided, prefer that one
    acc[job.binaryName][job.binaryTarget] = job.envVarPath || job.targetFilePath

    return acc
  }, {} as BinaryPaths)
}

async function binaryNeedsToBeDownloaded(
  job: BinaryDownloadJob,
  nativePlatform: string,
  version: string,
): Promise<boolean> {
  // If there is an ENV Override and the file exists then it does not need to be downloaded
  if (job.envVarPath && fs.existsSync(job.envVarPath)) {
    return false
  }
  // 1. Check if file exists
  const targetExists = await exists(job.targetFilePath)
  // 2. If exists, check, if cached file exists and is up to date and has same hash as file.
  // If not, copy cached file over
  const cachedFile = await getCachedBinaryPath({
    ...job,
    version,
  })

  if (cachedFile) {
    // for local development, when using `enginesOverride`
    // we don't have the sha256 hash, so we can't check it
    if (job.skipCacheIntegrityCheck === true) {
      await overwriteFile(cachedFile, job.targetFilePath)

      return false
    }

    const sha256FilePath = cachedFile + '.sha256'
    if (await exists(sha256FilePath)) {
      const sha256File = await fs.promises.readFile(sha256FilePath, 'utf-8')
      const sha256Cache = await getHash(cachedFile)
      if (sha256File === sha256Cache) {
        if (!targetExists) {
          debug(`copying ${cachedFile} to ${job.targetFilePath}`)

          // TODO Remove when https://github.com/docker/for-linux/issues/1015 is fixed
          // Workaround for https://github.com/prisma/prisma/issues/7037
          await fs.promises.utimes(cachedFile, new Date(), new Date())

          await overwriteFile(cachedFile, job.targetFilePath)
        }
        const targetSha256 = await getHash(job.targetFilePath)
        if (sha256File !== targetSha256) {
          debug(`overwriting ${job.targetFilePath} with ${cachedFile} as hashes do not match`)
          await overwriteFile(cachedFile, job.targetFilePath)
        }
        return false
      } else {
        return true
      }
    } else if (process.env.PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING) {
      // If the env var is truthy we do not error if the checksum file is missing
      debug(
        `The checksum file: ${sha256FilePath} is missing but this was ignored as the PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING environment variable is truthy.`,
      )
      return false
    } else {
      return true
    }
  }

  // If there is no cache and the file doesn't exist, we for sure need to download it
  if (!targetExists) {
    debug(`file ${job.targetFilePath} does not exist and must be downloaded`)

    return true
  }

  // 3. If same platform, check --version and compare to expected version
  if (job.binaryTarget === nativePlatform) {
    const currentVersion = await getVersion(job.targetFilePath, job.binaryName)

    if (currentVersion?.includes(version) !== true) {
      debug(`file ${job.targetFilePath} exists but its version is ${currentVersion} and we expect ${version}`)

      return true
    }
  }

  return false
}

export async function getVersion(enginePath: string, binaryName: string) {
  try {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
    if (binaryName === BinaryType.QueryEngineLibrary) {
      assertNodeAPISupported()

      const commitHash = require(enginePath).version().commit
      return `${BinaryType.QueryEngineLibrary} ${commitHash}`
    } else {
      const result = await execa(enginePath, ['--version'])

      return result.stdout
    }
  } catch {}

  return undefined
}

export function getBinaryName(binaryName: BinaryType, binaryTarget: BinaryTarget): string {
  if (binaryName === BinaryType.QueryEngineLibrary) {
    return `${getNodeAPIName(binaryTarget, 'fs')}`
  }
  const extension = binaryTarget === 'windows' ? '.exe' : ''
  return `${binaryName}-${binaryTarget}${extension}`
}

type GetCachedBinaryOptions = BinaryDownloadJob & {
  version: string
  failSilent?: boolean
}

async function getCachedBinaryPath({
  version,
  binaryTarget,
  binaryName,
}: GetCachedBinaryOptions): Promise<string | null> {
  const cacheDir = await getCacheDir(channel, version, binaryTarget)
  if (!cacheDir) {
    return null
  }

  const cachedTargetPath = path.join(cacheDir, binaryName)

  if (!fs.existsSync(cachedTargetPath)) {
    return null
  }

  // All versions not called 'latest' are unique
  // only latest needs more checks
  if (version !== 'latest') {
    return cachedTargetPath
  }

  if (await exists(cachedTargetPath)) {
    return cachedTargetPath
  }

  return null
}

type DownloadBinaryOptions = BinaryDownloadJob & {
  version: string
  downloadUrl: string
  progressCb?: (progress: number) => void
  failSilent?: boolean
}

async function downloadBinary(options: DownloadBinaryOptions): Promise<void> {
  const { version, progressCb, targetFilePath, downloadUrl } = options

  const targetDir = path.dirname(targetFilePath)

  try {
    fs.accessSync(targetDir, fs.constants.W_OK)
    await ensureDir(targetDir)
  } catch (e) {
    if (options.failSilent || (e as NodeJS.ErrnoException).code !== 'EACCES') {
      return
    } else {
      throw new Error(`Can't write to ${targetDir} please make sure you install "prisma" with the right permissions.`)
    }
  }

  debug(`Downloading ${downloadUrl} to ${targetFilePath} ...`)

  if (progressCb) {
    progressCb(0)
  }

  const { sha256, zippedSha256 } = await downloadZip(downloadUrl, targetFilePath, progressCb)
  if (progressCb) {
    progressCb(1)
  }

  chmodPlusX(targetFilePath)

  // Cache result
  await saveFileToCache(options, version, sha256, zippedSha256)
}

async function saveFileToCache(
  job: BinaryDownloadJob,
  version: string,
  sha256: string | null,
  zippedSha256: string | null,
): Promise<void> {
  // always fail silent, as the cache is optional
  const cacheDir = await getCacheDir(channel, version, job.binaryTarget)
  if (!cacheDir) {
    return
  }

  const cachedTargetPath = path.join(cacheDir, job.binaryName)
  const cachedSha256Path = path.join(cacheDir, job.binaryName + '.sha256')
  const cachedSha256ZippedPath = path.join(cacheDir, job.binaryName + '.gz.sha256')

  try {
    await overwriteFile(job.targetFilePath, cachedTargetPath)
    if (sha256 != null) {
      await fs.promises.writeFile(cachedSha256Path, sha256)
    }
    if (zippedSha256 != null) {
      await fs.promises.writeFile(cachedSha256ZippedPath, zippedSha256)
    }
  } catch (e) {
    debug(e)
    // let this fail silently - the CI system may have reached the file size limit
  }
}

export async function maybeCopyToTmp(file: string): Promise<string> {
  // in this case, we are in a "pkg" context with a virtual fs
  // to make this work, we need to copy the binary to /tmp and execute it from there

  const dir = eval('__dirname')
  if (dir.match(vercelPkgPathRegex)) {
    const targetDir = path.join(tempDir, 'prisma-binaries')
    await ensureDir(targetDir)
    const target = path.join(targetDir, path.basename(file))
    const data = await fs.promises.readFile(file)
    await fs.promises.writeFile(target, data)
    // We have to read and write until https://github.com/zeit/pkg/issues/639
    // is resolved
    // await copyFile(file, target)
    plusX(target)
    return target
  }

  return file
}

export function plusX(file): void {
  const s = fs.statSync(file)
  const newMode = s.mode | 64 | 8 | 1
  if (s.mode === newMode) {
    return
  }
  const base8 = newMode.toString(8).slice(-3)
  fs.chmodSync(file, base8)
}


import { getBinaryTargetForCurrentPlatform } from '@prisma/get-platform'
import {
  ClientEngineType,
  extractPreviewFeatures,
  getClientEngineType,
  getConfig,
  getEnvPaths,
  getRelativeSchemaPath,
  parseEnvValue,
  printConfigWarnings,
} from '@prisma/internals'
import fs from 'fs'
import path from 'path'
import { parse } from 'stacktrace-parser'

import { getDMMF } from '../generation/getDMMF'
import { dmmfToRuntimeDataModel } from '../runtime/core/runtimeDataModel'
import type { GetPrismaClientConfig } from '../runtime/getPrismaClient'
import { getPrismaClient } from '../runtime/getPrismaClient'
import { ensureTestClientQueryEngine } from './ensureTestClientQueryEngine'
import { generateInFolder } from './generateInFolder'

//TODO Rename to generateTestClientInMemory
/**
 * Returns an in-memory client for testing
 */
export async function getTestClient(schemaDir?: string, printWarnings?: boolean): Promise<any> {
  const callSite = path.dirname(require.main?.filename ?? '')
  const absSchemaDir = path.resolve(callSite, schemaDir ?? '')
  const schemaPath = await getRelativeSchemaPath(absSchemaDir)
  const datamodel = await fs.promises.readFile(schemaPath!, 'utf-8')
  const config = await getConfig({ datamodel, ignoreEnvVarErrors: true })
  if (printWarnings) {
    printConfigWarnings(config.warnings)
  }

  const generator = config.generators.find((g) => parseEnvValue(g.provider) === 'prisma-client-js')
  const previewFeatures = extractPreviewFeatures(config)
  const binaryTarget = await getBinaryTargetForCurrentPlatform()
  const clientEngineType = getClientEngineType(generator!)
  ;(global as any).TARGET_BUILD_TYPE = clientEngineType === ClientEngineType.Library ? 'library' : 'binary'

  await ensureTestClientQueryEngine(clientEngineType, binaryTarget)

  const document = await getDMMF({
    datamodel,
    previewFeatures,
  })
  const outputDir = absSchemaDir
  const relativeEnvPaths = getEnvPaths(schemaPath, { cwd: absSchemaDir })
  const activeProvider = config.datasources[0].activeProvider
  const options: GetPrismaClientConfig = {
    runtimeDataModel: dmmfToRuntimeDataModel(document.datamodel),
    generator,
    dirname: absSchemaDir,
    relativePath: path.relative(outputDir, absSchemaDir),
    clientVersion: '0.0.0',
    engineVersion: '0000000000000000000000000000000000000000',
    relativeEnvPaths,
    datasourceNames: config.datasources.map((d) => d.name),
    activeProvider,
    inlineDatasources: { db: { url: config.datasources[0].url } },
    inlineSchema: btoa(datamodel),
    inlineSchemaHash: '',
  }

  return getPrismaClient(options)
}

/**
 * Options of `generateTestClient` function.
 */
type GenerateTestClientOptions = {
  /**
   * Directory to search for the schema in and generate the client in.
   */
  projectDir?: string

  /**
   * Overrides the query engine type, if specified, and makes the client ignore
   * the `PRISMA_CLIENT_ENGINE_TYPE` environment variable and `engineType` schema field.
   */
  engineType?: ClientEngineType
}

/**
 * Actually generates a test client with its own query-engine into ./@prisma/client
 */
export async function generateTestClient({ projectDir, engineType }: GenerateTestClientOptions = {}): Promise<any> {
  if (!projectDir) {
    const callsite = parse(new Error('').stack!)
    projectDir = path.dirname(callsite[1].file!)
  }

  await generateInFolder({
    projectDir,
    useLocalRuntime: false,
    transpile: true,
    useBuiltRuntime: false,
    overrideEngineType: engineType,
  })
}


# Readme

Tests that we are compatible with some tooling (eg. `sst`, or `electron`) that
will bundle and move folders around. In simple words, we want to make sure that
if you generate in a project like this:

/project /a/b/c (output to your generated client)

We also want to make sure that you can bundle and move the generated client:

/project/dist /b/c (note how the client is one level up)


import { defineMatrix } from '../_utils/defineMatrix'
import { allProviders } from '../_utils/providers'

export default defineMatrix(() => [allProviders])


* The target distro is the Linux distro associated with the Prisma Engines.

* E.g., on Arch Linux, Debian, and Ubuntu, the target distro is `debian`. On Linux Alpine, the target distro is `musl`.

keep keep keep replace keep keep keep keep

targetDistro?: 'rhel' | 'debian' | 'musl' | 'arm' | 'nixos' | 'freebsd11' | 'freebsd12' | 'freebsd13' | 'freebsd14'


@prisma/instrumentation

OpenTelemetry compliant instrumentation for Prisma Client

prisma instrumentation opentelemetry otel

DEV=true node -r esbuild-register helpers/build.ts


// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
  output   = "client"
}

datasource db {
  provider = "postgresql"
  url      = env("TEST_E2E_POSTGRES_URI")
}

model User {
  id        Int      @default(autoincrement()) @id
  email     String   @unique
  name      String?
  posts     Post[]
  profile   Profile?
}

model Post {
  id        Int      @default(autoincrement()) @id
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

model Profile {
  id     Int     @default(autoincrement()) @id
  bio    String?
  user   User    @relation(fields: [userId], references: [id])
  userId Int     @unique
}


my-prisma-project

echo "Error: no test specified" && exit 1


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

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

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

keep keep keep keep replace keep keep 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 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)
  })
})


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

describe('validatable.ts', () => {
  const Mock = Validatable.extend({
    render: h => h('div'),
  })

  type Instance = InstanceType<typeof Mock>
  let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
  beforeEach(() => {
    mountFunction = (options?: MountOptions<Instance>) => {
      return mount(Mock, {
        mocks: {
          $vuetify: {
            theme: { dark: false },
          },
        },
        ...options,
      })
    }
  })

  it('should register/unregister with injected form is available', () => {
    const form = {
      register: jest.fn(),
      unregister: jest.fn(),
    }

    const wrapper = mountFunction({
      provide: { form },
    })

    expect(form.register).toHaveBeenCalled()

    wrapper.destroy()

    expect(form.unregister).toHaveBeenCalled()
  })

  it('should manually set isResetting', () => {
    const wrapper = mountFunction()

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

    wrapper.vm.resetValidation()

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

  ;[true, false].forEach(returns => {
    it('should reset valid flag on resetValidation - ' + String(returns), async () => {
      jest.useFakeTimers()
      const wrapper = mountFunction({
        propsData: {
          rules: [() => returns || String(returns)],
        },
      })

      expect(wrapper.vm.valid).toBe(returns)

      wrapper.setData({ valid: !returns })

      wrapper.vm.resetValidation()
      await wrapper.vm.$nextTick()
      jest.runAllTimers()
      expect(wrapper.vm.valid).toBe(returns)
      jest.useRealTimers()
    })
  })

  /* eslint-disable-next-line max-statements */
  it('should manually validate', () => {
    const wrapper = mountFunction()

    expect(wrapper.vm.errorBucket).toEqual([])

    // Function failing
    wrapper.setProps({ rules: [() => 'fizzbuzz'] })

    wrapper.vm.validate()

    expect(wrapper.vm.errorBucket).toEqual(['fizzbuzz'])

    // Function passing
    wrapper.setProps({ rules: [val => val.length > 3 || 'fizzbuzz'] })

    wrapper.vm.validate(false, 'foo')

    expect(wrapper.vm.errorBucket).toEqual(['fizzbuzz'])

    wrapper.vm.validate(false, 'foobar')

    expect(wrapper.vm.errorBucket).toEqual([])

    // Boolean
    wrapper.setProps({ rules: [false] })

    wrapper.vm.validate()

    // https://github.com/vuetifyjs/vuetify/issues/9976
    expect(wrapper.vm.errorBucket).toEqual([''])

    // Boolean true sets no messages
    wrapper.setProps({ rules: [true] })

    wrapper.vm.validate()

    expect(wrapper.vm.errorBucket).toEqual([])

    // String
    wrapper.setProps({ rules: ['foobar'] })

    wrapper.vm.validate()

    expect(wrapper.vm.errorBucket).toEqual(['foobar'])

    // Warning
    wrapper.setProps({ rules: [undefined] })

    wrapper.vm.validate()

    expect(`Rules should return a string or boolean, received 'undefined' instead`).toHaveBeenWarned()

    // Force validation state
    wrapper.setProps({ rules: [false] })

    expect(wrapper.vm.hasInput).toBe(false)
    expect(wrapper.vm.hasFocused).toBe(false)

    wrapper.vm.validate(true)

    expect(wrapper.vm.hasInput).toBe(true)
    expect(wrapper.vm.hasFocused).toBe(true)
  })

  // https://github.com/vuetifyjs/vuetify/issues/5362
  it('should not validate on blur readonly or disabled when blurring', async () => {
    const focusBlur = async wrapper => {
      wrapper.setData({ isFocused: true })
      await wrapper.vm.$nextTick()
      wrapper.setData({ isFocused: false })
    }

    const validate = jest.fn()
    const wrapper = mountFunction({
      propsData: {
        validateOnBlur: true,
      },
      methods: { validate },
    })

    // Initial state from beforeMount
    expect(validate).toHaveBeenCalledTimes(1)

    // Readonly - no validation
    expect(wrapper.vm.isFocused).toBe(false)

    await focusBlur(wrapper)

    expect(validate).toHaveBeenCalledTimes(2)

    // Disabled - no validation
    wrapper.setProps({ disabled: true })

    await focusBlur(wrapper)

    expect(validate).toHaveBeenCalledTimes(2)

    // Validation!
    wrapper.setProps({ disabled: false })

    await focusBlur(wrapper)

    expect(validate).toHaveBeenCalledTimes(3)
  })

  it('should have success', () => {
    const wrapper = mountFunction()

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

    wrapper.setProps({ success: true })

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

    wrapper.setProps({ success: false, successMessages: ['foobar'] })

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

    wrapper.setProps({ successMessages: [] })

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

    wrapper.setProps({ successMessages: null })

    expect(wrapper.vm.hasSuccess).toBe(false)
  })

  /* eslint-disable-next-line max-statements */
  it('should have messages', () => {
    const wrapper = mountFunction()

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

    // Null message
    wrapper.setProps({ messages: null })
    expect(wrapper.vm.hasMessages).toBe(false)

    // String message
    wrapper.setProps({ messages: 'foo' })
    expect(wrapper.vm.hasMessages).toBe(true)

    // Array message
    wrapper.setProps({ messages: ['foo'] })
    expect(wrapper.vm.hasMessages).toBe(true)
    wrapper.setProps({ messages: [] }) // Reset

    // Null error
    wrapper.setProps({ errorMessages: null })
    expect(wrapper.vm.hasMessages).toBe(false)

    // String error
    wrapper.setProps({ errorMessages: 'bar' })
    expect(wrapper.vm.hasMessages).toBe(true)

    // Array error
    wrapper.setProps({ errorMessages: ['bar'] })
    expect(wrapper.vm.hasMessages).toBe(true)
    wrapper.setProps({ errorMessages: [] }) // Reset

    // Null success
    wrapper.setProps({ successMessages: null })
    expect(wrapper.vm.hasMessages).toBe(false)

    // String success
    wrapper.setProps({ successMessages: 'fizz' })
    expect(wrapper.vm.hasMessages).toBe(true)

    // Array success
    wrapper.setProps({ successMessages: ['fizz'] })
    expect(wrapper.vm.hasMessages).toBe(true)
    wrapper.setProps({ successMessages: [] }) // Reset

    // Error bucket
    wrapper.setProps({ rules: [() => 'fizzbuzz'] })
    expect(wrapper.vm.shouldValidate).toBe(false)

    wrapper.setData({ hasInput: true })

    expect(wrapper.vm.shouldValidate).toBe(true)
    expect(wrapper.vm.hasMessages).toBe(true)

    wrapper.setData({ hasInput: false, hasFocused: true })

    expect(wrapper.vm.shouldValidate).toBe(true)
    expect(wrapper.vm.hasMessages).toBe(true)

    wrapper.setData({ isResetting: true })

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

    wrapper.setData({ isResetting: false })
    wrapper.setProps({ validateOnBlur: true })

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

  it('should have state', () => {
    const wrapper = mountFunction()

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

    wrapper.setProps({ success: true })

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

    wrapper.setProps({ success: false, error: true })

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

    wrapper.setProps({ error: false })

    expect(wrapper.vm.hasState).toBe(false)
  })

  it('should return validation state', () => {
    const wrapper = mountFunction()

    expect(wrapper.vm.validationState).toBeUndefined()

    wrapper.setProps({ error: true })
    expect(wrapper.vm.validationState).toBe('error')

    wrapper.setProps({ error: false, success: true })
    expect(wrapper.vm.validationState).toBe('success')

    wrapper.setProps({ success: false, color: 'blue' })
    wrapper.setData({ hasColor: true })
    expect(wrapper.vm.validationState).toBe('blue')
  })

  it('should return a sliced amount based on error count', () => {
    const wrapper = mountFunction({
      propsData: {
        errorMessages: [
          'foobar',
          'fizzbuzz',
        ],
      },
    })

    expect(wrapper.vm.validations).toHaveLength(1)

    wrapper.setProps({ errorCount: 2 })

    expect(wrapper.vm.validations).toHaveLength(2)
  })

  it('should validate when internalValue changes', async () => {
    const validate = jest.fn()
    const wrapper = mountFunction({
      methods: { validate },
    })

    expect(wrapper.vm.hasInput).toBe(false)
    wrapper.setProps({ value: 'foo' })

    // Wait for watcher's $nextTick call
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.hasInput).toBe(true)
    expect(validate).toHaveBeenCalled()
  })

  it('should update values when resetting after timeout', async () => {
    const wrapper = mountFunction()

    wrapper.setData({
      hasInput: true,
      hasFocused: true,
      isResetting: true,
    })

    expect(wrapper.vm.hasInput).toBe(true)
    expect(wrapper.vm.hasFocused).toBe(true)
    expect(wrapper.vm.isResetting).toBe(true)

    // Wait for watcher
    await wrapper.vm.$nextTick()

    // Wait for watcher's timeout
    await wait()

    expect(wrapper.vm.hasInput).toBe(false)
    expect(wrapper.vm.hasFocused).toBe(false)
    expect(wrapper.vm.isResetting).toBe(false)
  })

  it('should emit error update when value changes and shouldValidate', async () => {
    const wrapper = mountFunction()

    const onError = jest.fn()

    wrapper.vm.$on('update:error', onError)

    wrapper.setProps({ error: true })

    await wrapper.vm.$nextTick()

    wrapper.setProps({ error: false })

    await wrapper.vm.$nextTick()

    expect(onError).toHaveBeenCalledTimes(1)
  })

  it('should reset validation and internalValue', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: 'foobar',
      },
    })

    wrapper.vm.reset()

    expect(wrapper.vm.isResetting).toBe(true)
    expect(wrapper.vm.internalValue).toBeNull()

    wrapper.setProps({ value: ['foobar'] })

    await wrapper.vm.$nextTick()

    wrapper.vm.reset()
    expect(wrapper.vm.isResetting).toBe(true)
    expect(wrapper.vm.internalValue).toEqual([])
  })

  // https://github.com/vuetifyjs/vuetify/issues/6025
  it('should accept null for external messages', () => {
    const wrapper = mountFunction({
      propsData: {
        errorMessages: ['Foobar'],
      },
    })

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

    wrapper.setProps({ errorMessages: [] })
    expect(wrapper.vm.externalError).toBe(false)

    wrapper.setProps({ errorMessages: 'Fizzbuzz' })
    expect(wrapper.vm.externalError).toBe(true)

    wrapper.setProps({ errorMessages: null })
    expect(wrapper.vm.externalError).toBe(false)
  })

  it('should return white when no color and isDark', () => {
    const wrapper = mountFunction({
      computed: { rootIsDark: () => false },
      propsData: { dark: true },
    })

    expect(wrapper.vm.computedColor).toBe('white')

    wrapper.setProps({ color: 'blue' })
    expect(wrapper.vm.computedColor).toBe('blue')

    wrapper.setProps({ color: undefined, dark: undefined })
    expect(wrapper.vm.computedColor).toBe('primary')

    const wrapper2 = mountFunction({
      computed: { rootIsDark: () => true },
    })

    expect(wrapper2.vm.computedColor).toBe('primary')

    wrapper2.setProps({ color: 'blue' })
    expect(wrapper2.vm.computedColor).toBe('blue')

    wrapper2.setProps({ color: undefined, light: true })
    expect(wrapper2.vm.computedColor).toBe('primary')
  })

  it('should return undefined for color and validation state if disabled', () => {
    const wrapper = mountFunction({
      propsData: {
        color: 'blue',
        dark: true,
        disabled: true,
      },
    })

    expect(wrapper.vm.computedColor).toBeUndefined()
    expect(wrapper.vm.validationState).toBeUndefined()
    expect(wrapper.vm.hasState).toBe(false)
  })

  // https://github.com/vuetifyjs/vuetify/issues/10174
  it('should validate correct value when blurring', async () => {
    const wrapper = mountFunction({
      propsData: {
        rules: [v => !!v || 'Mandatory Field'],
        validateOnBlur: true,
        value: 'Foo',
      },
    })

    wrapper.setData({ isFocused: true })

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

    wrapper.setData({ isFocused: false })
    await wrapper.vm.$nextTick()

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

    wrapper.setData({ isFocused: true })

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

    wrapper.setData({ isFocused: false })
    await wrapper.vm.$nextTick()

    expect(wrapper.vm.hasError).toBe(false)
  })
})


html
  font-size: $font-size-root
  overflow-x: hidden
  text-rendering: optimizeLegibility
  -webkit-font-smoothing: antialiased
  -moz-osx-font-smoothing: grayscale
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0)

html.overflow-y-hidden
  overflow-y: hidden !important

.v-application
  font-family: $body-font-family
  line-height: $line-height-root

  ::-ms-clear,
  ::-ms-reveal
    display: none

// iOS Safari hack to allow click events on body
@supports (-webkit-touch-callout: none)
  body
    cursor: pointer


module.exports = {
  'v-virtual-scroll': {
    slots: [
      {
        name: 'default',
        props: {
          index: 'number',
          item: 'any',
        },
      },
    ],
  },
}


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

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

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


// Styles
import './VColorPickerEdit.sass'

// Components
import VBtn from '../VBtn'
import VIcon from '../VIcon'

// Helpers
import { parseHex } from '../../util/colorUtils'

// Types
import Vue, { VNode, PropType } from 'vue'
import { VColorPickerColor, fromRGBA, fromHexa, fromHSLA } from './util'

type Input = [string, number, string]

export type Mode = {
  inputs?: Input[]
  from: Function
}

export const modes = {
  rgba: {
    inputs: [
      ['r', 255, 'int'],
      ['g', 255, 'int'],
      ['b', 255, 'int'],
      ['a', 1, 'float'],
    ],
    from: fromRGBA,
  },
  hsla: {
    inputs: [
      ['h', 360, 'int'],
      ['s', 1, 'float'],
      ['l', 1, 'float'],
      ['a', 1, 'float'],
    ],
    from: fromHSLA,
  },
  hexa: {
    from: fromHexa,
  },
} as { [key: string]: Mode }

export default Vue.extend({
  name: 'v-color-picker-edit',

  props: {
    color: Object as PropType<VColorPickerColor>,
    disabled: Boolean,
    hideAlpha: Boolean,
    hideModeSwitch: Boolean,
    mode: {
      type: String,
      default: 'rgba',
      validator: (v: string) => Object.keys(modes).includes(v),
    },
  },

  data () {
    return {
      modes,
      internalMode: this.mode,
    }
  },

  computed: {
    currentMode (): Mode {
      return this.modes[this.internalMode]
    },
  },

  watch: {
    mode (mode) {
      this.internalMode = mode
    },
  },

  created () {
    this.internalMode = this.mode
  },

  methods: {
    getValue (v: any, type: string) {
      if (type === 'float') return Math.round(v * 100) / 100
      else if (type === 'int') return Math.round(v)
      else return 0
    },
    parseValue (v: string, type: string) {
      if (type === 'float') return parseFloat(v)
      else if (type === 'int') return parseInt(v, 10) || 0
      else return 0
    },
    changeMode () {
      const modes = Object.keys(this.modes)
      const index = modes.indexOf(this.internalMode)
      const newMode = modes[(index + 1) % modes.length]
      this.internalMode = newMode
      this.$emit('update:mode', newMode)
    },
    genInput (target: string, attrs: any, value: any, on: any): VNode {
      return this.$createElement('div', {
        staticClass: 'v-color-picker__input',
      }, [
        this.$createElement('input', {
          key: target,
          attrs,
          domProps: {
            value,
          },
          on,
        }),
        this.$createElement('span', target.toUpperCase()),
      ])
    },
    genInputs (): VNode[] | VNode {
      if (this.internalMode === 'hexa') {
        const hex = this.color.hexa
        const value = this.hideAlpha && hex.endsWith('FF') ? hex.substr(0, 7) : hex
        return this.genInput(
          'hex',
          {
            maxlength: this.hideAlpha ? 7 : 9,
            disabled: this.disabled,
          },
          value,
          {
            change: (e: Event) => {
              const el = e.target as HTMLInputElement
              this.$emit('update:color', this.currentMode.from(parseHex(el.value)))
            },
          }
        )
      } else {
        const inputs = this.hideAlpha ? this.currentMode.inputs!.slice(0, -1) : this.currentMode.inputs!
        return inputs.map(([target, max, type]) => {
          const value = this.color[this.internalMode as keyof VColorPickerColor] as any
          return this.genInput(
            target,
            {
              type: 'number',
              min: 0,
              max,
              step: type === 'float' ? '0.01' : type === 'int' ? '1' : undefined,
              disabled: this.disabled,
            },
            this.getValue(value[target], type),
            {
              input: (e: Event) => {
                const el = e.target as HTMLInputElement
                const newVal = this.parseValue(el.value || '0', type)

                this.$emit('update:color', this.currentMode.from(
                  Object.assign({}, value, { [target]: newVal }),
                  this.color.alpha
                ))
              },
            }
          )
        })
      }
    },
    genSwitch (): VNode {
      return this.$createElement(VBtn, {
        props: {
          small: true,
          icon: true,
          disabled: this.disabled,
        },
        on: {
          click: this.changeMode,
        },
      }, [
        this.$createElement(VIcon, '$unfold'),
      ])
    },
  },

  render (h): VNode {
    return h('div', {
      staticClass: 'v-color-picker__edit',
    }, [
      this.genInputs(),
      !this.hideModeSwitch && this.genSwitch(),
    ])
  },
})


const rimraf = require('rimraf')
const { resolve } = require('path')
const { writeJsonFile } = require('./helpers/write')
const { capitalize, kebabCase, pascalize } = require('./helpers/text')
const { getComponentsApi, getDirectivesApi } = require('./index')
const pkg = require('../package.json')

const createVeturApi = () => {
  const components = getComponentsApi(['en'])

  const tags = components.reduce((obj, component) => {
    return {
      ...obj,
      [component.name]: {
        attributes: component.props.map(p => kebabCase(p.name)).sort(),
        description: '',
      },
    }
  }, {})

  const attributes = components.reduce((obj, component) => {
    const attrs = component.props.reduce((a, prop) => {
      let type = prop.type || ''

      if (!type) type = ''
      else if (Array.isArray(type)) type = type.map(t => t.toLowerCase()).join('|')
      else type = type.toLowerCase()

      a[`${component.name}/${kebabCase(prop.name)}`] = {
        type,
        description: prop.description.en || '',
      }

      return a
    }, {})

    return { ...obj, ...attrs }
  }, {})

  writeJsonFile(tags, 'dist/tags.json')
  writeJsonFile(attributes, 'dist/attributes.json')
}

// Create web-types.json to provide autocomplete in JetBrains IDEs
const createWebTypesApi = () => {
  const components = getComponentsApi(['en'])
  const directives = getDirectivesApi(['en'])

  const getDocUrl = (cmp, heading = null) => `https://www.vuetifyjs.com/api/${cmp}${heading ? `#${heading}` : ''}`

  const createTag = component => {
    const createTagSlot = slot => {
      return {
        name: slot.name,
        pattern: undefined,
        description: slot.description.en || '',
        'doc-url': getDocUrl(component.name, 'slots'),
        'vue-properties': slot.props && Object.keys(slot.props).map(key => createTypedEntity(key, slot.props[key])),
      }
    }

    const createTagEvent = event => {
      return {
        name: event.name,
        description: event.description.en || '',
        'doc-url': getDocUrl(component.name, 'events'),
        arguments: [createTypedEntity('argument', event.value)], // TODO: value should be replaced by arguments array
      }
    }

    const createTagAttribute = prop => {
      return {
        name: prop.name,
        description: prop.description.en || '',
        'doc-url': getDocUrl(component.name, 'props'),
        default: typeof prop.default !== 'string' ? JSON.stringify(prop.default) : prop.default,
        required: undefined, // TODO: implement this
        value: createTagValue(prop.type),
        type: prop.type === 'boolean' ? 'boolean' : undefined, // this is deprecated but should be const 'boolean' for compatibility with 2019.2
      }
    }

    const createTagValue = type => {
      return {
        kind: 'expression',
        type,
      }
    }

    const pascalName = pascalize(component.name)

    return {
      name: pascalName,
      source: {
        module: './src/components/index.ts',
        symbol: pascalName,
      },
      aliases: undefined, // TODO: are we using this? deprecated name changes?
      description: '', // TODO: we should probably include component description in locale files
      'doc-url': getDocUrl(component.name),
      attributes: component.props.map(createTagAttribute),
      events: component.events.map(createTagEvent),
      slots: component.slots.map(createTagSlot),
      'vue-model': { // TODO: we should expose this in api data if we can
        prop: 'value',
        event: 'input',
      },
    }
  }

  const createTypedEntity = (name, type) => {
    return {
      name,
      type: typeof type !== 'string' ? JSON.stringify(type) : type,
    }
  }

  const createAttribute = directive => {
    const createAttributeVueArgument = argument => {
      return {
        pattern: undefined,
        description: argument.description && argument.description.en ? argument.description.en : '',
        'doc-url': getDocUrl(directive.name, 'argument'),
        required: undefined,
      }
    }

    const createAttributeVueModifier = modifier => {
      return {
        name: modifier.name,
        pattern: undefined,
        description: modifier.description.en || '',
        'doc-url': getDocUrl(directive.name, 'modifiers'),
      }
    }

    const createAttributeValue = argument => {
      return {
        kind: 'expression',
        type: argument.type,
      }
    }

    return {
      name: directive.name,
      aliases: undefined,
      description: '', // TODO: we should probably include directive description in locale files
      'doc-url': getDocUrl(directive.name),
      default: '',
      required: false,
      value: createAttributeValue(directive.argument),
      source: {
        module: './src/directives/index.ts',
        symbol: capitalize(directive.name.slice(2)),
      },
      'vue-argument': directive.argument && createAttributeVueArgument(directive.argument), // TODO: how to use this in comparison to value?
      'vue-modifiers': directive.modifiers && directive.modifiers.map(createAttributeVueModifier),
    }
  }

  const tags = components.map(createTag)
  const attributes = directives.map(createAttribute)

  const webTypes = {
    $schema: 'http://json.schemastore.org/web-types',
    framework: 'vue',
    name: 'vuetify',
    version: pkg.version,
    contributions: {
      html: {
        'types-syntax': 'typescript',
        'description-markup': 'markdown',
        tags,
        attributes,
      },
    },
  }

  writeJsonFile(webTypes, 'dist/web-types.json')
}

rimraf.sync(resolve('./dist'))

createVeturApi()
createWebTypesApi()