/** * AKSRT Input Expand * * Lightweight input field enhancements: * - Textarea auto-resize on input * - Character counter with limit display * - Validation feedback (min/max length, required) * * No external dependencies — pure vanilla JS. * * Usage: * * * * * @package AKSRT */ (function () { 'use strict'; var STYLE_ID = 'aksrt-input-expand-style'; function injectStyle() { if (document.getElementById(STYLE_ID)) return; var style = document.createElement('style'); style.id = STYLE_ID; style.textContent = '.aksrt-counter{display:block;text-align:right;font-size:12px;color:#8a909c;margin-top:4px;transition:color 0.2s;}' + '.aksrt-counter.is-over{color:#e74c3c;}' + '.aksrt-counter.is-warn{color:#f39c12;}' + '.aksrt-validate-msg{display:block;font-size:12px;margin-top:4px;color:#e74c3c;opacity:0;max-height:0;overflow:hidden;transition:all 0.2s;}' + '.aksrt-validate-msg.is-visible{opacity:1;max-height:30px;}' + 'textarea[data-aksrt-autoresize]{overflow:hidden;resize:none;}'; document.head.appendChild(style); } // ── Auto-resize Textarea ── function initAutoResize() { var textareas = document.querySelectorAll('textarea[data-aksrt-autoresize]'); textareas.forEach(function (ta) { function resize() { ta.style.height = 'auto'; ta.style.height = ta.scrollHeight + 'px'; } ta.addEventListener('input', resize); // Initial resize resize(); }); } // ── Character Counter ── function initCounters() { var fields = document.querySelectorAll('[data-aksrt-counter]'); fields.forEach(function (field) { var max = parseInt(field.getAttribute('data-aksrt-maxlength') || field.getAttribute('maxlength') || '0', 10); var counter = document.createElement('span'); counter.className = 'aksrt-counter'; field.parentNode.insertBefore(counter, field.nextSibling); function update() { var len = (field.value || '').length; if (max > 0) { counter.textContent = len + ' / ' + max; counter.classList.toggle('is-over', len > max); counter.classList.toggle('is-warn', len > max * 0.9 && len <= max); } else { counter.textContent = len + ' 字'; counter.classList.remove('is-over', 'is-warn'); } } field.addEventListener('input', update); update(); }); } // ── Validation Feedback ── function initValidation() { var fields = document.querySelectorAll('[data-aksrt-validate]'); fields.forEach(function (field) { var minLen = parseInt(field.getAttribute('data-aksrt-minlength') || field.getAttribute('minlength') || '0', 10); var maxLen = parseInt(field.getAttribute('data-aksrt-maxlength') || field.getAttribute('maxlength') || '0', 10); var required = field.hasAttribute('required'); var msg = document.createElement('span'); msg.className = 'aksrt-validate-msg'; field.parentNode.insertBefore(msg, field.nextSibling); function validate() { var val = (field.value || '').trim(); var error = ''; if (required && val === '') { error = '此项为必填'; } else if (minLen > 0 && val.length > 0 && val.length < minLen) { error = '至少需要 ' + minLen + ' 个字符'; } else if (maxLen > 0 && val.length > maxLen) { error = '不能超过 ' + maxLen + ' 个字符'; } msg.textContent = error; msg.classList.toggle('is-visible', error !== ''); field.classList.toggle('is-invalid', error !== ''); field.classList.toggle('is-valid', error === '' && val !== ''); } field.addEventListener('input', validate); field.addEventListener('blur', validate); }); } // ── Main Init ── function init() { injectStyle(); initAutoResize(); initCounters(); initValidation(); } jQuery(document).off('auto_fun.aksrt-input-expand').on('auto_fun.aksrt-input-expand', function () { init(document); }); })();