mirror of
https://github.com/element-plus/element-plus.git
synced 2025-01-06 10:38:31 +08:00
feat(components): select-v2 support filter-method & remote-search (#3092)
This commit is contained in:
parent
8f67fb7645
commit
a15f5f293f
@ -24,7 +24,7 @@ const clickClearButton = async wrapper => {
|
||||
const select = wrapper.findComponent(Select)
|
||||
const selectVm = select.vm as any
|
||||
selectVm.states.comboBoxHovering = true
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const clearBtn = wrapper.find(`.${selectVm.clearIcon}`)
|
||||
expect(clearBtn.exists()).toBeTruthy()
|
||||
await clearBtn.trigger('click')
|
||||
@ -38,6 +38,7 @@ interface SelectProps {
|
||||
clearable?: boolean
|
||||
multiple?: boolean
|
||||
filterable?: boolean
|
||||
remote?: boolean
|
||||
multipleLimit?: number
|
||||
allowCreate?: boolean
|
||||
popperAppendToBody?: boolean
|
||||
@ -51,6 +52,8 @@ interface SelectEvents {
|
||||
onRemoveTag?: (tag?: string) => void
|
||||
onFocus?: (event?: FocusEvent) => void
|
||||
onBlur?: (event?) => void
|
||||
filterMethod?: (query: string) => void | undefined
|
||||
remoteMethod?: (query: string) => void | undefined
|
||||
[key: string]: (...args) => any
|
||||
}
|
||||
|
||||
@ -76,6 +79,9 @@ const createSelect = (options: {
|
||||
:popper-append-to-body="popperAppendToBody"
|
||||
:placeholder="placeholder"
|
||||
:allow-create="allowCreate"
|
||||
:remote="remote"
|
||||
${options.methods && options.methods.filterMethod ? `:filter-method="filterMethod"` : ''}
|
||||
${options.methods && options.methods.remoteMethod ? `:remote-method="remoteMethod"` : ''}
|
||||
@change="onChange"
|
||||
@visible-change="onVisibleChange"
|
||||
@remove-tah="onRemoveTag"
|
||||
@ -95,6 +101,7 @@ const createSelect = (options: {
|
||||
disabled: false,
|
||||
clearable: false,
|
||||
multiple: false,
|
||||
remote: false,
|
||||
filterable: false,
|
||||
multipleLimit: 0,
|
||||
popperAppendToBody: true,
|
||||
@ -115,7 +122,7 @@ const createSelect = (options: {
|
||||
|
||||
function getOptions(): HTMLElement[] {
|
||||
return Array.from(document.querySelectorAll<HTMLElement>(
|
||||
`body > div:last-child .${OPTION_ITEM_CLASS_NAME}`,
|
||||
`.${OPTION_ITEM_CLASS_NAME}`,
|
||||
))
|
||||
}
|
||||
|
||||
@ -133,7 +140,7 @@ describe('Select', () => {
|
||||
|
||||
it('create', async () => {
|
||||
const wrapper = createSelect()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.classes()).toContain(CLASS_NAME)
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toContain(DEFAULT_PLACEHOLDER)
|
||||
const select = wrapper.findComponent(Select)
|
||||
@ -143,7 +150,7 @@ describe('Select', () => {
|
||||
|
||||
it('options rendered correctly', async() => {
|
||||
const wrapper = createSelect()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const options = document.getElementsByClassName(OPTION_ITEM_CLASS_NAME)
|
||||
const result = [].every.call(options, (option, index) => {
|
||||
@ -159,7 +166,7 @@ describe('Select', () => {
|
||||
popperClass: 'custom-dropdown',
|
||||
}),
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(document.querySelector('.el-popper').classList).toContain('custom-dropdown')
|
||||
})
|
||||
|
||||
@ -184,7 +191,7 @@ describe('Select', () => {
|
||||
}),
|
||||
})
|
||||
const vm = wrapper.vm as any
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(vm.options[1].label)
|
||||
})
|
||||
|
||||
@ -194,23 +201,23 @@ describe('Select', () => {
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
vm.value = vm.options[2].value
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(placeholder.text()).toBe(vm.options[2].label)
|
||||
vm.value = null
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
})
|
||||
|
||||
it('sync set value and options', async () => {
|
||||
const wrapper = createSelect()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
const vm = wrapper.vm as any
|
||||
vm.value = vm.options[1].value
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(placeholder.text()).toBe(vm.options[1].label)
|
||||
vm.options[1].label = 'option bb aa'
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(placeholder.text()).toBe('option bb aa')
|
||||
})
|
||||
|
||||
@ -227,18 +234,18 @@ describe('Select', () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const options = getOptions()
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
expect(vm.value).toBe('')
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
options[2].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value).toBe(vm.options[2].value)
|
||||
expect(placeholder.text()).toBe(vm.options[2].label)
|
||||
options[4].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value).toBe(vm.options[4].value)
|
||||
expect(placeholder.text()).toBe(vm.options[4].label)
|
||||
expect(vm.count).toBe(2)
|
||||
@ -268,22 +275,22 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
const option = document.querySelector<HTMLElement>(`.el-select-dropdown__option-item.is-disabled`)
|
||||
expect(option.textContent).toBe(vm.options[1].label)
|
||||
option.click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value).toBe('')
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
vm.options[2].disabled = true
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const options = document.querySelectorAll<HTMLElement>(`.el-select-dropdown__option-item.is-disabled`)
|
||||
expect(options.length).toBe(2)
|
||||
expect(options.item(1).textContent).toBe(vm.options[2].label)
|
||||
options.item(1).click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value).toBe('')
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
})
|
||||
@ -296,7 +303,7 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.find(`.${WRAPPER_CLASS_NAME}`).classes()).toContain('is-disabled')
|
||||
})
|
||||
|
||||
@ -313,7 +320,7 @@ describe('Select', () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
await wrapper.trigger('click')
|
||||
expect(vm.visible).toBeTruthy()
|
||||
@ -325,7 +332,7 @@ describe('Select', () => {
|
||||
})
|
||||
const vm = wrapper.vm as any
|
||||
vm.value = vm.options[1].value
|
||||
await nextTick
|
||||
await nextTick()
|
||||
await clickClearButton(wrapper)
|
||||
expect(vm.value).toBe('')
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
@ -343,15 +350,15 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const options = getOptions()
|
||||
options[1].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(1)
|
||||
expect(vm.value[0]).toBe(vm.options[1].value)
|
||||
options[3].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(2)
|
||||
expect(vm.value[1]).toBe(vm.options[3].value)
|
||||
const tagIcon = wrapper.find('.el-tag__close')
|
||||
@ -373,7 +380,7 @@ describe('Select', () => {
|
||||
},
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const options = getOptions()
|
||||
options[0].click()
|
||||
@ -400,16 +407,16 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const options = getOptions()
|
||||
options[1].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
options[2].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(2)
|
||||
options[3].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(2)
|
||||
})
|
||||
})
|
||||
@ -431,12 +438,12 @@ describe('Select', () => {
|
||||
const selectVm = select.vm as any
|
||||
// Simulate focus state to trigger menu multiple times
|
||||
selectVm.toggleMenu()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
selectVm.toggleMenu()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
// Simulate click the outside
|
||||
selectVm.handleClickOutside()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(onFocus).toHaveBeenCalledTimes(1)
|
||||
expect(onBlur).toHaveBeenCalled()
|
||||
})
|
||||
@ -463,20 +470,20 @@ describe('Select', () => {
|
||||
const selectVm = select.vm as any
|
||||
// Simulate focus state to trigger menu multiple times
|
||||
selectVm.toggleMenu()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
selectVm.toggleMenu()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
// Select multiple items in multiple mode without triggering focus
|
||||
const options = getOptions()
|
||||
options[1].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
options[2].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(onFocus).toHaveBeenCalledTimes(1)
|
||||
// Simulate click the outside
|
||||
selectVm.handleClickOutside()
|
||||
await nextTick
|
||||
await nextTick
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
expect(onBlur).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@ -487,14 +494,14 @@ describe('Select', () => {
|
||||
onChange: handleChanged,
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
vm.value = 'option_2'
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(handleChanged).toHaveBeenCalledTimes(0)
|
||||
const options = getOptions()
|
||||
options[4].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(handleChanged).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@ -505,6 +512,7 @@ describe('Select', () => {
|
||||
const wrapper = createSelect({
|
||||
data: () => {
|
||||
return {
|
||||
popperAppendToBody: false,
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
@ -525,23 +533,25 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
const vm = wrapper.vm as any
|
||||
const input = wrapper.find('input')
|
||||
await wrapper.trigger('click')
|
||||
// create a new option
|
||||
await input.trigger('compositionupdate', {
|
||||
data: '1111',
|
||||
})
|
||||
const options = getOptions()
|
||||
await nextTick()
|
||||
const select = wrapper.findComponent(Select)
|
||||
const selectVm = select.vm as any
|
||||
selectVm.expanded = true
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const input = wrapper.find('input')
|
||||
// create a new option
|
||||
input.element.value = '1111'
|
||||
await input.trigger('input')
|
||||
await nextTick()
|
||||
expect(selectVm.filteredOptions.length).toBe(1)
|
||||
// selected the new option
|
||||
await options[0].click()
|
||||
selectVm.onSelect(selectVm.filteredOptions[0])
|
||||
expect(vm.value).toBe('1111')
|
||||
// closed the menu
|
||||
await wrapper.trigger('click')
|
||||
selectVm.expanded = false
|
||||
await nextTick()
|
||||
selectVm.expanded = true
|
||||
await nextTick()
|
||||
expect(selectVm.filteredOptions.length).toBe(4)
|
||||
selectVm.handleClear()
|
||||
expect(selectVm.filteredOptions.length).toBe(3)
|
||||
@ -572,24 +582,24 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
await wrapper.trigger('click')
|
||||
await wrapper.find('input').trigger('compositionupdate', {
|
||||
data: '1111',
|
||||
})
|
||||
const options = getOptions()
|
||||
const input = wrapper.find('input')
|
||||
input.element.value = '1111'
|
||||
await input.trigger('input')
|
||||
await nextTick()
|
||||
const select = wrapper.findComponent(Select)
|
||||
const selectVm = select.vm as any
|
||||
expect(selectVm.filteredOptions.length).toBe(1)
|
||||
// selected the new option
|
||||
await options[0].click()
|
||||
selectVm.onSelect(selectVm.filteredOptions[0])
|
||||
// closed the menu
|
||||
await wrapper.trigger('click')
|
||||
await wrapper.find('input').trigger('compositionupdate', {
|
||||
data: '2222',
|
||||
})
|
||||
await getOptions()[0].click()
|
||||
input.element.value = '2222'
|
||||
await input.trigger('input')
|
||||
await nextTick()
|
||||
selectVm.onSelect(selectVm.filteredOptions[0])
|
||||
expect(JSON.stringify(vm.value)).toBe(JSON.stringify(['1111', '2222']))
|
||||
await wrapper.trigger('click')
|
||||
expect(selectVm.filteredOptions.length).toBe(5)
|
||||
@ -618,7 +628,7 @@ describe('Select', () => {
|
||||
empty: '<div class="empty-slot">EmptySlot</div>',
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.find('.empty-slot').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
@ -645,11 +655,11 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
vm.value = '2'
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const select = wrapper.findComponent(Select)
|
||||
const selectVm = select.vm as any
|
||||
selectVm.toggleMenu()
|
||||
@ -666,12 +676,12 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
vm.value = undefined
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(placeholder.text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
})
|
||||
|
||||
@ -683,7 +693,7 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
const vm = wrapper.vm as any
|
||||
const placeholder = wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`)
|
||||
expect(placeholder.text()).toBe(vm.value)
|
||||
@ -707,7 +717,7 @@ describe('Select', () => {
|
||||
`,
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.custom-renderer').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
@ -719,24 +729,24 @@ describe('Select', () => {
|
||||
options: [
|
||||
{
|
||||
value: 1,
|
||||
lable: 'option 1',
|
||||
label: 'option 1',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
lable: 'option 2',
|
||||
label: 'option 2',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
lable: 'option 3',
|
||||
label: 'option 3',
|
||||
},
|
||||
],
|
||||
value: [2, 3],
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.el-tag').length).toBe(2)
|
||||
const tagCloseIcons = wrapper.findAll('.el-tag__close')
|
||||
expect(tagCloseIcons.length).toBe(1)
|
||||
@ -754,11 +764,11 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.el-tag').length).toBe(3)
|
||||
const vm = wrapper.vm as any
|
||||
vm.value.splice(0, 1)
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.el-tag').length).toBe(2)
|
||||
})
|
||||
|
||||
@ -773,7 +783,7 @@ describe('Select', () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).exists()).toBeFalsy()
|
||||
// When all tags are removed, the placeholder should be displayed
|
||||
const tagCloseIcon = wrapper.find('.el-tag__close')
|
||||
@ -782,14 +792,109 @@ describe('Select', () => {
|
||||
// The placeholder should disappear after it is selected again
|
||||
const options = getOptions()
|
||||
options[0].click()
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).exists()).toBeFalsy()
|
||||
// Simulate keyboard events
|
||||
const selectInput = wrapper.find('input')
|
||||
selectInput.trigger('keydown', {
|
||||
key: EVENT_CODE.backspace,
|
||||
})
|
||||
await nextTick
|
||||
await nextTick()
|
||||
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe(DEFAULT_PLACEHOLDER)
|
||||
})
|
||||
|
||||
describe('filter method', () => {
|
||||
async function testFilterMethod({ multiple = false }) {
|
||||
const filterMethod = jest.fn()
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
filterable: true,
|
||||
multiple,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterMethod,
|
||||
},
|
||||
})
|
||||
const input = wrapper.find('input')
|
||||
input.element.value = 'query'
|
||||
await input.trigger('input')
|
||||
expect(filterMethod).toHaveBeenCalled()
|
||||
}
|
||||
it('should call filter method', async () => {
|
||||
await testFilterMethod({ multiple: false })
|
||||
})
|
||||
|
||||
it('should call filter method in multiple mode', async () => {
|
||||
await testFilterMethod({ multiple: true })
|
||||
})
|
||||
|
||||
it('should re-render', async () => {
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterMethod () {
|
||||
this.options = [
|
||||
{
|
||||
value: 1,
|
||||
label: 'option 1',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'option 2',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'option 3',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
})
|
||||
const input = wrapper.find('input')
|
||||
input.element.value = 'query'
|
||||
await input.trigger('input')
|
||||
await nextTick()
|
||||
input.element.value = ''
|
||||
await input.trigger('input')
|
||||
await nextTick()
|
||||
const options = getOptions()
|
||||
expect(options.length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('remote search', () => {
|
||||
async function testRemoteSearch({ multiple = false }) {
|
||||
const remoteMethod = jest.fn()
|
||||
const wrapper = createSelect({
|
||||
data() {
|
||||
return {
|
||||
filterable: true,
|
||||
remote: true,
|
||||
multiple,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remoteMethod,
|
||||
},
|
||||
})
|
||||
const input = wrapper.find('input')
|
||||
input.element.value = 'query'
|
||||
await input.trigger('input')
|
||||
expect(remoteMethod).toHaveBeenCalled()
|
||||
}
|
||||
it('should call remote method', async () => {
|
||||
await testRemoteSearch({ multiple: false })
|
||||
})
|
||||
|
||||
it('should call remote method in multiple mode', async () => {
|
||||
await testRemoteSearch({ multiple: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -6,7 +6,7 @@
|
||||
'el-select-dropdown__option-item': true,
|
||||
'is-selected': selected,
|
||||
'is-disabled': disabled,
|
||||
'is-craeted': created,
|
||||
'is-created': created,
|
||||
'hover': hovering
|
||||
}"
|
||||
@mouseenter="hoverItem"
|
||||
|
@ -10,7 +10,7 @@
|
||||
>
|
||||
<el-popper
|
||||
ref="popper"
|
||||
v-model:visible="expanded"
|
||||
v-model:visible="dropdownMenuVisible"
|
||||
:append-to-body="popperAppendToBody"
|
||||
:popper-class="`el-select-v2__popper ${popperClass}`"
|
||||
:gpu-acceleration="false"
|
||||
@ -109,11 +109,11 @@
|
||||
:name="name"
|
||||
:unselectable="expanded ? 'on' : undefined"
|
||||
@update:modelValue="onUpdateInputValue"
|
||||
@click.stop.prevent="handleInputBoxClick"
|
||||
@focus="handleFocus"
|
||||
@input="onInput"
|
||||
@compositionupdate="onCompositionUpdate"
|
||||
@compositionend="onInput"
|
||||
@compositionstart="handleCompositionStart"
|
||||
@compositionupdate="handleCompositionUpdate"
|
||||
@compositionend="handleCompositionEnd"
|
||||
@keydown.esc.stop.prevent="handleEsc"
|
||||
@keydown.delete.stop="handleDel"
|
||||
>
|
||||
@ -149,9 +149,9 @@
|
||||
spellcheck="false"
|
||||
type="text"
|
||||
:unselectable="expanded ? 'on' : undefined"
|
||||
@click.stop.prevent="handleInputBoxClick"
|
||||
@compositionend="onInput"
|
||||
@compositionupdate="onCompositionUpdate"
|
||||
@compositionstart="handleCompositionStart"
|
||||
@compositionupdate="handleCompositionUpdate"
|
||||
@compositionend="handleCompositionEnd"
|
||||
@focus="handleFocus"
|
||||
@input="onInput"
|
||||
@keydown.esc.stop.prevent="handleEsc"
|
||||
@ -203,7 +203,7 @@
|
||||
</template>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<p class="el-select-v2__empty">{{ emptyText }}</p>
|
||||
<p class="el-select-v2__empty">{{ emptyText ? emptyText : '' }}</p>
|
||||
</slot>
|
||||
</template>
|
||||
</el-select-menu>
|
||||
|
32
packages/components/select-v2/src/useInput.ts
Normal file
32
packages/components/select-v2/src/useInput.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { ref } from 'vue'
|
||||
import { isKorean } from '@element-plus/utils/isDef'
|
||||
import { isFunction } from '@vue/shared'
|
||||
|
||||
export function useInput(handleInput: (event: InputEvent) => void) {
|
||||
const isComposing = ref(false)
|
||||
|
||||
const handleCompositionStart = () => {
|
||||
isComposing.value = true
|
||||
}
|
||||
|
||||
const handleCompositionUpdate = event => {
|
||||
const text = event.target.value
|
||||
const lastCharacter = text[text.length - 1] || ''
|
||||
isComposing.value = !isKorean(lastCharacter)
|
||||
}
|
||||
|
||||
const handleCompositionEnd = event => {
|
||||
if (isComposing.value) {
|
||||
isComposing.value = false
|
||||
if (isFunction(handleInput)) {
|
||||
handleInput(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
handleCompositionStart,
|
||||
handleCompositionUpdate,
|
||||
handleCompositionEnd,
|
||||
}
|
||||
}
|
@ -33,9 +33,10 @@ import { flattenOptions } from './util'
|
||||
import type { ExtractPropTypes, CSSProperties } from 'vue'
|
||||
import type { ElFormContext, ElFormItemContext } from '@element-plus/tokens'
|
||||
import type { OptionType, Option } from './select.types'
|
||||
import { useInput } from './useInput'
|
||||
|
||||
const DEFAULT_INPUT_PLACEHOLDER = ''
|
||||
const MINIMUM_INPUT_WIDTH = 4
|
||||
const MINIMUM_INPUT_WIDTH = 11
|
||||
|
||||
const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
|
||||
@ -64,6 +65,7 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
inputWidth: 240,
|
||||
initialInputHeight: 0,
|
||||
previousQuery: null,
|
||||
previousValue: '',
|
||||
query: '',
|
||||
selectedLabel: '',
|
||||
softFocus: false,
|
||||
@ -116,8 +118,8 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
if (props.loading) {
|
||||
return props.loadingText || t('el.select.loading')
|
||||
} else {
|
||||
if (props.remote && states.query === '' && options.length === 0) return false
|
||||
if (props.filterable && states.query && options.length > 0) {
|
||||
if (props.remote && states.inputValue === '' && options.length === 0) return false
|
||||
if (props.filterable && states.inputValue && options.length > 0) {
|
||||
return props.noMatchText || t('el.select.noMatch')
|
||||
}
|
||||
if (options.length === 0) {
|
||||
@ -135,7 +137,9 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
const containsQueryString = query ? o.label.includes(query) : true
|
||||
return containsQueryString
|
||||
}
|
||||
|
||||
if (props.loading) {
|
||||
return []
|
||||
}
|
||||
return flattenOptions((props.options as OptionType[]).concat(states.createdOptions).map(v => {
|
||||
if (isArray(v.options)) {
|
||||
const filtered = v.options.filter(isValidOption)
|
||||
@ -146,7 +150,7 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isValidOption(v as Option)) {
|
||||
if (props.remote || isValidOption(v as Option)) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
@ -161,13 +165,11 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
const calculatePopperSize = () => {
|
||||
popperSize.value = selectRef.value?.getBoundingClientRect?.()?.width || 200
|
||||
}
|
||||
// const readonly = computed(() => !props.filterable || props.multiple || (!isIE() && !isEdge() && !expanded.value))
|
||||
|
||||
const inputWrapperStyle = computed(() => {
|
||||
|
||||
return {
|
||||
width: `${
|
||||
// 7 represents the margin-left value
|
||||
states.calculatedWidth === 0
|
||||
? MINIMUM_INPUT_WIDTH
|
||||
: Math.ceil(states.calculatedWidth) + MINIMUM_INPUT_WIDTH
|
||||
@ -209,8 +211,11 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
return -1
|
||||
})
|
||||
|
||||
const dropdownMenuVisible = computed(() => expanded.value && emptyText.value !== false)
|
||||
|
||||
// hooks
|
||||
const { createNewOption, removeNewOption, selectNewOption, clearAllNewOption } = useAllowCreate(props, states)
|
||||
const { handleCompositionStart, handleCompositionUpdate, handleCompositionEnd } = useInput(e => onInput(e))
|
||||
|
||||
// methods
|
||||
const focusAndUpdatePopup = () => {
|
||||
@ -221,78 +226,35 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
const toggleMenu = () => {
|
||||
if (props.automaticDropdown) return
|
||||
if (!selectDisabled.value) {
|
||||
// if (states.menuVisibleOnFocus) {
|
||||
// states.menuVisibleOnFocus = false
|
||||
// } else {
|
||||
// if (expanded.value) {
|
||||
// expanded.value = false
|
||||
// }
|
||||
if (states.isComposing) states.softFocus = true
|
||||
expanded.value = !expanded.value
|
||||
inputRef.value?.focus?.()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
const handleQueryChange = (val: string) => {
|
||||
if (states.previousQuery === val || states.isOnComposition) return
|
||||
if (
|
||||
states.previousQuery === null &&
|
||||
(isFunction(props.filterMethod) || isFunction(props.remoteMethod))
|
||||
) {
|
||||
states.previousQuery = val
|
||||
return
|
||||
}
|
||||
states.previousQuery = val
|
||||
nextTick(() => {
|
||||
if (expanded.value) popper.value?.update?.()
|
||||
})
|
||||
states.hoveringIndex = -1
|
||||
if (props.multiple && props.filterable) {
|
||||
nextTick(() => {
|
||||
const length = inputRef.value.value.length * 15 + 20
|
||||
states.inputLength = props.collapseTags ? Math.min(50, length) : length
|
||||
resetInputHeight()
|
||||
})
|
||||
}
|
||||
if (props.remote && isFunction(props.remoteMethod)) {
|
||||
states.hoveringIndex = -1
|
||||
props.remoteMethod(val)
|
||||
} else if (isFunction(props.filterMethod)) {
|
||||
props.filterMethod(val)
|
||||
// states.selectEmitter.emit('elOptionGroupQueryChange')
|
||||
} else {
|
||||
// states.selectEmitter.emit('elOptionQueryChange', val)
|
||||
// states.selectEmitter.emit('elOptionGroupQueryChange')
|
||||
}
|
||||
if (props.defaultFirstOption && (props.filterable || props.remote)) {
|
||||
// checkDefaultFirstOption()
|
||||
}
|
||||
}
|
||||
|
||||
// const handleComposition = event => {
|
||||
// const text = event.target.value
|
||||
// if (event.type === 'compositionend') {
|
||||
// states.isOnComposition = false
|
||||
// nextTick(() => handleQueryChange(text))
|
||||
// } else {
|
||||
// const lastCharacter = text[text.length - 1] || ''
|
||||
// states.isOnComposition = !isKorean(lastCharacter)
|
||||
// }
|
||||
// }
|
||||
|
||||
const onInputChange = () => {
|
||||
if (props.filterable && states.inputValue !== states.selectedLabel) {
|
||||
states.query = states.selectedLabel
|
||||
handleQueryChange(states.query)
|
||||
}
|
||||
handleQueryChange(states.inputValue)
|
||||
return nextTick(() => {
|
||||
createNewOption(states.inputValue)
|
||||
})
|
||||
}
|
||||
|
||||
const debouncedOnInputChange = lodashDebounce(onInputChange, debounce.value)
|
||||
|
||||
const debouncedQueryChange = lodashDebounce(e => {
|
||||
handleQueryChange(e.target.value)
|
||||
}, debounce.value)
|
||||
const handleQueryChange = (val: string) => {
|
||||
if (states.previousQuery === val) {
|
||||
return
|
||||
}
|
||||
states.previousQuery = val
|
||||
if (props.filterable && isFunction(props.filterMethod)) {
|
||||
props.filterMethod(val)
|
||||
} else if (props.filterable && props.remote && isFunction(props.remoteMethod)) {
|
||||
props.remoteMethod(val)
|
||||
}
|
||||
}
|
||||
|
||||
const emitChange = (val: any | any[]) => {
|
||||
if (!isEqual(props.modelValue, val)) {
|
||||
@ -303,9 +265,9 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
const update = (val: any) => {
|
||||
emit(UPDATE_MODEL_EVENT, val)
|
||||
emitChange(val)
|
||||
states.previousValue = val.toString()
|
||||
}
|
||||
|
||||
// TODO 提取
|
||||
const getValueIndex = (arr = [], value: unknown) => {
|
||||
if (!isObject(value)) return arr.indexOf(value)
|
||||
|
||||
@ -425,12 +387,6 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
const handleInputBoxClick = () => {
|
||||
if (states.displayInputValue.length === 0 && expanded.value) {
|
||||
expanded.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = (event: FocusEvent) => {
|
||||
const focused = states.isComposing
|
||||
states.isComposing = true
|
||||
@ -565,7 +521,9 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
}
|
||||
}
|
||||
|
||||
const onInput = () => {
|
||||
const onInput = event => {
|
||||
const value = event.target.value
|
||||
onUpdateInputValue(value)
|
||||
if (states.displayInputValue.length > 0 && !expanded.value) {
|
||||
expanded.value = true
|
||||
}
|
||||
@ -574,13 +532,11 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
if (props.multiple) {
|
||||
resetInputHeight()
|
||||
}
|
||||
debouncedOnInputChange()
|
||||
createNewOption(states.displayInputValue)
|
||||
}
|
||||
|
||||
const onCompositionUpdate = (e: CompositionEvent) => {
|
||||
onUpdateInputValue(states.displayInputValue += e.data)
|
||||
onInput()
|
||||
if (props.remote) {
|
||||
debouncedOnInputChange()
|
||||
} else {
|
||||
return onInputChange()
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickOutside = () => {
|
||||
@ -590,7 +546,7 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
|
||||
const handleMenuEnter = () => {
|
||||
states.inputValue = states.displayInputValue
|
||||
nextTick(() => {
|
||||
return nextTick(() => {
|
||||
if (~indexRef.value) {
|
||||
scrollToItem(indexRef.value)
|
||||
}
|
||||
@ -642,8 +598,20 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
}
|
||||
})
|
||||
|
||||
watch([() => props.modelValue, () => props.options], () => {
|
||||
initStates()
|
||||
watch(() => props.modelValue, val => {
|
||||
if (!val || val.toString() !== states.previousValue) {
|
||||
initStates()
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
watch(() => props.options, () => {
|
||||
const input = inputRef.value
|
||||
// filter or remote-search scenarios are not initialized
|
||||
if (!input || (input && document.activeElement !== input)) {
|
||||
initStates()
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
@ -674,6 +642,7 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
iconClass,
|
||||
inputWrapperStyle,
|
||||
popperSize,
|
||||
dropdownMenuVisible,
|
||||
// readonly,
|
||||
shouldShowPlaceholder,
|
||||
selectDisabled,
|
||||
@ -694,7 +663,6 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
|
||||
// methods exports
|
||||
debouncedOnInputChange,
|
||||
debouncedQueryChange,
|
||||
deleteTag,
|
||||
getLabel,
|
||||
getValueKey,
|
||||
@ -704,16 +672,17 @@ const useSelect = (props: ExtractPropTypes<typeof SelectProps>, emit) => {
|
||||
handleDel,
|
||||
handleEsc,
|
||||
handleFocus,
|
||||
handleInputBoxClick,
|
||||
handleMenuEnter,
|
||||
toggleMenu,
|
||||
scrollTo: scrollToItem,
|
||||
onCompositionUpdate,
|
||||
onInput,
|
||||
onKeyboardNavigate,
|
||||
onKeyboardSelect,
|
||||
onSelect,
|
||||
onUpdateInputValue,
|
||||
handleCompositionStart,
|
||||
handleCompositionEnd,
|
||||
handleCompositionUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,7 @@ $--input-inline-start: 15px !default;
|
||||
right: 5px;
|
||||
height: 40px;
|
||||
top: 50%;
|
||||
margin-top: -20px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
@include e(caret) {
|
||||
|
@ -365,7 +365,78 @@ Create and select new items that are not included in select options
|
||||
|
||||
### Remote search
|
||||
|
||||
WIP 👷♀️
|
||||
Enter keywords and search data from server.
|
||||
|
||||
:::demo Set the value of `filterable` and `remote` with `true` to enable remote search, and you should pass the `remote-method`. `remote-method` is a `Function` that gets called when the input value changes, and its parameter is the current input value.
|
||||
```html
|
||||
<template>
|
||||
<el-select-v2
|
||||
v-model="value"
|
||||
style="width:200px"
|
||||
multiple
|
||||
size="medium"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
clearable
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
placeholder="Please enter a keyword"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.list = this.states.map(item => {
|
||||
return { value: `value:${item}`, label: `label:${item}` }
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
remoteMethod(query) {
|
||||
if (query !== '') {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.options = this.list.filter(item => {
|
||||
return item.label.toLowerCase()
|
||||
.indexOf(query.toLowerCase()) > -1
|
||||
})
|
||||
}, 200)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
states: ['Alabama', 'Alaska', 'Arizona',
|
||||
'Arkansas', 'California', 'Colorado',
|
||||
'Connecticut', 'Delaware', 'Florida',
|
||||
'Georgia', 'Hawaii', 'Idaho', 'Illinois',
|
||||
'Indiana', 'Iowa', 'Kansas', 'Kentucky',
|
||||
'Louisiana', 'Maine', 'Maryland',
|
||||
'Massachusetts', 'Michigan', 'Minnesota',
|
||||
'Mississippi', 'Missouri', 'Montana',
|
||||
'Nebraska', 'Nevada', 'New Hampshire',
|
||||
'New Jersey', 'New Mexico', 'New York',
|
||||
'North Carolina', 'North Dakota', 'Ohio',
|
||||
'Oklahoma', 'Oregon', 'Pennsylvania',
|
||||
'Rhode Island', 'South Carolina',
|
||||
'South Dakota', 'Tennessee', 'Texas',
|
||||
'Utah', 'Vermont', 'Virginia',
|
||||
'Washington', 'West Virginia', 'Wisconsin',
|
||||
'Wyoming'],
|
||||
options: [],
|
||||
value: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Keyboard navigation
|
||||
|
||||
|
@ -364,9 +364,80 @@ Crear y seleccionar nuevos items que no están incluidas en las opciones de sele
|
||||
```
|
||||
:::
|
||||
|
||||
### Remote search
|
||||
### Búsqueda remota
|
||||
|
||||
WIP 👷♀️
|
||||
Introduzca palabras y datos para buscar desde el servidor.
|
||||
|
||||
:::demo Configure el valor de `filterable` y `remote` con `true` para habilitar la búsqueda remota, y debería pasar el método `remote-method`. `remote-method` es una función que se llama cuando el valor del input cambia, y su parámetro es el valor del input actual.
|
||||
```html
|
||||
<template>
|
||||
<el-select-v2
|
||||
v-model="value"
|
||||
style="width:200px"
|
||||
multiple
|
||||
size="medium"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
clearable
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
placeholder="Please enter a keyword"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.list = this.states.map(item => {
|
||||
return { value: `value:${item}`, label: `label:${item}` }
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
remoteMethod(query) {
|
||||
if (query !== '') {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.options = this.list.filter(item => {
|
||||
return item.label.toLowerCase()
|
||||
.indexOf(query.toLowerCase()) > -1
|
||||
})
|
||||
}, 200)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
states: ['Alabama', 'Alaska', 'Arizona',
|
||||
'Arkansas', 'California', 'Colorado',
|
||||
'Connecticut', 'Delaware', 'Florida',
|
||||
'Georgia', 'Hawaii', 'Idaho', 'Illinois',
|
||||
'Indiana', 'Iowa', 'Kansas', 'Kentucky',
|
||||
'Louisiana', 'Maine', 'Maryland',
|
||||
'Massachusetts', 'Michigan', 'Minnesota',
|
||||
'Mississippi', 'Missouri', 'Montana',
|
||||
'Nebraska', 'Nevada', 'New Hampshire',
|
||||
'New Jersey', 'New Mexico', 'New York',
|
||||
'North Carolina', 'North Dakota', 'Ohio',
|
||||
'Oklahoma', 'Oregon', 'Pennsylvania',
|
||||
'Rhode Island', 'South Carolina',
|
||||
'South Dakota', 'Tennessee', 'Texas',
|
||||
'Utah', 'Vermont', 'Virginia',
|
||||
'Washington', 'West Virginia', 'Wisconsin',
|
||||
'Wyoming'],
|
||||
options: [],
|
||||
value: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Keyboard navigation
|
||||
|
||||
@ -391,6 +462,9 @@ Some APIs are still undergoing (comparing to the non-virtualized select), becaus
|
||||
| autocomplete | select input 的 autocomplete 属性 | string | — | off |
|
||||
| placeholder | the autocomplete attribute of select input | string | — | Please select |
|
||||
| filterable | is filterable | boolean | — | false |
|
||||
| filter-method | método de filtrado personalizado | function | — | — |
|
||||
| remote | si las opciones se traerán desde el servidor | boolean | — | false |
|
||||
| remote-method | método de búsqueda remota personalizada | function | — | — |
|
||||
| allow-create | si esta permitido crear nuevos items. Para usar esto, `filterable` debe ser `true`. | boolean | — | false |
|
||||
| no-data-text | displayed text when there is no options, you can also use slot empty | string | — | No Data |
|
||||
| popper-class | custom class name for Select's dropdown | string | — | — |
|
||||
|
@ -365,9 +365,80 @@ Vous pouvez entrer des choix dans le champ de sélection qui ne sont pas incluse
|
||||
```
|
||||
:::
|
||||
|
||||
### Remote search
|
||||
### Recherche à distance
|
||||
|
||||
WIP 👷♀️
|
||||
Vous pouvez aller chercher les options sur le serveur de manière dynamique.
|
||||
|
||||
:::demo Ajoutez `filterable` et `remote` pour activer la recherche distante, ainsi que `remote-method`. Cette dernière est une `Function` qui est appelée lorsque la valeur change, avec pour paramètre la valeur courante.
|
||||
```html
|
||||
<template>
|
||||
<el-select-v2
|
||||
v-model="value"
|
||||
style="width:200px"
|
||||
multiple
|
||||
size="medium"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
clearable
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
placeholder="Entrez un mot-clé"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.list = this.states.map(item => {
|
||||
return { value: `value:${item}`, label: `label:${item}` }
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
remoteMethod(query) {
|
||||
if (query !== '') {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.options = this.list.filter(item => {
|
||||
return item.label.toLowerCase()
|
||||
.indexOf(query.toLowerCase()) > -1
|
||||
})
|
||||
}, 200)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
states: ['Alabama', 'Alaska', 'Arizona',
|
||||
'Arkansas', 'California', 'Colorado',
|
||||
'Connecticut', 'Delaware', 'Florida',
|
||||
'Georgia', 'Hawaii', 'Idaho', 'Illinois',
|
||||
'Indiana', 'Iowa', 'Kansas', 'Kentucky',
|
||||
'Louisiana', 'Maine', 'Maryland',
|
||||
'Massachusetts', 'Michigan', 'Minnesota',
|
||||
'Mississippi', 'Missouri', 'Montana',
|
||||
'Nebraska', 'Nevada', 'New Hampshire',
|
||||
'New Jersey', 'New Mexico', 'New York',
|
||||
'North Carolina', 'North Dakota', 'Ohio',
|
||||
'Oklahoma', 'Oregon', 'Pennsylvania',
|
||||
'Rhode Island', 'South Carolina',
|
||||
'South Dakota', 'Tennessee', 'Texas',
|
||||
'Utah', 'Vermont', 'Virginia',
|
||||
'Washington', 'West Virginia', 'Wisconsin',
|
||||
'Wyoming'],
|
||||
options: [],
|
||||
value: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Keyboard navigation
|
||||
|
||||
@ -392,6 +463,9 @@ Some APIs are still undergoing (comparing to the non-virtualized select), becaus
|
||||
| autocomplete | select input 的 autocomplete 属性 | string | — | off |
|
||||
| placeholder | the autocomplete attribute of select input | string | — | Please select |
|
||||
| filterable | is filterable | boolean | — | false |
|
||||
| filter-method | Méthode de filtrage personnalisée. | function | — | — |
|
||||
| remote | Si les options sont chargées dynamiquement depuis le serveur. | boolean | — | false |
|
||||
| remote-method | Méthode pour la recherche distante. | function | — | — |
|
||||
| allow-create | Si l'utilisateur peut créer des options. Dans ce cas `filterable` doit être activé. | boolean | — | false |
|
||||
| no-data-text | displayed text when there is no options, you can also use slot empty | string | — | No Data |
|
||||
| popper-class | custom class name for Select's dropdown | string | — | — |
|
||||
|
@ -363,9 +363,80 @@ We can clear all the selected options at once, also applicable for single select
|
||||
```
|
||||
:::
|
||||
|
||||
### Remote search
|
||||
### リモート検索
|
||||
|
||||
WIP 👷♀️
|
||||
サーバーからキーワードや検索データを入力します。
|
||||
|
||||
:::demo リモート検索を有効にするには `filterable` と `remote` を `true` を設定し、`remote-method` を渡す必要がある。`remote-method`は入力値が変化したときに呼び出される `Function` であり、そのパラメータは現在の入力値である。
|
||||
```html
|
||||
<template>
|
||||
<el-select-v2
|
||||
v-model="value"
|
||||
style="width:200px"
|
||||
multiple
|
||||
size="medium"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
clearable
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
placeholder="Please enter a keyword"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.list = this.states.map(item => {
|
||||
return { value: `value:${item}`, label: `label:${item}` }
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
remoteMethod(query) {
|
||||
if (query !== '') {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.options = this.list.filter(item => {
|
||||
return item.label.toLowerCase()
|
||||
.indexOf(query.toLowerCase()) > -1
|
||||
})
|
||||
}, 200)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
states: ['Alabama', 'Alaska', 'Arizona',
|
||||
'Arkansas', 'California', 'Colorado',
|
||||
'Connecticut', 'Delaware', 'Florida',
|
||||
'Georgia', 'Hawaii', 'Idaho', 'Illinois',
|
||||
'Indiana', 'Iowa', 'Kansas', 'Kentucky',
|
||||
'Louisiana', 'Maine', 'Maryland',
|
||||
'Massachusetts', 'Michigan', 'Minnesota',
|
||||
'Mississippi', 'Missouri', 'Montana',
|
||||
'Nebraska', 'Nevada', 'New Hampshire',
|
||||
'New Jersey', 'New Mexico', 'New York',
|
||||
'North Carolina', 'North Dakota', 'Ohio',
|
||||
'Oklahoma', 'Oregon', 'Pennsylvania',
|
||||
'Rhode Island', 'South Carolina',
|
||||
'South Dakota', 'Tennessee', 'Texas',
|
||||
'Utah', 'Vermont', 'Virginia',
|
||||
'Washington', 'West Virginia', 'Wisconsin',
|
||||
'Wyoming'],
|
||||
options: [],
|
||||
value: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Keyboard navigation
|
||||
|
||||
@ -390,6 +461,9 @@ Some APIs are still undergoing (comparing to the non-virtualized select), becaus
|
||||
| autocomplete | select input 的 autocomplete 属性 | string | — | off |
|
||||
| placeholder | the autocomplete attribute of select input | string | — | Please select |
|
||||
| filterable | is filterable | boolean | — | false |
|
||||
| filter-method | カスタムフィルタ方式 | function | — | — |
|
||||
| remote | オプションがサーバから読み込まれているかどうか | boolean | — | false |
|
||||
| remote-method | カスタムリモート検索法 | function | — | — |
|
||||
| allow-create | 新しいアイテムの作成を許可するかどうかを指定します。これを使うには、`filterable` がtrueでなければなりません。 | boolean | — | false |
|
||||
| no-data-text | displayed text when there is no options, you can also use slot empty | string | — | No Data |
|
||||
| popper-class | custom class name for Select's dropdown | string | — | — |
|
||||
|
@ -366,7 +366,77 @@
|
||||
|
||||
### 远程搜索
|
||||
|
||||
WIP (该功能还在施工中👷♀️)
|
||||
从服务器搜索数据,输入关键字进行查找
|
||||
:::demo 为了启用远程搜索,需要将`filterable`和`remote`设置为`true`,同时传入一个`remote-method`。`remote-method`为一个`Function`,它会在输入值发生变化时调用,参数为当前输入值。
|
||||
```html
|
||||
<template>
|
||||
<el-select-v2
|
||||
v-model="value"
|
||||
style="width:200px"
|
||||
multiple
|
||||
size="medium"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
clearable
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
placeholder="请输入关键词"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.list = this.states.map(item => {
|
||||
return { value: `value:${item}`, label: `label:${item}` }
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
remoteMethod(query) {
|
||||
if (query !== '') {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.options = this.list.filter(item => {
|
||||
return item.label.toLowerCase()
|
||||
.indexOf(query.toLowerCase()) > -1
|
||||
})
|
||||
}, 200)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
states: ['Alabama', 'Alaska', 'Arizona',
|
||||
'Arkansas', 'California', 'Colorado',
|
||||
'Connecticut', 'Delaware', 'Florida',
|
||||
'Georgia', 'Hawaii', 'Idaho', 'Illinois',
|
||||
'Indiana', 'Iowa', 'Kansas', 'Kentucky',
|
||||
'Louisiana', 'Maine', 'Maryland',
|
||||
'Massachusetts', 'Michigan', 'Minnesota',
|
||||
'Mississippi', 'Missouri', 'Montana',
|
||||
'Nebraska', 'Nevada', 'New Hampshire',
|
||||
'New Jersey', 'New Mexico', 'New York',
|
||||
'North Carolina', 'North Dakota', 'Ohio',
|
||||
'Oklahoma', 'Oregon', 'Pennsylvania',
|
||||
'Rhode Island', 'South Carolina',
|
||||
'South Dakota', 'Tennessee', 'Texas',
|
||||
'Utah', 'Vermont', 'Virginia',
|
||||
'Washington', 'West Virginia', 'Wisconsin',
|
||||
'Wyoming'],
|
||||
options: [],
|
||||
value: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### 键盘操作
|
||||
|
||||
@ -391,6 +461,9 @@ WIP (该功能还在施工中👷♀️)
|
||||
| autocomplete | select input 的 autocomplete 属性 | string | — | off |
|
||||
| placeholder | 占位符 | string | — | 请选择 |
|
||||
| filterable | 是否可搜索 | boolean | — | false |
|
||||
| filter-method | 自定义搜索方法 | function | — | — |
|
||||
| remote | 是否为远程搜索 | boolean | — | false |
|
||||
| remote-method | 远程搜索方法 | function | — | — |
|
||||
| allow-create | 是否允许用户创建新条目,需配合 `filterable` 使用 | boolean | — | false |
|
||||
| no-data-text | 选项为空时显示的文字,也可以使用`#empty`设置 | string | — | 无数据 |
|
||||
| popper-class | Select 下拉框的类名 | string | — | — |
|
||||
|
Loading…
Reference in New Issue
Block a user