Fixed #892 - show password feature

pull/938/head
Cagatay Civici 2021-02-03 12:42:05 +03:00
parent c29299537e
commit c360807e23
6 changed files with 185 additions and 17 deletions

View File

@ -4,7 +4,6 @@
@import '../../components/checkbox/Checkbox.css'; @import '../../components/checkbox/Checkbox.css';
@import '../../components/colorpicker/ColorPicker.css'; @import '../../components/colorpicker/ColorPicker.css';
@import '../../components/inputtext/InputText.css'; @import '../../components/inputtext/InputText.css';
@import '../../components/password/Password.css';
@import '../../components/radiobutton/RadioButton.css'; @import '../../components/radiobutton/RadioButton.css';
@import '../../components/ripple/Ripple.css'; @import '../../components/ripple/Ripple.css';
@import '../../components/tooltip/Tooltip.css'; @import '../../components/tooltip/Tooltip.css';

View File

@ -9,6 +9,8 @@ declare class Password extends Vue {
mediumLabel?: string; mediumLabel?: string;
strongLabel?: string; strongLabel?: string;
feedback?: boolean; feedback?: boolean;
appendTo?: string;
toggleMask?: boolean;
$emit(eventName: string, event: Event): this; $emit(eventName: string, event: Event): this;
} }

View File

@ -1,19 +1,24 @@
<template> <template>
<input ref="input" type="password" :class="['p-inputtext p-component', {'p-filled': filled}]" :value="modelValue" <div :class="containerClass" :style="style">
@input="onInput" @focus="onFocus" @blur="onBlur" @keyup="onKeyUp" v-bind="$attrs" /> <PInputText ref="input" :type="inputType" :value="modelValue" @input="onInput" @focus="onFocus" @blur="onBlur" @keyup="onKeyUp" v-bind="$attrs" autocomplete="fff"/>
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave"> <i v-if="toggleMask" :class="toggleIconClass" @click="onMaskToggle" />
<div :ref="overlayRef" class="p-password-panel p-component" v-if="overlayVisible"> <transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave">
<div class="p-password-meter" :style="{'background-position': meterPosition}"></div> <div :ref="overlayRef" class="p-password-panel p-component" v-if="overlayVisible">
<div class="p-password-info"> <slot name="header"></slot>
{{infoText}} <slot name="content">
<div class="p-password-meter" :style="{'background-position': meterPosition}"></div>
<div class="p-password-info">{{infoText}}</div>
</slot>
<slot name="footer"></slot>
</div> </div>
</div> </transition>
</transition> </div>
</template> </template>
<script> <script>
import {ConnectedOverlayScrollHandler} from 'primevue/utils'; import {ConnectedOverlayScrollHandler} from 'primevue/utils';
import {DomHandler} from 'primevue/utils'; import {DomHandler} from 'primevue/utils';
import InputText from 'primevue/inputtext';
export default { export default {
emits: ['update:modelValue'], emits: ['update:modelValue'],
@ -47,13 +52,25 @@ export default {
feedback: { feedback: {
type: Boolean, type: Boolean,
default: true default: true
} },
appendTo: {
type: String,
default: null
},
toggleMask: {
type: Boolean,
default: false
},
style: null,
class: null
}, },
data() { data() {
return { return {
overlayVisible: false, overlayVisible: false,
meterPosition: '', meterPosition: '',
infoText: this.promptLabel infoText: null,
focused: false,
unmasked: false
}; };
}, },
mediumCheckRegExp: null, mediumCheckRegExp: null,
@ -62,10 +79,12 @@ export default {
scrollHandler: null, scrollHandler: null,
overlay: null, overlay: null,
mounted() { mounted() {
this.infoText = this.promptText;
this.mediumCheckRegExp = new RegExp(this.mediumRegex); this.mediumCheckRegExp = new RegExp(this.mediumRegex);
this.strongCheckRegExp = new RegExp(this.strongRegex); this.strongCheckRegExp = new RegExp(this.strongRegex);
}, },
beforeUnmount() { beforeUnmount() {
this.restoreAppend();
this.unbindResizeListener(); this.unbindResizeListener();
if (this.scrollHandler) { if (this.scrollHandler) {
this.scrollHandler.destroy(); this.scrollHandler.destroy();
@ -75,8 +94,8 @@ export default {
methods: { methods: {
onOverlayEnter() { onOverlayEnter() {
this.overlay.style.zIndex = String(DomHandler.generateZIndex()); this.overlay.style.zIndex = String(DomHandler.generateZIndex());
this.overlay.style.minWidth = DomHandler.getOuterWidth(this.$refs.input) + 'px'; this.appendContainer();
DomHandler.absolutePosition(this.overlay, this.$refs.input); this.alignOverlay();
this.bindScrollListener(); this.bindScrollListener();
this.bindResizeListener(); this.bindResizeListener();
}, },
@ -85,6 +104,41 @@ export default {
this.unbindResizeListener(); this.unbindResizeListener();
this.overlay = null; this.overlay = null;
}, },
alignOverlay() {
if (this.appendTo) {
this.overlay.style.minWidth = DomHandler.getOuterWidth(this.$refs.input.$el) + 'px';
DomHandler.absolutePosition(this.overlay, this.$refs.input.$el);
}
else {
DomHandler.relativePosition(this.overlay, this.$refs.input.$el);
}
},
appendContainer() {
if (this.appendTo) {
if (this.appendTo === 'body')
document.body.appendChild(this.overlay);
else
document.getElementById(this.appendTo).appendChild(this.overlay);
}
},
restoreAppend() {
if (this.overlay && this.appendTo) {
if (this.appendTo === 'body')
document.body.removeChild(this.overlay);
else
document.getElementById(this.appendTo).removeChild(this.overlay);
}
},
bindOutsideClickListener() {
if (!this.outsideClickListener) {
this.outsideClickListener = (event) => {
if (this.overlayVisible && this.overlay && this.isOutsideClicked(event)) {
this.hideOverlay();
}
};
document.addEventListener('click', this.outsideClickListener);
}
},
testStrength(str) { testStrength(str) {
let level = 0; let level = 0;
@ -101,11 +155,13 @@ export default {
this.$emit('update:modelValue', event.target.value) this.$emit('update:modelValue', event.target.value)
}, },
onFocus() { onFocus() {
this.focused = true;
if (this.feedback) { if (this.feedback) {
this.overlayVisible = true; this.overlayVisible = true;
} }
}, },
onBlur() { onBlur() {
this.focused = false;
if (this.feedback) { if (this.feedback) {
this.overlayVisible = false; this.overlayVisible = false;
} }
@ -148,7 +204,7 @@ export default {
}, },
bindScrollListener() { bindScrollListener() {
if (!this.scrollHandler) { if (!this.scrollHandler) {
this.scrollHandler = new ConnectedOverlayScrollHandler(this.$refs.input, () => { this.scrollHandler = new ConnectedOverlayScrollHandler(this.$refs.input.$el, () => {
if (this.overlayVisible) { if (this.overlayVisible) {
this.overlayVisible = false; this.overlayVisible = false;
} }
@ -180,9 +236,30 @@ export default {
}, },
overlayRef(el) { overlayRef(el) {
this.overlay = el; this.overlay = el;
},
onMaskToggle() {
this.unmasked = !this.unmasked;
} }
}, },
computed: { computed: {
containerClass() {
return ['p-password p-component p-inputwrapper', this.class, {
'p-inputwrapper-filled': this.filled,
'p-inputwrapper-focus': this.focused,
'p-input-icon-right': this.toggleMask
}];
},
inputClass() {
return ['p-password-input', {
'p-disabled': this.$attrs.disabled
}];
},
toggleIconClass() {
return this.unmasked ? 'pi pi-eye-slash' : 'pi pi-eye';
},
inputType() {
return this.unmasked ? 'text' : 'password';
},
filled() { filled() {
return (this.modelValue != null && this.modelValue.toString().length > 0) return (this.modelValue != null && this.modelValue.toString().length > 0)
}, },
@ -198,6 +275,28 @@ export default {
promptText() { promptText() {
return this.promptLabel || this.$primevue.config.locale.passwordPrompt; return this.promptLabel || this.$primevue.config.locale.passwordPrompt;
} }
},
components: {
'PInputText': InputText
} }
} }
</script> </script>
<style>
.p-password {
position: relative;
display: inline-flex;
}
.p-password-panel {
position: absolute;
}
.p-password .p-password-panel {
min-width: 100%;
}
.p-password-meter {
height: 10px;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

View File

@ -10,7 +10,32 @@
<div class="content-section implementation"> <div class="content-section implementation">
<div class="card"> <div class="card">
<Password v-model="value" /> <h5>Basic</h5>
<Password v-model="value1" :feedback="false" />
<h5>Password Meter</h5>
<Password v-model="value2" />
<h5>Show Password</h5>
<Password v-model="value3" toggleMask></Password>
<h5>Templating</h5>
<Password v-model="value4">
<template #header>
<h6>Pick a password</h6>
</template>
<template #footer="sp">
{{sp.level}}
<Divider />
<p class="p-mt-2">Suggestions</p>
<ul class="p-pl-2 p-ml-2 p-mt-0" style="line-height: 1.5">
<li>At least one lowercase</li>
<li>At least one uppercase</li>
<li>At least one numeric</li>
<li>Minimum 8 characters</li>
</ul>
</template>
</Password>
</div> </div>
</div> </div>
@ -24,7 +49,10 @@ import PasswordDoc from './PasswordDoc';
export default { export default {
data() { data() {
return { return {
value: '' value1: null,
value2: null,
value3: null,
value4: null
} }
}, },
components: { components: {
@ -32,3 +60,9 @@ export default {
} }
} }
</script> </script>
<style lang="scss" scoped>
::v-deep(.p-password input) {
width: 15rem
}
</style>

View File

@ -39,6 +39,28 @@ import Password from 'primevue/password';
<p>It is possible to define your own checks with the <i>mediumRegex</i> and <i>strongRegex</i> properties.</p> <p>It is possible to define your own checks with the <i>mediumRegex</i> and <i>strongRegex</i> properties.</p>
<h5>Templating</h5>
<p>3 slots are included to customize the overlay. These are <i>header</i>, <i>content</i> and <i>footer</i>. Note that content overrides the default meter.</p>
<pre v-code>
<code>
&lt;Password v-model="value4"&gt;
&lt;template #header&gt;
&lt;h6&gt;Pick a password&lt;/h6&gt;
&lt;/template&gt;
&lt;template #footer&gt;
&lt;Divider /&gt;
&lt;p class="p-mt-2"&gt;Suggestions&lt;/p&gt;
&lt;ul class="p-pl-2 p-ml-2 p-mt-0" style="line-height: 1.5"&gt;
&lt;li&gt;At least one lowercase&lt;/li&gt;
&lt;li&gt;At least one uppercase&lt;/li&gt;
&lt;li&gt;At least one numeric&lt;/li&gt;
&lt;li&gt;Minimum 8 characters&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;
&lt;/Password&gt;
</code></pre>
<h5>Properties</h5> <h5>Properties</h5>
<p>Any property such as name and placeholder are passed to the underlying input element. Following are the additional properties to configure the component.</p> <p>Any property such as name and placeholder are passed to the underlying input element. Following are the additional properties to configure the component.</p>
<div class="doc-tablewrapper"> <div class="doc-tablewrapper">
@ -99,6 +121,18 @@ import Password from 'primevue/password';
<td>boolean</td> <td>boolean</td>
<td>true</td> <td>true</td>
<td>Whether to show the strength indicator or not.</td> <td>Whether to show the strength indicator or not.</td>
</tr>
<tr>
<td>toggleMask</td>
<td>boolean</td>
<td>false</td>
<td>Whether to show an icon to display the password as plain text.</td>
</tr>
<tr>
<td>appendTo</td>
<td>string</td>
<td>null</td>
<td>Id of the element or "body" for document where the overlay should be appended to.</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>