From e10516d32f5f3b3fe96ed208cb25f64dca81bd73 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Thu, 30 Apr 2026 09:13:06 +0200 Subject: [PATCH] ux: pulse error border on repeated validation failures --- public/components/modal.js | 14 ++++++++++++++ public/styles/layout.css | 16 ++++++++++++++++ test-modal-utils.js | 8 ++++++++ 3 files changed, 38 insertions(+) diff --git a/public/components/modal.js b/public/components/modal.js index c124191..23e9133 100644 --- a/public/components/modal.js +++ b/public/components/modal.js @@ -608,6 +608,20 @@ function _validateField(input) { group?.classList.toggle('form-field--error', !hasValue); group?.classList.toggle('form-field--valid', hasValue); input.setAttribute('aria-invalid', String(!hasValue)); + + if (!hasValue && group) { + const count = parseInt(group.dataset.errorCount ?? '0', 10) + 1; + group.dataset.errorCount = String(count); + if (count >= 2) { + group.classList.remove('form-field--error-repeat'); + void group.offsetWidth; + group.classList.add('form-field--error-repeat'); + group.addEventListener('animationend', () => group.classList.remove('form-field--error-repeat'), { once: true }); + } + } else if (hasValue && group) { + group.dataset.errorCount = '0'; + } + return hasValue; } diff --git a/public/styles/layout.css b/public/styles/layout.css index 63fb3a5..bf2853a 100755 --- a/public/styles/layout.css +++ b/public/styles/layout.css @@ -1388,6 +1388,22 @@ html.fab-anim-done .page-fab { display: flex; } +/* Pulse-Animation bei wiederholtem Fehler */ +@keyframes field-error-pulse { + 0%, 100% { box-shadow: none; } + 40% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-danger) 35%, transparent); } +} + +.form-field--error-repeat .input, +.form-field--error-repeat .form-input { + animation: field-error-pulse 500ms var(--ease-out); +} + +@media (prefers-reduced-motion: reduce) { + .form-field--error-repeat .input, + .form-field--error-repeat .form-input { animation: none; } +} + /* -------------------------------------------------------- * Toggle-Switch * Custom iOS-style toggle, ersetzt native Checkboxen. diff --git a/test-modal-utils.js b/test-modal-utils.js index 6dd680e..cd29347 100644 --- a/test-modal-utils.js +++ b/test-modal-utils.js @@ -39,12 +39,20 @@ const _origSetTimeout = setTimeout; function makeField() { const classes = new Set(); + const listeners = {}; + const dataset = {}; return { + dataset, + offsetWidth: 0, classList: { toggle(cls, force) { force ? classes.add(cls) : classes.delete(cls); }, + add(cls) { classes.add(cls); }, + remove(cls) { classes.delete(cls); }, contains(cls) { return classes.has(cls); }, }, + addEventListener(event, fn) { listeners[event] = fn; }, _classes: classes, + _listeners: listeners, }; }