Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { mount } from '@vue/test-utils';
import { nextTick, ref } from 'vue';
import DAutoComplete from '../src/auto-complete';

// delay api
const wait = (delay = 300) =>
new Promise(resolve => setTimeout(() => resolve(true), delay));
Expand All @@ -13,7 +12,7 @@ describe('auto-complete', () => {
<d-auto-complete
:source="source"
v-model="value"
:dAutoCompleteWidth="450"
:width="450"
/>
`,
setup() {
Expand Down Expand Up @@ -71,7 +70,7 @@ describe('auto-complete', () => {
:source="source"
v-model="value"
:disabled="isDisabled"
/>
/>
<button @click="toggle">{{ isDisabled ? 'Enable AutoComplete' : 'Disable AutoComplete' }}</button>
</div>
`,
Expand Down Expand Up @@ -128,8 +127,8 @@ describe('auto-complete', () => {
disabledKey="disabled"
isSearching
:formatter="formatter"
>
<template #searchingTemplate="slotProps" >
>
<template #searching="slotProps" >
<div id="devui-is-searching-template">
{{slotProps}}
</div>
Expand Down Expand Up @@ -234,13 +233,13 @@ describe('auto-complete', () => {
<d-auto-complete
:source="source"
v-model="value"
>
<template #itemTemplate="slotProps" >
>
<template #item="slotProps" >
<div>
第{{slotProps.index}}项: {{slotProps.item}}
</div>
</template>
<template #noResultItemTemplate="slotProps" >
<template #nothing="slotProps" >
<div id="noResultItemTemplate">
{{slotProps}}
</div>
Expand Down Expand Up @@ -345,13 +344,16 @@ describe('auto-complete', () => {
expect(selectValueCB).toHaveBeenCalledTimes(1);
});
it('allowEmptyValueSearch ', async () => {
const div = document.createElement('div');
div.id="app";
document.body.appendChild(div);
const wrapper = mount({
components: {'d-auto-complete': DAutoComplete },
template: `
<d-auto-complete
:source="source"
v-model="value"
:allowEmptyValueSearch="allowEmptyValueSearch"
:allow-empty-value-search="allowEmptyValueSearch"
/>
`,
setup() {
Expand All @@ -377,41 +379,38 @@ describe('auto-complete', () => {
expect(input.element.value).toBe('');
await input.trigger('focus');
await nextTick();
await input.setValue('');
await wait(300);
await nextTick();
expect(wrapper.find('ul').element.childElementCount).toBe(5);
});

it('appendToBody & appendToBodyDirections', async () => {
it('appendToBody & position', async () => {
const wrapper = mount({
components: {'d-auto-complete': DAutoComplete },
template: `
<d-auto-complete
:source="source"
v-model="value"
:appendToBodyDirections="appendToBodyDirections"
:allowEmptyValueSearch="allowEmptyValueSearch"
:append-to-body="appendToBody"
:position="position"
/>
`,
setup() {
const value = ref('');
const allowEmptyValueSearch = ref(true);
const appendToBody = ref(true);
const source = [
'CC#',
'C',
'C++',
'CPython',
'CoffeeScript',
];
const appendToBodyDirections = ref({
originX: 'left',
originY: 'bottom',
overlayX: 'left',
overlayY: 'top',
});
const position = ref(['bottom']);
return {
value,
source,
allowEmptyValueSearch,
appendToBodyDirections
appendToBody,
position
};
}
});
Expand All @@ -424,10 +423,20 @@ describe('auto-complete', () => {
await nextTick();
await wait(300);
await nextTick();
expect(wrapper.find('ul').element.childElementCount).toBe(5);
expect(wrapper.find('.selected').element.innerHTML).toBe('CC#');
expect(wrapper.find('.devui-select-open').exists()).toBe(true);
const ul = document.querySelector('.devui-list-unstyled');
let lis = 0;
if(ul&&ul.getElementsByTagName('li')){
lis=ul.getElementsByTagName('li').length;
}
expect(lis).toBe(5);
const li_ed = document.querySelector('.selected');
let li_text = '';
if(li_ed&&li_ed.getElementsByTagName('li')){
li_text=li_ed.innerHTML;
}
expect(li_text).toBe('CC#');
});

it('latestSource',async () => {
const wrapper = mount({
components: {'d-auto-complete': DAutoComplete },
Expand Down Expand Up @@ -530,6 +539,7 @@ describe('auto-complete', () => {
expect(wrapper.find('.devui-auto-complete').exists()).toBe(true);
const input = wrapper.find('input');
expect(input.element.value).toBe('');
await input.trigger('click');
await input.setValue('c');
await nextTick();
expect(wrapper.find('.devui-dropdown-menu').exists()).toBe(true);
Expand Down
6 changes: 2 additions & 4 deletions packages/devui-vue/devui/auto-complete/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { App } from 'vue';
import AutoComplete from './src/auto-complete';

AutoComplete.install = function(app: App): void {
app.component(AutoComplete.name, AutoComplete);
};
export * from './src/auto-complete-types';

export { AutoComplete };

Expand All @@ -12,6 +10,6 @@ export default {
category: '数据录入',
status: '100%',
install(app: App): void {
app.use(AutoComplete as any);
app.component(AutoComplete.name, AutoComplete);
}
};
158 changes: 89 additions & 69 deletions packages/devui-vue/devui/auto-complete/src/auto-complete-types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import type { PropType, ExtractPropTypes, InjectionKey, SetupContext, Ref } from 'vue';
const defaultFormatter = (item: any) => (item ? item.label || item.toString() : '');
const defaultValueParse = (item: any) => item;
export interface SourceItemObj {
label: string;
disabled: boolean;
[propName: string]: unknown;
}
const defaultFormatter = (item: string | SourceItemObj) => {
if(typeof item === 'string'){
return item;
}
return item!==null ? item.label || item.toString() : '';
};
const defaultValueParse = (item: string | SourceItemObj) => item;
export type Placement =
| 'top'
| 'right'
Expand All @@ -14,122 +24,132 @@ export type Placement =
| 'bottom-end'
| 'left-start'
| 'left-end';


export type SourceType = Array<string>| Array<SourceItemObj>;

export const autoCompleteProps = {
modelValue: {
type: String,
default:''
default: ''
},
source:{
type :Array,
default:null
source: {
type : Array as PropType<SourceType>,
default: null
},
allowEmptyValueSearch:{
type:Boolean,
default:false
allowEmptyValueSearch: {
type: Boolean,
default: false
},
position :{
appendToBody:{
type: Boolean,
default: false
},
position : {
type: Array as PropType<Array<Placement>>,
default: ['bottom-end'],
},
disabled:{
type:Boolean,
default:false
disabled: {
type: Boolean,
default: false
},
delay:{
type:Number,
default:300
delay: {
type: Number,
default: 300
},
disabledKey:{
type:String,
default:null
disabledKey: {
type: String,
default: null
},
formatter: {
type:Function as PropType<(item: any) => string>,
default:defaultFormatter
type: Function as PropType<(item: string | SourceItemObj) => string>,
default: defaultFormatter
},
isSearching: {
type:Boolean,
default:false
type: Boolean,
default: false
},
sceneType:{
type:String,
default:null
sceneType: {
type: String,
default: null
},
searchFn:{
type:Function as PropType<(term: string) => Array<any>>,
default:null
searchFn: {
type: Function as PropType<(term: string) => SourceType>,
default: null
},
tipsText:{
type:String,
tipsText: {
type: String,
default:'最近输入'
},
latestSource:{
type:Array,
default:null
latestSource: {
type: Array,
default: null
},
valueParser:{
type:Function as PropType<(item: any) => any>,
default:defaultValueParse
valueParser: {
type: Function as PropType<(item: string | SourceItemObj) => string>,
default: defaultValueParse
},
enableLazyLoad: {
type:Boolean,
default:false
type: Boolean,
default: false
},
width:{
width: {
type: Number,
default:400
default: 400
},
showAnimation:{
type:Boolean,
default:true
showAnimation: {
type: Boolean,
default: true
},
maxHeight:{
type:Number,
default:300
maxHeight: {
type: Number,
default: 300
},
transInputFocusEmit:{
type:Function as PropType<() => void>,
default:null
transInputFocusEmit: {
type: Function as PropType<() => void>,
default: null
},
selectValue:{
type:Function as PropType<(val: any) => any>,
default:null
type: Function as PropType<(val: string) => string>,
default: null
},
loadMore:{
type:Function as PropType<() => void>,
default:null
loadMore: {
type: Function as PropType<() => void>,
default: null
}
} as const;

export type AutoCompleteProps = ExtractPropTypes<typeof autoCompleteProps>;

export interface AutoCompleteRootType {
ctx: SetupContext<any>;
ctx: SetupContext;
props: AutoCompleteProps;
}
export type SearchFnType = (term: string) => Array<any>;
export type FormatterType = (item: any) => string;
export type DefaultFuncType = (arg?: any) => any;
export type HandleSearch = (term?: string | string,enableLazyLoad?: boolean) => void;
export type RecentlyFocus = (latestSource: Array<any>) => void;
export type InputDebounceCb = (...rest: any) => Promise<void>;
export type TransInputFocusEmit = (any?: any) => void;
export type SelectOptionClick = (any?: any) => void;
export type SearchFnType = (term: string) => SourceType;
export type FormatterType = (item: string | SourceItemObj) => string;
export type DefaultFuncType = () => void;
export type HandleSearch = (term: string,enableLazyLoad?: boolean) => void;
export type RecentlyFocus = (latestSource: SourceType) => void;
export type InputDebounceCb = (value: string) => void;
export type TransInputFocusEmit = () => unknown;
export type SelectOptionClick = (item: string | SourceItemObj) => void;
export type SelectValueType = (value: string) => unknown;
// 弹出选择框参数
export type DropdownProps = {
props: AutoCompleteProps;
searchList: Ref<any[]>;
searchList: Ref<SourceType>;
searchStatus?: Ref<boolean>;
showNoResultItemTemplate: Ref<boolean>;
term?: string;
visible: Ref<boolean>;
selectedIndex: Ref<number>;
selectOptionClick: HandleSearch;
dropDownRef: Ref<HTMLUListElement>;
selectOptionClick: SelectOptionClick;
dropDownRef: Ref;
showLoading: Ref<boolean>;
loadMore: (arg?: any) => void;
latestSource: Ref<any[]>;
loadMore: () => void;
latestSource: Ref<Array<SourceItemObj>>;
modelValue: Ref<string>;
hoverIndex: Ref<number>;
valueParser: () => void;
};
export const DropdownPropsKey: InjectionKey<DropdownProps>=Symbol('DropdownPropsKey');
Loading