mirror of
https://github.com/tenrok/vue-select.git
synced 2026-06-22 10:30:34 +03:00
create dropdown component, inject context
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import vAppendToBody from '@/directives/appendToBody.js'
|
||||||
|
import { VueSelectInjectionKey } from '@/symbols'
|
||||||
|
import type { VueSelectInjectedProps } from '@/components/Select.vue'
|
||||||
|
|
||||||
|
const context = inject<VueSelectInjectedProps>(VueSelectInjectionKey)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul
|
||||||
|
v-if="context.dropdownOpen"
|
||||||
|
:id="`vs${context.uid}__listbox`"
|
||||||
|
ref="dropdownMenu"
|
||||||
|
:key="`vs${context.uid}__listbox`"
|
||||||
|
v-append-to-body
|
||||||
|
class="vs__dropdown-menu"
|
||||||
|
role="listbox"
|
||||||
|
tabindex="-1"
|
||||||
|
@mousedown.prevent="context.onMousedown"
|
||||||
|
@mouseup="context.onMouseUp"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</ul>
|
||||||
|
<ul
|
||||||
|
v-else
|
||||||
|
:id="`vs${context.uid}__listbox`"
|
||||||
|
role="listbox"
|
||||||
|
style="display: none; visibility: hidden"
|
||||||
|
></ul>
|
||||||
|
</template>
|
||||||
+28
-28
@@ -83,18 +83,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<transition :name="transition">
|
<transition :name="transition">
|
||||||
<ul
|
<DropdownMenu>
|
||||||
v-if="dropdownOpen"
|
|
||||||
:id="`vs${uid}__listbox`"
|
|
||||||
ref="dropdownMenu"
|
|
||||||
:key="`vs${uid}__listbox`"
|
|
||||||
v-append-to-body
|
|
||||||
class="vs__dropdown-menu"
|
|
||||||
role="listbox"
|
|
||||||
tabindex="-1"
|
|
||||||
@mousedown.prevent="onMousedown"
|
|
||||||
@mouseup="onMouseUp"
|
|
||||||
>
|
|
||||||
<slot name="list-header" v-bind="scope.listHeader" />
|
<slot name="list-header" v-bind="scope.listHeader" />
|
||||||
<li
|
<li
|
||||||
v-for="(option, index) in filteredOptions"
|
v-for="(option, index) in filteredOptions"
|
||||||
@@ -123,34 +112,32 @@
|
|||||||
</slot>
|
</slot>
|
||||||
</li>
|
</li>
|
||||||
<slot name="list-footer" v-bind="scope.listFooter" />
|
<slot name="list-footer" v-bind="scope.listFooter" />
|
||||||
</ul>
|
</DropdownMenu>
|
||||||
<ul
|
|
||||||
v-else
|
|
||||||
:id="`vs${uid}__listbox`"
|
|
||||||
role="listbox"
|
|
||||||
style="display: none; visibility: hidden"
|
|
||||||
></ul>
|
|
||||||
</transition>
|
</transition>
|
||||||
<slot name="footer" v-bind="scope.footer" />
|
<slot name="footer" v-bind="scope.footer" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import pointerScroll from '@/mixins/pointerScroll.js'
|
import pointerScroll from '@/mixins/pointerScroll.js'
|
||||||
import typeAheadPointer from '@/mixins/typeAheadPointer.js'
|
import typeAheadPointer from '@/mixins/typeAheadPointer.js'
|
||||||
import ajax from '@/mixins/ajax.js'
|
import ajax from '@/mixins/ajax.js'
|
||||||
import childComponents from '@/components/childComponents.js'
|
import childComponents from '@/components/childComponents.js'
|
||||||
import appendToBody from '@/directives/appendToBody.js'
|
|
||||||
import sortAndStringify from '@/utility/sortAndStringify.js'
|
import sortAndStringify from '@/utility/sortAndStringify.js'
|
||||||
import uniqueId from '@/utility/uniqueId.js'
|
import uniqueId from '@/utility/uniqueId.js'
|
||||||
|
import { computed, ComputedRef, defineComponent } from 'vue'
|
||||||
|
import { VueSelectInjectionKey } from '@/symbols.js'
|
||||||
|
import DropdownMenu from '@/components/DropdownMenu.vue'
|
||||||
|
|
||||||
/**
|
export interface VueSelectContext {
|
||||||
* @name VueSelect
|
uid: ComputedRef<string>
|
||||||
*/
|
dropdownOpen: ComputedRef<boolean>
|
||||||
export default {
|
onMousedown: (e: MouseEvent) => void
|
||||||
components: { ...childComponents },
|
onMouseup: (e: MouseEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
directives: { appendToBody },
|
export default defineComponent({
|
||||||
|
components: { DropdownMenu, ...childComponents },
|
||||||
|
|
||||||
mixins: [pointerScroll, typeAheadPointer, ajax],
|
mixins: [pointerScroll, typeAheadPointer, ajax],
|
||||||
|
|
||||||
@@ -689,6 +676,19 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
[VueSelectInjectionKey]: computed(() => {
|
||||||
|
return {
|
||||||
|
uid: this.uid,
|
||||||
|
dropdownOpen: this.dropdownOpen,
|
||||||
|
onMousedown: this.onMousedown,
|
||||||
|
onMouseup: this.onMouseUp,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
search: '',
|
search: '',
|
||||||
@@ -1362,5 +1362,5 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { InjectionKey } from 'vue'
|
||||||
|
|
||||||
|
export const VueSelectInjectionKey = Symbol(
|
||||||
|
'VueSelectInjectionKey'
|
||||||
|
) as InjectionKey<string>
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
import { shallowMount } from '@vue/test-utils'
|
import { mount, shallowMount } from '@vue/test-utils'
|
||||||
import VueSelect from '@/components/Select.vue'
|
import VueSelect from '@/components/Select.vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +50,7 @@ export const selectTag = async (Wrapper, searchText) => {
|
|||||||
* @returns {Wrapper<Vue>}
|
* @returns {Wrapper<Vue>}
|
||||||
*/
|
*/
|
||||||
export const selectWithProps = (props = {}) => {
|
export const selectWithProps = (props = {}) => {
|
||||||
return shallowMount(VueSelect, { props })
|
return mount(VueSelect, { props })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +60,7 @@ export const selectWithProps = (props = {}) => {
|
|||||||
* @return {Wrapper<Vue>}
|
* @return {Wrapper<Vue>}
|
||||||
*/
|
*/
|
||||||
export const mountDefault = (props = {}, options = {}) => {
|
export const mountDefault = (props = {}, options = {}) => {
|
||||||
return shallowMount(VueSelect, {
|
return mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
options: ['one', 'two', 'three'],
|
options: ['one', 'two', 'three'],
|
||||||
...props,
|
...props,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { it, describe, expect, vi } from 'vitest'
|
import { it, describe, expect, vi } from 'vitest'
|
||||||
import { shallowMount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import VueSelect from '@/components/Select.vue'
|
import VueSelect from '@/components/Select.vue'
|
||||||
import { selectWithProps } from '@tests/helpers.js'
|
import { selectWithProps } from '@tests/helpers.js'
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ describe('Labels', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should display a placeholder if the value is empty', () => {
|
it('should display a placeholder if the value is empty', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
options: ['one'],
|
options: ['one'],
|
||||||
},
|
},
|
||||||
@@ -66,7 +66,7 @@ describe('Labels', () => {
|
|||||||
*/
|
*/
|
||||||
// it('will not call getOptionLabel if both scoped option slots are used and a filter is provided', () => {
|
// it('will not call getOptionLabel if both scoped option slots are used and a filter is provided', () => {
|
||||||
// const spy = spyOn(VueSelect.props.getOptionLabel, 'default')
|
// const spy = spyOn(VueSelect.props.getOptionLabel, 'default')
|
||||||
// const Select = shallowMount(VueSelect, {
|
// const Select = mount(VueSelect, {
|
||||||
// props: {
|
// props: {
|
||||||
// options: [{ name: 'one' }],
|
// options: [{ name: 'one' }],
|
||||||
// filter: () => {},
|
// filter: () => {},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { it, describe, expect, vi, beforeEach, afterEach } from 'vitest'
|
import { it, describe, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
import { mount, shallowMount } from '@vue/test-utils'
|
import { mount, mount } from '@vue/test-utils'
|
||||||
import VueSelect from '@/components/Select.vue'
|
import VueSelect from '@/components/Select.vue'
|
||||||
import typeAheadPointer from '@/mixins/typeAheadPointer.js'
|
import typeAheadPointer from '@/mixins/typeAheadPointer.js'
|
||||||
import { mountDefault } from '@tests/helpers.js'
|
import { mountDefault } from '@tests/helpers.js'
|
||||||
@@ -20,14 +20,14 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can accept an array with pre-selected values', () => {
|
it('can accept an array with pre-selected values', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: defaultProps,
|
props: defaultProps,
|
||||||
})
|
})
|
||||||
expect(Select.vm.selectedValue[0]).toEqual(Select.vm.modelValue)
|
expect(Select.vm.selectedValue[0]).toEqual(Select.vm.modelValue)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can accept an array of objects and pre-selected value (single)', () => {
|
it('can accept an array of objects and pre-selected value (single)', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
modelValue: { label: 'This is Foo', value: 'foo' },
|
modelValue: { label: 'This is Foo', value: 'foo' },
|
||||||
options: [
|
options: [
|
||||||
@@ -40,7 +40,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can accept an array of objects and pre-selected values (multiple)', () => {
|
it('can accept an array of objects and pre-selected values (multiple)', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
modelValue: [
|
modelValue: [
|
||||||
{ label: 'This is Foo', value: 'foo' },
|
{ label: 'This is Foo', value: 'foo' },
|
||||||
@@ -59,7 +59,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
|
|
||||||
it('can select an option on tab', () => {
|
it('can select an option on tab', () => {
|
||||||
spy = vi.spyOn(typeAheadPointer.methods, 'typeAheadSelect')
|
spy = vi.spyOn(typeAheadPointer.methods, 'typeAheadSelect')
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
selectOnTab: true,
|
selectOnTab: true,
|
||||||
},
|
},
|
||||||
@@ -71,7 +71,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can deselect a pre-selected object', () => {
|
it('can deselect a pre-selected object', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
options: [
|
options: [
|
||||||
@@ -93,7 +93,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can deselect a pre-selected string', () => {
|
it('can deselect a pre-selected string', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
options: ['foo', 'bar'],
|
options: ['foo', 'bar'],
|
||||||
@@ -107,7 +107,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can deselect an option when multiple is false', () => {
|
it('can deselect an option when multiple is false', () => {
|
||||||
const Select = shallowMount(VueSelect)
|
const Select = mount(VueSelect)
|
||||||
|
|
||||||
Select.vm.$data._value = 'foo'
|
Select.vm.$data._value = 'foo'
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can determine if the value prop is empty', () => {
|
it('can determine if the value prop is empty', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
options: ['one', 'two', 'three'],
|
options: ['one', 'two', 'three'],
|
||||||
},
|
},
|
||||||
@@ -142,7 +142,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should reset the selected values when the multiple property changes', () => {
|
it('should reset the selected values when the multiple property changes', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
options: ['one', 'two', 'three'],
|
options: ['one', 'two', 'three'],
|
||||||
@@ -157,7 +157,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can retain values present in a new array of options', () => {
|
it('can retain values present in a new array of options', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
modelValue: ['one'],
|
modelValue: ['one'],
|
||||||
options: ['one', 'two', 'three'],
|
options: ['one', 'two', 'three'],
|
||||||
@@ -169,7 +169,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can determine if an object is already selected', () => {
|
it('can determine if an object is already selected', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
modelValue: [{ label: 'one' }],
|
modelValue: [{ label: 'one' }],
|
||||||
options: [{ label: 'one' }],
|
options: [{ label: 'one' }],
|
||||||
@@ -195,7 +195,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can check if a string value is selected when the value is an object and multiple is true', () => {
|
it('can check if a string value is selected when the value is an object and multiple is true', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: {
|
props: {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
modelValue: [{ label: 'foo', value: 'bar' }],
|
modelValue: [{ label: 'foo', value: 'bar' }],
|
||||||
@@ -238,13 +238,13 @@ describe('VS - Selecting Values', () => {
|
|||||||
|
|
||||||
describe('input Event', () => {
|
describe('input Event', () => {
|
||||||
it('will trigger the input event when the selection changes', () => {
|
it('will trigger the input event when the selection changes', () => {
|
||||||
const Select = shallowMount(VueSelect)
|
const Select = mount(VueSelect)
|
||||||
Select.vm.select('bar')
|
Select.vm.select('bar')
|
||||||
expect(Select.emitted('update:modelValue')[0]).toEqual(['bar'])
|
expect(Select.emitted('update:modelValue')[0]).toEqual(['bar'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('will trigger the input event when the selection changes and multiple is true', () => {
|
it('will trigger the input event when the selection changes and multiple is true', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
props: { multiple: true, modelValue: ['foo'], options: ['foo', 'bar'] },
|
props: { multiple: true, modelValue: ['foo'], options: ['foo', 'bar'] },
|
||||||
})
|
})
|
||||||
Select.vm.select('bar')
|
Select.vm.select('bar')
|
||||||
@@ -252,7 +252,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('will not trigger the input event when multiple is true and selection is repeated', () => {
|
it('will not trigger the input event when multiple is true and selection is repeated', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: {
|
propsData: {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
value: ['foo ', 'bar'],
|
value: ['foo ', 'bar'],
|
||||||
@@ -267,13 +267,13 @@ describe('VS - Selecting Values', () => {
|
|||||||
|
|
||||||
describe('option:selecting Event', () => {
|
describe('option:selecting Event', () => {
|
||||||
it('will trigger the option:selecting event when an option is selected', () => {
|
it('will trigger the option:selecting event when an option is selected', () => {
|
||||||
const Select = shallowMount(VueSelect)
|
const Select = mount(VueSelect)
|
||||||
Select.vm.select('bar')
|
Select.vm.select('bar')
|
||||||
expect(Select.emitted('option:selecting')[0]).toEqual(['bar'])
|
expect(Select.emitted('option:selecting')[0]).toEqual(['bar'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('will trigger the option:selecting event regardless of current value', () => {
|
it('will trigger the option:selecting event regardless of current value', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: { value: ['foo'], options: ['foo', 'bar'] },
|
propsData: { value: ['foo'], options: ['foo', 'bar'] },
|
||||||
})
|
})
|
||||||
Select.vm.select('foo')
|
Select.vm.select('foo')
|
||||||
@@ -282,7 +282,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('will trigger the option:selecting event with current selected item when multiple is true', () => {
|
it('will trigger the option:selecting event with current selected item when multiple is true', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: { multiple: true, value: ['foo'], options: ['foo', 'bar'] },
|
propsData: { multiple: true, value: ['foo'], options: ['foo', 'bar'] },
|
||||||
})
|
})
|
||||||
Select.vm.select('bar')
|
Select.vm.select('bar')
|
||||||
@@ -290,7 +290,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('will trigger the option:selecting event regardless of current value when multiple is true', () => {
|
it('will trigger the option:selecting event regardless of current value when multiple is true', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: {
|
propsData: {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
value: ['foo', 'bar'],
|
value: ['foo', 'bar'],
|
||||||
@@ -305,7 +305,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
|
|
||||||
describe('option:deselected Event', () => {
|
describe('option:deselected Event', () => {
|
||||||
it('will trigger the option:deselected event when an option is deselected', () => {
|
it('will trigger the option:deselected event when an option is deselected', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: { value: ['foo'], options: ['foo', 'bar'] },
|
propsData: { value: ['foo'], options: ['foo', 'bar'] },
|
||||||
})
|
})
|
||||||
Select.vm.deselect('foo')
|
Select.vm.deselect('foo')
|
||||||
@@ -313,7 +313,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('will trigger the option:deselected event regardless of current value', () => {
|
it('will trigger the option:deselected event regardless of current value', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: { value: ['foo'], options: ['foo', 'bar'] },
|
propsData: { value: ['foo'], options: ['foo', 'bar'] },
|
||||||
})
|
})
|
||||||
Select.vm.deselect('foo')
|
Select.vm.deselect('foo')
|
||||||
@@ -322,7 +322,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('will trigger the option:selected event with current selected item when multiple is true', () => {
|
it('will trigger the option:selected event with current selected item when multiple is true', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: { multiple: true, value: ['foo'], options: ['foo', 'bar'] },
|
propsData: { multiple: true, value: ['foo'], options: ['foo', 'bar'] },
|
||||||
})
|
})
|
||||||
Select.vm.deselect('bar')
|
Select.vm.deselect('bar')
|
||||||
@@ -330,7 +330,7 @@ describe('VS - Selecting Values', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('will trigger the option:selected event regardless of current value when multiple is true', () => {
|
it('will trigger the option:selected event regardless of current value when multiple is true', () => {
|
||||||
const Select = shallowMount(VueSelect, {
|
const Select = mount(VueSelect, {
|
||||||
propsData: {
|
propsData: {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
value: ['foo', 'bar'],
|
value: ['foo', 'bar'],
|
||||||
|
|||||||
Reference in New Issue
Block a user