<commit_before>// Styles
import '../VTextField/VTextField.sass'
import './VSelect.sass'

// Components
import VChip from '../VChip'
import VMenu from '../VMenu'
import VSelectList from './VSelectList'

// Extensions
import VInput from '../VInput'
import VTextField from '../VTextField/VTextField'

// Mixins
import Comparable from '../../mixins/comparable'
import Filterable from '../../mixins/filterable'

// Directives
import ClickOutside from '../../directives/click-outside'

// Utilities
import { getPropertyFromItem, keyCodes } from '../../util/helpers'
import { consoleError } from '../../util/console'
import mergeData from '../../util/mergeData'

// Types
import mixins from '../../util/mixins'
import { VNode, VNodeDirective, PropType } from 'vue'
import { SelectItemKey } from 'types'

export const defaultMenuProps = {
  closeOnClick: false,
  closeOnContentClick: false,
  disableKeys: true,
  openOnClick: false,
  maxHeight: 304,
}

// Types
const baseMixins = mixins(
  VTextField,
  Comparable,
  Filterable
)

interface options extends InstanceType<typeof baseMixins> {
  $refs: {
    menu: InstanceType<typeof VMenu>
    label: HTMLElement
    input: HTMLInputElement
    'prepend-inner': HTMLElement
    'append-inner': HTMLElement
    prefix: HTMLElement
    suffix: HTMLElement
  }
}

/* @vue/component */
export default baseMixins.extend<options>().extend({
  name: 'v-select',

  directives: {
    ClickOutside,
  },

  props: {
    appendIcon: {
      type: String,
      default: '$dropdown',
    },
    attach: {
      type: null as unknown as PropType<string | boolean | Element | VNode>,
      default: false,
    },
    cacheItems: Boolean,
    chips: Boolean,
    clearable: Boolean,
    deletableChips: Boolean,
    disableLookup: Boolean,
    eager: Boolean,
    hideSelected: Boolean,
    items: {
      type: Array,
      default: () => [],
    },
    itemColor: {
      type: String,
      default: 'primary',
    },
    itemDisabled: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'disabled',
    },
    itemText: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'text',
    },
    itemValue: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'value',
    },
    menuProps: {
      type: [String, Array, Object],
      default: () => defaultMenuProps,
    },
    multiple: Boolean,
    openOnClear: Boolean,
    returnObject: Boolean,
    smallChips: Boolean,
  },

  data () {
    return {
      cachedItems: this.cacheItems ? this.items : [],
      isBooted: false,
      isMenuActive: false,
      lastItem: 20,
      // As long as a value is defined, show it
      // Otherwise, check if multiple
      // to determine which default to provide
      lazyValue: this.value !== undefined
        ? this.value
        : this.multiple ? [] : undefined,
      selectedIndex: -1,
      selectedItems: [] as any[],
      keyboardLookupPrefix: '',
      keyboardLookupLastTime: 0,
    }
  },

  computed: {
    /* All items that the select has */
    allItems (): object[] {
      return this.filterDuplicates(this.cachedItems.concat(this.items))
    },
    classes (): object {
      return {
        ...VTextField.options.computed.classes.call(this),
        'v-select': true,
        'v-select--chips': this.hasChips,
        'v-select--chips--small': this.smallChips,
        'v-select--is-menu-active': this.isMenuActive,
        'v-select--is-multi': this.multiple,
      }
    },
    /* Used by other components to overwrite */
    computedItems (): object[] {
      return this.allItems
    },
    computedOwns (): string {
      return `list-${this._uid}`
    },
    computedCounterValue (): number {
      return this.multiple
        ? this.selectedItems.length
        : (this.getText(this.selectedItems[0]) || '').toString().length
    },
    directives (): VNodeDirective[] | undefined {
      return this.isFocused ? [{
        name: 'click-outside',
        value: this.blur,
        args: {
          closeConditional: this.closeConditional,
        },
      } as VNodeDirective] : undefined
    },
    dynamicHeight () {
      return 'auto'
    },
    hasChips (): boolean {
      return this.chips || this.smallChips
    },
    hasSlot (): boolean {
      return Boolean(this.hasChips || this.$scopedSlots.selection)
    },
    isDirty (): boolean {
      return this.selectedItems.length > 0
    },
    listData (): object {
      const scopeId = this.$vnode && (this.$vnode.context!.$options as { [key: string]: any })._scopeId
      const attrs = scopeId ? {
        [scopeId]: true,
      } : {}

      return {
        attrs: {
          ...attrs,
          id: this.computedOwns,
        },
        props: {
          action: this.multiple,
          color: this.itemColor,
          dense: this.dense,
          hideSelected: this.hideSelected,
          items: this.virtualizedItems,
          itemDisabled: this.itemDisabled,
          itemText: this.itemText,
          itemValue: this.itemValue,
          noDataText: this.$vuetify.lang.t(this.noDataText),
          selectedItems: this.selectedItems,
        },
        on: {
          select: this.selectItem,
        },
        scopedSlots: {
          item: this.$scopedSlots.item,
        },
      }
    },
    staticList (): VNode {
      if (this.$slots['no-data'] || this.$slots['prepend-item'] || this.$slots['append-item']) {
        consoleError('assert: staticList should not be called if slots are used')
      }

      return this.$createElement(VSelectList, this.listData)
    },
    virtualizedItems (): object[] {
      return (this.$_menuProps as any).auto
        ? this.computedItems
        : this.computedItems.slice(0, this.lastItem)
    },
    menuCanShow: () => true,
    $_menuProps (): object {
      let normalisedProps = typeof this.menuProps === 'string'
        ? this.menuProps.split(',')
        : this.menuProps

      if (Array.isArray(normalisedProps)) {
        normalisedProps = normalisedProps.reduce((acc, p) => {
          acc[p.trim()] = true
          return acc
        }, {})
      }

      return {
        ...defaultMenuProps,
        eager: this.eager,
        value: this.menuCanShow && this.isMenuActive,
        nudgeBottom: normalisedProps.offsetY ? 1 : 0, // convert to int
        ...normalisedProps,
      }
    },
  },

  watch: {
    internalValue (val) {
      this.initialValue = val
      this.setSelectedItems()
    },
    isBooted () {
      this.$nextTick(() => {
        if (this.getContent() && this.getContent().addEventListener) {
          this.getContent().addEventListener('scroll', this.onScroll, false)
        }
      })
    },
    isMenuActive (val) {
      this.$nextTick(() => this.onMenuActiveChange(val))

      if (!val) return

      this.isBooted = true
    },
    items: {
      immediate: true,
      handler (val) {
        if (this.cacheItems) {
          // Breaks vue-test-utils if
          // this isn't calculated
          // on the next tick
          this.$nextTick(() => {
            this.cachedItems = this.filterDuplicates(this.cachedItems.concat(val))
          })
        }

        this.setSelectedItems()
      },
    },
  },

  methods: {
    /** @public */
    blur (e?: Event) {
      VTextField.options.methods.blur.call(this, e)
      this.isMenuActive = false
      this.isFocused = false
      this.selectedIndex = -1
    },
    /** @public */
    activateMenu () {
      if (
        this.disabled ||
        this.readonly ||
        this.isMenuActive
      ) return

      this.isMenuActive = true
    },
    clearableCallback () {
      this.setValue(this.multiple ? [] : undefined)
      this.setMenuIndex(-1)
      this.$nextTick(() => this.$refs.input && this.$refs.input.focus())

      if (this.openOnClear) this.isMenuActive = true
    },
    closeConditional (e: Event) {
      return (
        !this._isDestroyed &&

        // Click originates from outside the menu content
        this.getContent() &&<commit_msg>fix(VSelect): allow blur from outside click<commit_after>// Styles
import '../VTextField/VTextField.sass'
import './VSelect.sass'

// Components
import VChip from '../VChip'
import VMenu from '../VMenu'
import VSelectList from './VSelectList'

// Extensions
import VInput from '../VInput'
import VTextField from '../VTextField/VTextField'

// Mixins
import Comparable from '../../mixins/comparable'
import Filterable from '../../mixins/filterable'

// Directives
import ClickOutside from '../../directives/click-outside'

// Utilities
import { getPropertyFromItem, keyCodes } from '../../util/helpers'
import { consoleError } from '../../util/console'
import mergeData from '../../util/mergeData'

// Types
import mixins from '../../util/mixins'
import { VNode, VNodeDirective, PropType } from 'vue'
import { SelectItemKey } from 'types'

export const defaultMenuProps = {
  closeOnClick: false,
  closeOnContentClick: false,
  disableKeys: true,
  openOnClick: false,
  maxHeight: 304,
}

// Types
const baseMixins = mixins(
  VTextField,
  Comparable,
  Filterable
)

interface options extends InstanceType<typeof baseMixins> {
  $refs: {
    menu: InstanceType<typeof VMenu>
    label: HTMLElement
    input: HTMLInputElement
    'prepend-inner': HTMLElement
    'append-inner': HTMLElement
    prefix: HTMLElement
    suffix: HTMLElement
  }
}

/* @vue/component */
export default baseMixins.extend<options>().extend({
  name: 'v-select',

  directives: {
    ClickOutside,
  },

  props: {
    appendIcon: {
      type: String,
      default: '$dropdown',
    },
    attach: {
      type: null as unknown as PropType<string | boolean | Element | VNode>,
      default: false,
    },
    cacheItems: Boolean,
    chips: Boolean,
    clearable: Boolean,
    deletableChips: Boolean,
    disableLookup: Boolean,
    eager: Boolean,
    hideSelected: Boolean,
    items: {
      type: Array,
      default: () => [],
    },
    itemColor: {
      type: String,
      default: 'primary',
    },
    itemDisabled: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'disabled',
    },
    itemText: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'text',
    },
    itemValue: {
      type: [String, Array, Function] as PropType<SelectItemKey>,
      default: 'value',
    },
    menuProps: {
      type: [String, Array, Object],
      default: () => defaultMenuProps,
    },
    multiple: Boolean,
    openOnClear: Boolean,
    returnObject: Boolean,
    smallChips: Boolean,
  },

  data () {
    return {
      cachedItems: this.cacheItems ? this.items : [],
      isBooted: false,
      isMenuActive: false,
      lastItem: 20,
      // As long as a value is defined, show it
      // Otherwise, check if multiple
      // to determine which default to provide
      lazyValue: this.value !== undefined
        ? this.value
        : this.multiple ? [] : undefined,
      selectedIndex: -1,
      selectedItems: [] as any[],
      keyboardLookupPrefix: '',
      keyboardLookupLastTime: 0,
    }
  },

  computed: {
    /* All items that the select has */
    allItems (): object[] {
      return this.filterDuplicates(this.cachedItems.concat(this.items))
    },
    classes (): object {
      return {
        ...VTextField.options.computed.classes.call(this),
        'v-select': true,
        'v-select--chips': this.hasChips,
        'v-select--chips--small': this.smallChips,
        'v-select--is-menu-active': this.isMenuActive,
        'v-select--is-multi': this.multiple,
      }
    },
    /* Used by other components to overwrite */
    computedItems (): object[] {
      return this.allItems
    },
    computedOwns (): string {
      return `list-${this._uid}`
    },
    computedCounterValue (): number {
      return this.multiple
        ? this.selectedItems.length
        : (this.getText(this.selectedItems[0]) || '').toString().length
    },
    directives (): VNodeDirective[] | undefined {
      return this.isFocused ? [{
        name: 'click-outside',
        value: this.blur,
        args: {
          closeConditional: this.closeConditional,
        },
      } as VNodeDirective] : undefined
    },
    dynamicHeight () {
      return 'auto'
    },
    hasChips (): boolean {
      return this.chips || this.smallChips
    },
    hasSlot (): boolean {
      return Boolean(this.hasChips || this.$scopedSlots.selection)
    },
    isDirty (): boolean {
      return this.selectedItems.length > 0
    },
    listData (): object {
      const scopeId = this.$vnode && (this.$vnode.context!.$options as { [key: string]: any })._scopeId
      const attrs = scopeId ? {
        [scopeId]: true,
      } : {}

      return {
        attrs: {
          ...attrs,
          id: this.computedOwns,
        },
        props: {
          action: this.multiple,
          color: this.itemColor,
          dense: this.dense,
          hideSelected: this.hideSelected,
          items: this.virtualizedItems,
          itemDisabled: this.itemDisabled,
          itemText: this.itemText,
          itemValue: this.itemValue,
          noDataText: this.$vuetify.lang.t(this.noDataText),
          selectedItems: this.selectedItems,
        },
        on: {
          select: this.selectItem,
        },
        scopedSlots: {
          item: this.$scopedSlots.item,
        },
      }
    },
    staticList (): VNode {
      if (this.$slots['no-data'] || this.$slots['prepend-item'] || this.$slots['append-item']) {
        consoleError('assert: staticList should not be called if slots are used')
      }

      return this.$createElement(VSelectList, this.listData)
    },
    virtualizedItems (): object[] {
      return (this.$_menuProps as any).auto
        ? this.computedItems
        : this.computedItems.slice(0, this.lastItem)
    },
    menuCanShow: () => true,
    $_menuProps (): object {
      let normalisedProps = typeof this.menuProps === 'string'
        ? this.menuProps.split(',')
        : this.menuProps

      if (Array.isArray(normalisedProps)) {
        normalisedProps = normalisedProps.reduce((acc, p) => {
          acc[p.trim()] = true
          return acc
        }, {})
      }

      return {
        ...defaultMenuProps,
        eager: this.eager,
        value: this.menuCanShow && this.isMenuActive,
        nudgeBottom: normalisedProps.offsetY ? 1 : 0, // convert to int
        ...normalisedProps,
      }
    },
  },

  watch: {
    internalValue (val) {
      this.initialValue = val
      this.setSelectedItems()
    },
    isBooted () {
      this.$nextTick(() => {
        if (this.getContent() && this.getContent().addEventListener) {
          this.getContent().addEventListener('scroll', this.onScroll, false)
        }
      })
    },
    isMenuActive (val) {
      this.$nextTick(() => this.onMenuActiveChange(val))

      if (!val) return

      this.isBooted = true
    },
    items: {
      immediate: true,
      handler (val) {
        if (this.cacheItems) {
          // Breaks vue-test-utils if
          // this isn't calculated
          // on the next tick
          this.$nextTick(() => {
            this.cachedItems = this.filterDuplicates(this.cachedItems.concat(val))
          })
        }

        this.setSelectedItems()
      },
    },
  },

  methods: {
    /** @public */
    blur (e?: Event) {
      VTextField.options.methods.blur.call(this, e)
      this.isMenuActive = false
      this.isFocused = false
      this.selectedIndex = -1
    },
    /** @public */
    activateMenu () {
      if (
        this.disabled ||
        this.readonly ||
        this.isMenuActive
      ) return

      this.isMenuActive = true
    },
    clearableCallback () {
      this.setValue(this.multiple ? [] : undefined)
      this.setMenuIndex(-1)
      this.$nextTick(() => this.$refs.input && this.$refs.input.focus())

      if (this.openOnClear) this.isMenuActive = true
    },
    closeConditional (e: Event) {
      if (!this.isMenuActive) return true

      return (
        !this._isDestroyed &&

        // Click originates from outside the menu content
        this.getContent() &&
{'fraction_non_alphanumeric': 0.1352253756260434, 'fraction_numerical': 0.0017312805292771904, 'mean_word_length': 2.1851122489168966, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3948928', 'n_tokens_mistral': 4859, 'n_tokens_neox': 4843, 'n_words': 1063}