UNPKG

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
// 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 */ @media (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 */ @media (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;