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

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

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

/* @vue/component */
export default {
  name: 'v-radio',

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

  inheritAttrs: false,

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

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

  computed: {
    computedData () {
      return this.setTextColor(!this.parentError && this.isActive && this.color, {
        staticClass: 'v-radio',
        'class': {
          'v-radio--is-disabled': this.isDisabled,
          'v-radio--is-focused': this.isFocused,
          ...this.themeClasses
        }
      })
    },
    computedColor () {
      return this.isActive ? this.color : this.radio.validationState || false
    },
    computedIcon () {
      return this.isActive
        ? this.onIcon
        : this.offIcon
    },
    hasState () {
      return this.isActive || !!this.radio.validationState
    },
    isDisabled () {
      return this.disabled || !!this.radio.disabled
    },
    isReadonly () {
      return this.readonly || !!this.radio.readonly
    }
  },

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

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

  methods: {
    genInput (...args) {
      // We can't actually use the mixin directly because
      // it's made for standalone components, but its
      // genInput method is exactly what we need
      return Selectable.options.methods.genInput.call(this, ...args)
    },
    genLabel () {
      return this.$createElement(VLabel, {
        on: { click: this.onChange },
        attrs: {
          for: this.id
        },
        props: {
          color: this.radio.validationState || false,
          dark: this.dark,
          focused: this.hasState,
          light: this.light
        }
      }, this.$slots.label || this.label)
    },
    genRadio () {
      return this.$createElement('div', {
        staticClass: 'v-input--selection-controls__input'
      }, [
        this.genInput('radio', {
          name: this.radio.name || (this.radio._uid ? 'v-radio-' + this.radio._uid : false),
          value: this.value,
          ...this.$attrs
        }),
        this.genRipple(this.setTextColor(this.computedColor)),
        this.$createElement(VIcon, this.setTextColor(this.computedColor, {
          props: {
            dark: this.dark,
            light: this.light
          }
        }), this.computedIcon)
      ])
    },
    onFocus () {
      this.isFocused = true
    },
    onBlur (e) {
      this.isFocused = false
      this.$emit('blur', e)
    },
    onChange () {
      if (this.isDisabled || this.isReadonly) return

      if (!this.isDisabled && (!this.isActive || !this.radio.mandatory)) {
        this.$emit('change', this.value)
      }
    },
    onKeydown () {}
  },

  render (h) {
    return h('div', this.computedData, [
      this.genRadio(),
      this.genLabel()
    ])
  }
}


keep keep keep keep replace keep keep keep keep

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

.v-otp-input
  @include tools.rounded(4px)

  align-items: center
  display: flex
  justify-content: center
  padding: .5rem
  position: relative

  .v-field
    height: 100%
    margin: 0 .25rem

.v-otp-input__divider
  margin: 0 8px

.v-otp-input__content
  align-items: center
  display: flex
  height: 64px
  padding: .5rem
  justify-content: center
  max-width: 320px
  position: relative
  border-radius: inherit

  .v-otp-input--divided &
    max-width: 360px

.v-otp-input__field
  color: inherit
  font-size: 1.25rem
  height: 100%
  outline: none
  text-align: center
  width: 100%

  &[type=number]::-webkit-outer-spin-button,
  &[type=number]::-webkit-inner-spin-button
    -webkit-appearance: none
    margin: 0

  &[type=number]
    -moz-appearance: textfield

.v-otp-input__loader
  align-items: center
  display: flex
  height: 100%
  justify-content: center
  width: 100%


<template>
  <div class="text-center">
    <v-pagination
      v-model="page"
      :length="4"
      rounded="circle"
    ></v-pagination>

    <v-pagination
      v-model="page"
      :length="4"
      rounded="0"
    ></v-pagination>
  </div>
</template>

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

  const page = ref(1)
</script>

<script>
  export default {
    data () {
      return {
        page: 1,
      }
    },
  }
</script>


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

// Components
import { VAppBar } from '..'
import { VLayout } from '@/components/VLayout'
import { VMain } from '@/components/VMain'

// Utilities
import { ref } from 'vue'

// Constants
const SCROLL_OPTIONS = { ensureScrollable: true, duration: 50 }

describe('VAppBar', () => {
  it('allows custom height', () => {
    cy
      .mount(({ height }: any) => (
        <VLayout>
          <VAppBar height={ height } />
        </VLayout>
      ))
      .get('.v-app-bar').should('have.css', 'height', '64px')
      .setProps({ height: 128 })
      .get('.v-app-bar').should('have.css', 'height', '128px')
  })

  it('supports density', () => {
    cy
      .mount(({ density = 'default' }: any) => (
        <VLayout>
          <VAppBar density={ density } />
        </VLayout>
      ))
      .get('.v-app-bar').should('have.css', 'height', '64px')
      .setProps({ density: 'prominent' })
      .get('.v-app-bar').should('have.css', 'height', '128px')
      .setProps({ density: 'comfortable' })
      .get('.v-app-bar').should('have.css', 'height', '56px')
      .setProps({ density: 'compact' })
      .get('.v-app-bar').should('have.css', 'height', '48px')
  })

  it('is hidden on mount', () => {
    const model = ref(false)

    cy
      .mount(() => (
        <VLayout>
          <VAppBar v-model={ model.value } />
        </VLayout>
      ))
      .get('.v-app-bar')
      .should('not.be.visible')
      .then(() => (model.value = true))
      .get('.v-app-bar')
      .should('be.visible')
  })

  describe('scroll behavior', () => {
    it('hides', () => {
      cy.mount(({ scrollBehavior }: any) => (
        <VLayout>
          <VAppBar scrollBehavior={ scrollBehavior } />

          <VMain style="min-height: 200vh;" />
        </VLayout>
      ))
        .setProps({ scrollBehavior: 'hide' })
        .get('.v-app-bar').should('be.visible')
        .window().scrollTo(0, 500, SCROLL_OPTIONS)
        .get('.v-app-bar').should('not.be.visible')
        .window().scrollTo(0, 250, SCROLL_OPTIONS)
        .get('.v-app-bar').should('be.visible')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)
        .get('.v-app-bar').should('be.visible')

        .setProps({ scrollBehavior: 'hide inverted' })
        .get('.v-app-bar').should('not.be.visible')
        .window().scrollTo(0, 500, SCROLL_OPTIONS)
        .get('.v-app-bar').should('be.visible')
        .window().scrollTo(0, 250, SCROLL_OPTIONS)
        .get('.v-app-bar').should('not.be.visible')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)
        .get('.v-app-bar').should('not.be.visible')
    })

    it('collapses', () => {
      cy.mount(({ scrollBehavior }: any) => (
        <VLayout>
          <VAppBar scrollBehavior={ scrollBehavior } />

          <VMain style="min-height: 200vh;" />
        </VLayout>
      ))
        .setProps({ scrollBehavior: 'collapse' })
        .get('.v-app-bar').should('be.visible')
        .get('.v-app-bar').should('have.not.class', 'v-toolbar--collapse')
        .window().scrollTo(0, 500, SCROLL_OPTIONS)
        .get('.v-app-bar').should('have.class', 'v-toolbar--collapse')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)

        .setProps({ scrollBehavior: 'collapse inverted' })
        .get('.v-app-bar').should('be.visible')
        .get('.v-app-bar').should('have.class', 'v-toolbar--collapse')
        .window().scrollTo(0, 500, SCROLL_OPTIONS)
        .get('.v-app-bar').should('not.have.class', 'v-toolbar--collapse')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)
    })

    it('elevates', () => {
      cy.mount(({ scrollBehavior }: any) => (
        <VLayout>
          <VAppBar scrollBehavior={ scrollBehavior } />

          <VMain style="min-height: 200vh;" />
        </VLayout>
      ))
        .setProps({ scrollBehavior: 'elevate' })
        .get('.v-app-bar').should('have.class', 'v-toolbar--flat')
        .window().scrollTo(0, 500, SCROLL_OPTIONS)
        .get('.v-app-bar').should('not.have.class', 'v-toolbar--flat')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)

        .setProps({ scrollBehavior: 'elevate inverted' })
        .get('.v-app-bar').should('not.have.class', 'v-toolbar--flat')
        .window().scrollTo(0, 500, SCROLL_OPTIONS)
        .get('.v-app-bar').should('have.class', 'v-toolbar--flat')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)
    })

    it('fades image', () => {
      cy.mount(({ scrollBehavior, image }: any) => (
        <VLayout>
          <VAppBar
            image={ image }
            scrollBehavior={ scrollBehavior }
          />

          <VMain style="min-height: 200vh;" />
        </VLayout>
      ))
        .setProps({
          image: 'https://picsum.photos/1920/1080?random',
          scrollBehavior: 'fade-image',
        })
        .get('.v-toolbar__image').should('have.css', 'opacity', '1')
        .window().scrollTo(0, 150, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '0.5')
        .window().scrollTo(0, 300, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '0')
        .window().scrollTo(0, 60, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '0.8')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '1')

        .setProps({ scrollBehavior: 'fade-image inverted' })
        .get('.v-toolbar__image').should('have.css', 'opacity', '0')
        .window().scrollTo(0, 150, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '0.5')
        .window().scrollTo(0, 300, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '1')
        .window().scrollTo(0, 60, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '0.2')
        .window().scrollTo(0, 0, SCROLL_OPTIONS)
        .get('.v-toolbar__image').should('have.css', 'opacity', '0')
    })
  })
})


---
layout: wireframe
meta:
  title: Steam Wireframe
  keywords: vuetify wireframe, vuetify app, vue app
  description: A steam wireframe template for Vuetify
---
<script setup>
  import Steam from '@/examples/wireframes/steam.vue'
</script>

<steam />


position: relative
.v-field
height: 100%
margin: 0 .25rem
.v-otp-input__divider
margin: 0 8px
.v-otp-input__content

keep keep keep keep replace keep keep keep keep keep


<template>
  <v-container class="bg-surface-variant">
    <v-row no-gutters>
      <v-col cols="9">
        <v-sheet class="pa-2 ma-2">
          .v-col-9
        </v-sheet>
      </v-col>
      <v-col cols="4">
        <v-sheet class="pa-2 ma-2">
          .v-col-4
        </v-sheet>
      </v-col>
      <v-col cols="6">
        <v-sheet class="pa-2 ma-2">
          .v-col-6
        </v-sheet>
      </v-col>
    </v-row>
  </v-container>
</template>


<template>
  <v-row justify="center">
    <v-dialog
      v-model="dialog"
      width="800"
    >
      <template v-slot:activator="{ props }">
        <v-btn
          color="primary"
          v-bind="props"
        >
          Open Dialog
        </v-btn>
      </template>
      <v-card>
        <v-card-title>
          Use Google's location service?
        </v-card-title>
        <v-card-text>
          Lorem ipsum dolor sit amet, semper quis, sapien id natoque elit. Nostra urna at, magna at neque sed sed ante imperdiet, dolor mauris cursus velit, velit non, sem nec. Volutpat sem ridiculus placerat leo, augue in, duis erat proin condimentum in a eget, sed fermentum sed vestibulum varius ac, vestibulum volutpat orci ut elit eget tortor. Ultrices nascetur nulla gravida ante arcu. Pharetra rhoncus morbi ipsum, nunc tempor debitis, ipsum pellentesque, vitae id quam ut mauris dui tempor, aptent non. Quisque turpis. Phasellus quis lectus luctus orci eget rhoncus. Amet donec vestibulum mattis commodo, nulla aliquet, nibh praesent, elementum nulla. Sit lacus pharetra tempus magna neque pellentesque, nulla vel erat.
          Justo ex quisque nulla accusamus venenatis, sed quis. Nibh phasellus gravida metus in, fusce aenean ut erat commodo eros. Ut turpis, dui integer, nonummy pede placeat nec in sit leo. Faucibus porttitor illo taciti odio, amet viverra scelerisque quis quis et tortor, curabitur morbi a. Enim tempor at, rutrum elit condimentum, amet rutrum vitae tempor torquent nunc. Praesent vestibulum integer maxime felis. Neque aenean quia vitae nostra, tempus elit enim id dui, at egestas pulvinar. Integer libero vestibulum, quis blandit scelerisque mattis fermentum nulla, tortor donec vestibulum dolor amet eget, elit nullam. Aliquam leo phasellus aliquam curabitur metus a, nulla justo mattis duis interdum vel, mollis vitae et id, vestibulum erat ridiculus sit pulvinar justo sed. Vehicula convallis, et nulla wisi, amet vestibulum risus, quam ac egestas.
          Et vitae, nulla gravida erat scelerisque nullam nunc pellentesque, a dictumst cras augue, purus imperdiet non. Varius montes cursus varius vel tortor, nec leo a qui, magni cras, velit vel consectetuer lobortis vel. Nibh erat et wisi felis leo porttitor, sapien nibh sapien pede mi, sed eget porttitor, repellendus arcu ac quis. Luctus vulputate aut est sem magna, placerat accumsan nunc vestibulum ipsum ac auctor, maecenas lorem in ut nec mauris tortor, doloribus varius sem tortor vestibulum mollis, eleifend tortor felis tempus lacus eu eu. Eleifend vel eu, nullam maecenas mauris nec nunc euismod, tortor porta ridiculus potenti, massa tristique nam magna, et wisi placerat et erat ante. Eget pede erat in facilisis, fermentum venenatis sodales. Ac tortor sociis et non animi tristique, rhoncus malesuada, ut arcu volutpat scelerisque sollicitudin, elit curabitur dui pede purus dolor, integer aenean risus taciti nulla eleifend accumsan. At pulvinar diam parturient, interdum mi velit aliquet et a. Arcu at ac placerat eget justo semper, purus sociis curabitur mi ipsum consequat ut, mollis vestibulum, est ante ornare lacus sem. Neque magna mauris, commodo quisque, praesent semper suscipit lobortis nam. Justo malesuada cursus ac nunc litora nunc. Tellus ac, in lobortis nunc, montes lectus purus fermentum.
          Ac sit wisi. Sodales aliquam, sed vestibulum nullam arcu sit risus arcu, id luctus vitae lorem nibh, integer nec nullam class cursus mi, purus arcu lectus. Vel ante suscipit volutpat potenti mattis sed, wisi eu placerat aliquam erat, lectus morbi lobortis at assumenda. Consequat neque purus ipsum voluptas odio, netus vestibulum ut nec, suspendisse pellentesque nec enim in. Wisi dictum sed semper a, ipsum erat tellus habitasse est, erat sem ornare, vitae quisque ultricies. Dui sed blandit. Tempor et faucibus justo sed luctus, nec vitae vitae. Nunc nibh pede, ipsum vestibulum aenean leo ante ultricies, nam cras quis sed penatibus amet. In mauris a. Integer metus mauris tortor, et rutrum vestibulum ultricies, ut phasellus in ullamcorper ut mollit, eu justo. Cursus pretium venenatis.
          Cras pellentesque vel sodales accumsan aenean. Feugiat metus sit nec in aliquet amet, porttitor pretium vulputate massa. Consequat ipsum luctus quisque adipiscing libero. Wisi sollicitudin. Eget vitae ac lobortis, lorem natoque vestibulum et, aliquet faucibus at morbi nibh, vel condimentum. Massa unde orci sed id sed, odio donec congue nec praesent amet. Hymenaeos velit lacus, quis vivamus libero tempus duis, eu nisi eu, ipsum at accumsan pede justo morbi donec, massa et libero sit risus neque tortor. Ut sed sed etiam hendrerit dapibus, quis metus suspendisse nibh.
          Fringilla tempor felis augue magna. Cum arcu a, id vitae. Pellentesque pharetra in cras sociis adipiscing est. Nibh nec mattis at maecenas, nisl orci aliquam nulla justo egestas venenatis, elementum duis vel porta eros, massa vitae, eligendi imperdiet amet. Nec neque luctus suscipit, justo sem praesent, ut nisl quisque, volutpat torquent wisi tellus aliquam reprehenderit, curabitur cras at quis massa porttitor mauris. Eros sed ultrices. Amet dignissim justo urna feugiat mauris litora, etiam accumsan, lobortis a orci suspendisse. Semper ac mauris, varius bibendum pretium, orci urna nunc ullamcorper auctor, saepe sem integer quam, at feugiat egestas duis. Urna ligula ante. Leo elementum nonummy. Sagittis mauris est in ipsum, nulla amet non justo, proin id potenti platea posuere sit ut, nunc sit erat bibendum. Nibh id auctor, ab nulla vivamus ultrices, posuere morbi nunc tellus gravida vivamus.
          Mauris nec, facilisi quam fermentum, ut mauris integer, orci tellus tempus diam ut in pellentesque. Wisi faucibus tempor et odio leo diam, eleifend quis integer curabitur sit scelerisque ac, mauris consequat luctus quam penatibus fringilla dis, vitae lacus in, est eu ac tempus. Consectetuer amet ipsum amet dui, sed blandit id sed. Tellus integer, dignissim id pede sodales quis, felis dolorem id mauris orci, orci tempus ut. Nullam hymenaeos. Curabitur in a, tortor ut praesent placerat tincidunt interdum, ac dignissim metus nonummy hendrerit wisi, etiam ut.
          Semper praesent integer fusce, tortor suspendisse, augue ligula orci ante asperiores ullamcorper. In sit per mi sed sed, mi vestibulum mus nam, morbi mauris neque vitae aliquam proin senectus. Ac amet arcu mollis ante congue elementum, inceptos eget optio quam pellentesque quis lobortis, sollicitudin sed vestibulum sollicitudin, lectus parturient nullam, leo orci ligula ultrices. At tincidunt enim, suspendisse est sit sem ac. Amet tellus molestie est purus magna augue, non etiam et in wisi id. Non commodo, metus lorem facilisi lobortis ac velit, montes neque sed risus consectetuer fringilla dolor. Quam justo et integer aliquam, cursus nulla enim orci, nam cursus adipiscing, integer torquent non, fringilla per maecenas. Libero ipsum sed tellus purus et. Duis molestie placerat erat donec ut. Dolor enim erat massa faucibus ultrices in, ante ultricies orci lacus, libero consectetuer mauris magna feugiat neque dapibus, donec pretium et. Aptent dui, aliquam et et amet nostra ligula.
          Augue curabitur duis dui volutpat, tempus sed ut pede donec. Interdum luctus, lectus nulla aenean elit, id sit magna, vulputate ultrices pellentesque vel id fermentum morbi. Tortor et. Adipiscing augue lorem cum non lacus, rutrum sodales laoreet duis tortor, modi placerat facilisis et malesuada eros ipsum, vehicula tempus. Ac vivamus amet non aliquam venenatis lectus, sociosqu adipiscing consequat nec arcu odio. Blandit orci nec nec, posuere in pretium, enim ut, consectetuer nullam urna, risus vel. Nullam odio vehicula massa sed, etiam sociis mauris, lacus ullamcorper, libero imperdiet non sodales placerat justo vehicula. Nec morbi imperdiet. Fermentum sem libero iaculis bibendum et eros, eget maecenas non nunc, ad pellentesque. Ut nec diam elementum interdum. Elementum vitae tellus lacus vitae, ipsum phasellus, corporis vehicula in ac sed massa vivamus, rutrum elit, ultricies metus volutpat.
          Semper wisi et, sollicitudin nunc vestibulum, cursus accumsan nunc pede tempus mi ipsum, ligula sed. Non condimentum ac dolor sit. Mollis eu aliquam, vel mattis mollis massa ut dolor ante, tempus lacinia arcu. Urna vestibulum lorem, nulla fermentum, iaculis ut congue ac vivamus. Nam libero orci, pulvinar nulla, enim pellentesque consectetuer leo, feugiat rhoncus rhoncus vel. Magna sociosqu donec, dictum cursus ullamcorper viverra. Ultricies quis orci lorem, suspendisse ut vestibulum integer, purus sed lorem pulvinar habitasse turpis.
          +
        </v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn
            color="green-darken-1"
            variant="text"
            @click="dialog = false"
          >
            Disagree
          </v-btn>
          <v-btn
            color="green-darken-1"
            variant="text"
            @click="dialog = false"
          >
            Agree
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>


name: CI
on: [push, pull_request]
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
  CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

jobs:
  pre_job:
    runs-on: ubuntu-latest
    outputs:
      should_skip: ${{ steps.skip_check.outputs.should_skip }}
    steps:
      - id: skip_check
        if: ${{ !startswith(github.ref, 'refs/tags/v') }}
        uses: fkirc/skip-duplicate-actions@master
        with:
          skip_after_successful_duplicate: 'true'
          concurrent_skipping: same_content
          do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'

  build-vuetify:
    name: Build vuetify
    needs: pre_job
    if: needs.pre_job.outputs.should_skip != 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ./.github/actions/yarn-install
      - run: yarn build vuetify
      - uses: ./.github/actions/upload-artifact
        with:
          name: vuetify-dist
          path: >
            packages/vuetify/dist
            packages/vuetify/lib

  lint:
    name: Lint
    needs: [pre_job, build-vuetify]
    if: needs.pre_job.outputs.should_skip != 'true'
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        scopes: ['--scope vuetify --scope @vuetify/api-generator', '--scope vuetifyjs.com']
    steps:
      - uses: actions/checkout@v2
      - uses: ./.github/actions/download-artifact
        with:
          name: vuetify-dist
      - uses: ./.github/actions/yarn-install
      - run: yarn lerna run lint $SCOPES
        env:
          SCOPES: ${{ matrix.scopes }}

  test-jest:
    name: Test (Jest)
    needs: pre_job
    if: needs.pre_job.outputs.should_skip != 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ./.github/actions/yarn-install
      - run: yarn run test:coverage -i
        working-directory: ./packages/vuetify
      - uses: codecov/codecov-action@v1

  test-cypress:
    name: Test (Cypress)
    needs: pre_job
    if: needs.pre_job.outputs.should_skip != 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ./.github/actions/yarn-install
      - run: yarn cy:run --record --parallel --ci-build-id $GITHUB_RUN_ID
        if: ${{ !startswith(github.ref, 'refs/tags/v') && github.repository_owner == 'vuetifyjs' }}
        working-directory: ./packages/vuetify
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
      - run: yarn cy:run
        if: ${{ !startswith(github.ref, 'refs/tags/v') && github.repository_owner != 'vuetifyjs' }}
        working-directory: ./packages/vuetify
      - uses: actions/upload-artifact@v2
        if: failure()
        with:
          name: cypress-screenshots
          path: ./packages/vuetify/cypress/screenshots/
          if-no-files-found: ignore

  deploy:
    needs: [lint, test-jest, test-cypress, build-vuetify]
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && startswith(github.ref, 'refs/tags/v') && github.repository_owner == 'vuetifyjs'
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - uses: ./.github/actions/download-artifact
        with:
          name: vuetify-dist
      - uses: ./.github/actions/yarn-install
      - run: yarn build api
      - run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
      - name: NPM Release
        run: bash scripts/deploy.sh
        env:
          NPM_API_KEY: ${{ secrets.NPM_TOKEN }}
          RELEASE_TAG: ${{ env.RELEASE_TAG }}
      - name: GitHub release
        id: create_release
        run: yarn conventional-github-releaser -p vuetify
        env:
          CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  build-docs:
    name: Build docs
    needs: [pre_job, build-vuetify]
    if: needs.pre_job.outputs.should_skip != 'true' && github.event_name == 'push' && github.repository_owner == 'vuetifyjs' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/next')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ./.github/actions/download-artifact
        with:
          name: vuetify-dist
      - uses: ./.github/actions/yarn-install
      - uses: ./.github/actions/download-locales
      - run: yarn build api
      - run: yarn build docs
        env:
          NODE_OPTIONS: --max-old-space-size=4096
          VITE_COSMIC_2_BUCKET_SLUG: ${{ secrets.COSMIC_2_BUCKET_SLUG }}
          VITE_COSMIC_2_BUCKET_READ_KEY: ${{ secrets.COSMIC_2_BUCKET_READ_KEY }}
          VITE_COSMIC_BUCKET_SLUG: ${{ secrets.COSMIC_BUCKET_SLUG }}
          VITE_COSMIC_BUCKET_READ_KEY: ${{ secrets.COSMIC_BUCKET_READ_KEY }}
          VITE_COSMIC_BUCKET_SLUG_STORE: ${{ secrets.COSMIC_BUCKET_SLUG_STORE }}
          VITE_COSMIC_BUCKET_READ_KEY_STORE: ${{ secrets.COSMIC_BUCKET_READ_KEY_STORE }}
          VITE_EMAILJS_PUBLIC_KEY: ${{ secrets.EMAILJS_PUBLIC_KEY }}
          VITE_EMAILJS_SERVICE_ID: ${{ secrets.EMAILJS_SERVICE_ID }}
          VITE_EMAILJS_TEMPLATE_ID: ${{ secrets.EMAILJS_TEMPLATE_ID }}
          VITE_AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }}
          VITE_AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }}
          VITE_API_SERVER_URL: ${{ secrets.API_SERVER_URL }}
          VITE_GITHUB_SHA: ${{ github.sha }}
      - uses: ./.github/actions/upload-artifact
        with:
          name: docs-dist
          path: packages/docs/dist

  publish-docs:
    needs: [lint, test-jest, build-docs]
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.repository_owner == 'vuetifyjs' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/next')
    steps:
      - uses: actions/checkout@v2
      - uses: ./.github/actions/download-artifact
        with:
          name: docs-dist
      - uses: ./.github/actions/yarn-install
      - run: yarn global add vercel
      - run: node scripts/deploy-and-alias.js ${{ github.ref }}
        env:
          NOW_TOKEN: ${{ secrets.NOW_TOKEN }}


.v-otp-input__content
align-items: center
display: flex
height: 64px


// Styles
import './VProgressCircular.sass'

// Composables
import { useTextColor } from '@/composables/color'
import { makeComponentProps } from '@/composables/component'
import { useIntersectionObserver } from '@/composables/intersectionObserver'
import { useResizeObserver } from '@/composables/resizeObserver'
import { makeSizeProps, useSize } from '@/composables/size'
import { makeTagProps } from '@/composables/tag'
import { makeThemeProps, provideTheme } from '@/composables/theme'

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

// Types
import type { PropType } from 'vue'

export const makeVProgressCircularProps = propsFactory({
  bgColor: String,
  color: String,
  indeterminate: [Boolean, String] as PropType<boolean | 'disable-shrink'>,
  modelValue: {
    type: [Number, String],
    default: 0,
  },
  rotate: {
    type: [Number, String],
    default: 0,
  },
  width: {
    type: [Number, String],
    default: 4,
  },

  ...makeComponentProps(),
  ...makeSizeProps(),
  ...makeTagProps({ tag: 'div' }),
  ...makeThemeProps(),
}, 'VProgressCircular')

type VProgressCircularSlots = {
  default: { value: number }
}

export const VProgressCircular = genericComponent<VProgressCircularSlots>()({
  name: 'VProgressCircular',

  props: makeVProgressCircularProps(),

  setup (props, { slots }) {
    const MAGIC_RADIUS_CONSTANT = 20
    const CIRCUMFERENCE = 2 * Math.PI * MAGIC_RADIUS_CONSTANT

    const root = ref<HTMLElement>()

    const { themeClasses } = provideTheme(props)
    const { sizeClasses, sizeStyles } = useSize(props)
    const { textColorClasses, textColorStyles } = useTextColor(toRef(props, 'color'))
    const { textColorClasses: underlayColorClasses, textColorStyles: underlayColorStyles } = useTextColor(toRef(props, 'bgColor'))
    const { intersectionRef, isIntersecting } = useIntersectionObserver()
    const { resizeRef, contentRect } = useResizeObserver()

    const normalizedValue = computed(() => Math.max(0, Math.min(100, parseFloat(props.modelValue))))
    const width = computed(() => Number(props.width))
    const size = computed(() => {
      // Get size from element if size prop value is small, large etc
      return sizeStyles.value
        ? Number(props.size)
        : contentRect.value
          ? contentRect.value.width
          : Math.max(width.value, 32)
    })
    const diameter = computed(() => (MAGIC_RADIUS_CONSTANT / (1 - width.value / size.value)) * 2)
    const strokeWidth = computed(() => width.value / size.value * diameter.value)
    const strokeDashOffset = computed(() => convertToUnit(((100 - normalizedValue.value) / 100) * CIRCUMFERENCE))

    watchEffect(() => {
      intersectionRef.value = root.value
      resizeRef.value = root.value
    })

    useRender(() => (
      <props.tag
        ref={ root }
        class={[
          'v-progress-circular',
          {
            'v-progress-circular--indeterminate': !!props.indeterminate,
            'v-progress-circular--visible': isIntersecting.value,
            'v-progress-circular--disable-shrink': props.indeterminate === 'disable-shrink',
          },
          themeClasses.value,
          sizeClasses.value,
          textColorClasses.value,
          props.class,
        ]}
        style={[
          sizeStyles.value,
          textColorStyles.value,
          props.style,
        ]}
        role="progressbar"
        aria-valuemin="0"
        aria-valuemax="100"
        aria-valuenow={ props.indeterminate ? undefined : normalizedValue.value }
      >
        <svg
          style={{
            transform: `rotate(calc(-90deg + ${Number(props.rotate)}deg))`,
          }}
          xmlns="http://www.w3.org/2000/svg"
          viewBox={ `0 0 ${diameter.value} ${diameter.value}` }
        >
          <circle
            class={[
              'v-progress-circular__underlay',
              underlayColorClasses.value,
            ]}
            style={ underlayColorStyles.value }
            fill="transparent"
            cx="50%"
            cy="50%"
            r={ MAGIC_RADIUS_CONSTANT }
            stroke-width={ strokeWidth.value }
            stroke-dasharray={ CIRCUMFERENCE }
            stroke-dashoffset={ 0 }
          />

          <circle
            class="v-progress-circular__overlay"
            fill="transparent"
            cx="50%"
            cy="50%"
            r={ MAGIC_RADIUS_CONSTANT }
            stroke-width={ strokeWidth.value }
            stroke-dasharray={ CIRCUMFERENCE }
            stroke-dashoffset={ strokeDashOffset.value }
          />
        </svg>

        { slots.default && (
          <div class="v-progress-circular__content">
            { slots.default({ value: normalizedValue.value }) }
          </div>
        )}
      </props.tag>
    ))

    return {}
  },
})

export type VProgressCircular = InstanceType<typeof VProgressCircular>


---
meta:
  nav: Breadcrumbs
  title: Breadcrumbs component
  description: The breadcrumbs component is a navigational helper for pages. It can accept a Material Icons icon or characters as a divider.
  keywords: breadcrumbs, vuetify breadcrumbs component, vue breadcrumbs component, v-breadcrumbs component
related:
  - /components/buttons/
  - /components/navigation-drawers/
  - /components/icons/
features:
  figma: true
  label: 'C: VBreadcrumbs'
  report: true
  github: /components/VBreadcrumbs/
---

# Breadcrumbs

The `v-breadcrumbs` component is used as a navigational helper and hierarchy for pages.

<!-- ![breadcrumbs Entry](https://cdn.vuetifyjs.com/docs/images/components-temp/v-breadcrumbs/v-breadcrumbs-entry.png) -->

<page-features />

## Usage

By default, breadcrumbs use a text divider. This can be any string.

<usage name="v-breadcrumbs" />

<entry />

::: tip

Use [slots](/api/v-breadcrumbs/#slots) for more control of the breadcrumbs, either utilizing `v-breadcrumbs-item` or other custom markup.

:::

## API

| Component | Description |
| - | - |
| [v-breadcrumbs](/api/v-breadcrumbs/) | Primary Component |
| [v-breadcrumbs-item](/api/v-breadcrumbs-item/) | Sub-component used for each breadcrumb |
| [v-breadcrumbs-divider](/api/v-breadcrumbs-divider/) | Sub-component used for dividing breadcrumbs |

<api-inline hide-links />

::: info
  By default `v-breadcrumbs` will disable all crumbs up to the current page in a nested paths. You can prevent this behavior by using `exact: true` on each applicable breadcrumb in the `items` array.
:::

## Examples

### Props

#### Divider

Breadcrumbs separator can be set using `divider` property.

<example file="v-breadcrumbs/prop-divider" />

### Slots

#### Prepend

Prepend content with the `prepend` slot.

<example file="v-breadcrumbs/slot-prepend" />

#### Dividers

To customize the divider, use the `divider` slot.

<example file="v-breadcrumbs/slot-icon-dividers" />

#### Title

You can use the `title` slot to customize each breadcrumb title.

<example file="v-breadcrumbs/slot-title" />


<template>
  <v-divider class="my-5" />
</template>

<script setup>
  //
</script>


height: 64px
padding: .5rem
justify-content: center
max-width: 320px
position: relative
border-radius: inherit


Formats day of the month string that appears in a day to a specified locale.

The ending date on the calendar (inclusive) in the format of `YYYY-MM-DD`. This may be ignored depending on the `type` of the calendar.

If the header at the top of the `day` view should be visible.

Sets the day that determines the first week of the year, starting with 0 for **Sunday**. For ISO 8601 this should be 4.

The minimum number of weeks to display in the `month` or `week` view.

Formats month string that appears in a day to specified locale.

Override the day & time which is considered now. This is in the format of `YYYY-MM-DD hh:mm:ss`. The calendar is styled according to now.

Whether the short versions of a month should be used (Jan vs January).

Whether the short versions of a weekday should be used (Mon vs Monday).

Whether the name of the month should be displayed on the first day of the month.

Whether week numbers should be displayed when using the `month` view.

The starting date on the calendar (inclusive) in the format of `YYYY-MM-DD`. This may be ignored depending on the `type` of the calendar.

Formats day of the week string that appears in the header to specified locale.

Specifies which days of the week to display. To display Monday through Friday only, a value of `[1, 2, 3, 4, 5]` can be used. To display a week starting on Monday a value of `[1, 2, 3, 4, 5, 6, 0]` can be used.


<template>
  <v-responsive
    class="align-center"
    min-height="118"
  >
    <template v-if="!error1 && !error2">
      <promoted-base
        ref="script"
        :class="[
          isDark ? 'theme--dark' : 'theme--light',
        ]"
        border
        max-width="360"
      >
        <promoted-script
          v-if="!error1"
          id="carbonads-script"
          script-id="_carbonads_js"
          src="//cdn.carbonads.com/carbon.js?serve=CWYDC27W&placement=v3vuetifyjscom"
          @script:error="error1 = true"
        />

        <promoted-script
          v-if="error1"
          id="bsa-zone_1691166982595-9_123456"
          :src="`https://cdn4.buysellads.net/pub/vuetifyjs.js?${Date.now() % 600000}`"
          script-id="bsa-optimize"
          @script:error="error2 = true"
        />
      </promoted-base>
    </template>

    <promotion-card v-else />
  </v-responsive>

  <br>
</template>

<script setup lang="ts">
  // Components
  import PromotionCard from '@/components/promotions/PromotionCard.vue'
  import PromotedBase from './Base.vue'
  import PromotedScript from './Script.vue'

  // Composables
  import { useTheme } from 'vuetify'

  // Utilities
  import { computed, onBeforeUnmount, onMounted, onScopeDispose, shallowRef, watch } from 'vue'

  const error1 = shallowRef(false)
  const error2 = shallowRef(false)
  const script = shallowRef(null)
  let timer = -1 as any

  function checkForElement (id: string, cb?: () => void) {
    return setTimeout(() => {
      if (document.getElementById(id)) return

      clearTimeout(timer)

      cb?.()
    }, 2000)
  }

  watch(error1, val => {
    if (!val) return

    timer = checkForElement('bsa-zone_1691166982595-9_123456', () => {
      error2.value = true
    })
  })

  onMounted(() => {
    timer = checkForElement('carbonads', () => {
      error1.value = true
    })
  })

  onBeforeUnmount(() => {
    document.getElementById('carbonads-script')?.remove()
    document.getElementById('bsa-zone_1691166982595-9_123456')?.remove()
  })

  onScopeDispose(() => {
    clearTimeout(timer)
  })

  const theme = useTheme()

  const isDark = computed(() => theme.current.value.dark)
</script>

<style lang="sass">
  @media only screen and (min-width: 0px) and (min-height: 0px)
    div[id^="bsa-zone_1691166982595-9_123456"]
      min-width: 300px
      min-height: 250px

  @media only screen and (min-width: 760px) and (min-height: 0px)
    div[id^="bsa-zone_1691166982595-9_123456"]
      min-width: 728px
      min-height: 90px

  #carbonads-script
    width: 100%

  #carbonads,
  #carbonads_1,
  #carbonads_2
    width: 100%

    > span
      display: flex
      position: relative

    .carbon-wrap
      display: flex

    .carbon-text,
    .carbon-poweredby
      max-width: 200px
      padding: 0 0 0 16px
      text-decoration: none

    .carbon-img
      display: inline-flex
      margin: 0.5rem

      img
        border-radius: 4px 0 0 4px
        max-height: 100px

    .carbon-text
      color: inherit
      font-size: 0.75rem
      padding: 0.475rem

    .carbon-poweredby
      bottom: 0.5rem
      font-size: 0.625rem
      font-weight: 400
      letter-spacing: 0.09375rem
      position: absolute
      right: 0.5rem
      text-transform: uppercase

  .v-app-ad.theme--light
    .carbon-poweredby
      color: rgba(0, 0, 0, .6)

  .v-app-ad.theme--dark
    .carbon-poweredby
      color: inherit
</style>


---
meta:
  nav: Virtual scrollers
  title: Virtual scroll component
  description: The Virtual scroll component is a container that renders only visible elements. It is useful when you need to display large amounts of uniform data.
  keywords: virtual scroll, vuetify virtual scroll component, vue virtual scroll component, v-virtual-scroll component
related:
  - /components/lists/
  - /components/data-tables/virtual-tables/
  - /components/combobox/
features:
  github: /components/VVirtualScroller/
  label: 'C: VVirtualScroller'
  report: true
---

# Virtual scrollers

The `v-virtual-scroll` component displays a virtual, _infinite_ list. It supports dynamic height and scrolling vertically and is a good alternative to pagination.

![Virtual scroll Entry](https://cdn.vuetifyjs.com/docs/images/components/v-virtual-scroll/v-virtual-scroll-entry.png)

<page-features />

::: warning
This feature requires [v3.2.0 (Orion)](/getting-started/release-notes/?version=v3.2.0)
:::

## Usage

The virtual scroller displays just enough records to fill the viewport and uses the existing component, rehydrating it with new data.

<usage name="v-virtual-scroll" />

<entry />

## API

| Component | Description |
| - | - |
| [v-virtual-scroll](/api/v-virtual-scroll/) | Primary Component |

<api-inline hide-links />

## Anatomy

The `v-virtual-scroll` component contains only a default slot with no styling options. It is typically used with large collections of [v-list-item](/components/lists/)s.

![Virtual scroll Anatomy](https://cdn.vuetifyjs.com/docs/images/components/v-virtual-scroll/v-virtual-scroll-anatomy.png)

| Element / Area | Description |
| - | - |
| 1. Container | The rendered content area from the provided **items** prop |

## Guide

The `v-virtual-scroll` allows you to display thousands of records on a single page without the performance hit of actually showing all of them at once. `v-virtual-scroll` is devoid of styling and pairs well with components such as [v-card](/components/cards/) to provide a better visual experience.

### Props

The `v-virtual-scroll` component has a small API mainly used to configure the root and item height.

#### Height

The `v-virtual-scroll` component does not have any initial height set on itself.

The following code snippet uses the **height** prop:

<example file="v-virtual-scroll/prop-height" />

Another way of making sure that the component has height is to place it inside an element with `display: flex`.

<example file="v-virtual-scroll/prop-height-parent" />

#### Item Height

For lists where the item height is static and uniform for all items, it's recommended that you define a specific **item-height**. This value is used for `v-virtual-scroll`'s calculations.

<example file="v-virtual-scroll/prop-item-height" />

If your items are not of a uniform size, omit the **item-height** prop to have `v-virtual-scroll` dynamically calculate each item.

<example file="v-virtual-scroll/prop-dynamic-item-height" />

### Examples

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

#### User Directory

The v-virtual-scroll component can render an large amount of items by rendering only what it needs to fill the scroller’s viewport.

<example file="v-virtual-scroll/misc-user-directory" />


export const makeVOtpInputProps = propsFactory({

keep keep keep add keep keep keep keep keep


@mixin typography ($font-size, $font-weight, $letter-spacing, $line-height, $text-transform)
  font-size: $font-size
  font-weight: $font-weight
  letter-spacing: $letter-spacing
  line-height: $line-height
  text-transform: $text-transform


<template>
  <v-treeview
    v-model="tree"
    :open="initiallyOpen"
    :items="items"
    activatable
    item-key="name"
    open-on-click
  >
    <template v-slot:prepend="{ item, open }">
      <v-icon v-if="!item.file">
        {{ open ? 'mdi-folder-open' : 'mdi-folder' }}
      </v-icon>
      <v-icon v-else>
        {{ files[item.file] }}
      </v-icon>
    </template>
  </v-treeview>
</template>

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

  const initiallyOpen = ref(['public'])
  const files = ref({
    html: 'mdi-language-html5',
    js: 'mdi-nodejs',
    json: 'mdi-code-json',
    md: 'mdi-language-markdown',
    pdf: 'mdi-file-pdf-box',
    png: 'mdi-file-image',
    txt: 'mdi-file-document-outline',
    xls: 'mdi-file-excel',
  })
  const tree = ref([])
  const items = ref([
    {
      name: '.git',
    },
    {
      name: 'node_modules',
    },
    {
      name: 'public',
      children: [
        {
          name: 'static',
          children: [{
            name: 'logo.png',
            file: 'png',
          }],
        },
        {
          name: 'favicon.ico',
          file: 'png',
        },
        {
          name: 'index.html',
          file: 'html',
        },
      ],
    },
    {
      name: '.gitignore',
      file: 'txt',
    },
    {
      name: 'babel.config.js',
      file: 'js',
    },
    {
      name: 'package.json',
      file: 'json',
    },
    {
      name: 'README.md',
      file: 'md',
    },
    {
      name: 'vue.config.js',
      file: 'js',
    },
    {
      name: 'yarn.lock',
      file: 'txt',
    },
  ])
</script>

<script>
  export default {
    data: () => ({
      initiallyOpen: ['public'],
      files: {
        html: 'mdi-language-html5',
        js: 'mdi-nodejs',
        json: 'mdi-code-json',
        md: 'mdi-language-markdown',
        pdf: 'mdi-file-pdf-box',
        png: 'mdi-file-image',
        txt: 'mdi-file-document-outline',
        xls: 'mdi-file-excel',
      },
      tree: [],
      items: [
        {
          name: '.git',
        },
        {
          name: 'node_modules',
        },
        {
          name: 'public',
          children: [
            {
              name: 'static',
              children: [{
                name: 'logo.png',
                file: 'png',
              }],
            },
            {
              name: 'favicon.ico',
              file: 'png',
            },
            {
              name: 'index.html',
              file: 'html',
            },
          ],
        },
        {
          name: '.gitignore',
          file: 'txt',
        },
        {
          name: 'babel.config.js',
          file: 'js',
        },
        {
          name: 'package.json',
          file: 'json',
        },
        {
          name: 'README.md',
          file: 'md',
        },
        {
          name: 'vue.config.js',
          file: 'js',
        },
        {
          name: 'yarn.lock',
          file: 'txt',
        },
      ],
    }),
  }
</script>


<template>
  <v-timeline direction="horizontal" line-inset="12">
    <v-timeline-item>
      <template v-slot:opposite>
        Opposite
      </template>
      Content
    </v-timeline-item>

    <v-timeline-item>
      <template v-slot:opposite>
        Opposite
      </template>
      Content
    </v-timeline-item>

    <v-timeline-item>
      <template v-slot:opposite>
        Opposite
      </template>
      Content
    </v-timeline-item>
  </v-timeline>
</template>


Text for 'All' option in items-per-page dropdown.

Array of options to show in the items-per-page dropdown.

Show current page number between prev/next icons.

Adds content to the empty space in the footer.

Defines content for the items-per-page text.


<template>
  <v-row
    align="center"
    justify="space-around"
  >
    <v-btn
      tile
      color="success"
    >
      <v-icon start>
        mdi-pencil
      </v-icon>
      Edit
    </v-btn>
  </v-row>
</template>


Property on supplied `items` that contains the boolean value indicating if the item is selectable.

Property on supplied `items` that contains its value.

Changes the selection behavior to return the object directly rather than the value specified with **item-value**.


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

$otp-input-content-padding: .5rem 0 !default;


// Styles
import './VRadioGroup.sass'

// Components
import { makeVInputProps, VInput } from '@/components/VInput/VInput'
import { VLabel } from '@/components/VLabel'
import { VSelectionControl } from '@/components/VSelectionControl'
import { makeSelectionControlGroupProps, VSelectionControlGroup } from '@/components/VSelectionControlGroup/VSelectionControlGroup'

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

// Utilities
import { computed } from 'vue'
import { filterInputAttrs, genericComponent, getUid, omit, propsFactory, useRender } from '@/util'

// Types
import type { VInputSlots } from '@/components/VInput/VInput'

export type VRadioGroupSlots = Omit<VInputSlots, 'default'> & {
  default: never
  label: {
    label: string | undefined
    props: Record<string, any>
  }
}

export const makeVRadioGroupProps = propsFactory({
  height: {
    type: [Number, String],
    default: 'auto',
  },

  ...makeVInputProps(),
  ...omit(makeSelectionControlGroupProps(), ['multiple']),

  trueIcon: {
    type: IconValue,
    default: '$radioOn',
  },
  falseIcon: {
    type: IconValue,
    default: '$radioOff',
  },
  type: {
    type: String,
    default: 'radio',
  },
}, 'VRadioGroup')

export const VRadioGroup = genericComponent<VRadioGroupSlots>()({
  name: 'VRadioGroup',

  inheritAttrs: false,

  props: makeVRadioGroupProps(),

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

  setup (props, { attrs, slots }) {
    const uid = getUid()
    const id = computed(() => props.id || `radio-group-${uid}`)
    const model = useProxiedModel(props, 'modelValue')

    useRender(() => {
      const [rootAttrs, controlAttrs] = filterInputAttrs(attrs)
      const [inputProps, _1] = VInput.filterProps(props)
      const [controlProps, _2] = VSelectionControl.filterProps(props)
      const label = slots.label
        ? slots.label({
          label: props.label,
          props: { for: id.value },
        })
        : props.label

      return (
        <VInput
          class={[
            'v-radio-group',
            props.class,
          ]}
          style={ props.style }
          { ...rootAttrs }
          { ...inputProps }
          v-model={ model.value }
          id={ id.value }
        >
          {{
            ...slots,
            default: ({
              id,
              messagesId,
              isDisabled,
              isReadonly,
            }) => (
              <>
                { label && (
                  <VLabel id={ id.value }>
                    { label }
                  </VLabel>
                )}

                <VSelectionControlGroup
                  { ...controlProps }
                  id={ id.value }
                  aria-describedby={ messagesId.value }
                  defaultsTarget="VRadio"
                  trueIcon={ props.trueIcon }
                  falseIcon={ props.falseIcon }
                  type={ props.type }
                  disabled={ isDisabled.value }
                  readonly={ isReadonly.value }
                  aria-labelledby={ label ? id.value : undefined }
                  multiple={ false }
                  { ...controlAttrs }
                  v-model={ model.value }
                  v-slots={ slots }
                />
              </>
            ),
          }}
        </VInput>
      )
    })

    return {}
  },
})

export type VRadioGroup = InstanceType<typeof VRadioGroup>


// Composables
import { useProxiedModel } from './proxiedModel'

// Utilities
import { computed, inject, onBeforeUnmount, onMounted, provide, reactive, toRef, watch } from 'vue'
import { consoleWarn, deepEqual, findChildrenWithProvide, getCurrentInstance, getUid, propsFactory, wrapInArray } from '@/util'

// Types
import type { ComponentInternalInstance, ComputedRef, ExtractPropTypes, InjectionKey, PropType, Ref, UnwrapRef } from 'vue'

export interface GroupItem {
  id: number
  value: Ref<unknown>
  disabled: Ref<boolean | undefined>
}

export interface GroupProps {
  disabled: boolean
  modelValue: unknown
  multiple?: boolean
  mandatory?: boolean | 'force' | undefined
  max?: number | undefined
  selectedClass: string | undefined
  'onUpdate:modelValue': ((val: unknown) => void) | undefined
}

export interface GroupProvide {
  register: (item: GroupItem, cmp: ComponentInternalInstance) => void
  unregister: (id: number) => void
  select: (id: number, value: boolean) => void
  selected: Ref<Readonly<number[]>>
  isSelected: (id: number) => boolean
  prev: () => void
  next: () => void
  selectedClass: Ref<string | undefined>
  items: ComputedRef<{
    id: number
    value: unknown
    disabled: boolean | undefined
  }[]>
  disabled: Ref<boolean | undefined>
  getItemIndex: (value: unknown) => number
}

export interface GroupItemProvide {
  id: number
  isSelected: Ref<boolean>
  toggle: () => void
  select: (value: boolean) => void
  selectedClass: Ref<(string | undefined)[] | false>
  value: Ref<unknown>
  disabled: Ref<boolean | undefined>
  group: GroupProvide
}

export const makeGroupProps = propsFactory({
  modelValue: {
    type: null,
    default: undefined,
  },
  multiple: Boolean,
  mandatory: [Boolean, String] as PropType<boolean | 'force'>,
  max: Number,
  selectedClass: String,
  disabled: Boolean,
}, 'group')

export const makeGroupItemProps = propsFactory({
  value: null,
  disabled: Boolean,
  selectedClass: String,
}, 'group-item')

export interface GroupItemProps extends ExtractPropTypes<ReturnType<typeof makeGroupItemProps>> {
  'onGroup:selected': ((val: { value: boolean }) => void) | undefined
}

// Composables
export function useGroupItem (
  props: GroupItemProps,
  injectKey: InjectionKey<GroupProvide>,
  required?: true,
): GroupItemProvide
export function useGroupItem (
  props: GroupItemProps,
  injectKey: InjectionKey<GroupProvide>,
  required: false,
): GroupItemProvide | null
export function useGroupItem (
  props: GroupItemProps,
  injectKey: InjectionKey<GroupProvide>,
  required = true,
): GroupItemProvide | null {
  const vm = getCurrentInstance('useGroupItem')

  if (!vm) {
    throw new Error(
      '[Vuetify] useGroupItem composable must be used inside a component setup function'
    )
  }

  const id = getUid()

  provide(Symbol.for(`${injectKey.description}:id`), id)

  const group = inject(injectKey, null)

  if (!group) {
    if (!required) return group

    throw new Error(`[Vuetify] Could not find useGroup injection with symbol ${injectKey.description}`)
  }

  const value = toRef(props, 'value')
  const disabled = computed(() => !!(group.disabled.value || props.disabled))

  group.register({
    id,
    value,
    disabled,
  }, vm)

  onBeforeUnmount(() => {
    group.unregister(id)
  })

  const isSelected = computed(() => {
    return group.isSelected(id)
  })

  const selectedClass = computed(() => isSelected.value && [group.selectedClass.value, props.selectedClass])

  watch(isSelected, value => {
    vm.emit('group:selected', { value })
  })

  return {
    id,
    isSelected,
    toggle: () => group.select(id, !isSelected.value),
    select: (value: boolean) => group.select(id, value),
    selectedClass,
    value,
    disabled,
    group,
  }
}

export function useGroup (
  props: GroupProps,
  injectKey: InjectionKey<GroupProvide>
) {
  let isUnmounted = false
  const items = reactive<GroupItem[]>([])
  const selected = useProxiedModel(
    props,
    'modelValue',
    [],
    v => {
      if (v == null) return []

      return getIds(items, wrapInArray(v))
    },
    v => {
      const arr = getValues(items, v)

      return props.multiple ? arr : arr[0]
    }
  )

  const groupVm = getCurrentInstance('useGroup')

  function register (item: GroupItem, vm: ComponentInternalInstance) {
    // Is there a better way to fix this typing?
    const unwrapped = item as unknown as UnwrapRef<GroupItem>

    const key = Symbol.for(`${injectKey.description}:id`)
    const children = findChildrenWithProvide(key, groupVm?.vnode)
    const index = children.indexOf(vm)

    if (index > -1) {
      items.splice(index, 0, unwrapped)
    } else {
      items.push(unwrapped)
    }
  }

  function unregister (id: number) {
    if (isUnmounted) return

    // TODO: re-evaluate this line's importance in the future
    // should we only modify the model if mandatory is set.
    // selected.value = selected.value.filter(v => v !== id)

    forceMandatoryValue()

    const index = items.findIndex(item => item.id === id)
    items.splice(index, 1)
  }

  // If mandatory and nothing is selected, then select first non-disabled item
  function forceMandatoryValue () {
    const item = items.find(item => !item.disabled)
    if (item && props.mandatory === 'force' && !selected.value.length) {
      selected.value = [item.id]
    }
  }

  onMounted(() => {
    forceMandatoryValue()
  })

  onBeforeUnmount(() => {
    isUnmounted = true
  })

  function select (id: number, value?: boolean) {
    const item = items.find(item => item.id === id)
    if (value && item?.disabled) return

    if (props.multiple) {
      const internalValue = selected.value.slice()
      const index = internalValue.findIndex(v => v === id)
      const isSelected = ~index
      value = value ?? !isSelected

      // We can't remove value if group is
      // mandatory, value already exists,
      // and it is the only value
      if (
        isSelected &&
        props.mandatory &&
        internalValue.length <= 1
      ) return

      // We can't add value if it would
      // cause max limit to be exceeded
      if (
        !isSelected &&
        props.max != null &&
        internalValue.length + 1 > props.max
      ) return

      if (index < 0 && value) internalValue.push(id)
      else if (index >= 0 && !value) internalValue.splice(index, 1)

      selected.value = internalValue
    } else {
      const isSelected = selected.value.includes(id)
      if (props.mandatory && isSelected) return

      selected.value = (value ?? !isSelected) ? [id] : []
    }
  }

  function step (offset: number) {
    // getting an offset from selected value obviously won't work with multiple values
    if (props.multiple) consoleWarn('This method is not supported when using "multiple" prop')

    if (!selected.value.length) {
      const item = items.find(item => !item.disabled)
      item && (selected.value = [item.id])
    } else {
      const currentId = selected.value[0]
      const currentIndex = items.findIndex(i => i.id === currentId)

      let newIndex = (currentIndex + offset) % items.length
      let newItem = items[newIndex]

      while (newItem.disabled && newIndex !== currentIndex) {
        newIndex = (newIndex + offset) % items.length
        newItem = items[newIndex]
      }

      if (newItem.disabled) return

      selected.value = [items[newIndex].id]
    }
  }

  const state: GroupProvide = {
    register,
    unregister,
    selected,
    select,
    disabled: toRef(props, 'disabled'),
    prev: () => step(items.length - 1),
    next: () => step(1),
    isSelected: (id: number) => selected.value.includes(id),
    selectedClass: computed(() => props.selectedClass),
    items: computed(() => items),
    getItemIndex: (value: unknown) => getItemIndex(items, value),
  }

  provide(injectKey, state)

  return state
}

function getItemIndex (items: UnwrapRef<GroupItem[]>, value: unknown) {
  const ids = getIds(items, [value])

  if (!ids.length) return -1

  return items.findIndex(item => item.id === ids[0])
}

function getIds (items: UnwrapRef<GroupItem[]>, modelValue: any[]) {
  const ids: number[] = []

  modelValue.forEach(value => {
    const item = items.find(item => deepEqual(value, item.value))
    const itemByIndex = items[value]

    if (item?.value != null) {
      ids.push(item.id)
    } else if (itemByIndex != null) {
      ids.push(itemByIndex.id)
    }
  })

  return ids
}

function getValues (items: UnwrapRef<GroupItem[]>, ids: any[]) {
  const values: unknown[] = []

  ids.forEach(id => {
    const itemIndex = items.findIndex(item => item.id === id)
    if (~itemIndex) {
      const item = items[itemIndex]
      values.push(item.value != null ? item.value : itemIndex)
    }
  })

  return values
}


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

import { Point, Boundary, Bar } from '../VSparkline'

export function genPoints (
  values: number[],
  boundary: Boundary
): Point[] {
  const { minX, maxX, minY, maxY } = boundary
  const totalValues = values.length
  const maxValue = Math.max(...values)
  const minValue = Math.min(...values)

  const gridX = (maxX - minX) / (totalValues - 1)
  const gridY = (maxY - minY) / ((maxValue - minValue) || 1)

  return values.map((value, index) => {
    return {
      x: minX + index * gridX,
      y: maxY - (value - minValue) * gridY,
      value,
    }
  })
}

export function genBars (
  values: number[],
  boundary: Boundary
): Bar[] {
  const { minX, maxX, minY, maxY } = boundary
  const totalValues = values.length
  let maxValue = Math.max(...values)
  let minValue = Math.min(...values)

  if (minValue > 0) minValue = 0
  if (maxValue < 0) maxValue = 0

  const gridX = maxX / totalValues
  const gridY = (maxY - minY) / ((maxValue - minValue) || 1)
  const horizonY = maxY - Math.abs(minValue * gridY)

  return values.map((value, index) => {
    const height = Math.abs(gridY * value)

    return {
      x: minX + index * gridX,
      y: horizonY - height +
        +(value < 0) * height,
      height,
      value,
    }
  })
}


Prisma Framework CLI, see [https://github.com/prisma/prisma2](https://github.com/prisma/prisma2)

# Prisma Framework CLI

## Installation

The Prisma Framework CLI currently requires [Node 8](https://nodejs.org/en/download/releases/) (or higher).

### npm

```
npm install -g prisma2
```

### Yarn

```
yarn global add prisma2
```

## General

### `prisma2 init`

Sets up a new Prisma project via an interactive wizard.

### `prisma2 generate`

Invokes the generators specified in the Prisma project file.

### `prisma2 introspect`

Introspects the database and generates a data model from it.

## Lift

### `prisma2 lift save`

Creates a new migration folder based on current data model changes.

### `prisma2 lift up`

Apply any migrations that have not been applied yet.

### `prisma2 lift down`

Undo migrations.


export interface Dictionary<T> {
  [key: string]: T
}

export const keyBy: <T>(collection: T[], iteratee: (value: T) => string) => Dictionary<T> = (collection, iteratee) => {
  return collection.reduce<any>((acc, curr) => {
    acc[iteratee(curr)] = curr
    return acc
  }, {})
}


import { deepExtend } from './deepExtend'

function setShallowState(newState) {
  this.state = { ...this.state, ...newState }
  this.listeners.forEach(listener => {
    listener(this.state)
  })
}

function setState(newState) {
  this.state = deepExtend({}, this.state, newState)
  this.listeners.forEach(listener => {
    listener(this.state)
  })
}

function useCustom(React) {
  const newListener = React.useState()[1]
  React.useEffect(() => {
    this.listeners.push(newListener)
    return () => {
      this.listeners = this.listeners.filter(listener => listener !== newListener)
    }
  }, [])
  return [this.state, this.actions]
}

function associateActions(store, actions) {
  const associatedActions = {}
  Object.keys(actions).forEach(key => {
    if (typeof actions[key] === 'function') {
      associatedActions[key] = actions[key].bind(null, store)
    }
    if (typeof actions[key] === 'object') {
      associatedActions[key] = associateActions(store, actions[key])
    }
  })
  return associatedActions
}

const useGlobalHook = (React, initialState, actions) => {
  const store: any = { state: initialState, listeners: [] }
  store.setState = setState.bind(store)
  store.setShallowState = setShallowState.bind(store)
  store.actions = associateActions(store, actions)
  return useCustom.bind(store, React)
}

export default useGlobalHook


import { Key } from 'readline'

export type ActionKey =
  | false
  | 'first'
  | 'abort'
  | 'last'
  | 'reset'
  | 'submit'
  | 'delete'
  | 'deleteForward'
  | 'next'
  | 'nextPage'
  | 'prevPage'
  | 'up'
  | 'down'
  | 'right'
  | 'left'
  | 'deleteToStart'

export function action(key: Key): ActionKey {
  if (key.ctrl) {
    if (key.name === 'a') return 'first'
    if (key.name === 'c') return 'abort'
    if (key.name === 'd') return 'abort'
    if (key.name === 'e') return 'last'
    if (key.name === 'g') return 'reset'
    if (key.name === 'u') return 'deleteToStart'
    if (key.name === 'w') return 'deleteToStart'
  }

  if (key.name === 'return') return 'submit'
  if (key.name === 'enter') return 'submit' // ctrl + J
  if (key.name === 'backspace') return 'delete'
  if (key.name === 'delete') return 'deleteForward'
  if (key.name === 'abort') return 'abort'
  if (key.name === 'escape') return 'abort'
  if (key.name === 'tab') return 'next'
  if (key.name === 'pagedown') return 'nextPage'
  if (key.name === 'pageup') return 'prevPage'

  if (key.name === 'up') return 'up'
  if (key.name === 'down') return 'down'
  if (key.name === 'right') return 'right'
  if (key.name === 'left') return 'left'

  return false
}

export const BACK_SYMBOL = process.platform !== 'win32' ? '❮' : '<'


The Prisma Framework CLI currently requires [Node 8](https://nodejs.org/en/download/releases/) (or higher).

keep keep replace keep keep keep keep keep

The Prisma Framework CLI currently requires [Node.js 10](https://nodejs.org/en/download/releases/) (or higher).


# Generated Photon API (JavaScript/TypeScript)

Photon is a type-safe database client auto-generated based on your [data model definition](../data-modeling.md#data-model-definition) (which is a declarative representation of your database schema). This page explains the generated API operations you have available when using Photon.

- [Overview](#overview)
- [CRUD](#crud)
- [Aggregations](#aggregations)
- [Field selection](#field-selection)
- [Relations](#relations)
- [Raw databases access](#raw-database-access)
- [Scalar lists](#scalar-lists)
- [Bring your own ID](#bring-your-own-id)
- [API Reference](#api-reference)
- [Filtering](#filtering)
- [Debugging](#debugging)
- [Reusing query sub-parts](#reusing-query-sub-parts)
- [Managing connections](#managing-connections)

## Overview

Using Photon typically follows this high-level workflow:

1. Add Photon.js to your project using: `npm install @prisma/photon`
1. Define/update your data model definition (e.g. by manually adding a new model or by (re)introspecting your database)
1. Generate your Photon database client based on the changes in the data model definition

Note that steps 2. and 3. might happen repeatedly as you evolve your application.

The `Photon` constructor can then be imported from `node_modules/@prisma/photon`.

Assume you have the following data model definition:

```prisma
model User {
  id    Int    @id
  name  String
  role  Role
  posts Post[]
}

model Post {
  id     Int    @id
  title  String
  author User
}

enum Role {
  USER
  ADMIN
}
```

## CRUD

Your generated Photon API will expose the following CRUD operations for the `User` and `Post` models:

- [`findOne`](#findOne)
- [`findMany`](#findMany)
- [`create`](#create)
- [`update`](#update)
- [`updateMany`](#updateMany)
- [`upsert`](#upsert)
- [`delete`](#delete)
- [`deleteMany`](#deleteMany)

You can access each function via the respective model property on your generated `Photon` instance, e.g. `users` for the `User` model:

```ts
import { Photon } from '@prisma/photon'

const photon = new Photon()

async function main() {
  await photon.connect()
  const result = await photon.users.findOne({
    where: { id: 1 },
  })
  // result = { id: 1, name: "Alice", role: "USER" }
  await photon.disconnect()
}
```

Note that the name of the `users` property is auto-generated using the [`pluralize`](https://github.com/blakeembrey/pluralize) package.

## Aggregations

In addition to CRUD operations, Photon.js also allows for [_aggregation_ queries](https://github.com/prisma/photonjs/issues/5).

### `count`

To return the number of elements in a list, you can the `count()` method on any model property on your `Photon` instance, for example:

```ts
const userCount = await photon.users.count()
// userCount = 42
```

## Field selection

This section explains how to predict and control which fields of a model are returned in a Photon API call.

### Selection sets

To understand which fields are being returned by a certain API call, you need to be aware of its **selection set**.

The selection set defines the **set of fields on a model instance that is returned in a Photon API call**.

For example, in the `findOne` API call from above, the selection set includes the `id`, `name` and `role` fields of the model `User`. In that example, the selection set has not been manipulated and the API call therefore returns the _default selection set_ (read below).

### The default selection set

If the selection set is not manipulated (via `select` or `include`), a Photon API call returns the **default selection set** for a model. It includes all [_scalar_](./data-modeling.md#scalar-types) fields (including [enums](./data-modeling.md#enums)) fields of the model.

Considering the sample datamodel from above:

- The default selection set for the `User` model includes `id`, `name`, `role`. It does **not** include `posts` because that's a _relation_ and not a scalar field.
- The default selection set for the `Post` model includes `id`, `title`. It does **not** include `author` because that's a _relation_ and not a scalar field.

### Manipulating the selection set

There are two ways how the _default selection set_ can be manipulated to specify which fields should be returned by a Photon API call:

- **Select exclusively** (via `select`): When using `select`, the selection set only contains the fields that are explicitly provided as arguments to `select`.
- **Include additionally** (via `include`): When using `include`, the default selection set gets extended with additional fields that are provided as arguments to `include`.

#### Select exclusively via `select`

In this example, we're using `select` to exclusively select the `name` field of the returned `User` object:

```ts
const result = await photon.users.findOne({
  where: { id: 1 },
  select: { name: true },
})
// result = { name: "Alice" }
```

#### Include additionally via `include`

Sometimes you want to directly include a relation when retrieving data from a database. To eagerly load and include the relations of a model in an API call right away, you can use `include`:

```ts
const result = await photon.users.findOne({
  where: { id: 1 },
  include: { posts: true },
})
// result = {
//   id: 1,
//   name: "Alice",
//   role: "USER",
//   posts: [
//     { id: 1, title: "Hello World"},
//   ]
// }
```

### Lazy loading

Coming soon.

## Relations

Learn more about relations in the generated Photon API [here](../relations.md#relations-in-the-generated-Photon-API).

## Bring your own ID

With Photon.js, you can set your own values for fields that are annotated with the `@id` attribute. This attribute express that the respective field is considered a _primary key_. Consider the following model:

```prisma
model User {
  id    Int    @id
  name  String
}
```

You can provide the `id` as input value in [`create`](#create) and [`update`](#update) operations, for example:

```ts
const user = await photon.users.create({
  data: {
    id: 1
  }
})
```

Note that Photon.js will throw an error if you're trying to create/update a record with an `id` value that already belongs to another record since the `@id` attribute always implies _uniqueness_.

## Raw database access

Coming soon.

## Scalar lists

Photon.js provides a dedicated API for (re)setting _scalar lists_ using a `set` field inside the `data` argument when creating or updating a [Prisma model](../data-modeling.md#models), for example:

```prisma
model User {
  id        Int       @id
  coinFlips Boolean[]
}
```

When creating or updating a `User` record, you can create a new list or replace the current one with a new list like so:

```ts
await photon.users.create({
  data: {
    coinFlips: {
      set: [true, false]
    }
  }
})

await photon.users.update({
  where: { id: 42 ,}
  data: {
    coinFlips: {
      set: [true, false]
    }
  }
})
```

## API reference

For simplicity, we're assuming the `User` model from above as foundation for the generated code.

### Constructor

Creates a new `Photon` instance.

#### Options

| Name          | Type          | Required | Description                                                                                |
| ------------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `debug`       | `boolean`     | No       | When set to `true`, the `Photon` instance prints additional logging output to the console when sending requests to Prisma's query engine. **Default**: `false`.                                                                     |
| `log` | `boolean | LogOption[]` | No | This allows to specify one of the following log levels: `INFO`, `WARN`, `QUERY`. If set to `true`, all log levels are applied. If set to `false`, no log levels are applied. **Default**: `true`.

#### Examples

```ts
const photon = new Photon({ debug: true })
```

### `findOne`

Returns a single object identified by a _unique_ value (e.g. `id` or `email`). You can use the `select` and `include` arguments to determine which fields should be included on the returned object.

#### Options

| Name     | Type                   | Required | Description                                                                      |
| -------- | ---------------------- | -------- | -------------------------------------------------------------------------------- |
| `where`  | `UserWhereUniqueInput` | **Yes**  | Wraps all _unique_ fields of a model so that individual records can be selected. |
| `select` | `UserSelect`           | No       | Specifies which fields to include in the [selection set](#selection-sets).       |

#### Examples

```ts
const user = await photon.users.findOne({
  where: { id: 1 },
})
```

### `findMany`

Returns a list of objects. The list can be altered using _pagination_, _filtering_ and _ordering_ arguments. You can use the `select` and `include` arguments to determine which fields should be included on each object in the returned list.

For more filtering examples, look [here](#filtering).

#### Options

| Name      | Type               | Required | Description                                                                                |
| --------- | ------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| `where`   | `UserWhereInput`   | No       | Wraps _all_ fields of a model so that the list can be filtered by any model property.                       |
| `orderBy` | `UserOrderByInput` | No       | Lets you order the returned list by any model property.                                                     |
| `skip`    | `string`           | No       | Specifies how many of the returned objects in the list should be skipped.                                   |
| `after`   | `string`           | No       | Specifies the starting object for the list (the value typically specifies an `id` or another unique value). |
| `before`  | `string`           | No       | Specifies the last object for the list (the value typically specifies an `id` or another unique value).     |
| `first`   | `number`           | No       | Specifies how many elements should be returned in the list (as seen from the _beginning_ of the list).      |
| `last`    | `number`           | No       | Specifies how many elements should be returned in the list (as seen from the _end_ of the list).            |
| `select`  | `UserSelect`       | No       | Specifies which fields to include in the [selection set](#selection-sets).                                  |

#### Examples

```ts
const user = await photon.users.findMany({
  where: { name: 'Alice' },
})
```

### `create`

Creates a new record and returns the corresponding object. You can use the `select` and `include` arguments to determine which fields should be included on the returned object. `create` also lets you perform transactional _nested inserts_ (e.g. create a new `User` and `Post` in the same API call).

#### Options

| Name     | Type              | Required | Description                                                                                |
| -------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data`   | `UserCreateInput` | **Yes**  | Wraps all the fields of the model so that they can be provided when creating new records. It also includes relation fields which lets you perform (transactional) nested inserts. Fields that are marked as optional or have default values in the datamodel are optional on `data`. |
| `select` | `UserSelect`      | No       | Specifies which fields to include in the [selection set](#selection-sets).                                                                                |

#### Examples

```ts
const user = await photon.users.create({
  data: { name: 'Alice' },
})
```

### `update`

Updates an existing record and returns the corresponding object. You can use the `select` and `include` arguments to determine which fields should be included on the returned object.

#### Options

| Name     | Type                   | Required | Description                                                                                |
| -------- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data`   | `UserUpdateInput`      | **Yes**  | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional on `data`. |
| `where`  | `UserWhereUniqueInput` | **Yes**  | Wraps all _unique_ fields of a model so that individual records can be selected.                                                                                |
| `select` | `UserSelect`           | No       | Specifies which fields to include in the [selection set](#selection-sets).                                                                                |

#### Examples

```ts
const user = await photon.users.update({
  where: { id: 1 },
  data: { name: 'ALICE' },
})
```

### `updateMany`

Updates a batch of existing records in bulk and returns the number of updated records.

#### Options

| Name    | Type                          | Required | Description                                                                                |
| ------- | ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data`  | `UserUpdateManyMutationInput` | **Yes**  | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional on `data`. |
| `where` | `UserWhereInput`              | No       | Wraps _all_ fields of a model so that the list can be filtered by any model property.                                                                                |

#### Examples

```ts
const updatedUserCount = await photon.users.updateMany({
  where: { name: 'Alice' },
  data: { name: 'ALICE' },
})
```

### `upsert`

Updates an existing or creates a new record and returns the corresponding object. You can use the `select` and `include` arguments to determine which fields should be included on the returned object.

#### Options

| Name     | Type                   | Required | Description                                                                                |
| -------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `create` | `UserCreateInput`      | **Yes**  | Wraps all the fields of the model so that they can be provided when creating new records. It also includes relation fields which lets you perform (transactional) nested inserts. Fields that are marked as optional or have default values in the datamodel are optional on `data`. |
| `update` | `UserUpdateInput`      | **Yes**  | Wraps all the fields of the model so that they can be provided when updating an existing record. Fields that are marked as optional or have default values in the datamodel are optional on `data`.                                                                                |
| `where`  | `UserWhereUniqueInput` | **Yes**  | Wraps all _unique_ fields of a model so that individual records can be selected.                                                                                |
| `select` | `UserSelect`           | No       | Specifies which fields to include in the [selection set](#selection-sets).                                                                                |

#### Examples

```ts
const user = await photon.users.upsert({
  where: { id: 1 },
  update: { name: "ALICE" },
  create: { name: "ALICE" }
})
```

### `delete`

Deletes an existing record and returns the corresponding object. You can use the `select` and `include` arguments to determine which fields should be included on the returned object.

#### Options

| Name     | Type                   | Required | Description                                                                      |
| -------- | ---------------------- | -------- | -------------------------------------------------------------------------------- |
| `where`  | `UserWhereUniqueInput` | **Yes**  | Wraps all _unique_ fields of a model so that individual records can be selected. |
| `select` | `UserSelect`           | No       | Specifies which fields to include in the [selection set](#selection-sets).       |

#### Examples

```ts
const user = await photon.users.delete({
  where: { id: 1 },
})
```

### `deleteMany`

Deletes a batch of existing records in bulk and returns the number of deleted records.

#### Options

| Name    | Type             | Required | Description                                                                           |
| ------- | ---------------- | -------- | ------------------------------------------------------------------------------------- |
| `where` | `UserWhereInput` | No       | Wraps _all_ fields of a model so that the list can be filtered by any model property. |

#### Examples

```ts
const deletedUserCount = await photon.users.deleteMany({
  where: { name: 'Alice' },
})
```

### `count`

Returns the number of elements in a list as a value of type `number`.

#### Options

The `count()` method doesn't take any input arguments.

#### Examples

```ts
const userCount = await photon.users.count()
// userCount = 42
```

## Filtering

The Photon.js API offers filtering options for constraining the items that are returned from API calls that return lists via the `where` argument.

The following examples are based on this data model:

```prisma
model User {
  id     Int    @id
  name   String
  email  String
  role   String
  active Boolean
}

enum Role {
  USER
  ADMIN
}
```

Filtering can be applied to this data model. It is not the same as manipulating the selection set. Based on the `User` model, Photon generates the `UserWhereInput` type, which holds the filtering properties.

```ts
export declare type UserWhereInput = {
  id?: number | IntFilter | null
  name?: string | StringFilter | null
  email?: string | StringFilter | null
  role?: Role | RoleFilter | null
  active?: boolean | BooleanFilter | null
  AND?: Enumerable<UserWhereInput>
  OR?: Enumerable<UserWhereInput>
  NOT?: Enumerable<UserWhereInput>
}
```

For example, to get the record for the user with the `id` 1, `where` is used in combination with the `id` `IntFilter`:

```ts
const result = await photon.users.findMany({
  where: { id: 1 },
})
// result = [{
//   id: 1,
//   name: "Alice",
//   email: "alice@prisma.io",
//   role: "USER",
//   active: true
// }]
```

> Note: As a recap, the `findMany` API returns a list of objects which can be filtered by any model property.

To get the record for the user with the `name` Alice with a USER `role`, `where` is used in combination with the `name` `StringFilter` and the `role` `RoleFilter`:

```ts
const result = await photon.users.findMany({
  where: {
    name: 'Alice',
    role: 'USER',
  },
})
// result = [{
//   id: 1,
//   name: "Alice",
//   email: "alice@prisma.io",
//   role: "USER",
//   active: true
// }]
```

To apply one of the operator filters (`AND`, `OR`, `NOT`), filter for the record where the user with the `name` Alice has a non-active status. Here, `where` is used in combination with the `name` `StringFilter`, the `active` `BooleanFilter`, and the `NOT` operator:

```ts
const result = await photon.users.findMany({
  where: {
    name: 'Alice',
    NOT: {
      active: true,
    },
  },
})
// result = {
//
// }
```

## Debugging

You can view the generated database queries that Photon.js sends to your database by setting the `debug` option to `true` when instantiating `Photon`:

```ts
const photon = new Photon({ debug: true })
```

You can also configure log levels via the `log` option:

```ts
const photon = new Photon({
  debug: true,
  log: true
})
```

This logs all log levels:

- `INFO`: Logs general information
- `WARN`: Logs warnings
- `QUERY`: Logs the queries that generated by a Photon.js API call

To specify more fine-grained log-levels, you can pass an array of log options to `log`:

```ts
const photon = new Photon({
  debug: true,
  log: [{
    level: 'QUERY'
  }]
})
```

## Reusing query sub-parts

You can reuse subparts of a Photon.js query by not immediately evaluating the promise that's returned by any Photon.js API call. Depending on your evaluating the promise, you can do this either by leaving out the prepended `await` keyword or the appended call to `.then()`.

```ts
// Note the missing `await` here.
const currentUserQuery = photon.users.findOne({ where: { id: currentUserId } })

// Now you have the sub-part of a query that you can reuse.
const postsOfUser = await currentUserQuery.posts()
const profileOfUser = await currentUserQuery.profile()
```

## Managing connections

Photon connects and disconnects from your data sources using the following two methods:

- `connect(): Promise<void>`
- `disconnect(): Promise<void>`

Unless you want to employ a specific optimization, calling `photon.connect()` is not necessary thanks to the _lazy connect_ behavior: The `Photon` instance connects lazily when the first request is made to the API (`connect()` is called for you under the hood).

If you need the first request to respond instantly and can't wait for the lazy connection to be established, you can explicitly call `photon.connect()` to establish a connection to the data source.

**IMPORTANT**: It is recommended to always explicitly call `photon.disconnect()` in your code. Generally the `Photon` instance disconnects automatically. However, if your program terminates but still has an unhandled promise rejection, the port will keep the connection to the data source open beyond the lifetime of your program!


export class RustPanic extends Error {
  public request: any
  public rustStack: string
  public schemaPath?: string
  public area: ErrorArea
  public sqlDump?: string
  public schema?: string
  constructor(
    message: string,
    rustStack: string,
    request: any,
    area: ErrorArea,
    schemaPath?: string,
    schema?: string,
    sqlDump?: string,
  ) {
    super(message)
    this.rustStack = rustStack
    this.request = request
    this.schemaPath = schemaPath
    this.area = area
    this.schema = schema
    this.sqlDump = sqlDump
  }
}

export enum ErrorArea {
  LIFT_CLI = 'LIFT_CLI',
  PHOTON_STUDIO = 'PHOTON_STUDIO',
  INTROSPECTION_CLI = 'INTROSPECTION_CLI',
}


#!/bin/bash

set -ex

git clone git@github.com:timsuchanek/last-git-changes.git
cd last-git-changes
npm install
npm run build
cd ..
node last-git-changes/bin.js --exclude='docs,examples,scripts,README.md,LICENSE,CONTRIBUTING.md'
export CHANGED_COUNT=$(node last-git-changes/bin.js --exclude='docs,examples,scripts,README.md,LICENSE,CONTRIBUTING.md' | wc -l)

echo $BUILDKITE_TAG
echo $CHANGED_COUNT

if [ $CHANGED_COUNT -gt 0 ]; then
  buildkite-agent pipeline upload .buildkite/trigger.yml
else
  echo "Nothing changed"
fi


Invokes the generators specified in the Prisma project file.

Introspects the database and generates a data model from it.

Watches the Prisma project file and runs `generate` when the file changes.


const { Photon } = require('../tmp')

async function main() {
  const photon = new Photon()
  const result = await photon.teams.findMany()
  console.log(result)
  photon.disconnect()
}

main()


import chalk from 'chalk'

/**
 * Unknown command
 */
export function unknownCommand(helpTemplate: string, cmd: string): HelpError {
  return new HelpError(
    `\n${chalk.bold.red(`!`)} Unknown command "${cmd}"\n${helpTemplate}`,
  )
}

/**
 * Custom help error used to display help
 * errors without printing a stack trace
 */
export class HelpError extends Error {
  constructor(msg: string) {
    super(msg)
    // setPrototypeOf is needed for custom errors to work
    Object.setPrototypeOf(this, HelpError.prototype)
  }
}


import { useStdin as inkUseStdin } from 'ink'
import React from 'react'
import { Key, emitKeypressEvents } from 'readline'
import { action } from '../helpers'

export function useStdin(
  keyHandler: ({ actionKey: ActionKey, text: string, key: Key }) => void,
  deps: any[] = [],
) {
  let { stdin, setRawMode } = inkUseStdin()

  // stdin = stdin || process.stdin
  // setRawMode = setRawMode || process.stdin.setRawMode

  emitKeypressEvents(stdin)

  let didCancel = false

  React.useEffect(() => {
    function handler(text: string, key: Key) {
      if (!didCancel) {
        keyHandler({ actionKey: action(key), text, key })
      }
    }

    setRawMode!(true)
    stdin.on('keypress', handler)

    return () => {
      // didCancel = true
      stdin.removeListener('keypress', handler)
      setRawMode!(false)
    }
  }, [stdin, setRawMode, ...deps])
}


# Getting started

The quickest way to get started with Photon and/or Lift is by installing the Prisma CLI and running the interactive `init` command:

```
npm install -g prisma2
prisma2 init hello-prisma
```

> **Note**: Lift and Photon are currently in Preview! [Limitations](https://github.com/prisma/prisma2/blob/master/docs/limitations.md) include missing features and limited performance issues.

## The `prisma2 init` flow

When running `prisma2 init`, the Prisma Framework CLI launches an **interactive wizard** that helps you get started with Photon and/or Lift.

The following sections explain a few screens you might encounter as you run through the `prisma2 init` flow. Because the flow and the order of the screens vary depending on your selections, the screens below might not follow the order in which you encounter them.

### Blank project vs Starter kits

At first, the wizard prompts you to select either of two options:

- **Blank project**: Blank projects can be used when you want to start a new project from scratch. This option also supports introspecting an existing database.
- **Starter kit**: Starter kits provide runnable examples for various use cases, they're based on the example projects in the [`prisma-examples`](https://github.com/prisma/prisma-examples/tree/prisma2) repository. The wizard will help you connect a starter kit to your own database. Note that starter kits can only be used in _empty directories_ and with _empty databases_. If you don't have a database running, you can select **SQLite**.

### Database selection

Later, the wizard asks you which kind of database you want to use with Photon/Lift. Currently, the following databases are supported:

- **SQLite**
- **MySQL**
- **PostgreSQL**
- MongoDB (coming soon)

Note that both, **MySQL** and **PostgreSQL** options, required you to run a database that you can connect to in the next step. If you don't have a database running, choose **SQLite** and let the wizard create a new SQLite database file for you.

### Database credentials

After having selected **MySQL** or **PostgreSQL** in the database selection, you need to provide the database connection details and user credentials for your database server. Check out the [MySQL](./core/connectors/mysql.md) and [PostgreSQL](./core/connectors/postgresql.md) connector docs to learn about the connection string format and the required info.

### Selecting Prisma tools (Photon/Lift)

If you start with an existing database and the Prisma Framework CLI performed introspection against it to generate the [Prisma schema](./prisma-schema-file.md), you will be asked to select which Prisma tools you want to use:

- Use Photon and Lift
- [Use only Photon](./photon/use-only-photon.md) (for database access (ORM))
- [Use only Lift](./lift/use-only-lift.md) (for database migrations)

> **Note**: If you select **only Photon** or **only Lift**, it will still be possible to add the other tool to your project later.

### Language selection

If you're starting with a new database or are using a starter kit, you're prompted for the language in which you want to access your database. Currently Photon is available in the following languages:

- **JavaScript**
- **TypeScript**
- Go (coming soon)

## Installing the Prisma CLI

### NPM

```
npm install -g prisma2
```

### Yarn

```
yarn global add prisma2
```


cd subdir && ../../../build/index.js generate


Creates a new migration folder based on current data model changes.

### `prisma2 migrate save --experimental`


#!/usr/bin/env node

// let's provoke this to fail
require('ms-node/register') // ms-node doesn't exist

const { generatorHandler } = require('../generatorHandler')

generatorHandler({
  async onGenerate() {
    await new Promise(r => {
      setTimeout(r, 100)
    })
  },
})


const { download } = require('@prisma/fetch-engine')
const pkg = require('../package.json')
const fs = require('fs')
const path = require('path')
const pkgUp = require('pkg-up')
const Debug = require('debug')
const debug = Debug('prisma2:download')

const binaryPath = eval(`require('path').join(__dirname, '../')`)

const version = (pkg && pkg.prisma && pkg.prisma.version) || 'latest'

download({
  binaries: {
    'query-engine': binaryPath,
    'migration-engine': binaryPath,
    'introspection-engine': binaryPath,
  },
  showProgress: true,
  version,
  failSilent: true,
})

// if we are in a Now context, ensure that `prisma2 generate` is in the postinstall hook
if (process.env.INIT_CWD && process.env.NOW_BUILDER) {
  ensurePostInstall().catch(e => {
    debug(e)
  })
}

async function ensurePostInstall() {
  const appPkg = path.resolve(process.env.INIT_CWD, 'package.json')
  if (fs.existsSync(appPkg)) {
    ensurePostInstallPackage(appPkg)
  } else {
    const pkgPath = await pkgUp({
      cwd: path.join(process.cwd(), '..'),
    })
    if (pkgPath) {
      ensurePostInstallPackage(pkgPath)
    }
  }
}

function ensurePostInstallPackage(pkgPath) {
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))

  pkg.scripts = pkg.scripts || {}

  if (!pkg.scripts.postinstall) {
    pkg.scripts.postinstall = 'prisma2 generate'
  } else {
    pkg.scripts.postinstall = `prisma2 generate && ${pkg.scripts.postinstall}`
  }

  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
}


import { Command, format } from '@prisma/cli'
import chalk from 'chalk'

export class Dev implements Command {
  static new(): Dev {
    return new Dev()
  }
  private constructor() {}
  async parse(argv: string[]) {
    return format(`
      ${chalk.redBright('Prisma CLI does not include a \`dev\` command any more right now.')}

      If you want to run Prisma Studio, use ${chalk.green('prisma2 studio')}.
      If you want to generate the Prisma Client, use ${chalk.green('prisma2 generate')} (or ${chalk.green('prisma2 generate --watch')})
      If you want to update your schema, use ${chalk.green('prisma2 introspect')}.
      If you want to migrate your database, use ${chalk.green('prisma2 migrate')}.
    `)
  }
}


Apply any migrations that have not been applied yet.