Skip to content

Commit

Permalink
Fixed #892 - show password feature
Browse files Browse the repository at this point in the history
  • Loading branch information
cagataycivici committed Feb 3, 2021
1 parent c292995 commit c360807
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 17 deletions.
1 change: 0 additions & 1 deletion src/assets/styles/primevue.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
@import '../../components/checkbox/Checkbox.css';
@import '../../components/colorpicker/ColorPicker.css';
@import '../../components/inputtext/InputText.css';
@import '../../components/password/Password.css';
@import '../../components/radiobutton/RadioButton.css';
@import '../../components/ripple/Ripple.css';
@import '../../components/tooltip/Tooltip.css';
2 changes: 2 additions & 0 deletions src/components/password/Password.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ declare class Password extends Vue {
mediumLabel?: string;
strongLabel?: string;
feedback?: boolean;
appendTo?: string;
toggleMask?: boolean;
$emit(eventName: string, event: Event): this;
}

Expand Down
127 changes: 113 additions & 14 deletions src/components/password/Password.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
<template>
<input ref="input" type="password" :class="['p-inputtext p-component', {'p-filled': filled}]" :value="modelValue"
@input="onInput" @focus="onFocus" @blur="onBlur" @keyup="onKeyUp" v-bind="$attrs" />
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave">
<div :ref="overlayRef" class="p-password-panel p-component" v-if="overlayVisible">
<div class="p-password-meter" :style="{'background-position': meterPosition}"></div>
<div class="p-password-info">
{{infoText}}
<div :class="containerClass" :style="style">
<PInputText ref="input" :type="inputType" :value="modelValue" @input="onInput" @focus="onFocus" @blur="onBlur" @keyup="onKeyUp" v-bind="$attrs" autocomplete="fff"/>
<i v-if="toggleMask" :class="toggleIconClass" @click="onMaskToggle" />
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave">
<div :ref="overlayRef" class="p-password-panel p-component" v-if="overlayVisible">
<slot name="header"></slot>
<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>
</transition>
</transition>
</div>
</template>

<script>
import {ConnectedOverlayScrollHandler} from 'primevue/utils';
import {DomHandler} from 'primevue/utils';
import InputText from 'primevue/inputtext';
export default {
emits: ['update:modelValue'],
Expand Down Expand Up @@ -47,13 +52,25 @@ export default {
feedback: {
type: Boolean,
default: true
}
},
appendTo: {
type: String,
default: null
},
toggleMask: {
type: Boolean,
default: false
},
style: null,
class: null
},
data() {
return {
overlayVisible: false,
meterPosition: '',
infoText: this.promptLabel
infoText: null,
focused: false,
unmasked: false
};
},
mediumCheckRegExp: null,
Expand All @@ -62,10 +79,12 @@ export default {
scrollHandler: null,
overlay: null,
mounted() {
this.infoText = this.promptText;
this.mediumCheckRegExp = new RegExp(this.mediumRegex);
this.strongCheckRegExp = new RegExp(this.strongRegex);
},
beforeUnmount() {
this.restoreAppend();
this.unbindResizeListener();
if (this.scrollHandler) {
this.scrollHandler.destroy();
Expand All @@ -75,8 +94,8 @@ export default {
methods: {
onOverlayEnter() {
this.overlay.style.zIndex = String(DomHandler.generateZIndex());
this.overlay.style.minWidth = DomHandler.getOuterWidth(this.$refs.input) + 'px';
DomHandler.absolutePosition(this.overlay, this.$refs.input);
this.appendContainer();
this.alignOverlay();
this.bindScrollListener();
this.bindResizeListener();
},
Expand All @@ -85,6 +104,41 @@ export default {
this.unbindResizeListener();
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) {
let level = 0;
Expand All @@ -101,11 +155,13 @@ export default {
this.$emit('update:modelValue', event.target.value)
},
onFocus() {
this.focused = true;
if (this.feedback) {
this.overlayVisible = true;
}
},
onBlur() {
this.focused = false;
if (this.feedback) {
this.overlayVisible = false;
}
Expand Down Expand Up @@ -148,7 +204,7 @@ export default {
},
bindScrollListener() {
if (!this.scrollHandler) {
this.scrollHandler = new ConnectedOverlayScrollHandler(this.$refs.input, () => {
this.scrollHandler = new ConnectedOverlayScrollHandler(this.$refs.input.$el, () => {
if (this.overlayVisible) {
this.overlayVisible = false;
}
Expand Down Expand Up @@ -180,9 +236,30 @@ export default {
},
overlayRef(el) {
this.overlay = el;
},
onMaskToggle() {
this.unmasked = !this.unmasked;
}
},
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() {
return (this.modelValue != null && this.modelValue.toString().length > 0)
},
Expand All @@ -198,6 +275,28 @@ export default {
promptText() {
return this.promptLabel || this.$primevue.config.locale.passwordPrompt;
}
},
components: {
'PInputText': InputText
}
}
</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 removed src/components/password/images/password-meter.png
Binary file not shown.
38 changes: 36 additions & 2 deletions src/views/password/PasswordDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,32 @@

<div class="content-section implementation">
<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>

Expand All @@ -24,11 +49,20 @@ import PasswordDoc from './PasswordDoc';
export default {
data() {
return {
value: ''
value1: null,
value2: null,
value3: null,
value4: null
}
},
components: {
'PasswordDoc': PasswordDoc
}
}
</script>

<style lang="scss" scoped>
::v-deep(.p-password input) {
width: 15rem
}
</style>
34 changes: 34 additions & 0 deletions src/views/password/PasswordDoc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>

<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>
<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">
Expand Down Expand Up @@ -99,6 +121,18 @@ import Password from 'primevue/password';
<td>boolean</td>
<td>true</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>
</tbody>
</table>
Expand Down

0 comments on commit c360807

Please sign in to comment.