اعتبارسنجی ورودی پس‌از تعامل

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

مشکل

نمایش خطاهای اعتبارسنجی در لحظه‌ای که کاربر روی فیلدی تمرکز می‌کند و شروع به تایپ می‌کند، زودرس و حواس‌پرت‌کننده است. برای مثال، وقتی کاربر نشانی ایمیل (مثلاً «user@gm») یا گذرواژه‌ای با الزامات پیچیده را تایپ می‌کند، فیلد ازنظر فنی تا زمان تکمیل نامعتبر است. سبک‌بندی استاندارد :invalid باعث می‌شود حالت خطا فوراً ظاهر شود و کاربر را ناامید کند.

راه‌حل

کلاس کاذب :user-invalid به شما امکان می‌دهد حالت خطا را تا زمانی که کاربر مقداری را «تأیید» کند (با محو کردن فیلد) یا برای ارسال فرم تلاش کند، به‌تعویق بیندازید. این کار باعث می‌شود بازخورد اعتبارسنجی فقط پس‌از اینکه کاربر تعامل با فیلد را تمام کرد ارائه شود.

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

  1. محدودیت HTML: از مشخصه‌های استاندارد HTML5 مانند type="email"،‏ pattern، و required برای فعال کردن منطق اعتبارسنجی داخلی مرورگر استفاده کنید.
  2. بازخورد دیداری: از :user-invalid برای اعمال سبک‌بندی خطا فقط پس‌از تعامل استفاده کنید.
  3. تقویت مثبت: درصورت تمایل، از :user-valid استفاده کنید تا پس‌از برآورده شدن الزامات، نشانگر سبز «موفقیت» را نشان دهید.
  4. بازیابی ملایم: به‌محض اینکه کاربر ورودی را به قالب معتبری اصلاح کند، :user-invalid مطابقت دادن را متوقف می‌کند و حالت خطا فوراً برداشته می‌شود.

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

مورد استفاده ۱: اعتبارسنجی ایمیل

الزامی: برای فیلدهای ایمیل، به مشخصه‌های استاندارد HTML5 تکیه کنید. پیام خطا به‌طور پیش‌فرض پنهان است و فقط زمانی نشان داده می‌شود که مرورگر تشخیص دهد کاربر فیلد را در وضعیت نامعتبر رها کرده است.

<form>
  <div class="field">
    <label for="email">Email Address</label>
    <!-- MANDATORY: Place format hints above the input so autocomplete popovers don't cover them during editing -->
    <span id="email-hint" class="hint">Format: you@example.com</span>
    <!-- DO: Use standard HTML validation attributes like type="email" and required -->
    <input
      type="email"
      id="email"
      name="email"
      required
      autocomplete="email"
      aria-describedby="email-hint"
      aria-errormessage="email-error"
    >
    <div id="email-error" class="error-msg">
      <span aria-hidden="true">❌</span> Please enter a valid email address.
    </div>
  </div>
</form>
.hint {
  display: block;
  color: #5f6368;
  font-size: 0.85rem;
  margin-bottom: 0.25rem;
}

.error-msg {
  display: none;
  color: #d93025;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

/*
  DO: Only show error styles after user interaction.
  Use multiple indicators (border/background shift + icon/text) to avoid color-only states.
*/
input:user-invalid {
  border-color: #d93025;
  background-color: #fce8e6;
}

/* DO: Reveal the error message using the adjacent sibling selector */
input:user-invalid + .error-msg {
  display: block;
}

/* DO: Provide a clear success indication on :user-valid */
input:user-valid {
  border-color: #188038;
}

مورد استفاده ۲: پیچیدگی گذرواژه

اجباری: قانون پیچیدگی را بااستفاده از الگوی Regex Lookahead در مشخصه pattern تعریف کنید. فهرست قوانین در بالای ورودی نشان داده می‌شود تا کاربر را راهنمایی کند و اگر خطایی وجود داشته باشد، برجسته می‌شود.

<form>
  <div class="field">
    <label for="password">New Password</label>
    <!-- MANDATORY: Place hints and rules above the input so mobile keyboards do not obscure them -->
    <ul id="password-rules" class="rules-list">
      <li>At least 8 characters</li>
      <li>One uppercase letter</li>
      <li>One number</li>
      <li>One special character</li>
    </ul>
    <!-- DO: Use pattern and minlength for complex password validation
         DO: Match all constraints with lookaheads via pattern attribute
     -->
    <input
      type="password"
      id="password"
      autocomplete="new-password"
      required
      pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\W_]).{8,}"
      minlength="8"
      aria-describedby="password-rules"
    >
  </div>
</form>
/* DO: State the default styling as neutral */
.rules-list { 
  color: #5f6368; 
  margin-bottom: 0.5rem;
}

/* DO: Show invalid state (After interaction): Error */
input:user-invalid {
  border-color: #d93025;
  background-color: #fce8e6;
}

/* DO: Highlight rules list when error is shown using the modern :has() selector */
.field:has(input:user-invalid) .rules-list {
  color: #d93025;
  font-weight: 600;
}

/* DO: Add success indications for :user-valid state */
input:user-valid {
  border-color: #188038;
}

/* DO: Hide rules once satisfied */
.field:has(input:user-valid) .rules-list {
  display: none;
}

پشتیبانی از مرورگر و بازگشت به حالت قبلی

پشتیبانی مرورگر و بازگشت برای :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. دسترس‌پذیری:
    • MANDATORY: Use aria-describedby to link the rules list to the input.
    • نباید: فهرست قوانین را تا زمانی که ورودی معتبر شود کاملاً پنهان کنید؛ کاربران باید بدانند چه چیزی تایپ کنند!
  2. محدودیت‌های مشخصه الگو: اجباری: مشخصه pattern مطابقت کامل انجام می‌دهد (^...$ ضمنی). مطمئن شوید عبارت باقاعده گذرواژه شما کل رشته را دربرمی‌گیرد.
  3. سخت‌گیری اعتبارسنجی: توجه داشته باشید که اعتبارسنجی پیش‌فرض type="email" مرورگر بسیار آسان‌گیر است (مثلاً user@localserver ممکن است پذیرفته شود). اگر به اعتبارسنجی سخت‌گیرانه‌تری نیاز دارید، ممکن است لازم باشد از کتابخانه اعتبارسنجی قوی‌تر یا تابع اعتبارسنجی سفارشی در کنار type="email" استفاده کنید.
  4. مدیریت تمرکز: اجباری: اگر کاربر فرمی را با فیلد نامعتبر ارسال کند، مرورگر به‌طور خودکار روی اولین فیلد نامعتبر تمرکز می‌کند. سبک‌های :user-invalid شما فوراً اعمال خواهد شد زیرا تلاش برای ارسال به‌عنوان تعامل محسوب می‌شود.
  5. تجربه ARIA سازگار: :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);
});