neumorphic-peripheral
Version:
A lightweight, framework-agnostic JavaScript/TypeScript library for beautiful neumorphic styling
289 lines (288 loc) • 11.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PasswordComponent = void 0;
exports.password = password;
const input_1 = require("./input");
const utils_1 = require("../utils");
const validators_1 = require("../validators");
class PasswordComponent extends input_1.InputComponent {
constructor(element, config = {}) {
if (!(element instanceof HTMLInputElement)) {
throw new Error('Password component requires an HTMLInputElement');
}
super(element, config);
this._isVisible = false;
this._passwordConfig = {
showToggle: true,
togglePosition: 'right',
strengthIndicator: false,
maskCharacter: '•',
...config
};
this._originalType = this.inputElement.type;
}
init() {
super.init();
this.setupPasswordField();
this.createToggleButton();
this.createStrengthIndicator();
}
bindEvents() {
super.bindEvents();
// Monitor password input for strength calculation
if (this._passwordConfig.strengthIndicator) {
this.addEventListener(this._element, 'input', () => {
this.updateStrengthIndicator();
});
}
}
setupPasswordField() {
(0, utils_1.addClassName)(this._element, 'password');
// Ensure input type is password initially
this.inputElement.type = 'password';
// Create wrapper if toggle is enabled
if (this._passwordConfig.showToggle) {
this.createInputWrapper();
}
}
createInputWrapper() {
const wrapper = (0, utils_1.createElement)('div', {
class: 'np-password-wrapper'
});
// Style the wrapper
wrapper.style.position = 'relative';
wrapper.style.display = 'inline-block';
wrapper.style.width = '100%';
// Wrap the input
const parent = this._element.parentElement;
parent.insertBefore(wrapper, this._element);
wrapper.appendChild(this._element);
// Adjust input styles for wrapper
this._element.style.paddingRight = this._passwordConfig.togglePosition === 'right' ? '45px' : this._element.style.paddingRight;
this._element.style.paddingLeft = this._passwordConfig.togglePosition === 'left' ? '45px' : this._element.style.paddingLeft;
}
createToggleButton() {
if (!this._passwordConfig.showToggle)
return;
this._toggleButton = (0, utils_1.createElement)('button', {
type: 'button',
class: 'np-password-toggle',
'aria-label': 'Toggle password visibility'
});
// Style the toggle button
this._toggleButton.style.position = 'absolute';
this._toggleButton.style.top = '50%';
this._toggleButton.style.transform = 'translateY(-50%)';
this._toggleButton.style[this._passwordConfig.togglePosition] = '12px';
this._toggleButton.style.background = 'none';
this._toggleButton.style.border = 'none';
this._toggleButton.style.cursor = 'pointer';
this._toggleButton.style.padding = '4px';
this._toggleButton.style.color = this._theme.colors.textSecondary;
this._toggleButton.style.fontSize = '16px';
this._toggleButton.style.lineHeight = '1';
this._toggleButton.style.borderRadius = '4px';
this._toggleButton.style.transition = `color ${this._theme.animation.duration} ${this._theme.animation.easing}`;
// Add eye icon
this.updateToggleIcon();
// Add hover effect
this._toggleButton.addEventListener('mouseenter', () => {
this._toggleButton.style.color = this._theme.colors.text;
});
this._toggleButton.addEventListener('mouseleave', () => {
this._toggleButton.style.color = this._theme.colors.textSecondary;
});
// Add click handler
this._toggleButton.addEventListener('click', (e) => {
e.preventDefault();
this.toggleVisibility();
});
// Append to wrapper or parent
const wrapper = this._element.parentElement;
wrapper.appendChild(this._toggleButton);
}
updateToggleIcon() {
if (!this._toggleButton)
return;
// Use Unicode eye symbols or create SVG icons
const eyeIcon = this._isVisible ? '👁️' : '👁️🗨️';
const ariaLabel = this._isVisible ? 'Hide password' : 'Show password';
this._toggleButton.innerHTML = this.createEyeIcon(this._isVisible);
this._toggleButton.setAttribute('aria-label', ariaLabel);
}
createEyeIcon(isVisible) {
if (isVisible) {
// Eye with slash (hidden)
return `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
<line x1="1" y1="1" x2="23" y2="23"/>
</svg>
`;
}
else {
// Regular eye (visible)
return `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
`;
}
}
createStrengthIndicator() {
if (!this._passwordConfig.strengthIndicator)
return;
this._strengthIndicator = (0, utils_1.createElement)('div', {
class: 'np-password-strength'
});
// Style the strength indicator
this._strengthIndicator.style.marginTop = '8px';
this._strengthIndicator.style.display = 'flex';
this._strengthIndicator.style.gap = '4px';
this._strengthIndicator.style.alignItems = 'center';
// Create strength bars
for (let i = 0; i < 4; i++) {
const bar = (0, utils_1.createElement)('div', {
class: 'np-strength-bar'
});
bar.style.height = '4px';
bar.style.flex = '1';
bar.style.borderRadius = '2px';
bar.style.backgroundColor = this._theme.colors.surface;
bar.style.boxShadow = this.createShadowStyle('inset');
bar.style.transition = `background-color ${this._theme.animation.duration} ${this._theme.animation.easing}`;
this._strengthIndicator.appendChild(bar);
}
// Add strength text
const strengthText = (0, utils_1.createElement)('span', {
class: 'np-strength-text'
});
strengthText.style.fontSize = '12px';
strengthText.style.color = this._theme.colors.textSecondary;
strengthText.style.marginLeft = '8px';
strengthText.style.minWidth = '60px';
strengthText.textContent = 'Weak';
this._strengthIndicator.appendChild(strengthText);
// Insert after input wrapper or input
const wrapper = this._element.parentElement;
const parent = wrapper.parentElement;
parent.insertBefore(this._strengthIndicator, wrapper.nextSibling);
}
updateStrengthIndicator() {
if (!this._strengthIndicator)
return;
const password = this.getValue();
const strength = (0, validators_1.validatePasswordStrength)(password);
const bars = this._strengthIndicator.querySelectorAll('.np-strength-bar');
const text = this._strengthIndicator.querySelector('.np-strength-text');
// Color mapping
const colors = {
weak: this._theme.colors.error,
fair: '#ff9500', // Orange
good: '#007aff', // Blue
strong: this._theme.colors.success
};
// Update bars
bars.forEach((bar, index) => {
if (index < strength.score) {
bar.style.backgroundColor = colors[strength.strength];
bar.style.boxShadow = 'none';
}
else {
bar.style.backgroundColor = this._theme.colors.surface;
bar.style.boxShadow = this.createShadowStyle('inset');
}
});
// Update text
text.textContent = strength.strength.charAt(0).toUpperCase() + strength.strength.slice(1);
text.style.color = colors[strength.strength];
// Emit strength change event
this.emit('strength-change', { strength });
}
// Public API methods
toggleVisibility() {
this._isVisible = !this._isVisible;
if (this._isVisible) {
this.inputElement.type = 'text';
}
else {
this.inputElement.type = 'password';
}
this.updateToggleIcon();
this.emit('visibility-toggle', { visible: this._isVisible });
}
isVisible() {
return this._isVisible;
}
showPassword() {
if (!this._isVisible) {
this.toggleVisibility();
}
}
hidePassword() {
if (this._isVisible) {
this.toggleVisibility();
}
}
getPasswordStrength() {
const password = this.getValue();
return password ? (0, validators_1.validatePasswordStrength)(password) : null;
}
onUpdate(newConfig) {
super.onUpdate(newConfig);
const oldConfig = { ...this._passwordConfig };
this._passwordConfig = { ...this._passwordConfig, ...newConfig };
// Update toggle visibility
if (newConfig.showToggle !== oldConfig.showToggle) {
if (newConfig.showToggle && !this._toggleButton) {
this.createInputWrapper();
this.createToggleButton();
}
else if (!newConfig.showToggle && this._toggleButton) {
this._toggleButton.remove();
this._toggleButton = undefined;
}
}
// Update strength indicator
if (newConfig.strengthIndicator !== oldConfig.strengthIndicator) {
if (newConfig.strengthIndicator && !this._strengthIndicator) {
this.createStrengthIndicator();
}
else if (!newConfig.strengthIndicator && this._strengthIndicator) {
this._strengthIndicator.remove();
this._strengthIndicator = undefined;
}
}
// Update toggle position
if (newConfig.togglePosition !== oldConfig.togglePosition && this._toggleButton) {
this._toggleButton.style[oldConfig.togglePosition] = 'auto';
this._toggleButton.style[newConfig.togglePosition] = '12px';
// Update input padding
if (newConfig.togglePosition === 'right') {
this._element.style.paddingRight = '45px';
this._element.style.paddingLeft = '16px';
}
else {
this._element.style.paddingLeft = '45px';
this._element.style.paddingRight = '16px';
}
}
}
onDestroy() {
super.onDestroy();
if (this._toggleButton) {
this._toggleButton.remove();
}
if (this._strengthIndicator) {
this._strengthIndicator.remove();
}
// Restore original input type
this.inputElement.type = this._originalType;
}
}
exports.PasswordComponent = PasswordComponent;
// Factory function for easy usage
function password(element, config = {}) {
return new PasswordComponent(element, config);
}