المشكلة
بالنسبة إلى القوائم المنسدلة الإلزامية (مثل "اختيار بلد")، تضع عملية التحقّق العادية علامة على الحقل على أنّه غير صالح على الفور إذا كان الخيار التلقائي يحتوي على قيمة فارغة. يمكن أن يؤدي ذلك إلى حدوث تشويش بصري. نريد إظهار الخطأ فقط إذا فتح المستخدم القائمة وأغلقها دون اختيار خيار، أو حاول إرسال النموذج.
الحل
تعمل الفئة الزائفة :user-invalid بسلاسة مع عناصر <select>. ويراعي هذا الحقل تدفق تفاعل المستخدم: فمجرد تحميل الصفحة أو التركيز/التمويه بدون إجراء تغيير لا يُعتبر تفاعلاً، وبالتالي يظل الحقل محايدًا حتى يحاول المستخدم إجراء اختيار بشكل نشط.
استراتيجية التنفيذ
- قيود HTML: استخدِم
<select>معrequired. يجب أن يحتوي الخيار الأول علىvalue=""ومن الأفضل إيقافه أو إخفاؤه لفرض خيار صالح. - الملاحظات المرئية: استخدِم
:user-invalidلتصميم حدود مربّع الاختيار. - التوقيت: يعتبر المتصفّح أنّ الحقل "تم التفاعل معه" إذا غيّر المستخدم القيمة (حتى لو عاد إلى الحالة التلقائية غير الصالحة) قبل إلغاء التركيز على عنصر التحكّم أو عند إرسال النموذج.
دليل الاستخدام
1. بنية HTML
يُعدّ خيار "العنصر النائب" أساسيًا هنا.
<form>
<div class="field">
<label for="country">Country</label>
<select
id="country"
name="country"
required
aria-errormessage="country-error"
>
<option value="" disabled selected>Select a country...</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>
<div id="country-error" class="error-msg">
Please select a country.
</div>
</div>
</form>
2. CSS
.error-msg {
display: none;
color: #d93025;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/*
Only show error after the user visits the select menu.
*/
select:user-invalid {
border-color: #d93025;
background-color: #fce8e6;
}
select:user-invalid + .error-msg {
display: block;
}
select:user-valid {
border-color: #188038;
}
العودة إلى الإعدادات التلقائية والتوافق مع المتصفّح
الخيارات الاحتياطية وتوافق المتصفّح مع :user-valid و :user-invalid
CSS للإجراء الاحتياطي
input:user-invalid,
input.user-invalid-fallback {
border-color: #d93025;
background-color: #fce8e6;
}
input:user-invalid + .error-msg,
input.user-invalid-fallback + .error-msg {
display: block;
}
الاحتياطي لـ JavaScript
استخدِم أداة مساعدة قابلة لإعادة الاستخدام لتتبُّع حالة التفاعل باستخدام WeakMap. يؤدي ذلك إلى تجنُّب تلويث نموذج العناصر في المستند (DOM) بفئات أو سمات بيانات "غير صالحة".
const UserInvalidFallback = (() => {
const dirtyState = new WeakMap();
const updateState = (input) => {
const isValid = input.checkValidity();
// Update both visual and ARIA state
input.classList.toggle('user-invalid-fallback', !isValid);
input.classList.toggle('user-valid-fallback', isValid);
if (!isValid) {
input.setAttribute('aria-invalid', 'true');
} else {
input.removeAttribute('aria-invalid');
}
};
const handleEvent = (event) => {
const input = event.target;
if (event.type === 'reset') {
const controls = input.elements || [];
for (const control of controls) {
dirtyState.delete(control);
control.classList.remove('user-invalid-fallback');
control.classList.remove('user-valid-fallback');
control.removeAttribute('aria-invalid');
}
return;
}
if (!input.checkValidity) return;
if (event.type === 'input' || event.type === 'change') {
const state = dirtyState.get(input) || { hasInteracted: false, hasBlurred: false };
state.hasInteracted = true;
dirtyState.set(input, state);
if (state.hasBlurred) {
updateState(input);
}
} else if (event.type === 'blur') {
const state = dirtyState.get(input) || { hasInteracted: false, hasBlurred: false };
state.hasBlurred = true;
dirtyState.set(input, state);
if (state.hasInteracted) {
updateState(input);
}
}
};
const init = (root = document) => {
if (CSS.supports('selector(:user-invalid)')) return;
root.addEventListener('blur', handleEvent, true); // Capture phase
root.addEventListener('input', handleEvent);
root.addEventListener('change', handleEvent);
root.addEventListener('reset', handleEvent, true); // Capture resets
};
return { init };
})();
// Initialize for a specific form
const form = document.querySelector('#demo-form');
UserInvalidFallback.init(form);
اعتبارات أخرى
- السلوك على الأجهزة الجوّالة: على الأجهزة الجوّالة، قد يحدث "التمويه" بشكل مختلف حسب أداة اختيار نظام التشغيل. يُنصح بإجراء الاختبار على أجهزة فعلية.
- تسهيل الاستخدام: لا تتم مزامنة سمات
:user-invalidالأصلية تلقائيًا مع سمات ARIA. أضِف رمز JavaScript التالي لإبقاءaria-invalidمتزامنًا مع الحالة المرئية:
// Sync aria-invalid with the CSS :user-invalid state
const syncAria = (el) => {
el.setAttribute?.('aria-invalid', el.matches(':user-invalid') ? 'true' : 'false');
};
// Update on blur (to show error) and input (to clear it)
document.addEventListener('blur', (e) => syncAria(e.target), true);
document.addEventListener('input', (e) => {
if (e.target.hasAttribute('aria-invalid')) syncAria(e.target);
});