From 116d1031c8517b336811bdadec82bd185ad1d40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Mon, 1 Jul 2024 10:12:25 +0800 Subject: [PATCH 01/10] feat(input): support clearTrigger --- src/input/input.en-US.md | 3 +- src/input/input.md | 3 +- src/input/input.tsx | 61 ++++++++++++++++++++++++---------------- src/input/props.ts | 14 ++++++++- src/input/type.ts | 5 ++++ 5 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/input/input.en-US.md b/src/input/input.en-US.md index 6a50570ca..1bf61a8b8 100644 --- a/src/input/input.en-US.md +++ b/src/input/input.en-US.md @@ -12,6 +12,7 @@ allowInputOverMax | Boolean | false | allow to continue input on value length is autocomplete | String | undefined | attribute of input element, [see here](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) | N autofocus | Boolean | false | autofocus on first rendered | N borderless | Boolean | false | input without border | N +clearTrigger | String | always | show clear icon, clicked to clear input value。options: always / focus | N clearable | Boolean | false | show clear icon, clicked to clear input value | N disabled | Boolean | undefined | make input to be disabled | N format | Function | - | input value formatter, `type=number` does not work. if you need to format number, `InputNumber` Component might be better。Typescript:`InputFormatType` `type InputFormatType = (value: InputValue) => string`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/input/type.ts) | N @@ -22,7 +23,7 @@ maxlength | String / Number | - | \- | N name | String | - | \- | N placeholder | String | undefined | \- | N prefixIcon | Slot / Function | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N -readonly | Boolean | false | \- | N +readonly | Boolean | undefined | \- | N size | String | small | `deprecated`。options: small/medium。Typescript:`'medium' \| 'small'` | N spellCheck | Boolean | false | attribute of input element, [see here](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/spellcheck) | N status | String | undefined | options: default/success/warning/error | N diff --git a/src/input/input.md b/src/input/input.md index 985a700c1..88250be74 100644 --- a/src/input/input.md +++ b/src/input/input.md @@ -11,6 +11,7 @@ allowInputOverMax | Boolean | false | 超出 `maxlength` 或 `maxcharacter` 之 autocomplete | String | undefined | 是否开启自动填充功能,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) | N autofocus | Boolean | false | 自动聚焦 | N borderless | Boolean | false | 是否开启无边框模式 | N +clearTrigger | String | always | 清空图标触发方式,仅在输入框有值时有效。可选项:always / focus | N clearable | Boolean | false | 是否可清空 | N disabled | Boolean | undefined | 是否禁用输入框 | N format | Function | - | 【开发中】指定输入框展示值的格式。TS 类型:`InputFormatType` `type InputFormatType = (value: InputValue) => string`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/input/type.ts) | N @@ -21,7 +22,7 @@ maxlength | String / Number | - | 用户最多可以输入的文本长度,一 name | String | - | 名称 | N placeholder | String | undefined | 占位符 | N prefixIcon | Slot / Function | - | 组件前置图标。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N -readonly | Boolean | false | 只读状态 | N +readonly | Boolean | undefined | 只读状态 | N size | String | small | 已废弃。输入框尺寸。可选项:small/medium。TS 类型:`'medium' \| 'small'` | N spellCheck | Boolean | false | 是否开启拼写检查,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/spellcheck) | N status | String | undefined | 输入框状态。默认情况会由组件内部根据实际情况呈现,如果文本过长引起的状态变化。可选项:default/success/warning/error | N diff --git a/src/input/input.tsx b/src/input/input.tsx index 019f249fd..55dc23280 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -40,12 +40,11 @@ export default defineComponent({ const isDisabled = useFormDisabled(); const inputRef = ref(); - const { autofocus } = toRefs(props); const [innerValue] = useDefault(props, context.emit, 'value', 'change'); const status = props.status || 'default'; const renderType = ref(props.type); - const { focused } = useFocus(inputRef, { initialValue: props.autofocus }); + const focused = ref(false); const inputClasses = computed(() => [ `${inputClass.value}__control`, @@ -64,6 +63,12 @@ export default defineComponent({ [`${inputClass.value}--border`]: !props.borderless, }, ]); + const showClearable = computed(() => { + if (props.clearable && innerValue.value && innerValue.value.length > 0) { + return props.clearTrigger === 'always' || (props.clearTrigger === 'focus' && focused.value); + } + return false; + }); const setInputValue = (v: InputValue = '') => { const input = inputRef.value as HTMLInputElement; @@ -101,25 +106,26 @@ export default defineComponent({ }; const focus = () => { - focused.value = true; + inputRef.value?.focus(); }; const blur = () => { - focused.value = false; - // inputRef.value?.blur(); + inputRef.value?.blur(); }; extendAPI({ focus, blur }); - const handleClear = (e: MouseEvent) => { + const handleClear = (e: TouchEvent) => { innerValue.value = ''; focused.value = true; - props.onClear?.({ e }); + props.onClear?.({ e: e as unknown as MouseEvent }); }; const handleFocus = (e: FocusEvent) => { + focused.value = true; props.onFocus?.(innerValue.value, { e }); }; const handleBlur = (e: FocusEvent) => { + focused.value = false; props.onBlur?.(innerValue.value, { e }); }; @@ -131,13 +137,17 @@ export default defineComponent({ renderType.value = renderType.value === 'password' ? 'text' : 'password'; }; - watch(autofocus, (autofocus, prevAutofocus) => { - if (autofocus === true) { - nextTick(() => { - focused.value = true; - }); - } - }); + watch( + () => props.autofocus, + (v) => { + if (v === true) { + nextTick(() => { + focused.value = true; + }); + } + }, + { immediate: true }, + ); watch( () => props.type, @@ -146,6 +156,18 @@ export default defineComponent({ }, { immediate: true }, ); + const readerClearable = () => { + if (showClearable.value) { + // pc端仅mousedown事件触发早于blur,移动端touch相关事件触发均早于blur + return ( +
+ +
+ ); + } + + return null; + }; return () => { const readerPrefix = () => { @@ -159,16 +181,7 @@ export default defineComponent({ ); }; - const readerClearable = () => { - if (props.clearable && innerValue.value && innerValue.value.length > 0) { - return ( -
- -
- ); - } - return null; - }; + const readerSuffix = () => { const suffix = readerTNodeJSX('suffix'); if (!suffix) { diff --git a/src/input/props.ts b/src/input/props.ts index 86d3be5ba..d63a0bf1a 100644 --- a/src/input/props.ts +++ b/src/input/props.ts @@ -28,6 +28,15 @@ export default { autofocus: Boolean, /** 是否开启无边框模式 */ borderless: Boolean, + /** 清空图标触发方式,仅在输入框有值时有效 */ + clearTrigger: { + type: String as PropType, + default: 'always' as TdInputProps['clearTrigger'], + validator(val: TdInputProps['clearTrigger']): boolean { + if (!val) return true; + return ['always', 'focus'].includes(val); + }, + }, /** 是否可清空 */ clearable: Boolean, /** 是否禁用输入框 */ @@ -75,7 +84,10 @@ export default { type: Function as PropType, }, /** 只读状态 */ - readonly: Boolean, + readonly: { + type: Boolean, + default: undefined, + }, /** 是否开启拼写检查,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/spellcheck) */ spellCheck: Boolean, /** 输入框状态。默认情况会由组件内部根据实际情况呈现,如果文本过长引起的状态变化 */ diff --git a/src/input/type.ts b/src/input/type.ts index 20fb58c27..c3125d4c5 100644 --- a/src/input/type.ts +++ b/src/input/type.ts @@ -31,6 +31,11 @@ export interface TdInputProps { * @default false */ borderless?: boolean; + /** + * 清空图标触发方式,仅在输入框有值时有效 + * @default always + */ + clearTrigger?: 'always' | 'focus'; /** * 是否可清空 * @default false From c041dbbc2a4a8af81b0950937738650212c77bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Mon, 1 Jul 2024 17:04:48 +0800 Subject: [PATCH 02/10] fix: clear --- src/input/input.tsx | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/input/input.tsx b/src/input/input.tsx index 55dc23280..a73e55d0c 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -44,7 +44,7 @@ export default defineComponent({ const status = props.status || 'default'; const renderType = ref(props.type); - const focused = ref(false); + const { focused } = useFocus(inputRef, { initialValue: props.autofocus }); const inputClasses = computed(() => [ `${inputClass.value}__control`, @@ -63,7 +63,7 @@ export default defineComponent({ [`${inputClass.value}--border`]: !props.borderless, }, ]); - const showClearable = computed(() => { + const showClear = computed(() => { if (props.clearable && innerValue.value && innerValue.value.length > 0) { return props.clearTrigger === 'always' || (props.clearTrigger === 'focus' && focused.value); } @@ -106,19 +106,20 @@ export default defineComponent({ }; const focus = () => { - inputRef.value?.focus(); + focused.value = true; }; const blur = () => { - inputRef.value?.blur(); + focused.value = false; }; extendAPI({ focus, blur }); - const handleClear = (e: TouchEvent) => { + const handleClear = (e: MouseEvent) => { + e.preventDefault(); innerValue.value = ''; focused.value = true; - props.onClear?.({ e: e as unknown as MouseEvent }); + props.onClear?.({ e }); }; const handleFocus = (e: FocusEvent) => { focused.value = true; @@ -146,7 +147,6 @@ export default defineComponent({ }); } }, - { immediate: true }, ); watch( @@ -156,18 +156,6 @@ export default defineComponent({ }, { immediate: true }, ); - const readerClearable = () => { - if (showClearable.value) { - // pc端仅mousedown事件触发早于blur,移动端touch相关事件触发均早于blur - return ( -
- -
- ); - } - - return null; - }; return () => { const readerPrefix = () => { @@ -181,7 +169,17 @@ export default defineComponent({ ); }; + const readerClearable = () => { + if (showClear.value) { + return ( +
+ +
+ ); + } + return null; + }; const readerSuffix = () => { const suffix = readerTNodeJSX('suffix'); if (!suffix) { From 118e67fd679d9043b1815c777021c994f7ef1a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Mon, 1 Jul 2024 21:57:36 +0800 Subject: [PATCH 03/10] fix: test clearable --- src/input/__test__/index.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/__test__/index.test.jsx b/src/input/__test__/index.test.jsx index 722ca4de0..fc79f13e2 100644 --- a/src/input/__test__/index.test.jsx +++ b/src/input/__test__/index.test.jsx @@ -76,7 +76,7 @@ describe('Input.vue', async () => { const wrapper = mount(); const closeIcon = wrapper.findComponent(CloseCircleFilledIcon); expect(closeIcon.exists()).toBeTruthy(); - await closeIcon.trigger('click'); + await closeIcon.trigger('mousedown'); expect(value.value).toBe(''); expect(handleClear).toBeCalled(); }); From 38aa360fa1f0da46cf6d9fa5a1942286d72f6329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Mon, 1 Jul 2024 22:00:11 +0800 Subject: [PATCH 04/10] chore: remove focused --- src/input/input.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/input/input.tsx b/src/input/input.tsx index a73e55d0c..8e2f35fcc 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -122,11 +122,9 @@ export default defineComponent({ props.onClear?.({ e }); }; const handleFocus = (e: FocusEvent) => { - focused.value = true; props.onFocus?.(innerValue.value, { e }); }; const handleBlur = (e: FocusEvent) => { - focused.value = false; props.onBlur?.(innerValue.value, { e }); }; From 5b385063403e001be453815757414b2cd91a7424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Mon, 1 Jul 2024 22:07:12 +0800 Subject: [PATCH 05/10] fix: showClear --- src/input/input.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/input/input.tsx b/src/input/input.tsx index 8e2f35fcc..09e8258e4 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -64,6 +64,8 @@ export default defineComponent({ }, ]); const showClear = computed(() => { + if (isDisabled.value || props.readonly === true) return false; + if (props.clearable && innerValue.value && innerValue.value.length > 0) { return props.clearTrigger === 'always' || (props.clearTrigger === 'focus' && focused.value); } From 31e38859e28baefcf4624bfb906639900b939649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Thu, 4 Jul 2024 11:47:28 +0800 Subject: [PATCH 06/10] fix: focused test not work --- src/input/__test__/index.test.jsx | 79 +++++++++++++++++++++++-------- src/input/input.tsx | 48 +++++++++++-------- 2 files changed, 85 insertions(+), 42 deletions(-) diff --git a/src/input/__test__/index.test.jsx b/src/input/__test__/index.test.jsx index fc79f13e2..205f1f519 100644 --- a/src/input/__test__/index.test.jsx +++ b/src/input/__test__/index.test.jsx @@ -140,31 +140,41 @@ describe('Input.vue', async () => { expect(attrDom1.attributes('type')).toBe('password'); }); - it(': onBlur', async () => { - const onBlur = vi.fn(); - const wrapper = mount(); - await nextTick(); - const input = wrapper.find('.t-input__wrap input'); - await input.trigger('blur'); - expect(onBlur).toBeCalled(); + it(': autofocus', async () => { + const value = ref('123'); + const wrapper = mount(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + wrapper.vm.blur(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + wrapper.vm.focus(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + }); - it(': onFocus', async () => { - const onFocus = vi.fn(); - const wrapper = mount(); - const input = wrapper.find('.t-input__wrap input'); - await input.trigger('focus'); - expect(onFocus).toBeCalled(); + it(': clearTrigger=always', async () => { + const value = ref('123'); + const handleClear = vi.fn(); + const wrapper = mount(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + await wrapper.find('.t-icon-close-circle-filled').trigger('mousedown'); + expect(value.value).toBe(''); + expect(handleClear).toBeCalled(); }); - it(': onChange', async () => { - const value = ref(''); - const onChange = vi.fn(); - const wrapper = mount(); - const el = wrapper.find('.t-input__wrap input').element; - await simulateEvent(el, '文本', 'input'); - expect(onChange).toBeCalledTimes(1); - expect(onChange).toHaveBeenCalledWith('文本'); + it(': clearTrigger=focus', async () => { + const value = ref('123'); + const handleClear = vi.fn(); + const wrapper = mount(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + wrapper.vm.focus(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + + await wrapper.find('.t-icon-close-circle-filled').trigger('mousedown'); + expect(value.value).toBe(''); + expect(handleClear).toBeCalled(); }); }); describe('event', async () => { @@ -200,6 +210,33 @@ describe('Input.vue', async () => { await $input.trigger('compositionend'); expect(onCompositionend).toBeCalled(); }); + + it(': onBlur', async () => { + const onBlur = vi.fn(); + const wrapper = mount(); + await nextTick(); + const input = wrapper.find('.t-input__wrap input'); + await input.trigger('blur'); + expect(onBlur).toBeCalled(); + }); + + it(': onFocus', async () => { + const onFocus = vi.fn(); + const wrapper = mount(); + const input = wrapper.find('.t-input__wrap input'); + await input.trigger('focus'); + expect(onFocus).toBeCalled(); + }); + + it(': onChange', async () => { + const value = ref(''); + const onChange = vi.fn(); + const wrapper = mount(); + const el = wrapper.find('.t-input__wrap input').element; + await simulateEvent(el, '文本', 'input'); + expect(onChange).toBeCalledTimes(1); + expect(onChange).toHaveBeenCalledWith('文本'); + }); }); describe('slots', async () => { diff --git a/src/input/input.tsx b/src/input/input.tsx index 09e8258e4..1a59595cc 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -1,10 +1,9 @@ -import { PropType, ref, computed, defineComponent, toRefs, nextTick, watch } from 'vue'; +import { PropType, ref, computed, defineComponent, nextTick, watch } from 'vue'; import { BrowseIcon as TBrowseIcon, BrowseOffIcon as TBrowseOffIcon, CloseCircleFilledIcon as TCloseCircleFilledIcon, } from 'tdesign-icons-vue-next'; -import { useFocus } from '@vueuse/core'; import config from '../config'; import InputProps from './props'; import { InputValue, TdInputProps } from './type'; @@ -35,7 +34,7 @@ export default defineComponent({ }, }, setup(props, context) { - const readerTNodeJSX = useTNodeJSX(); + const renderTNodeJSX = useTNodeJSX(); const inputClass = usePrefixClass('input'); const isDisabled = useFormDisabled(); @@ -44,7 +43,7 @@ export default defineComponent({ const status = props.status || 'default'; const renderType = ref(props.type); - const { focused } = useFocus(inputRef, { initialValue: props.autofocus }); + const focused = ref(props.autofocus); const inputClasses = computed(() => [ `${inputClass.value}__control`, @@ -109,10 +108,12 @@ export default defineComponent({ const focus = () => { focused.value = true; + inputRef.value?.focus(); }; const blur = () => { focused.value = false; + inputRef.value?.blur(); }; extendAPI({ focus, blur }); @@ -120,13 +121,17 @@ export default defineComponent({ const handleClear = (e: MouseEvent) => { e.preventDefault(); innerValue.value = ''; - focused.value = true; + focus(); props.onClear?.({ e }); }; + const handleFocus = (e: FocusEvent) => { + focused.value = true; props.onFocus?.(innerValue.value, { e }); }; + const handleBlur = (e: FocusEvent) => { + focused.value = false; props.onBlur?.(innerValue.value, { e }); }; @@ -143,10 +148,11 @@ export default defineComponent({ (v) => { if (v === true) { nextTick(() => { - focused.value = true; + inputRef.value?.focus(); }); } }, + { immediate: true }, ); watch( @@ -158,9 +164,9 @@ export default defineComponent({ ); return () => { - const readerPrefix = () => { - const prefixIcon = readerTNodeJSX('prefixIcon'); - const label = readerTNodeJSX('label'); + const renderPrefix = () => { + const prefixIcon = renderTNodeJSX('prefixIcon'); + const label = renderTNodeJSX('label'); return (
@@ -169,7 +175,7 @@ export default defineComponent({
); }; - const readerClearable = () => { + const renderClearable = () => { if (showClear.value) { return (
@@ -180,16 +186,16 @@ export default defineComponent({ return null; }; - const readerSuffix = () => { - const suffix = readerTNodeJSX('suffix'); + const renderSuffix = () => { + const suffix = renderTNodeJSX('suffix'); if (!suffix) { return null; } return
{suffix}
; }; - const readerSuffixIcon = () => { - let suffixIcon = readerTNodeJSX('suffixIcon'); + const renderSuffixIcon = () => { + let suffixIcon = renderTNodeJSX('suffixIcon'); if (props.type === 'password') { if (renderType.value === 'password') { suffixIcon = ; @@ -204,8 +210,8 @@ export default defineComponent({ return
{suffixIcon}
; }; - const readerTips = () => { - const tips = readerTNodeJSX('tips'); + const renderTips = () => { + const tips = renderTNodeJSX('tips'); if (!tips) { return null; } @@ -214,7 +220,7 @@ export default defineComponent({ return (
- {readerPrefix()} + {renderPrefix()}
- {readerClearable()} - {readerSuffix()} - {readerSuffixIcon()} + {renderClearable()} + {renderSuffix()} + {renderSuffixIcon()}
- {readerTips()} + {renderTips()}
); From 667c2d6c9fbc4136e3fb6cb136d2d2ef97882640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Fri, 5 Jul 2024 22:10:27 +0800 Subject: [PATCH 07/10] test: add test case --- src/input/__test__/index.test.jsx | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/input/__test__/index.test.jsx b/src/input/__test__/index.test.jsx index 205f1f519..8b0055b2a 100644 --- a/src/input/__test__/index.test.jsx +++ b/src/input/__test__/index.test.jsx @@ -163,6 +163,28 @@ describe('Input.vue', async () => { expect(handleClear).toBeCalled(); }); + it(': clearTrigger=always and disabled', async () => { + const value = ref('123'); + const handleClear = vi.fn(); + const wrapper = mount(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + await wrapper.setProps({ + disabled: false, + }); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + }); + + it(': clearTrigger=always and readonly', async () => { + const value = ref('123'); + const handleClear = vi.fn(); + const wrapper = mount(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + await wrapper.setProps({ + readonly: false, + }); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + }); + it(': clearTrigger=focus', async () => { const value = ref('123'); const handleClear = vi.fn(); @@ -176,6 +198,40 @@ describe('Input.vue', async () => { expect(value.value).toBe(''); expect(handleClear).toBeCalled(); }); + + it(': clearTrigger=focus and disabled', async () => { + const value = ref('123'); + const handleClear = vi.fn(); + const wrapper = mount(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + wrapper.vm.focus(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + await wrapper.setProps({ + disabled: false, + }); + wrapper.vm.focus(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + }); + + it(': clearTrigger=focus and readonly', async () => { + const value = ref('123'); + const handleClear = vi.fn(); + const wrapper = mount(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + wrapper.vm.focus(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeFalsy(); + await wrapper.setProps({ + readonly: false, + }); + wrapper.vm.focus(); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); + }); + + }); describe('event', async () => { it(': focus && blur', async () => { From f3da84a8950ef554e6bb8f06196470da2fd68119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Sat, 6 Jul 2024 18:48:00 +0800 Subject: [PATCH 08/10] feat: adjust autofocus --- src/input/__test__/index.test.jsx | 1 + src/input/input.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/input/__test__/index.test.jsx b/src/input/__test__/index.test.jsx index 8b0055b2a..ef80c6b8b 100644 --- a/src/input/__test__/index.test.jsx +++ b/src/input/__test__/index.test.jsx @@ -143,6 +143,7 @@ describe('Input.vue', async () => { it(': autofocus', async () => { const value = ref('123'); const wrapper = mount(); + await wrapper.vm.$nextTick(); expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); wrapper.vm.blur(); await wrapper.vm.$nextTick(); diff --git a/src/input/input.tsx b/src/input/input.tsx index 1a59595cc..1ed17507c 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -43,7 +43,7 @@ export default defineComponent({ const status = props.status || 'default'; const renderType = ref(props.type); - const focused = ref(props.autofocus); + const focused = ref(false); const inputClasses = computed(() => [ `${inputClass.value}__control`, @@ -148,7 +148,7 @@ export default defineComponent({ (v) => { if (v === true) { nextTick(() => { - inputRef.value?.focus(); + focus(); }); } }, From 217fef4783bd651c1de6033b5b879847131e4a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Mon, 8 Jul 2024 10:24:51 +0800 Subject: [PATCH 09/10] fix: show passwd --- src/input/__test__/index.test.jsx | 21 +++++++++++++++++++++ src/input/input.tsx | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/input/__test__/index.test.jsx b/src/input/__test__/index.test.jsx index ef80c6b8b..01b71abaa 100644 --- a/src/input/__test__/index.test.jsx +++ b/src/input/__test__/index.test.jsx @@ -128,6 +128,7 @@ describe('Input.vue', async () => { it(': type=password', async () => { const wrapper = mount(); + expect(wrapper.find('.t-icon-browse-off').exists()).toBeTruthy(); wrapper.find('.t-icon-browse-off').trigger('click'); await wrapper.vm.$nextTick(); expect(wrapper.find('.t-icon-browse').exists()).toBeTruthy(); @@ -140,6 +141,26 @@ describe('Input.vue', async () => { expect(attrDom1.attributes('type')).toBe('password'); }); + it(': type=password and disabled', async () => { + const wrapper = mount(); + expect(wrapper.find('.t-icon-browse-off').exists()).toBeTruthy(); + wrapper.find('.t-icon-browse-off').trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-browse-off').exists()).toBeTruthy(); + await wrapper.setProps({ + disabled: false, + }); + wrapper.find('.t-icon-browse-off').trigger('click'); + await wrapper.vm.$nextTick(); + const attrDom = wrapper.find('input'); + expect(attrDom.attributes('type')).toBe('text'); + wrapper.find('.t-icon-browse').trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.find('.t-icon-browse-off').exists()).toBeTruthy(); + const attrDom1 = wrapper.find('input'); + expect(attrDom1.attributes('type')).toBe('password'); + }); + it(': autofocus', async () => { const value = ref('123'); const wrapper = mount(); diff --git a/src/input/input.tsx b/src/input/input.tsx index 1ed17507c..c7ec81416 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -140,6 +140,8 @@ export default defineComponent({ }; const handlePwdIconClick = () => { + if (isDisabled.value) return; + renderType.value = renderType.value === 'password' ? 'text' : 'password'; }; From 0378a125e8ac9866f5d0645cdaf304923efaa4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Thu, 11 Jul 2024 13:53:51 +0800 Subject: [PATCH 10/10] feat: clear event mousedown change to touchend --- src/input/__test__/index.test.jsx | 6 +++--- src/input/input.en-US.md | 4 ++-- src/input/input.md | 4 ++-- src/input/input.tsx | 4 ++-- src/input/type.ts | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/input/__test__/index.test.jsx b/src/input/__test__/index.test.jsx index 01b71abaa..370b36a8a 100644 --- a/src/input/__test__/index.test.jsx +++ b/src/input/__test__/index.test.jsx @@ -76,7 +76,7 @@ describe('Input.vue', async () => { const wrapper = mount(); const closeIcon = wrapper.findComponent(CloseCircleFilledIcon); expect(closeIcon.exists()).toBeTruthy(); - await closeIcon.trigger('mousedown'); + await closeIcon.trigger('touchend'); expect(value.value).toBe(''); expect(handleClear).toBeCalled(); }); @@ -180,7 +180,7 @@ describe('Input.vue', async () => { const handleClear = vi.fn(); const wrapper = mount(); expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); - await wrapper.find('.t-icon-close-circle-filled').trigger('mousedown'); + await wrapper.find('.t-icon-close-circle-filled').trigger('touchend'); expect(value.value).toBe(''); expect(handleClear).toBeCalled(); }); @@ -216,7 +216,7 @@ describe('Input.vue', async () => { await wrapper.vm.$nextTick(); expect(wrapper.find('.t-icon-close-circle-filled').exists()).toBeTruthy(); - await wrapper.find('.t-icon-close-circle-filled').trigger('mousedown'); + await wrapper.find('.t-icon-close-circle-filled').trigger('touchend'); expect(value.value).toBe(''); expect(handleClear).toBeCalled(); }); diff --git a/src/input/input.en-US.md b/src/input/input.en-US.md index 1bf61a8b8..c42abd7b6 100644 --- a/src/input/input.en-US.md +++ b/src/input/input.en-US.md @@ -35,7 +35,7 @@ value | String / Number | - | input value。`v-model` and `v-model:value` is sup defaultValue | String / Number | - | input value。uncontrolled property。Typescript:`InputValue` `type InputValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/input/type.ts) | N onBlur | Function | | Typescript:`(value: InputValue, context: { e: FocusEvent }) => void`
| N onChange | Function | | Typescript:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' }) => void`
trigger on input value changed | N -onClear | Function | | Typescript:`(context: { e: MouseEvent }) => void`
| N +onClear | Function | | Typescript:`(context: { e: TouchEvent }) => void`
| N onFocus | Function | | Typescript:`(value: InputValue, context: { e: FocusEvent }) => void`
| N onValidate | Function | | Typescript:`(context: { error?: 'exceed-maximum' \| 'below-minimum' }) => void`
trigger on text length being over max length or max character | N @@ -45,7 +45,7 @@ name | params | description -- | -- | -- blur | `(value: InputValue, context: { e: FocusEvent })` | \- change | `(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' })` | trigger on input value changed -clear | `(context: { e: MouseEvent })` | \- +clear | `(context: { e: TouchEvent })` | \- focus | `(value: InputValue, context: { e: FocusEvent })` | \- validate | `(context: { error?: 'exceed-maximum' \| 'below-minimum' })` | trigger on text length being over max length or max character diff --git a/src/input/input.md b/src/input/input.md index 88250be74..1a54b6a60 100644 --- a/src/input/input.md +++ b/src/input/input.md @@ -34,7 +34,7 @@ value | String / Number | - | 输入框的值。支持语法糖 `v-model` 或 `v defaultValue | String / Number | - | 输入框的值。非受控属性。TS 类型:`InputValue` `type InputValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/input/type.ts) | N onBlur | Function | | TS 类型:`(value: InputValue, context: { e: FocusEvent }) => void`
失去焦点时触发 | N onChange | Function | | TS 类型:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' }) => void`
输入框值发生变化时触发。`trigger=initial` 表示传入的数据不符合预期,组件自动处理后触发 change 告知父组件。如:初始值长度超过 `maxlength` 限制 | N -onClear | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
清空按钮点击时触发 | N +onClear | Function | | TS 类型:`(context: { e: TouchEvent }) => void`
清空按钮点击时触发 | N onFocus | Function | | TS 类型:`(value: InputValue, context: { e: FocusEvent }) => void`
获得焦点时触发 | N onValidate | Function | | TS 类型:`(context: { error?: 'exceed-maximum' \| 'below-minimum' }) => void`
【暂不支持】字数超出限制时触发 | N @@ -44,7 +44,7 @@ onValidate | Function | | TS 类型:`(context: { error?: 'exceed-maximum' \| -- | -- | -- blur | `(value: InputValue, context: { e: FocusEvent })` | 失去焦点时触发 change | `(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' })` | 输入框值发生变化时触发。`trigger=initial` 表示传入的数据不符合预期,组件自动处理后触发 change 告知父组件。如:初始值长度超过 `maxlength` 限制 -clear | `(context: { e: MouseEvent })` | 清空按钮点击时触发 +clear | `(context: { e: TouchEvent })` | 清空按钮点击时触发 focus | `(value: InputValue, context: { e: FocusEvent })` | 获得焦点时触发 validate | `(context: { error?: 'exceed-maximum' \| 'below-minimum' })` | 【暂不支持】字数超出限制时触发 diff --git a/src/input/input.tsx b/src/input/input.tsx index c7ec81416..6a6693bad 100644 --- a/src/input/input.tsx +++ b/src/input/input.tsx @@ -118,7 +118,7 @@ export default defineComponent({ extendAPI({ focus, blur }); - const handleClear = (e: MouseEvent) => { + const handleClear = (e: TouchEvent) => { e.preventDefault(); innerValue.value = ''; focus(); @@ -180,7 +180,7 @@ export default defineComponent({ const renderClearable = () => { if (showClear.value) { return ( -
+
); diff --git a/src/input/type.ts b/src/input/type.ts index c3125d4c5..b90968778 100644 --- a/src/input/type.ts +++ b/src/input/type.ts @@ -135,7 +135,7 @@ export interface TdInputProps { /** * 清空按钮点击时触发 */ - onClear?: (context: { e: MouseEvent }) => void; + onClear?: (context: { e: TouchEvent }) => void; /** * 获得焦点时触发 */