/** * DS Input - Web Component * * Usage: * * * * * Attributes: * - type: text | password | email | number | search | tel | url * - placeholder: string * - value: string * - label: string * - error: string * - disabled: boolean * - required: boolean * - icon: string (SVG content or icon name) */ class DsInput extends HTMLElement { static get observedAttributes() { return ['type', 'placeholder', 'value', 'label', 'error', 'disabled', 'required', 'icon', 'tabindex', 'aria-label', 'aria-invalid', 'aria-describedby']; } constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.render(); this.setupEventListeners(); } disconnectedCallback() { this.cleanupEventListeners(); } attributeChangedCallback(name, oldValue, newValue) { if (this.shadowRoot.innerHTML && oldValue !== newValue) { if (name === 'value') { const input = this.shadowRoot.querySelector('input'); if (input && input.value !== newValue) { input.value = newValue || ''; } } else { this.cleanupEventListeners(); this.render(); this.setupEventListeners(); } } } get type() { return this.getAttribute('type') || 'text'; } get placeholder() { return this.getAttribute('placeholder') || ''; } get value() { const input = this.shadowRoot?.querySelector('input'); return input ? input.value : (this.getAttribute('value') || ''); } set value(val) { this.setAttribute('value', val); const input = this.shadowRoot?.querySelector('input'); if (input) input.value = val; } get label() { return this.getAttribute('label'); } get error() { return this.getAttribute('error'); } get disabled() { return this.hasAttribute('disabled'); } get required() { return this.hasAttribute('required'); } get icon() { return this.getAttribute('icon'); } setupEventListeners() { const input = this.shadowRoot.querySelector('input'); if (!input) return; // Store handler references for cleanup this.inputHandler = (e) => { this.dispatchEvent(new CustomEvent('ds-input', { bubbles: true, composed: true, detail: { value: e.target.value } })); }; this.changeHandler = (e) => { this.dispatchEvent(new CustomEvent('ds-change', { bubbles: true, composed: true, detail: { value: e.target.value } })); }; this.focusHandler = () => { this.dispatchEvent(new CustomEvent('ds-focus', { bubbles: true, composed: true })); }; this.blurHandler = () => { this.dispatchEvent(new CustomEvent('ds-blur', { bubbles: true, composed: true })); }; input.addEventListener('input', this.inputHandler); input.addEventListener('change', this.changeHandler); input.addEventListener('focus', this.focusHandler); input.addEventListener('blur', this.blurHandler); } cleanupEventListeners() { const input = this.shadowRoot?.querySelector('input'); if (!input) return; // Remove all event listeners if (this.inputHandler) { input.removeEventListener('input', this.inputHandler); delete this.inputHandler; } if (this.changeHandler) { input.removeEventListener('change', this.changeHandler); delete this.changeHandler; } if (this.focusHandler) { input.removeEventListener('focus', this.focusHandler); delete this.focusHandler; } if (this.blurHandler) { input.removeEventListener('blur', this.blurHandler); delete this.blurHandler; } } focus() { this.shadowRoot.querySelector('input')?.focus(); } blur() { this.shadowRoot.querySelector('input')?.blur(); } render() { const hasIcon = !!this.icon; const hasError = !!this.error; const errorClass = hasError ? 'ds-input--error' : ''; const tabindex = this.disabled ? '-1' : (this.getAttribute('tabindex') || '0'); const errorId = hasError ? 'error-' + Math.random().toString(36).substr(2, 9) : ''; // ARIA attributes const ariaLabel = this.getAttribute('aria-label') || this.label || ''; const ariaInvalid = hasError ? 'aria-invalid="true"' : ''; const ariaDescribedBy = hasError ? `aria-describedby="${errorId}"` : ''; this.shadowRoot.innerHTML = ` ${this.label ? ` ` : ''}
${hasIcon ? `` : ''}
${hasError ? `` : ''} `; } getIconSVG() { const icons = { search: ``, email: ``, lock: ``, user: ``, }; return icons[this.icon] || this.icon || ''; } } customElements.define('ds-input', DsInput); export default DsInput;