mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-11-04 06:23:17 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			776 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			776 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
function isEmpty(opt) {
 | 
						||
  if (opt === 0) return false
 | 
						||
  if (Array.isArray(opt) && opt.length === 0) return true
 | 
						||
  return !opt
 | 
						||
}
 | 
						||
 | 
						||
function not(fun) {
 | 
						||
  return (...params) => !fun(...params)
 | 
						||
}
 | 
						||
 | 
						||
function includes(str, query) {
 | 
						||
  /* istanbul ignore else */
 | 
						||
  if (str === undefined) str = 'undefined'
 | 
						||
  if (str === null) str = 'null'
 | 
						||
  if (str === false) str = 'false'
 | 
						||
  const text = str.toString().toLowerCase()
 | 
						||
  return text.indexOf(query.trim()) !== -1
 | 
						||
}
 | 
						||
 | 
						||
function filterOptions(options, search, label, customLabel) {
 | 
						||
  return options.filter((option) =>
 | 
						||
    includes(customLabel(option, label), search)
 | 
						||
  )
 | 
						||
}
 | 
						||
 | 
						||
function stripGroups(options) {
 | 
						||
  return options.filter((option) => !option.$isLabel)
 | 
						||
}
 | 
						||
 | 
						||
function flattenOptions(values, label) {
 | 
						||
  return (options) =>
 | 
						||
    options.reduce((prev, curr) => {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (curr[values] && curr[values].length) {
 | 
						||
        prev.push({
 | 
						||
          $groupLabel: curr[label],
 | 
						||
          $isLabel: true,
 | 
						||
        })
 | 
						||
        return prev.concat(curr[values])
 | 
						||
      }
 | 
						||
      return prev
 | 
						||
    }, [])
 | 
						||
}
 | 
						||
 | 
						||
function filterGroups(search, label, values, groupLabel, customLabel) {
 | 
						||
  return (groups) =>
 | 
						||
    groups.map((group) => {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (!group[values]) {
 | 
						||
        console.warn(
 | 
						||
          `Options passed to vue-multiselect do not contain groups, despite the config.`
 | 
						||
        )
 | 
						||
        return []
 | 
						||
      }
 | 
						||
      const groupOptions = filterOptions(
 | 
						||
        group[values],
 | 
						||
        search,
 | 
						||
        label,
 | 
						||
        customLabel
 | 
						||
      )
 | 
						||
 | 
						||
      return groupOptions.length
 | 
						||
        ? {
 | 
						||
            [groupLabel]: group[groupLabel],
 | 
						||
            [values]: groupOptions,
 | 
						||
          }
 | 
						||
        : []
 | 
						||
    })
 | 
						||
}
 | 
						||
 | 
						||
const flow = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
 | 
						||
 | 
						||
export default {
 | 
						||
  data() {
 | 
						||
    return {
 | 
						||
      search: '',
 | 
						||
      isOpen: false,
 | 
						||
      preferredOpenDirection: 'below',
 | 
						||
      optimizedHeight: this.maxHeight,
 | 
						||
    }
 | 
						||
  },
 | 
						||
  props: {
 | 
						||
    initialSearch: {
 | 
						||
      type: String,
 | 
						||
      default: '',
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Decide whether to filter the results based on search query.
 | 
						||
     * Useful for async filtering, where we search through more complex data.
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    internalSearch: {
 | 
						||
      type: Boolean,
 | 
						||
      default: true,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Array of available options: Objects, Strings or Integers.
 | 
						||
     * If array of objects, visible label will default to option.label.
 | 
						||
     * If `labal` prop is passed, label will equal option['label']
 | 
						||
     * @type {Array}
 | 
						||
     */
 | 
						||
    options: {
 | 
						||
      type: Array,
 | 
						||
      required: true,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Equivalent to the `multiple` attribute on a `<select>` input.
 | 
						||
     * @default false
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    multiple: {
 | 
						||
      type: Boolean,
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Presets the selected options value.
 | 
						||
     * @type {Object||Array||String||Integer}
 | 
						||
     */
 | 
						||
    value: {
 | 
						||
      type: null,
 | 
						||
      default() {
 | 
						||
        return []
 | 
						||
      },
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Key to compare objects
 | 
						||
     * @default 'id'
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    trackBy: {
 | 
						||
      type: String,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Label to look for in option Object
 | 
						||
     * @default 'label'
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    label: {
 | 
						||
      type: String,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Enable/disable search in options
 | 
						||
     * @default true
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    searchable: {
 | 
						||
      type: Boolean,
 | 
						||
      default: true,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Clear the search input after `)
 | 
						||
     * @default true
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    clearOnSelect: {
 | 
						||
      type: Boolean,
 | 
						||
      default: true,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Hide already selected options
 | 
						||
     * @default false
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    hideSelected: {
 | 
						||
      type: Boolean,
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Equivalent to the `placeholder` attribute on a `<select>` input.
 | 
						||
     * @default 'Select option'
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    placeholder: {
 | 
						||
      type: String,
 | 
						||
      default: 'Select option',
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Allow to remove all selected values
 | 
						||
     * @default true
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    allowEmpty: {
 | 
						||
      type: Boolean,
 | 
						||
      default: true,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Reset this.internalValue, this.search after this.internalValue changes.
 | 
						||
     * Useful if want to create a stateless dropdown.
 | 
						||
     * @default false
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    resetAfter: {
 | 
						||
      type: Boolean,
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Enable/disable closing after selecting an option
 | 
						||
     * @default true
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    closeOnSelect: {
 | 
						||
      type: Boolean,
 | 
						||
      default: true,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Function to interpolate the custom label
 | 
						||
     * @default false
 | 
						||
     * @type {Function}
 | 
						||
     */
 | 
						||
    customLabel: {
 | 
						||
      type: Function,
 | 
						||
      default(option, label) {
 | 
						||
        if (isEmpty(option)) return ''
 | 
						||
        return label ? option[label] : option
 | 
						||
      },
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Disable / Enable tagging
 | 
						||
     * @default false
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    taggable: {
 | 
						||
      type: Boolean,
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * String to show when highlighting a potential tag
 | 
						||
     * @default 'Press enter to create a tag'
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    tagPlaceholder: {
 | 
						||
      type: String,
 | 
						||
      default: 'Press enter to create a tag',
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * By default new tags will appear above the search results.
 | 
						||
     * Changing to 'bottom' will revert this behaviour
 | 
						||
     * and will proritize the search results
 | 
						||
     * @default 'top'
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    tagPosition: {
 | 
						||
      type: String,
 | 
						||
      default: 'top',
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Number of allowed selected options. No limit if 0.
 | 
						||
     * @default 0
 | 
						||
     * @type {Number}
 | 
						||
     */
 | 
						||
    max: {
 | 
						||
      type: [Number, Boolean],
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Will be passed with all events as second param.
 | 
						||
     * Useful for identifying events origin.
 | 
						||
     * @default null
 | 
						||
     * @type {String|Integer}
 | 
						||
     */
 | 
						||
    id: {
 | 
						||
      default: null,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Limits the options displayed in the dropdown
 | 
						||
     * to the first X options.
 | 
						||
     * @default 1000
 | 
						||
     * @type {Integer}
 | 
						||
     */
 | 
						||
    optionsLimit: {
 | 
						||
      type: Number,
 | 
						||
      default: 1000,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Name of the property containing
 | 
						||
     * the group values
 | 
						||
     * @default 1000
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    groupValues: {
 | 
						||
      type: String,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Name of the property containing
 | 
						||
     * the group label
 | 
						||
     * @default 1000
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    groupLabel: {
 | 
						||
      type: String,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Allow to select all group values
 | 
						||
     * by selecting the group label
 | 
						||
     * @default false
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    groupSelect: {
 | 
						||
      type: Boolean,
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Array of keyboard keys to block
 | 
						||
     * when selecting
 | 
						||
     * @default 1000
 | 
						||
     * @type {String}
 | 
						||
     */
 | 
						||
    blockKeys: {
 | 
						||
      type: Array,
 | 
						||
      default() {
 | 
						||
        return []
 | 
						||
      },
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Prevent from wiping up the search value
 | 
						||
     * @default false
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    preserveSearch: {
 | 
						||
      type: Boolean,
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Select 1st options if value is empty
 | 
						||
     * @default false
 | 
						||
     * @type {Boolean}
 | 
						||
     */
 | 
						||
    preselectFirst: {
 | 
						||
      type: Boolean,
 | 
						||
      default: false,
 | 
						||
    },
 | 
						||
  },
 | 
						||
  mounted() {
 | 
						||
    /* istanbul ignore else */
 | 
						||
    if (!this.multiple && this.max) {
 | 
						||
      console.warn(
 | 
						||
        '[Vue-Multiselect warn]: Max prop should not be used when prop Multiple equals false.'
 | 
						||
      )
 | 
						||
    }
 | 
						||
    if (
 | 
						||
      this.preselectFirst &&
 | 
						||
      !this.internalValue.length &&
 | 
						||
      this.options.length
 | 
						||
    ) {
 | 
						||
      this.select(this.filteredOptions[0])
 | 
						||
    }
 | 
						||
 | 
						||
    if (this.initialSearch) {
 | 
						||
      this.search = this.initialSearch
 | 
						||
    }
 | 
						||
  },
 | 
						||
  computed: {
 | 
						||
    internalValue() {
 | 
						||
      return this.value || this.value === 0
 | 
						||
        ? Array.isArray(this.value)
 | 
						||
          ? this.value
 | 
						||
          : [this.value]
 | 
						||
        : []
 | 
						||
    },
 | 
						||
    filteredOptions() {
 | 
						||
      const search = this.search || ''
 | 
						||
      const normalizedSearch = search.toLowerCase().trim()
 | 
						||
 | 
						||
      let options = this.options.concat()
 | 
						||
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.internalSearch) {
 | 
						||
        options = this.groupValues
 | 
						||
          ? this.filterAndFlat(options, normalizedSearch, this.label)
 | 
						||
          : filterOptions(
 | 
						||
              options,
 | 
						||
              normalizedSearch,
 | 
						||
              this.label,
 | 
						||
              this.customLabel
 | 
						||
            )
 | 
						||
      } else {
 | 
						||
        options = this.groupValues
 | 
						||
          ? flattenOptions(this.groupValues, this.groupLabel)(options)
 | 
						||
          : options
 | 
						||
      }
 | 
						||
 | 
						||
      options = this.hideSelected
 | 
						||
        ? options.filter(not(this.isSelected))
 | 
						||
        : options
 | 
						||
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (
 | 
						||
        this.taggable &&
 | 
						||
        normalizedSearch.length &&
 | 
						||
        !this.isExistingOption(normalizedSearch)
 | 
						||
      ) {
 | 
						||
        if (this.tagPosition === 'bottom') {
 | 
						||
          options.push({ isTag: true, label: search })
 | 
						||
        } else {
 | 
						||
          options.unshift({ isTag: true, label: search })
 | 
						||
        }
 | 
						||
      }
 | 
						||
 | 
						||
      return options.slice(0, this.optionsLimit)
 | 
						||
    },
 | 
						||
    valueKeys() {
 | 
						||
      if (this.trackBy) {
 | 
						||
        return this.internalValue.map((element) => element[this.trackBy])
 | 
						||
      } else {
 | 
						||
        return this.internalValue
 | 
						||
      }
 | 
						||
    },
 | 
						||
    optionKeys() {
 | 
						||
      const options = this.groupValues
 | 
						||
        ? this.flatAndStrip(this.options)
 | 
						||
        : this.options
 | 
						||
      return options.map((element) =>
 | 
						||
        this.customLabel(element, this.label).toString().toLowerCase()
 | 
						||
      )
 | 
						||
    },
 | 
						||
    currentOptionLabel() {
 | 
						||
      return this.multiple
 | 
						||
        ? this.searchable
 | 
						||
          ? ''
 | 
						||
          : this.placeholder
 | 
						||
        : this.internalValue.length
 | 
						||
        ? this.getOptionLabel(this.internalValue[0])
 | 
						||
        : this.searchable
 | 
						||
        ? ''
 | 
						||
        : this.placeholder
 | 
						||
    },
 | 
						||
  },
 | 
						||
  watch: {
 | 
						||
    internalValue() {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.resetAfter && this.internalValue.length) {
 | 
						||
        this.search = ''
 | 
						||
        this.$emit('input', this.multiple ? [] : null)
 | 
						||
      }
 | 
						||
    },
 | 
						||
    search() {
 | 
						||
      this.$emit('search-change', this.search, this.id)
 | 
						||
    },
 | 
						||
  },
 | 
						||
  methods: {
 | 
						||
    /**
 | 
						||
     * Returns the internalValue in a way it can be emited to the parent
 | 
						||
     * @returns {Object||Array||String||Integer}
 | 
						||
     */
 | 
						||
    getValue() {
 | 
						||
      return this.multiple
 | 
						||
        ? this.internalValue
 | 
						||
        : this.internalValue.length === 0
 | 
						||
        ? null
 | 
						||
        : this.internalValue[0]
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Filters and then flattens the options list
 | 
						||
     * @param  {Array}
 | 
						||
     * @returns {Array} returns a filtered and flat options list
 | 
						||
     */
 | 
						||
    filterAndFlat(options, search, label) {
 | 
						||
      return flow(
 | 
						||
        filterGroups(
 | 
						||
          search,
 | 
						||
          label,
 | 
						||
          this.groupValues,
 | 
						||
          this.groupLabel,
 | 
						||
          this.customLabel
 | 
						||
        ),
 | 
						||
        flattenOptions(this.groupValues, this.groupLabel)
 | 
						||
      )(options)
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Flattens and then strips the group labels from the options list
 | 
						||
     * @param  {Array}
 | 
						||
     * @returns {Array} returns a flat options list without group labels
 | 
						||
     */
 | 
						||
    flatAndStrip(options) {
 | 
						||
      return flow(
 | 
						||
        flattenOptions(this.groupValues, this.groupLabel),
 | 
						||
        stripGroups
 | 
						||
      )(options)
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Updates the search value
 | 
						||
     * @param  {String}
 | 
						||
     */
 | 
						||
    updateSearch(query) {
 | 
						||
      this.search = query
 | 
						||
      this.$emit('value', this.search)
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Finds out if the given query is already present
 | 
						||
     * in the available options
 | 
						||
     * @param  {String}
 | 
						||
     * @returns {Boolean} returns true if element is available
 | 
						||
     */
 | 
						||
    isExistingOption(query) {
 | 
						||
      return !this.options ? false : this.optionKeys.indexOf(query) > -1
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Finds out if the given element is already present
 | 
						||
     * in the result value
 | 
						||
     * @param  {Object||String||Integer} option passed element to check
 | 
						||
     * @returns {Boolean} returns true if element is selected
 | 
						||
     */
 | 
						||
    isSelected(option) {
 | 
						||
      const opt = this.trackBy ? option[this.trackBy] : option
 | 
						||
      return this.valueKeys.indexOf(opt) > -1
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Finds out if the given option is disabled
 | 
						||
     * @param  {Object||String||Integer} option passed element to check
 | 
						||
     * @returns {Boolean} returns true if element is disabled
 | 
						||
     */
 | 
						||
    isOptionDisabled(option) {
 | 
						||
      return !!option.$isDisabled
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Returns empty string when options is null/undefined
 | 
						||
     * Returns tag query if option is tag.
 | 
						||
     * Returns the customLabel() results and casts it to string.
 | 
						||
     *
 | 
						||
     * @param  {Object||String||Integer} Passed option
 | 
						||
     * @returns {Object||String}
 | 
						||
     */
 | 
						||
    getOptionLabel(option) {
 | 
						||
      if (isEmpty(option)) return ''
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (option.isTag) return option.label
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (option.$isLabel) return option.$groupLabel
 | 
						||
 | 
						||
      let label = this.customLabel(option, this.label)
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (isEmpty(label)) return ''
 | 
						||
      return label
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Add the given option to the list of selected options
 | 
						||
     * or sets the option as the selected option.
 | 
						||
     * If option is already selected -> remove it from the results.
 | 
						||
     *
 | 
						||
     * @param  {Object||String||Integer} option to select/deselect
 | 
						||
     * @param  {Boolean} block removing
 | 
						||
     */
 | 
						||
    select(option, key) {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (option.$isLabel && this.groupSelect) {
 | 
						||
        this.selectGroup(option)
 | 
						||
        return
 | 
						||
      }
 | 
						||
      if (
 | 
						||
        this.blockKeys.indexOf(key) !== -1 ||
 | 
						||
        this.disabled ||
 | 
						||
        option.$isDisabled ||
 | 
						||
        option.$isLabel
 | 
						||
      )
 | 
						||
        return
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.max && this.multiple && this.internalValue.length === this.max)
 | 
						||
        return
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (key === 'Tab' && !this.pointerDirty) return
 | 
						||
      if (option.isTag) {
 | 
						||
        this.$emit('tag', option.label, this.id)
 | 
						||
        this.search = ''
 | 
						||
        if (this.closeOnSelect && !this.multiple) this.deactivate()
 | 
						||
      } else {
 | 
						||
        const isSelected = this.isSelected(option)
 | 
						||
 | 
						||
        if (isSelected) {
 | 
						||
          if (key !== 'Tab') this.removeElement(option)
 | 
						||
          return
 | 
						||
        }
 | 
						||
 | 
						||
        this.$emit('select', option, this.id)
 | 
						||
 | 
						||
        if (this.multiple) {
 | 
						||
          this.$emit('input', this.internalValue.concat([option]), this.id)
 | 
						||
        } else {
 | 
						||
          this.$emit('input', option, this.id)
 | 
						||
        }
 | 
						||
 | 
						||
        /* istanbul ignore else */
 | 
						||
        if (this.clearOnSelect) this.search = ''
 | 
						||
      }
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.closeOnSelect) this.deactivate()
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Add the given group options to the list of selected options
 | 
						||
     * If all group optiona are already selected -> remove it from the results.
 | 
						||
     *
 | 
						||
     * @param  {Object||String||Integer} group to select/deselect
 | 
						||
     */
 | 
						||
    selectGroup(selectedGroup) {
 | 
						||
      const group = this.options.find((option) => {
 | 
						||
        return option[this.groupLabel] === selectedGroup.$groupLabel
 | 
						||
      })
 | 
						||
 | 
						||
      if (!group) return
 | 
						||
 | 
						||
      if (this.wholeGroupSelected(group)) {
 | 
						||
        this.$emit('remove', group[this.groupValues], this.id)
 | 
						||
 | 
						||
        const newValue = this.internalValue.filter(
 | 
						||
          (option) => group[this.groupValues].indexOf(option) === -1
 | 
						||
        )
 | 
						||
 | 
						||
        this.$emit('input', newValue, this.id)
 | 
						||
      } else {
 | 
						||
        const optionsToAdd = group[this.groupValues].filter(
 | 
						||
          (option) =>
 | 
						||
            !(this.isOptionDisabled(option) || this.isSelected(option))
 | 
						||
        )
 | 
						||
 | 
						||
        this.$emit('select', optionsToAdd, this.id)
 | 
						||
        this.$emit('input', this.internalValue.concat(optionsToAdd), this.id)
 | 
						||
      }
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Helper to identify if all values in a group are selected
 | 
						||
     *
 | 
						||
     * @param {Object} group to validated selected values against
 | 
						||
     */
 | 
						||
    wholeGroupSelected(group) {
 | 
						||
      return group[this.groupValues].every(
 | 
						||
        (option) => this.isSelected(option) || this.isOptionDisabled(option)
 | 
						||
      )
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Helper to identify if all values in a group are disabled
 | 
						||
     *
 | 
						||
     * @param {Object} group to check for disabled values
 | 
						||
     */
 | 
						||
    wholeGroupDisabled(group) {
 | 
						||
      return group[this.groupValues].every(this.isOptionDisabled)
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Removes the given option from the selected options.
 | 
						||
     * Additionally checks this.allowEmpty prop if option can be removed when
 | 
						||
     * it is the last selected option.
 | 
						||
     *
 | 
						||
     * @param  {type} option description
 | 
						||
     * @returns {type}        description
 | 
						||
     */
 | 
						||
    removeElement(option, shouldClose = true) {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.disabled) return
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (option.$isDisabled) return
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (!this.allowEmpty && this.internalValue.length <= 1) {
 | 
						||
        this.deactivate()
 | 
						||
        return
 | 
						||
      }
 | 
						||
 | 
						||
      const index =
 | 
						||
        typeof option === 'object'
 | 
						||
          ? this.valueKeys.indexOf(option[this.trackBy])
 | 
						||
          : this.valueKeys.indexOf(option)
 | 
						||
 | 
						||
      this.$emit('remove', option, this.id)
 | 
						||
      if (this.multiple) {
 | 
						||
        const newValue = this.internalValue
 | 
						||
          .slice(0, index)
 | 
						||
          .concat(this.internalValue.slice(index + 1))
 | 
						||
        this.$emit('input', newValue, this.id)
 | 
						||
      } else {
 | 
						||
        this.$emit('input', null, this.id)
 | 
						||
      }
 | 
						||
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.closeOnSelect && shouldClose) this.deactivate()
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Calls this.removeElement() with the last element
 | 
						||
     * from this.internalValue (selected element Array)
 | 
						||
     *
 | 
						||
     * @fires this#removeElement
 | 
						||
     */
 | 
						||
    removeLastElement() {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.blockKeys.indexOf('Delete') !== -1) return
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (
 | 
						||
        this.search.length === 0 &&
 | 
						||
        Array.isArray(this.internalValue) &&
 | 
						||
        this.internalValue.length
 | 
						||
      ) {
 | 
						||
        this.removeElement(
 | 
						||
          this.internalValue[this.internalValue.length - 1],
 | 
						||
          false
 | 
						||
        )
 | 
						||
      }
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Opens the multiselect’s dropdown.
 | 
						||
     * Sets this.isOpen to TRUE
 | 
						||
     */
 | 
						||
    activate() {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (this.isOpen || this.disabled) return
 | 
						||
 | 
						||
      this.adjustPosition()
 | 
						||
      /* istanbul ignore else  */
 | 
						||
      if (
 | 
						||
        this.groupValues &&
 | 
						||
        this.pointer === 0 &&
 | 
						||
        this.filteredOptions.length
 | 
						||
      ) {
 | 
						||
        this.pointer = 1
 | 
						||
      }
 | 
						||
 | 
						||
      this.isOpen = true
 | 
						||
      /* istanbul ignore else  */
 | 
						||
      if (this.searchable) {
 | 
						||
        if (!this.preserveSearch) this.search = ''
 | 
						||
        this.$nextTick(() => this.$refs.search && this.$refs.search.focus())
 | 
						||
      } else {
 | 
						||
        this.$el.focus()
 | 
						||
      }
 | 
						||
      this.$emit('open', this.id)
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Closes the multiselect’s dropdown.
 | 
						||
     * Sets this.isOpen to FALSE
 | 
						||
     */
 | 
						||
    deactivate() {
 | 
						||
      /* istanbul ignore else */
 | 
						||
      if (!this.isOpen) return
 | 
						||
      this.isOpen = false
 | 
						||
      /* istanbul ignore else  */
 | 
						||
      if (this.searchable) {
 | 
						||
        this.$refs.search && this.$refs.search.blur()
 | 
						||
      } else {
 | 
						||
        this.$el.blur()
 | 
						||
      }
 | 
						||
      if (!this.preserveSearch) this.search = ''
 | 
						||
      this.$emit('close', this.getValue(), this.id)
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Call this.activate() or this.deactivate()
 | 
						||
     * depending on this.isOpen value.
 | 
						||
     *
 | 
						||
     * @fires this#activate || this#deactivate
 | 
						||
     * @property {Boolean} isOpen indicates if dropdown is open
 | 
						||
     */
 | 
						||
    toggle() {
 | 
						||
      this.isOpen ? this.deactivate() : this.activate()
 | 
						||
    },
 | 
						||
    /**
 | 
						||
     * Updates the hasEnoughSpace variable used for
 | 
						||
     * detecting where to expand the dropdown
 | 
						||
     */
 | 
						||
    adjustPosition() {
 | 
						||
      if (typeof window === 'undefined') return
 | 
						||
 | 
						||
      const spaceAbove = this.$el.getBoundingClientRect().top
 | 
						||
      const spaceBelow =
 | 
						||
        window.innerHeight - this.$el.getBoundingClientRect().bottom
 | 
						||
      const hasEnoughSpaceBelow = spaceBelow > this.maxHeight
 | 
						||
 | 
						||
      if (
 | 
						||
        hasEnoughSpaceBelow ||
 | 
						||
        spaceBelow > spaceAbove ||
 | 
						||
        this.openDirection === 'below' ||
 | 
						||
        this.openDirection === 'bottom'
 | 
						||
      ) {
 | 
						||
        this.preferredOpenDirection = 'below'
 | 
						||
        this.optimizedHeight = Math.min(spaceBelow - 40, this.maxHeight)
 | 
						||
      } else {
 | 
						||
        this.preferredOpenDirection = 'above'
 | 
						||
        this.optimizedHeight = Math.min(spaceAbove - 40, this.maxHeight)
 | 
						||
      }
 | 
						||
    },
 | 
						||
  },
 | 
						||
}
 |