تعامل منو انتخاب

ویژگی‌های وب: :user-valid و :user-invalid

مشکل

برای منوهای کرکره‌ای اجباری (مثلاً «انتخاب کشور»)، اگر گزینه پیش‌فرض مقدار خالی داشته باشد، اعتبارسنجی استاندارد فوراً فیلد را به‌عنوان نامعتبر پرچم‌گذاری می‌کند. این کار می‌تواند نویز بصری ایجاد کند. می‌خواهیم خطا را فقط زمانی نشان دهیم که کاربر منو را باز می‌کند و بدون انتخاب گزینه‌ای آن را می‌بندد، یا تلاش می‌کند فرم را ارسال کند.

راه‌حل

کلاس کاذب :user-invalid به‌طور یکپارچه با عناصر <select> کار می‌کند. این ویژگی به جریان تعامل کاربر احترام می‌گذارد: صرفاً بار کردن صفحه یا تمرکز/محو کردن بدون ایجاد تغییر به‌عنوان تعامل محسوب نمی‌شود، بنابراین فیلد تا زمانی که کاربر فعالانه برای انتخاب تلاش نکند، خنثی می‌ماند.

راهبرد پیاده‌سازی

  1. محدودیت HTML: از <select> با required استفاده کنید. گزینه اول باید value="" داشته باشد و در حالت ایده‌آل غیرفعال/ پنهان باشد تا انتخاب معتبری را اجبار کند.
  2. بازخورد دیداری: از :user-invalid برای سبک‌دهی به حاشیه چارگوش انتخاب استفاده کنید.
  3. زمان‌بندی: اگر کاربر مقدار را قبل‌از محو کردن کنترل یا هنگام ارسال فرم تغییر دهد (حتی اگر به حالت نامعتبر پیش‌فرض برگردد)، مرورگر فیلد را «تعامل‌شده» درنظر می‌گیرد.

راهنمای پیاده‌سازی

۱. ساختار 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>

۲. 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;
}

بازگشت جاوا اسکریپت

از ابزار کمکی قابل‌استفاده مجددی استفاده کنید که وضعیت تعامل را بااستفاده از 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);

ملاحظات دیگر

  1. رفتار در تلفن همراه: در دستگاه‌های همراه، «محو کردن» ممکن است بسته به انتخابگر سیستم‌عامل متفاوت باشد. آزمایش کردن روی دستگاه‌های واقعی توصیه می‌شود.
  2. دسترس‌پذیری: :user-invalid بومی به‌طور خودکار با شاخصه‌های ARIA همگام‌سازی نمی‌شود. برای همگام نگه داشتن 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);
});