diff --git a/packages/devui-vue/devui/tag-input/__tests__/tag-input.spec.ts b/packages/devui-vue/devui/tag-input/__tests__/tag-input.spec.ts index 6edb124071..1c77d70e1a 100644 --- a/packages/devui-vue/devui/tag-input/__tests__/tag-input.spec.ts +++ b/packages/devui-vue/devui/tag-input/__tests__/tag-input.spec.ts @@ -14,6 +14,10 @@ jest.mock('../../locale/create', () => ({ })); const ns = useNamespace('tag-input', true); +const suggestionListCls = ns.e('suggestion-list'); +const suggestionListItemCls = ns.e('suggestion-list__item'); +const tagsItemCls = ns.e('tags__item'); +const inputCls = ns.e('input'); const customMount = (state: StateType) => mount({ components: { DTagInput }, @@ -47,11 +51,10 @@ describe('DTagInput', () => { }); const wrapper = customMount(state); expect(wrapper.find(ns.b()).exists()).toBe(true); - expect(wrapper.find('.devui-tags').exists()).toBe(true); - expect(wrapper.find('.devui-tag-list').exists()).toBe(true); - expect(wrapper.find('.devui-input').exists()).toBe(true); + expect(wrapper.find(ns.e('tags')).exists()).toBe(true); + expect(wrapper.find(inputCls).exists()).toBe(true); - const itemA = wrapper.find('.devui-tag-item'); + const itemA = wrapper.find(tagsItemCls); expect(itemA.exists()).toBe(true); expect(itemA.text()).toBe('Y.Chen'); @@ -72,13 +75,13 @@ describe('DTagInput', () => { ], }); const wrapper = customMount(state); - const input = wrapper.find('input.devui-input'); + const input = wrapper.find(inputCls); - expect(wrapper.find('.devui-suggestion-list').exists()).toBe(false); + expect(wrapper.find(suggestionListCls).exists()).toBe(false); await input.trigger('focus'); // 是否存在 devui-suggestion-list - const suggestionList = !!document.querySelectorAll('.devui-suggestion-list')[0]; + const suggestionList = !!document.querySelectorAll(suggestionListCls)[0]; expect(suggestionList).toBe(true); wrapper.unmount(); @@ -99,14 +102,16 @@ describe('DTagInput', () => { }, }); - expect(wrapper.find('.devui-disabled').exists()).toBe(false); - expect(wrapper.find('.devui-input').isVisible()).toBe(true); + expect(wrapper.find('.is-disabled').exists()).toBe(false); + expect(wrapper.find(inputCls).isVisible()).toBe(true); await wrapper.setProps({ disabled: true, }); - expect(wrapper.find('.devui-disabled').exists()).toBe(true); - expect(wrapper.find('.devui-input').isVisible()).toBe(false); + + expect(wrapper.find('.is-disabled').exists()).toBe(true); + // 禁用状态下不显示input + expect(wrapper.find(ns.e('input_hide')).exists()).toBe(true); expect(wrapper.find('.remove-button').exists()).toBe(false); wrapper.unmount(); @@ -133,7 +138,7 @@ describe('DTagInput', () => { wrapper.unmount(); }); - it('tag-input removeTag work', async () => { + it('tag-input removeTag work', () => { const state = reactive({ tags: [ { cname: 'a' }, @@ -144,13 +149,17 @@ describe('DTagInput', () => { ], }); const wrapper = customMount(state); - const removeSvg = wrapper.find('.remove-button'); - await removeSvg.trigger('click'); - expect(wrapper.findAll('.devui-tag-item').length).toBe(1); - expect(state.tags.length).toBe(1); - expect(state.suggestionList.length).toBe(2); - wrapper.unmount(); + // todo 使用await报错 The provided value is not of type 'Element' + wrapper.find('.remove-button').trigger('click'); + + nextTick(() => { + expect(wrapper.findAll(tagsItemCls).length).toBe(1); + expect(state.tags.length).toBe(1); + expect(state.suggestionList.length).toBe(2); + + wrapper.unmount(); + }); }); it('tag-input keydown work', async () => { @@ -196,17 +205,17 @@ describe('DTagInput', () => { const input = wrapper.find('input'); await input.trigger('focus'); - let suggestionList = document.querySelectorAll('.devui-suggestion-item'); + let suggestionList = document.querySelectorAll(suggestionListItemCls); expect(suggestionList.length).toBe(3); await input.setValue('xy'); await input.trigger('input'); - suggestionList = document.querySelectorAll('.devui-suggestion-item'); + suggestionList = document.querySelectorAll(suggestionListItemCls); expect(suggestionList.length).toBe(2); await input.setValue('xxx'); await input.trigger('input'); - suggestionList = document.querySelectorAll('.devui-suggestion-item'); + suggestionList = document.querySelectorAll(suggestionListItemCls); expect(suggestionList.length).toBe(1); wrapper.unmount(); @@ -226,7 +235,7 @@ describe('DTagInput', () => { }); const wrapper = customMount(state); await wrapper.find('input').trigger('focus'); - const suggestionList = document.querySelectorAll('.devui-suggestion-item'); + const suggestionList = document.querySelectorAll(suggestionListItemCls); const yyy = suggestionList[1]; yyy.dispatchEvent(new Event('click')); @@ -252,18 +261,18 @@ describe('DTagInput', () => { const wrapper = customMount(state); const input = wrapper.find('input'); await input.trigger('focus'); - let suggestionList = document.querySelectorAll('.devui-suggestion-item'); + let suggestionList = document.querySelectorAll(suggestionListItemCls); // 获取焦点默认第一个选中 expect(suggestionList[0].className).toContain('selected'); // 按下 下箭头,选中第二个数组第一个 await input.trigger('keydown', { key: 'ArrowDown' }); - suggestionList = document.querySelectorAll('.devui-suggestion-item'); + suggestionList = document.querySelectorAll(suggestionListItemCls); expect(suggestionList[1].className).toContain('selected'); await input.trigger('keydown', { key: 'ArrowUp' }); await input.trigger('keydown', { key: 'ArrowUp' }); - suggestionList = document.querySelectorAll('.devui-suggestion-item'); + suggestionList = document.querySelectorAll(suggestionListItemCls); expect(suggestionList[2].className).toContain('selected'); // 按下Enter选中数据 diff --git a/packages/devui-vue/devui/tag-input/src/tag-input-types.ts b/packages/devui-vue/devui/tag-input/src/tag-input-types.ts index 41ee6c497b..27987c255c 100644 --- a/packages/devui-vue/devui/tag-input/src/tag-input-types.ts +++ b/packages/devui-vue/devui/tag-input/src/tag-input-types.ts @@ -57,7 +57,7 @@ export const tagInputProps = { }, noData: { type: String, - default: '', + default: '暂无数据', }, caseSensitivity: { type: Boolean, diff --git a/packages/devui-vue/devui/tag-input/src/tag-input.scss b/packages/devui-vue/devui/tag-input/src/tag-input.scss index b79c7edc30..8c9a2b6b94 100644 --- a/packages/devui-vue/devui/tag-input/src/tag-input.scss +++ b/packages/devui-vue/devui/tag-input/src/tag-input.scss @@ -13,131 +13,130 @@ &:active { outline: 0; } -} -.#{$devui-prefix}-form-control.#{$devui-prefix}-tags { - -moz-appearance: textfield; - -webkit-appearance: textfield; - padding: 2px 4px; - overflow: hidden; - word-wrap: break-word; - cursor: text; - background-color: $devui-base-bg; - border: 1px solid $devui-line; - border-radius: $devui-border-radius; - height: 100%; - transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + .is-disabled { + border-color: $devui-disabled-line; + background-color: $devui-disabled-bg; + cursor: not-allowed; - &:hover { - border-color: $devui-list-item-hover-bg; - } + .#{$devui-prefix}-tag-input__tags { + min-height: 22px; - &.focused { - outline: 0; - } + &__item { + color: $devui-disabled-text; + background-color: $devui-disabled-bg; + border-color: $devui-disabled-line; - &.#{$devui-prefix}-dropdown-origin:focus-within { - border-color: $devui-brand; - } + span { + margin-right: 0; + } - .#{$devui-prefix}-tag-list { - margin: 0; - padding: 0; - list-style-type: none; + .remove-button { + background-color: $devui-disabled-line; + + svg path { + fill: $devui-light-text; + } + } + } + } } - .#{$devui-prefix}-tag-item { - margin: 1px; - padding: 0 10px; - display: inline-block; - min-height: 18px; - line-height: 18px; + // 已选择 + &__tags__wrapper { + -moz-appearance: textfield; + -webkit-appearance: textfield; + padding: 2px 4px; + overflow: hidden; + word-wrap: break-word; + cursor: text; + background-color: $devui-base-bg; + border: 1px solid $devui-line; border-radius: $devui-border-radius; - color: $devui-text; - background-color: $devui-label-bg; - position: relative; - border: 1px solid $devui-label-bg; + height: 100%; + transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - span { - line-height: 1.5; - margin-right: 25px; + &:hover { + border-color: $devui-list-item-hover-bg; } - .remove-button { - margin: 0 0 0 12px; - padding: 0; - border: none; - vertical-align: top; - font-size: $devui-font-size-page-title; - border-radius: 50%; - background-color: $devui-line; - width: 12px; - height: 12px; - display: inline-block; - line-height: 12px; - text-align: center; - transform: translateY(-50%); - position: absolute; - top: 50%; - right: 10px; - - svg path { - fill: $devui-light-text; //TODO: Color-Question - } + &:focus-within { + border-color: $devui-brand; + } - &:hover { - text-decoration: none; - } + &.focused { + outline: 0; } } - &:not(.#{$devui-prefix}-disabled) { - .#{$devui-prefix}-tag-item { - cursor: pointer; + &__tags { + margin: 0; + padding: 0; + list-style-type: none; + + &__item { + margin: 1px; + padding: 0 10px; + display: inline-block; + min-height: 18px; + line-height: 18px; + border-radius: $devui-border-radius; + color: $devui-text; + background-color: $devui-label-bg; + position: relative; + border: 1px solid $devui-label-bg; span { - &:hover { - color: $devui-list-item-hover-text; - } + line-height: 1.5; + margin-right: 25px; } .remove-button { + margin: 0 0 0 12px; + padding: 0; + border: none; + vertical-align: top; + font-size: $devui-font-size-page-title; + border-radius: 50%; + background-color: $devui-line; + width: 12px; + height: 12px; + display: inline-block; + line-height: 12px; + text-align: center; + transform: translateY(-50%); + position: absolute; + top: 50%; + right: 10px; + + svg path { + fill: $devui-light-text; //TODO: Color-Question + } + &:hover { - background-color: $devui-list-item-hover-text; + text-decoration: none; } } - } - } - - &.#{$devui-prefix}-disabled { - border-color: $devui-disabled-line; - background-color: $devui-disabled-bg; - cursor: not-allowed; - .#{$devui-prefix}-tag-item { - color: $devui-disabled-text; - background-color: $devui-disabled-bg; - border-color: $devui-disabled-line; - - span { - margin-right: 0; - } + &:not(.is-disabled) { + cursor: pointer; - .remove-button { - background-color: $devui-disabled-line; + span { + &:hover { + color: $devui-list-item-hover-text; + } + } - svg path { - fill: $devui-light-text; + .remove-button { + &:hover { + background-color: $devui-list-item-hover-text; + } } } } - - .#{$devui-prefix}-tag-list { - min-height: 22px; - } } - input.#{$devui-prefix}-input { + &__input { border: 0; outline: 0; float: left; @@ -149,54 +148,51 @@ &::-ms-clear { display: none; } - } -} -.#{$devui-prefix}-tags-autocomplete { - width: 100%; - padding: 8px; - border-radius: $devui-border-radius; - background-color: $devui-connected-overlay-bg; - box-shadow: $devui-shadow-length-connected-overlay $devui-shadow; + &_hide { + display: none; + } + } - .#{$devui-prefix}-suggestion-list { + // 建议列表 + &__suggestion-list { + position: relative; + width: 100%; + max-height: 280px; + padding: 8px; margin: 0; - padding: 0; + border-radius: $devui-border-radius; + background-color: $devui-connected-overlay-bg; + box-shadow: $devui-shadow-length-connected-overlay $devui-shadow; list-style-type: none; - max-height: 280px; overflow-y: auto; - position: relative; - .#{$devui-prefix}-suggestion-item { + &__item { padding: 5px 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + color: $devui-text; font-size: $devui-font-size; line-height: 20px; border-radius: $devui-border-radius; + transition: color $devui-animation-duration-fast $devui-animation-ease-in-smooth, background-color $devui-animation-duration-fast $devui-animation-ease-in-smooth; + cursor: pointer; - &:not(.#{$devui-prefix}-disabled) { - cursor: pointer; - color: $devui-text; - // background-color: $devui-base-bg; - &:hover { - background-color: $devui-list-item-hover-bg; - } + &:hover { + background-color: $devui-list-item-hover-bg; + } - &.selected { - color: $devui-brand; - background-color: $devui-list-item-active-bg; - } + &.selected { + color: $devui-brand; + background-color: $devui-list-item-active-bg; } } - } -} -.#{$devui-prefix}-tags-autocomplete { - .#{$devui-prefix}-suggestion-list { - .#{$devui-prefix}-suggestion-item { - transition: color $devui-animation-duration-fast $devui-animation-ease-in-smooth, background-color $devui-animation-duration-fast $devui-animation-ease-in-smooth; + &__no-data { + color: var(--devui-disabled-text, #adb0b8); + background-color: var(--devui-disabled-bg, #f5f5f5); + cursor: not-allowed; } } } diff --git a/packages/devui-vue/devui/tag-input/src/tag-input.tsx b/packages/devui-vue/devui/tag-input/src/tag-input.tsx index c17e48da8f..d400355f97 100644 --- a/packages/devui-vue/devui/tag-input/src/tag-input.tsx +++ b/packages/devui-vue/devui/tag-input/src/tag-input.tsx @@ -48,7 +48,7 @@ export default defineComponent({ }; const tagInputVal = ref(''); - const onInput = ($event: InputEvent) => { + const onInput = ($event: Event) => { const v = ($event.target as HTMLInputElement).value || ''; tagInputVal.value = v.trim(); }; @@ -64,12 +64,16 @@ export default defineComponent({ return suggestions; } - // 大小写敏感 - if (props.caseSensitivity) { - return suggestions.filter((item) => item[props.displayProperty].indexOf(tagInputVal.value) !== -1); - } else { - return suggestions.filter((item) => item[props.displayProperty].toLowerCase().indexOf(tagInputVal.value.toLowerCase()) !== -1); - } + return suggestions.filter((item: Suggestion) => { + const val = item[props.displayProperty] as string; + + // 大小写敏感 + if (props.caseSensitivity) { + return val.indexOf(tagInputVal.value) !== -1; + } else { + return val.toLowerCase().indexOf(tagInputVal.value.toLowerCase()) !== -1; + } + }); }); const selectIndex = ref(0); @@ -92,7 +96,7 @@ export default defineComponent({ }; const handleEnter = () => { - let res = { [props.displayProperty]: tagInputVal.value }; + let res: Suggestion = { [props.displayProperty]: tagInputVal.value }; if (tagInputVal.value === '' && mergedSuggestions.value.length === 0) { return false; } @@ -151,10 +155,10 @@ export default defineComponent({ // 已选择 tags 列表 const chosenTags = () => { - return