mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-31 13:41:09 -04: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)
 | ||
|       }
 | ||
|     },
 | ||
|   },
 | ||
| }
 |