Skip to content

Commit 8aca2ab

Browse files
author
Damian Dulisz
committed
Update /lib
1 parent 9ba13be commit 8aca2ab

File tree

6 files changed

+137
-123
lines changed

6 files changed

+137
-123
lines changed

lib/Multiselect.vue

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@
1010
@keyup.esc="deactivate()"
1111
class="multiselect">
1212
<div @mousedown.prevent="toggle()" class="multiselect__select"></div>
13-
<div v-el:tags="v-el:tags" class="multiselect__tags">
14-
<span v-if="multiple" v-for="option in visibleValue" track-by="$index" onmousedown="event.preventDefault()" class="multiselect__tag">
15-
{{ getOptionLabel(option) }}
16-
<i aria-hidden="true" tabindex="1" @keydown.enter.prevent="removeElement(option)" @mousedown.prevent="removeElement(option)" class="multiselect__tag-icon"></i>
13+
<div v-el:tags class="multiselect__tags">
14+
<span
15+
v-if="multiple"
16+
v-for="option in visibleValue"
17+
track-by="$index"
18+
onmousedown="event.preventDefault()"
19+
class="multiselect__tag">
20+
<span v-text="getOptionLabel(option)"></span>
21+
<i
22+
aria-hidden="true"
23+
tabindex="1"
24+
@keydown.enter.prevent="removeElement(option)"
25+
@mousedown.prevent="removeElement(option)"
26+
class="multiselect__tag-icon">
27+
</i>
1728
</span>
1829
<template v-if="value && value.length > limit">
19-
<strong>{{ limitText(value.length - limit) }}</strong>
30+
<strong v-text="limitText(value.length - limit)"></strong>
2031
</template>
2132
<div v-show="loading" transition="multiselect__loading" class="multiselect__spinner"></div>
2233
<input
@@ -29,16 +40,24 @@
2940
v-model="search"
3041
@focus.prevent="activate()"
3142
@blur.prevent="deactivate()"
32-
@input="pointerReset()"
3343
@keyup.esc="deactivate()"
3444
@keyup.down="pointerForward()"
3545
@keyup.up="pointerBackward()"
3646
@keydown.enter.stop.prevent.self="addPointerElement()"
3747
@keydown.delete="removeLastElement()"
3848
class="multiselect__input"/>
39-
<span v-if="!searchable && !multiple" class="multiselect__single">{{ getOptionLabel(value) ? getOptionLabel(value) : placeholder }}</span>
49+
<span
50+
v-if="!searchable && !multiple"
51+
class="multiselect__single"
52+
v-text="getOptionLabel(value) ? getOptionLabel(value) : placeholder">
53+
</span>
4054
</div>
41-
<ul transition="multiselect" :style="{ maxHeight: maxHeight + 'px' }" v-el:list="v-el:list" v-show="isOpen" class="multiselect__content">
55+
<ul
56+
transition="multiselect"
57+
:style="{ maxHeight: maxHeight + 'px' }"
58+
v-el:list
59+
v-show="isOpen"
60+
class="multiselect__content">
4261
<slot name="beforeList"></slot>
4362
<li v-if="multiple && max === value.length">
4463
<span class="multiselect__option">
@@ -51,12 +70,12 @@
5170
tabindex="0"
5271
:class="{ 'multiselect__option--highlight': $index === pointer && this.showPointer, 'multiselect__option--selected': !isNotSelected(option) }"
5372
@mousedown.prevent="select(option)"
54-
@mouseover="pointerSet($index)"
73+
@mouseenter="pointerSet($index)"
5574
:data-select="option.isTag ? tagPlaceholder : selectLabel"
5675
:data-selected="selectedLabel"
5776
:data-deselect="deselectLabel"
58-
class="multiselect__option">
59-
{{ getOptionLabel(option) }}
77+
class="multiselect__option"
78+
v-text="getOptionLabel(option)">
6079
</span>
6180
</li>
6281
</template>
@@ -132,6 +151,15 @@
132151
limitText: {
133152
type: Function,
134153
default: count => `and ${count} more`
154+
},
155+
/**
156+
* Set true to trigger the loading spinner.
157+
* @default False
158+
* @type {Boolean}
159+
*/
160+
loading: {
161+
type: Boolean,
162+
default: false
135163
}
136164
},
137165
computed: {
@@ -432,6 +460,7 @@
432460
433461
.multiselect__option--highlight:after {
434462
content: attr(data-select);
463+
background: #41B883;
435464
color: white;
436465
}
437466
@@ -452,6 +481,7 @@
452481
}
453482
454483
.multiselect__option--selected.multiselect__option--highlight:after {
484+
background: #FF6A6A;
455485
content: attr(data-deselect);
456486
color: #fff;
457487
}

lib/multiselectMixin.js

Lines changed: 59 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
1-
// Copied from Vuex’s util.js
2-
function deepClone (obj) {
3-
if (Array.isArray(obj)) {
4-
return obj.map(deepClone)
5-
} else if (obj && typeof obj === 'object') {
6-
var cloned = {}
7-
var keys = Object.keys(obj)
8-
for (var i = 0, l = keys.length; i < l; i++) {
9-
var key = keys[i]
10-
cloned[key] = deepClone(obj[key])
11-
}
12-
return cloned
13-
} else {
14-
return obj
15-
}
16-
}
1+
import { deepClone } from './utils'
172

183
module.exports = {
194
data () {
205
return {
216
search: '',
227
isOpen: false,
23-
value: [],
24-
loading: false
8+
value: []
259
}
2610
},
2711
props: {
@@ -126,29 +110,6 @@ module.exports = {
126110
type: Boolean,
127111
default: true
128112
},
129-
/**
130-
* Callback function to call after this.value changes
131-
* @callback onChange
132-
* @default false
133-
* @param {Array||Object||String||Integer} Current this.value
134-
* @param {Integer} $index of current selection
135-
* @type {Function}
136-
*/
137-
onChange: {
138-
type: Function,
139-
default: false
140-
},
141-
/**
142-
* Callback function to call after this.search changes
143-
* @callback onSearchChange
144-
* @default false
145-
* @param {String} Pass current search String
146-
* @type {Function}
147-
*/
148-
onSearchChange: {
149-
type: Function,
150-
default: false
151-
},
152113
/**
153114
* Value that indicates if the dropdown has been used.
154115
* Useful for validation.
@@ -197,19 +158,6 @@ module.exports = {
197158
type: Boolean,
198159
default: false
199160
},
200-
/**
201-
* Callback function to run when attemting to add a tag
202-
* @default suitable for primitive values
203-
* @param {String} Tag string to build a tag
204-
* @type {Function}
205-
*/
206-
onTag: {
207-
type: Function,
208-
default: function (tag) {
209-
this.options.push(tag)
210-
this.value.push(tag)
211-
}
212-
},
213161
/**
214162
* String to show when highlighting a potential tag
215163
* @default 'Press enter to create a tag'
@@ -228,9 +176,14 @@ module.exports = {
228176
type: Number,
229177
default: false
230178
},
231-
async: {
232-
type: Boolean,
233-
default: false
179+
/**
180+
* Will be passed with all events as second param.
181+
* Useful for identifying events origin.
182+
* @default null
183+
* @type {String|Integer}
184+
*/
185+
id: {
186+
default: null
234187
}
235188
},
236189
created () {
@@ -239,9 +192,7 @@ module.exports = {
239192
} else {
240193
this.value = deepClone(this.selected)
241194
}
242-
if (this.searchable && !this.multiple) {
243-
this.search = this.getOptionLabel(this.value)
244-
}
195+
if (this.searchable) this.adjustSearch()
245196
},
246197
computed: {
247198
filteredOptions () {
@@ -272,33 +223,18 @@ module.exports = {
272223
},
273224
watch: {
274225
'value' () {
275-
if (this.onChange && JSON.stringify(this.value) !== JSON.stringify(this.selected)) {
276-
this.onChange(deepClone(this.value))
277-
} else {
278-
this.$set('selected', deepClone(this.value))
279-
}
280226
if (this.resetAfter) {
281227
this.$set('value', null)
282228
this.$set('search', null)
283229
this.$set('selected', null)
284230
}
285-
if (!this.multiple && this.searchable && this.clearOnSelect) {
286-
this.search = this.getOptionLabel(this.value)
287-
}
231+
this.adjustSearch()
288232
},
289233
'search' () {
290-
this.$emit('on-search-change', this.search)
291-
this.loading = true
292-
if (this.async) {
293-
}
294-
},
295-
'options' () {
296-
this.onSearchChange && (this.loading = false)
234+
this.$emit('search-change', this.search, this.id)
297235
},
298236
'selected' (newVal, oldVal) {
299-
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
300-
this.value = deepClone(this.selected)
301-
}
237+
this.value = deepClone(this.selected)
302238
}
303239
},
304240
methods: {
@@ -374,28 +310,31 @@ module.exports = {
374310
select (option) {
375311
if (this.max && this.multiple && this.value.length === this.max) return
376312
if (option.isTag) {
377-
this.onTag(option.label)
313+
this.$emit('tag', option.label, this.id)
378314
this.search = ''
379315
} else {
380316
if (this.multiple) {
381317
if (!this.isNotSelected(option)) {
382318
this.removeElement(option)
383319
} else {
384320
this.value.push(option)
385-
if (this.clearOnSelect) { this.search = '' }
321+
322+
this.$emit('select', deepClone(option), this.id)
323+
this.$emit('update', deepClone(this.value), this.id)
386324
}
387325
} else {
388-
this.$set('value',
389-
!this.isNotSelected(option) && this.allowEmpty
390-
? null
391-
: option
392-
)
393-
if (this.closeOnSelect) {
394-
this.searchable
395-
? this.$els.search.blur()
396-
: this.$el.blur()
397-
}
326+
const isSelected = this.isSelected(option)
327+
328+
/* istanbul ignore else */
329+
if (isSelected && !this.allowEmpty) return
330+
331+
this.value = isSelected ? null : option
332+
333+
this.$emit('select', deepClone(option), this.id)
334+
this.$emit('update', deepClone(this.value), this.id)
398335
}
336+
337+
if (this.closeOnSelect) this.deactivate()
399338
}
400339
},
401340
/**
@@ -408,14 +347,16 @@ module.exports = {
408347
*/
409348
removeElement (option) {
410349
/* istanbul ignore else */
411-
if (this.allowEmpty || this.value.length > 1) {
412-
if (this.multiple && typeof option === 'object') {
413-
const index = this.valueKeys.indexOf(option[this.key])
414-
this.value.splice(index, 1)
415-
} else {
416-
this.value.$remove(option)
417-
}
350+
if (!this.allowEmpty && this.value.length <= 1) return
351+
352+
if (this.multiple && typeof option === 'object') {
353+
const index = this.valueKeys.indexOf(option[this.key])
354+
this.value.splice(index, 1)
355+
} else {
356+
this.value.$remove(option)
418357
}
358+
this.$emit('remove', deepClone(option), this.id)
359+
this.$emit('update', deepClone(this.value), this.id)
419360
},
420361
/**
421362
* Calls this.removeElement() with the last element
@@ -452,20 +393,29 @@ module.exports = {
452393
*/
453394
deactivate () {
454395
/* istanbul ignore else */
455-
if (this.isOpen) {
456-
this.isOpen = false
457-
this.touched = true
458-
/* istanbul ignore else */
459-
if (this.searchable) {
460-
this.$els.search.blur()
461-
this.search = this.multiple
462-
? ''
463-
: this.getOptionLabel(this.value)
464-
} else {
465-
this.$el.blur()
466-
}
396+
if (!this.isOpen) return
397+
398+
this.isOpen = false
399+
this.touched = true
400+
/* istanbul ignore else */
401+
if (this.searchable) {
402+
this.$els.search.blur()
403+
this.adjustSearch()
404+
} else {
405+
this.$el.blur()
467406
}
468407
},
408+
/**
409+
* Adjusts the Search property to equal the correct value
410+
* depending on the selected value.
411+
*/
412+
adjustSearch () {
413+
if (!this.searchable || !this.clearOnSelect) return
414+
415+
this.search = this.multiple
416+
? ''
417+
: this.getOptionLabel(this.value)
418+
},
469419
/**
470420
* Call this.activate() or this.deactivate()
471421
* depending on this.isOpen value.

lib/pointerMixin.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ module.exports = {
1515
default: true
1616
}
1717
},
18+
watch: {
19+
'filteredOptions' () {
20+
this.pointerAdjust()
21+
}
22+
},
1823
methods: {
1924
addPointerElement () {
2025
if (this.filteredOptions.length > 0) {
@@ -42,11 +47,20 @@ module.exports = {
4247
}
4348
},
4449
pointerReset () {
50+
if (!this.closeOnSelect) return
51+
4552
this.pointer = 0
4653
if (this.$els.list) {
4754
this.$els.list.scrollTop = 0
4855
}
4956
},
57+
pointerAdjust () {
58+
if (this.pointer >= this.filteredOptions.length - 1) {
59+
this.pointer = this.filteredOptions.length
60+
? this.filteredOptions.length - 1
61+
: 0
62+
}
63+
},
5064
pointerSet (index) {
5165
this.pointer = index
5266
}

0 commit comments

Comments
 (0)