From 1d024d6ad10f0ac3b993c65541f7f9e2f59672ba Mon Sep 17 00:00:00 2001 From: Jeff Sagal Date: Thu, 14 Nov 2019 11:05:05 -0800 Subject: [PATCH 1/9] refactor toggleDropdown click handler (#992) --- src/components/Select.vue | 39 ++++++++++---------------- tests/unit/Slots.spec.js | 59 +++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/components/Select.vue b/src/components/Select.vue index 28cd342..211e0fb 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -17,7 +17,7 @@ {{ getOptionLabel(option) }} - @@ -36,6 +36,7 @@ type="button" class="vs__clear" title="Clear selection" + ref="clearButton" > @@ -652,33 +653,23 @@ * @param {Event} e * @return {void} */ - toggleDropdown (e) { - const target = e.target; - const toggleTargets = [ - this.$el, - this.searchEl, - this.$refs.toggle, - this.$refs.actions, - this.$refs.selectedOptions, + toggleDropdown ({target}) { + // don't react to click on deselect/clear buttons, + // they dropdown state will be set in their click handlers + const ignoredButtons = [ + ...(this.$refs['deselectButtons'] || []), + ...([this.$refs['clearButton']] || []) ]; - if (typeof this.$refs.openIndicator !== 'undefined') { - toggleTargets.push( - this.$refs.openIndicator.$el, - // the line below is a bit gross, but required to support IE11 without adding polyfills - ...Array.prototype.slice.call(this.$refs.openIndicator.$el.childNodes), - ); + if (ignoredButtons.some(ref => ref.contains(target) || ref === target)) { + return; } - if (toggleTargets.indexOf(target) > -1 || target.classList.contains('vs__selected')) { - if (this.open) { - this.searchEl.blur(); // dropdown will close on blur - } else { - if (!this.disabled) { - this.open = true; - this.searchEl.focus(); - } - } + if (this.open) { + this.searchEl.blur(); + } else if (!this.disabled) { + this.open = true; + this.searchEl.focus(); } }, diff --git a/tests/unit/Slots.spec.js b/tests/unit/Slots.spec.js index c4201bc..16d242e 100644 --- a/tests/unit/Slots.spec.js +++ b/tests/unit/Slots.spec.js @@ -10,32 +10,49 @@ describe('Scoped Slots', () => { }, }); - expect(Select.find({ ref: 'selectedOptions' }).text()).toEqual('one') + expect(Select.find({ref: 'selectedOptions'}).text()).toEqual('one'); }); - it('receives an option object to the selected-option slot', () => { - const Select = mountDefault( - {value: 'one'}, - { - scopedSlots: { - 'selected-option': `{{ option.label }}`, - }, + describe('Slot: selected-option', () => { + it('receives an option object to the selected-option slot', () => { + const Select = mountDefault( + {value: 'one'}, + { + scopedSlots: { + 'selected-option': `{{ option.label }}`, + }, + }); + + expect(Select.find('.vs__selected').text()).toEqual('one'); + }); + + it('opens the dropdown when clicking an option in selected-option slot', + () => { + const Select = mountDefault( + {value: 'one'}, + { + scopedSlots: { + 'selected-option': `{{ option.label }}`, + }, + }); + + Select.find('.my-option').trigger('mousedown'); + expect(Select.vm.open).toEqual(true); }); - - expect(Select.find('.vs__selected').text()).toEqual('one') }); - it('receives an option object to the option slot in the dropdown menu', () => { - const Select = mountDefault( - {value: 'one'}, - { - scopedSlots: { - 'option': `{{ option.label }}`, - }, - }); + it('receives an option object to the option slot in the dropdown menu', + () => { + const Select = mountDefault( + {value: 'one'}, + { + scopedSlots: { + 'option': `{{ option.label }}`, + }, + }); - Select.vm.open = true; + Select.vm.open = true; - expect(Select.find({ref: 'dropdownMenu'}).text()).toEqual('onetwothree') - }); + expect(Select.find({ref: 'dropdownMenu'}).text()).toEqual('onetwothree'); + }); }); From 619771d0f0db560926d7bc0971e69d8ae06a819f Mon Sep 17 00:00:00 2001 From: Jeff Sagal Date: Thu, 14 Nov 2019 12:18:40 -0800 Subject: [PATCH 2/9] create selectable docs (#996) --- .../components/LimitSelectionQuantity.vue | 21 +++++++++ .../components/UnselectableExample.vue | 16 +++++++ docs/.vuepress/config.js | 1 + docs/guide/selectable.md | 44 +++++++++++++++++++ src/components/Select.vue | 3 +- 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 docs/.vuepress/components/LimitSelectionQuantity.vue create mode 100644 docs/.vuepress/components/UnselectableExample.vue create mode 100644 docs/guide/selectable.md diff --git a/docs/.vuepress/components/LimitSelectionQuantity.vue b/docs/.vuepress/components/LimitSelectionQuantity.vue new file mode 100644 index 0000000..f6890b7 --- /dev/null +++ b/docs/.vuepress/components/LimitSelectionQuantity.vue @@ -0,0 +1,21 @@ + + diff --git a/docs/.vuepress/components/UnselectableExample.vue b/docs/.vuepress/components/UnselectableExample.vue new file mode 100644 index 0000000..ee7d935 --- /dev/null +++ b/docs/.vuepress/components/UnselectableExample.vue @@ -0,0 +1,16 @@ + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 1f2e6a0..2bed2c8 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -118,6 +118,7 @@ module.exports = { collapsable: false, children: [ ['guide/validation', 'Validation'], + ['guide/selectable', 'Limiting Selections'], ['guide/vuex', 'Vuex'], ['guide/ajax', 'AJAX'], ['guide/loops', 'Using in Loops'], diff --git a/docs/guide/selectable.md b/docs/guide/selectable.md new file mode 100644 index 0000000..954fc73 --- /dev/null +++ b/docs/guide/selectable.md @@ -0,0 +1,44 @@ +## Selectable Prop + +The `selectable` prop determines if an option is selectable or not. If `selectable` returns false +for a given option, it will be displayed with a `vs__dropdown-option--disabled` class. The option +will be disabled and unable to be selected. + +```js +selectable: { + type: Function, + /** + * @param {Object|String} option + * @return {boolean} + */ + default: option => true, +}, +``` + +### Example + +Here `selectable` is used to prevent books by a certain author from being chosen. In this case, +the options passed to the component are objects: + +```json +{ + title: "Right Ho Jeeves", + author: { firstName: "P.D", lastName: "Woodhouse" }, +} +``` + +This object will be passed to `selectable`, so we can check if the author should be selectable or not. + + + +<<< @/.vuepress/components/UnselectableExample.vue{6} + +## Limiting the Number of Selections + +`selectable` can also be used a bit more creatively to limit the number selections that can be made +within the component. In this case, the user can select any author, but may only select a maximum +of three books. + + + +<<< @/.vuepress/components/LimitSelectionQuantity.vue{8} diff --git a/src/components/Select.vue b/src/components/Select.vue index 211e0fb..281ec7c 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -229,10 +229,11 @@ }, /** - * Decides wether an option is selectable or not. Not selectable options + * Decides whether an option is selectable or not. Not selectable options * are displayed but disabled and cannot be selected. * * @type {Function} + * @since 3.3.0 * @param {Object|String} option * @return {Boolean} */ From 22bf8f9475762adc0a1f49b6483c2840be186c7a Mon Sep 17 00:00:00 2001 From: Jeff Sagal Date: Thu, 14 Nov 2019 12:21:14 -0800 Subject: [PATCH 3/9] Create FUNDING.yml (#997) --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..999ac4a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [sagalbot] + From 5343b523d6f7113d150ee94519129f766109738e Mon Sep 17 00:00:00 2001 From: Jeff Sagal Date: Fri, 29 Nov 2019 09:43:10 -0800 Subject: [PATCH 4/9] add npmignore (#998) * add npmignore * use a glob --- .npmignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..665a970 --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +* +!src/**/* +!dist/**/* +.DS_Store From a253389d9de4a3754ede364c83846cc752095967 Mon Sep 17 00:00:00 2001 From: Jeff Sagal Date: Sat, 30 Nov 2019 11:05:49 -0800 Subject: [PATCH 5/9] =?UTF-8?q?v3.3=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index beb7fd1..162324c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-select", - "version": "3.2.0", + "version": "3.3.0", "description": "Everything you wish the HTML element could do, wrapped up into a lightweight, extensible Vue component.", "author": "Jeff Sagal ", "homepage": "https://vue-select.org", From 582a0f0facd3c75d5366d5f736c530996c5241dd Mon Sep 17 00:00:00 2001 From: Jeff Sagal Date: Mon, 2 Dec 2019 13:21:02 -0800 Subject: [PATCH 8/9] docs(resetOnOptionsChange): update documentation (#1019) Related #1015 --- docs/api/props.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/api/props.md b/docs/api/props.md index 80fefce..5050c76 100644 --- a/docs/api/props.md +++ b/docs/api/props.md @@ -339,12 +339,20 @@ createOption: { ## resetOnOptionsChange -When false, updating the options will not reset the select value +When false, updating the options will not reset the selected value. Accepts +a `boolean` or `function` that returns a `boolean`. If defined as a function, +it will receive the params listed below. ```js +/** +* @type {Boolean|Function} +* @param {Array} newOptions +* @param {Array} oldOptions +* @param {Array} selectedValue +*/ resetOnOptionsChange: { - type: Boolean, - default: false + default: false, + validator: (value) => ['function', 'boolean'].includes(typeof value) }, ``` From 3f5872c3fc08c7b24319034e5eab598bd1c4d729 Mon Sep 17 00:00:00 2001 From: Jeff Sagal Date: Mon, 2 Dec 2019 13:30:52 -0800 Subject: [PATCH 9/9] docs(resetOnOptionsChange): add versioning info (#1020) * docs(resetOnOptionsChange): update documentation Related #1015 * docs(resetOnOptionsChange): add versioning info --- docs/api/props.md | 8 +++++--- src/components/Select.vue | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/api/props.md b/docs/api/props.md index 5050c76..2ae8284 100644 --- a/docs/api/props.md +++ b/docs/api/props.md @@ -339,9 +339,11 @@ createOption: { ## resetOnOptionsChange -When false, updating the options will not reset the selected value. Accepts -a `boolean` or `function` that returns a `boolean`. If defined as a function, -it will receive the params listed below. +When false, updating the options will not reset the selected value. + +Since `v3.4+` the prop accepts either a `boolean` or `function` that returns a `boolean`. + +If defined as a function, it will receive the params listed below. ```js /** diff --git a/src/components/Select.vue b/src/components/Select.vue index adcd6a4..f5b41a0 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -412,6 +412,9 @@ * When false, updating the options will not reset the selected value. Accepts * a `boolean` or `function` that returns a `boolean`. If defined as a function, * it will receive the params listed below. + * + * @since 3.4 - Type changed to {Boolean|Function} + * * @type {Boolean|Function} * @param {Array} newOptions * @param {Array} oldOptions @@ -522,7 +525,7 @@ let shouldReset = () => typeof this.resetOnOptionsChange === 'function' ? this.resetOnOptionsChange(newOptions, oldOptions, this.selectedValue) : this.resetOnOptionsChange; - + if (!this.taggable && shouldReset()) { this.clearSelection(); }