muspe-cli
Version:
MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo
538 lines (447 loc) • 13.8 kB
JavaScript
// MusPE UI - Modern Input Component
class Input {
constructor(options = {}) {
this.options = {
type: 'text', // text, email, password, number, tel, url, search
placeholder: '',
label: '',
value: '',
disabled: false,
readonly: false,
required: false,
error: '',
success: false,
size: 'md', // sm, md, lg
variant: 'outline', // outline, filled, underline
icon: null,
iconPosition: 'left', // left, right
clearable: false,
maxLength: null,
pattern: null,
autocomplete: 'off',
...options
};
this.element = null;
this.inputElement = null;
this.callbacks = {
onInput: null,
onChange: null,
onFocus: null,
onBlur: null,
onClear: null,
...options.callbacks
};
}
render() {
this.element = document.createElement('div');
this.element.className = this.buildClasses();
this.element.innerHTML = `
${this.options.label ? `<label class="muspe-input__label">${this.options.label}${this.options.required ? ' *' : ''}</label>` : ''}
<div class="muspe-input__container">
${this.options.icon && this.options.iconPosition === 'left' ?
`<span class="muspe-input__icon muspe-input__icon--left">${this.options.icon}</span>` : ''}
<input
class="muspe-input__field"
type="${this.options.type}"
placeholder="${this.options.placeholder}"
value="${this.options.value}"
${this.options.disabled ? 'disabled' : ''}
${this.options.readonly ? 'readonly' : ''}
${this.options.required ? 'required' : ''}
${this.options.maxLength ? `maxlength="${this.options.maxLength}"` : ''}
${this.options.pattern ? `pattern="${this.options.pattern}"` : ''}
autocomplete="${this.options.autocomplete}"
/>
${this.options.clearable ? '<button type="button" class="muspe-input__clear" tabindex="-1">×</button>' : ''}
${this.options.icon && this.options.iconPosition === 'right' ?
`<span class="muspe-input__icon muspe-input__icon--right">${this.options.icon}</span>` : ''}
</div>
${this.options.error ? `<div class="muspe-input__error">${this.options.error}</div>` : ''}
${this.options.success ? '<div class="muspe-input__success">✓</div>' : ''}
${this.options.maxLength ? `<div class="muspe-input__counter"><span class="muspe-input__count">0</span>/${this.options.maxLength}</div>` : ''}
`;
this.inputElement = this.element.querySelector('.muspe-input__field');
this.attachEvents();
this.updateCounter();
return this.element;
}
buildClasses() {
const classes = ['muspe-input'];
classes.push(`muspe-input--${this.options.variant}`);
classes.push(`muspe-input--${this.options.size}`);
if (this.options.disabled) {
classes.push('muspe-input--disabled');
}
if (this.options.readonly) {
classes.push('muspe-input--readonly');
}
if (this.options.error) {
classes.push('muspe-input--error');
}
if (this.options.success) {
classes.push('muspe-input--success');
}
return classes.join(' ');
}
attachEvents() {
if (!this.inputElement) return;
// Input event
this.inputElement.addEventListener('input', (e) => {
this.options.value = e.target.value;
this.updateCounter();
this.updateClearButton();
if (this.callbacks.onInput) {
this.callbacks.onInput(e.target.value, e);
}
});
// Change event
this.inputElement.addEventListener('change', (e) => {
if (this.callbacks.onChange) {
this.callbacks.onChange(e.target.value, e);
}
});
// Focus event
this.inputElement.addEventListener('focus', (e) => {
this.element.classList.add('muspe-input--focused');
if (this.callbacks.onFocus) {
this.callbacks.onFocus(e);
}
});
// Blur event
this.inputElement.addEventListener('blur', (e) => {
this.element.classList.remove('muspe-input--focused');
if (this.callbacks.onBlur) {
this.callbacks.onBlur(e);
}
});
// Clear button
const clearButton = this.element.querySelector('.muspe-input__clear');
if (clearButton) {
clearButton.addEventListener('click', () => {
this.setValue('');
this.inputElement.focus();
if (this.callbacks.onClear) {
this.callbacks.onClear();
}
});
}
// Password toggle for password inputs
if (this.options.type === 'password' && this.options.togglePassword !== false) {
this.addPasswordToggle();
}
}
addPasswordToggle() {
const container = this.element.querySelector('.muspe-input__container');
const toggleButton = document.createElement('button');
toggleButton.type = 'button';
toggleButton.className = 'muspe-input__toggle';
toggleButton.innerHTML = '👁';
toggleButton.tabIndex = -1;
toggleButton.addEventListener('click', () => {
const isPassword = this.inputElement.type === 'password';
this.inputElement.type = isPassword ? 'text' : 'password';
toggleButton.innerHTML = isPassword ? '🙈' : '👁';
});
container.appendChild(toggleButton);
}
updateCounter() {
const counter = this.element?.querySelector('.muspe-input__count');
if (counter) {
counter.textContent = this.inputElement.value.length;
}
}
updateClearButton() {
const clearButton = this.element?.querySelector('.muspe-input__clear');
if (clearButton) {
clearButton.style.display = this.inputElement.value ? 'flex' : 'none';
}
}
setValue(value) {
this.options.value = value;
if (this.inputElement) {
this.inputElement.value = value;
this.updateCounter();
this.updateClearButton();
}
}
getValue() {
return this.inputElement ? this.inputElement.value : this.options.value;
}
setError(error) {
this.options.error = error;
this.options.success = false;
if (!this.element) return;
// Remove existing error/success
this.element.classList.remove('muspe-input--error', 'muspe-input--success');
const existingError = this.element.querySelector('.muspe-input__error');
const existingSuccess = this.element.querySelector('.muspe-input__success');
if (existingError) existingError.remove();
if (existingSuccess) existingSuccess.remove();
if (error) {
this.element.classList.add('muspe-input--error');
const errorElement = document.createElement('div');
errorElement.className = 'muspe-input__error';
errorElement.textContent = error;
this.element.appendChild(errorElement);
}
}
setSuccess(success = true) {
this.options.success = success;
this.options.error = '';
if (!this.element) return;
this.element.classList.remove('muspe-input--error', 'muspe-input--success');
const existingError = this.element.querySelector('.muspe-input__error');
const existingSuccess = this.element.querySelector('.muspe-input__success');
if (existingError) existingError.remove();
if (existingSuccess) existingSuccess.remove();
if (success) {
this.element.classList.add('muspe-input--success');
const successElement = document.createElement('div');
successElement.className = 'muspe-input__success';
successElement.textContent = '✓';
this.element.appendChild(successElement);
}
}
focus() {
if (this.inputElement) {
this.inputElement.focus();
}
}
blur() {
if (this.inputElement) {
this.inputElement.blur();
}
}
setDisabled(disabled) {
this.options.disabled = disabled;
if (this.inputElement) {
this.inputElement.disabled = disabled;
}
if (this.element) {
if (disabled) {
this.element.classList.add('muspe-input--disabled');
} else {
this.element.classList.remove('muspe-input--disabled');
}
}
}
destroy() {
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.element = null;
this.inputElement = null;
}
}
// CSS Styles for Input
const inputStyles = `
.muspe-input {
display: flex;
flex-direction: column;
gap: var(--muspe-space-2);
width: 100%;
}
.muspe-input__label {
font-size: var(--muspe-font-size-sm);
font-weight: 600;
color: var(--muspe-gray-700);
margin: 0;
}
.muspe-input__container {
position: relative;
display: flex;
align-items: center;
}
.muspe-input__field {
flex: 1;
font-family: var(--muspe-font-family);
font-size: var(--muspe-font-size-base);
color: var(--muspe-gray-900);
background: var(--muspe-white);
border: 2px solid var(--muspe-gray-300);
border-radius: var(--muspe-radius-lg);
padding: var(--muspe-space-3) var(--muspe-space-4);
transition: all var(--muspe-transition-fast);
width: 100%;
min-height: 44px;
}
.muspe-input__field:focus {
outline: none;
border-color: var(--muspe-primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.muspe-input__field::placeholder {
color: var(--muspe-gray-400);
}
.muspe-input__field:disabled {
background: var(--muspe-gray-100);
color: var(--muspe-gray-500);
cursor: not-allowed;
}
.muspe-input__field:readonly {
background: var(--muspe-gray-50);
cursor: default;
}
/* Variants */
.muspe-input--filled .muspe-input__field {
background: var(--muspe-gray-100);
border: 2px solid transparent;
}
.muspe-input--filled .muspe-input__field:focus {
background: var(--muspe-white);
border-color: var(--muspe-primary);
}
.muspe-input--underline .muspe-input__field {
background: transparent;
border: none;
border-bottom: 2px solid var(--muspe-gray-300);
border-radius: 0;
padding-left: 0;
padding-right: 0;
}
.muspe-input--underline .muspe-input__field:focus {
border-bottom-color: var(--muspe-primary);
box-shadow: none;
}
/* Sizes */
.muspe-input--sm .muspe-input__field {
font-size: var(--muspe-font-size-sm);
padding: var(--muspe-space-2) var(--muspe-space-3);
min-height: 36px;
}
.muspe-input--lg .muspe-input__field {
font-size: var(--muspe-font-size-lg);
padding: var(--muspe-space-4) var(--muspe-space-5);
min-height: 52px;
}
/* States */
.muspe-input--error .muspe-input__field {
border-color: var(--muspe-error);
}
.muspe-input--error .muspe-input__field:focus {
border-color: var(--muspe-error);
box-shadow: 0 0 0 3px rgba(255, 59, 48, 0.1);
}
.muspe-input--success .muspe-input__field {
border-color: var(--muspe-success);
}
.muspe-input--success .muspe-input__field:focus {
border-color: var(--muspe-success);
box-shadow: 0 0 0 3px rgba(52, 199, 89, 0.1);
}
.muspe-input--focused .muspe-input__field {
border-color: var(--muspe-primary);
}
/* Icons */
.muspe-input__icon {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
color: var(--muspe-gray-400);
pointer-events: none;
z-index: 1;
}
.muspe-input__icon--left {
left: var(--muspe-space-3);
}
.muspe-input__icon--right {
right: var(--muspe-space-3);
}
.muspe-input__icon + .muspe-input__field,
.muspe-input__field:has(+ .muspe-input__icon--left) {
padding-left: calc(var(--muspe-space-4) + 24px + var(--muspe-space-2));
}
.muspe-input__icon--right ~ .muspe-input__field,
.muspe-input__field:has(~ .muspe-input__icon--right) {
padding-right: calc(var(--muspe-space-4) + 24px + var(--muspe-space-2));
}
/* Clear and Toggle buttons */
.muspe-input__clear,
.muspe-input__toggle {
position: absolute;
right: var(--muspe-space-2);
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: transparent;
border: none;
border-radius: var(--muspe-radius-md);
color: var(--muspe-gray-400);
cursor: pointer;
transition: all var(--muspe-transition-fast);
font-size: 18px;
line-height: 1;
}
.muspe-input__clear:hover,
.muspe-input__toggle:hover {
background: var(--muspe-gray-100);
color: var(--muspe-gray-600);
}
.muspe-input__clear {
display: none;
font-size: 20px;
font-weight: bold;
}
/* Messages */
.muspe-input__error {
font-size: var(--muspe-font-size-sm);
color: var(--muspe-error);
margin: 0;
}
.muspe-input__success {
font-size: var(--muspe-font-size-sm);
color: var(--muspe-success);
margin: 0;
}
.muspe-input__counter {
font-size: var(--muspe-font-size-xs);
color: var(--muspe-gray-500);
text-align: right;
margin: 0;
}
/* Dark mode */
(prefers-color-scheme: dark) {
.muspe-input__label {
color: var(--muspe-gray-300);
}
.muspe-input__field {
background: var(--muspe-gray-800);
border-color: var(--muspe-gray-600);
color: var(--muspe-gray-100);
}
.muspe-input__field:disabled {
background: var(--muspe-gray-900);
color: var(--muspe-gray-600);
}
.muspe-input--filled .muspe-input__field {
background: var(--muspe-gray-700);
}
}
/* Touch optimizations */
(hover: none) and (pointer: coarse) {
.muspe-input__field {
min-height: 48px;
}
.muspe-input--sm .muspe-input__field {
min-height: 40px;
}
.muspe-input--lg .muspe-input__field {
min-height: 56px;
}
.muspe-input__clear,
.muspe-input__toggle {
width: 36px;
height: 36px;
}
}
`;
// Auto-inject styles
if (typeof document !== 'undefined') {
const styleSheet = document.createElement('style');
styleSheet.textContent = inputStyles;
document.head.appendChild(styleSheet);
}
export default Input;