fix(ux): locale reload feedback, submit validation, dedup blur logic
- Locale-Picker: disable select + fade before location.reload() (system mode) gives visual feedback before the page jumps - Extract _validateField() helper from wireBlurValidation to avoid duplication - Add validateAll(formContainer): validates all required fields on demand, marks inline errors, focuses first invalid field - tasks.js: call validateAll() at submit to catch untouched required fields
This commit is contained in:
@@ -484,19 +484,43 @@ export function confirmModal(message, { confirmLabel, danger = false } = {}) {
|
|||||||
// Inline Blur-Validierung
|
// Inline Blur-Validierung
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
|
|
||||||
|
function _validateField(input) {
|
||||||
|
const group = input.closest('.form-field') ?? input.parentElement;
|
||||||
|
const hasValue = input.value.trim().length > 0;
|
||||||
|
group?.classList.toggle('form-field--error', !hasValue);
|
||||||
|
group?.classList.toggle('form-field--valid', hasValue);
|
||||||
|
return hasValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktiviert Blur-Validierung für alle required-Inputs in einem Container.
|
* Aktiviert Blur-Validierung für alle required-Inputs in einem Container.
|
||||||
* @param {HTMLElement} formContainer
|
* @param {HTMLElement} formContainer
|
||||||
*/
|
*/
|
||||||
export function wireBlurValidation(formContainer) {
|
export function wireBlurValidation(formContainer) {
|
||||||
formContainer.querySelectorAll('input[required], select[required], textarea[required]').forEach((input) => {
|
formContainer.querySelectorAll('input[required], select[required], textarea[required]').forEach((input) => {
|
||||||
input.addEventListener('blur', () => {
|
input.addEventListener('blur', () => _validateField(input));
|
||||||
const group = input.closest('.form-field') ?? input.parentElement;
|
|
||||||
const hasValue = input.value.trim().length > 0;
|
|
||||||
group?.classList.toggle('form-field--error', !hasValue);
|
|
||||||
group?.classList.toggle('form-field--valid', hasValue);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validiert alle required-Inputs sofort (z.B. beim Submit ohne vorangehendes Blur).
|
||||||
|
* Markiert Fehler inline und fokussiert das erste ungültige Feld.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} formContainer
|
||||||
|
* @returns {boolean} true wenn alle Felder valide sind
|
||||||
|
*/
|
||||||
|
export function validateAll(formContainer) {
|
||||||
|
let firstInvalid = null;
|
||||||
|
let allValid = true;
|
||||||
|
|
||||||
|
formContainer.querySelectorAll('input[required], select[required], textarea[required]').forEach((input) => {
|
||||||
|
const valid = _validateField(input);
|
||||||
|
if (!valid && !firstInvalid) firstInvalid = input;
|
||||||
|
if (!valid) allValid = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (firstInvalid) firstInvalid.focus();
|
||||||
|
return allValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ class OikosLocalePicker extends HTMLElement {
|
|||||||
|
|
||||||
select.addEventListener('change', () => {
|
select.addEventListener('change', () => {
|
||||||
if (select.value === 'system') {
|
if (select.value === 'system') {
|
||||||
|
select.disabled = true;
|
||||||
|
select.style.opacity = '0.5';
|
||||||
localStorage.removeItem('oikos-locale');
|
localStorage.removeItem('oikos-locale');
|
||||||
location.reload();
|
// Kurze Verzögerung damit der Browser den disabled-Zustand rendert
|
||||||
|
setTimeout(() => location.reload(), 60);
|
||||||
} else {
|
} else {
|
||||||
setLocale(select.value);
|
setLocale(select.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { api } from '/api.js';
|
import { api } from '/api.js';
|
||||||
import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js';
|
import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js';
|
||||||
import { openModal as openSharedModal, closeModal, wireBlurValidation, btnSuccess, btnError, promptModal, confirmModal } from '/components/modal.js';
|
import { openModal as openSharedModal, closeModal, wireBlurValidation, validateAll, btnSuccess, btnError, promptModal, confirmModal } from '/components/modal.js';
|
||||||
import { stagger, vibrate } from '/utils/ux.js';
|
import { stagger, vibrate } from '/utils/ux.js';
|
||||||
import { t, formatDate } from '/i18n.js';
|
import { t, formatDate } from '/i18n.js';
|
||||||
import { esc } from '/utils/html.js';
|
import { esc } from '/utils/html.js';
|
||||||
@@ -430,6 +430,9 @@ async function handleFormSubmit(e, container) {
|
|||||||
const submitBtn = document.getElementById('task-submit-btn');
|
const submitBtn = document.getElementById('task-submit-btn');
|
||||||
const taskId = document.getElementById('task-id').value;
|
const taskId = document.getElementById('task-id').value;
|
||||||
|
|
||||||
|
// Alle required-Felder sofort validieren (auch unberührte)
|
||||||
|
if (!validateAll(form)) return;
|
||||||
|
|
||||||
errorEl.hidden = true;
|
errorEl.hidden = true;
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
submitBtn.textContent = t('common.saving');
|
submitBtn.textContent = t('common.saving');
|
||||||
|
|||||||
Reference in New Issue
Block a user