Browser default behavior monitors form submissions and prompts to save passwords when a visible type="password" input is detected. Too bypass this, use a text input instead and dynamically replace entered characters with dots.
<template>
<el-form :model="authCreds" :rules="authRules" ref="authFormRef">
<el-form-item prop="email">
<el-input type="text" placeholder="Email Address" v-model="authCreds.email" />
</el-form-item>
<el-form-item prop="maskedPwd">
<el-input
type="text"
placeholder="Secure Password"
v-model="authCreds.maskedPwd"
@input="maskInput"
@select="trackSelection"
@keyup.delete="handleDelete"
@copy.prevent
@cut.prevent
/>
</el-form-item>
<el-form-item>
<el-button :loading="isSubmitting" @click="submitAuth" type="primary">
<span v-if="!isSubmitting">Sign In</span>
<span v-else>Authenticating...</span>
</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import 'element-plus/dist/index.css'
const authFormRef = ref(null)
const isSubmitting = ref(false)
const hasSelection = ref(false)
const selectionRange = ref({ start: 0, end: 0 })
const authCreds = reactive({
email: '',
maskedPwd: '',
realPwd: '',
lastMasked: ''
})
const authRules = {
email: [{ required: true, message: 'Please enter your email', trigger: 'blur' }],
maskedPwd: [{ required: true, message: 'Please enter your password', trigger: 'blur' }]
}
const trackSelection = (e) => {
hasSelection.value = true
selectionRange.value = {
start: e.target.selectionStart,
end: e.target.selectionEnd
}
}
const handleDelete = (e) => {
const { realPwd, maskedPwd } = authCreds
const { start, end } = e.target.selection
const deleteCount = realPwd.length - maskedPwd.length
if (deleteCount === 1) {
authCreds.realPwd = realPwd.substring(0, start) + realPwd.substring(end + 1)
} else if (deleteCount > 1) {
authCreds.realPwd = hasSelection.value
? realPwd.substring(0, selectionRange.value.start) + realPwd.substring(selectionRange.value.end)
: realPwd.substring(0, realPwd.length - deleteCount)
}
authCreds.maskedPwd = '●'.repeat(authCreds.realPwd.length)
authCreds.lastMasked = authCreds.maskedPwd
hasSelection.value = false
selectionRange.value = { start: 0, end: 0 }
}
const maskInput = (val) => {
const chinesePattern = /[\u4E00-\u9FA5]/
const chinesePuncPattern = /[。?!,、;:""‘’()《》〈〉【】『』「」﹃﹄〔〕…—~﹏¥]/
if (chinesePattern.test(val) || chinesePuncPattern.test(val)) {
authCreds.maskedPwd = authCreds.lastMasked
ElMessage.error('Only English characters and standard symbols are allowed')
return
}
const unmaskedPart = val.replaceAll('●', '')
if (!unmaskedPart) return
const valArray = Array.from(val)
const { realPwd, lastMasked } = authCreds
const lastMaskedLength = lastMasked.length
if (!lastMaskedLength) {
authCreds.realPwd = unmaskedPart
} else {
let startDotCount = 0, endDotCount = 0
let startInputIndex = -1, endInputIndex = -1
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '●' && startInputIndex === -1) {
startDotCount++
} else if (valArray[i] !== '●' && startInputIndex === -1) {
startInputIndex = i
} else if (valArray[i] === '●' && startInputIndex !== -1 && endInputIndex === -1) {
endInputIndex = i
endDotCount++
} else if (valArray[i] === '●' && endInputIndex !== -1) {
endDotCount++
}
}
if (!startDotCount && !endDotCount) {
authCreds.realPwd = unmaskedPart
} else if (startDotCount && endDotCount) {
authCreds.realPwd = realPwd.substring(0, startDotCount) +
val.substring(startDotCount, val.length - endDotCount) +
realPwd.substring(lastMaskedLength - endDotCount)
} else if (startDotCount) {
authCreds.realPwd = realPwd.substring(0, startDotCount) + val.substring(startDotCount)
} else if (endDotCount) {
authCreds.realPwd = val.substring(0, val.length - endDotCount) +
realPwd.substring(lastMaskedLength - endDotCount, lastMaskedLength)
}
}
authCreds.maskedPwd = '●'.repeat(authCreds.realPwd.length)
authCreds.lastMasked = authCreds.maskedPwd
}
const submitAuth = async () => {
const valid = await authFormRef.value?.validate()
if (!valid) return
isSubmitting.value = true
try {
console.log('Authenticating with:', {
email: authCreds.email,
password: authCreds.realPwd
})
await new Promise(resolve => setTimeout(resolve, 2000))
ElMessage.success('Sign in successful')
} catch (error) {
ElMessage.error('Sign in failed. Please check credentials.')
} finally {
isSubmitting.value = false
}
}
</script>
Key logic explanations:
@inputtriggers charatcer masking and real password reconstruction@selecttracks text selection for accurate deletion/replacement handling@keyup.deletehandles deletion events across various selection scenarios@copy.preventand@cut.preventdisable clipboard operations for masked content