Skip to content
Open
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
46 changes: 40 additions & 6 deletions packages/vant/src/field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export const fieldProps = extend({}, cellSharedProps, fieldSharedProps, {
type: Boolean,
default: null,
},
showPasswordIcon: Boolean,
});

export type FieldProps = ExtractPropTypes<typeof fieldProps>;
Expand Down Expand Up @@ -160,6 +161,8 @@ export default defineComponent({
validateMessage: '',
});

let selectionInfo: { selectionStart: number; selectionEnd: number };

const inputRef = ref<HTMLInputElement>();
const clearIconRef = ref<ComponentInstance>();
const customValue = ref<() => unknown>();
Expand Down Expand Up @@ -409,6 +412,9 @@ export default defineComponent({
if (!event.target!.composing) {
updateValue((event.target as HTMLInputElement).value);
}
const { selectionStart, selectionEnd } = event.target as HTMLInputElement;
if (selectionStart === null || selectionEnd === null) return;
selectionInfo = { selectionStart, selectionEnd };
};

const blur = () => inputRef.value?.blur();
Expand Down Expand Up @@ -450,9 +456,22 @@ export default defineComponent({

const onClickLeftIcon = (event: MouseEvent) => emit('clickLeftIcon', event);

const onClickRightIcon = (event: MouseEvent) =>
const onClickRightIcon = (event: MouseEvent) => {
emit('clickRightIcon', event);

if (showPwdIcon.value) {
showPassword.value = !showPassword.value;
if (selectionInfo === undefined) return;
focus();
setTimeout(() => {
inputRef.value?.setSelectionRange(
selectionInfo.selectionStart,
selectionInfo.selectionEnd,
);
});
}
};

const onClear = (event: TouchEvent) => {
preventDefault(event);
emit('update:modelValue', '');
Expand Down Expand Up @@ -498,6 +517,14 @@ export default defineComponent({

const getValidationStatus = () => state.status;

const showPassword = ref(false);
const passwordIcon = computed(() =>
showPassword.value ? 'eye-o' : 'closed-eye',
);
const showPwdIcon = computed(
() => props.showPasswordIcon && !getProp('disabled') && !!getModelValue(),
);

const renderInput = () => {
const controlClass = bem('control', [
getProp('inputAlign'),
Expand Down Expand Up @@ -547,9 +574,13 @@ export default defineComponent({
return <textarea {...inputAttrs} inputmode={props.inputmode} />;
}

return (
<input {...mapInputType(props.type, props.inputmode)} {...inputAttrs} />
);
const type = props.showPasswordIcon
? showPassword.value
? 'text'
: 'password'
: props.type;

return <input {...mapInputType(type, props.inputmode)} {...inputAttrs} />;
};

const renderLeftIcon = () => {
Expand All @@ -570,14 +601,17 @@ export default defineComponent({

const renderRightIcon = () => {
const rightIconSlot = slots['right-icon'];
const rightIcon = showPwdIcon.value
? passwordIcon.value
: props.rightIcon;

if (props.rightIcon || rightIconSlot) {
if (rightIcon || rightIconSlot) {
return (
<div class={bem('right-icon')} onClick={onClickRightIcon}>
{rightIconSlot ? (
rightIconSlot()
) : (
<Icon name={props.rightIcon} classPrefix={props.iconPrefix} />
<Icon name={rightIcon} classPrefix={props.iconPrefix} />
)}
</div>
);
Expand Down
5 changes: 5 additions & 0 deletions packages/vant/src/field/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@
&[type='search'] {
-webkit-appearance: none;
}

// for edge
&[type='password']::-ms-reveal {
display: none;
}
}

&__clear,
Expand Down
26 changes: 26 additions & 0 deletions packages/vant/src/field/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,29 @@ test("should not be set label's for attribute when using input slot", async () =
wrapper.find('.van-field__label label').attributes('for'),
).toBeUndefined();
});

test('should change input field type when password icon is clicked', async () => {
const wrapper = mount(Field, {
props: {
modelValue: '',
type: 'password',
showPasswordIcon: true,
},
});
const input = wrapper.find('.van-field__control');
const rightIconHide = wrapper.find('.van-field__right-icon');
expect(rightIconHide.exists()).toBeFalsy();
await wrapper.setProps({ modelValue: 'password' });

const rightIconShow = wrapper.find('.van-field__right-icon');
expect(rightIconShow.exists()).toBeTruthy();
const passwordClosedIcon = rightIconShow.find('.van-icon-closed-eye');
expect(passwordClosedIcon.exists()).toBeTruthy();
expect(input.element.type).toEqual('password');
await passwordClosedIcon.trigger('click');

const passwordOpenIcon = rightIconShow.find('.van-icon-eye-o');
expect(rightIconShow.find('.van-icon-closed-eye').exists()).toBeFalsy();
expect(passwordOpenIcon.exists()).toBeTruthy();
expect(input.element.type).toEqual('text');
});