ارسال با کلید Enter در IME-safe

ویژگی‌های وب: رویدادهای صفحه‌کلید

بسیاری از میاناهای گپ وقتی کاربر Enter را در <textarea> فشار می‌دهد، پیام را ارسال می‌کنند.

این روش برای کاربرانی که با ورودی مستقیم صفحه‌کلید لاتین (مثلاً انگلیسی) تایپ می‌کنند کار می‌کند، اما برای کاربرانی که با «ویرایشگر روش ورودی» (IME) برای نوشتن متن به زبان‌هایی مانند ژاپنی، چینی، یا کره‌ای تایپ می‌کنند، کار نمی‌کند.

در این زمینه‌ها، از کلید Enter/Return برای تأیید گزینه تبدیل نویسه فعلی استفاده می‌شود. اگر جاوا اسکریپت سفارشی به keydown در Enter گوش دهد و بلافاصله ارسال کند، پیام کاربر درحالی ارسال می‌شود که هنوز درحال تبدیل نویسه‌ها است و این امر منجر به ارسال پیام‌های ناقص، تکه‌تکه، یا نادرست می‌شود.

بررسی composition-active برای هر گرداننده keydown سفارشی که روی Enter عمل می‌کند اعمال می‌شود، نه فقط برای فیلدهای چندخطی <textarea>. فیلدهای <input> تک‌خطی با مدیریت Enter خودشان، مانند نوارهای جستجو و یافتن، فیلدهای تغییر نام درون‌خطی، فیلدهای کادر گفتگوی مدال، و پالت‌های فرمان، همان کلید پایین Enter تأیید IME را به‌عنوان <textarea> دریافت می‌کنند، با isComposing تنظیم‌شده روی true. ارسال ضمنی بومی <form> از <input> بدون کنترل‌کننده Enter سفارشی به کنترل صریح نیاز ندارد، زیرا مرورگرها ارسال ضمنی را زمانی‌که کلید Enter توسط IME مصرف می‌شود، سرکوب می‌کنند.

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

برای <textarea> یا <input> با ارسال سفارشی با کلید Enter، قبل‌از ارسال محتوا، ویژگی isComposing بومی KeyboardEvent را بررسی کنید. همچنین event.preventDefault() را فراخوانی کنید: کنش پیش‌فرض Enter یک خط جدید در <textarea> درج می‌کند و در <input> داخل <form>، ارسال ضمنی مرورگر را راه‌اندازی می‌کند که باعث ارسال مجدد می‌شود.

<form id="chat-form">
  <label for="chat-input" class="visually-hidden">Message</label>
  <textarea id="chat-input" placeholder="Type a message..."></textarea>
  <button type="submit" id="send-button">Send</button>
</form>
const textarea = document.getElementById('chat-input');
const form = document.getElementById('chat-form');

textarea.addEventListener('keydown', (event) => {
  if (event.key === 'Enter' && !event.shiftKey) {
    // Prevent the default action (newline in a textarea, implicit form submission in an input)
    event.preventDefault();

    // If the user is composing text, return early.
    if (event.isComposing) {
      return;
    }

    form.requestSubmit();
  }
});

برای <input> تک‌خطی، از همان گرداننده بدون شرط !event.shiftKey استفاده کنید که فقط به‌دلیل اینکه Shift+Enter خط جدیدی در <textarea> درج می‌کند وجود دارد.

دسترس‌پذیری و آزمایش

  1. دکمه ارسال صریح: همیشه عنصر <button type="submit"> را اضافه کنید. میان‌برهای صفحه‌کلید کمک‌کننده هستند؛ نباید جایگزین مسیرهای ارسال فرم بومی شوند.
  2. ورودی‌های دسترس‌پذیر: مطمئن شوید همه عناصر <input> و <textarea> بااستفاده از مشخصه‌های id و for منطبق به‌طور برنامه‌نویسی‌شده با <label> مرتبط شده باشند.

راهبردهای جایگزین

در نسخه‌های اخیر Safari، یک مشکل ترتیب رویداد (اشکال WebKit 165004) compositionend را قبل‌از تأیید Enter keydown به گردانندگان اسکریپت تحویل داد، اگرچه رویدادهای اساسی به ترتیب مخالف ارسال شدند. تا زمانی که کنترل‌کننده keydown اجرا شد، event.isComposing قبلاً به false بازنشانی شده بود، به این معنی که بررسی استاندارد به‌تنهایی نمی‌توانست از ارسال زودهنگام جلوگیری کند.

اگر لازم است از سازگاری بین مرورگرها در نسخه‌های قدیمی‌تر Safari و پلاتفرم‌های دیگر پشتیبانی کنید، یکی از استراتژی‌های جایگزین زیر را اتخاذ کنید:

با جفت کردن event.isComposing با بررسی event.keyCode === 229، می‌توانید به‌طور قابل‌اعتمادی تأیید نامرتب Safari برای Enter keydown را بگیرید. چون این مورد در زیر دروازه event.key === 'Enter' قرار دارد، از صفحه‌کلیدهای مجازی تلفن همراه که ممکن است از 229 برای ورودی چیدمان نویسه معمولی استفاده کنند ایمن است.

textarea.addEventListener('keydown', (event) => {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault();

    // Block submission if composing natively or if keyCode is 229
    if (event.isComposing || event.keyCode === 229) {
      return;
    }

    form.requestSubmit();
  }
});

راهبرد ۲: راه حل پنجره event.timeStamp (جایگزین)

برای پایگاه‌های کدی که استفاده از میاناهای برنامه‌سازی کاربردی منسوخ‌شده مانند keyCode را اکیداً ممنوع می‌کنند، یا اگر مشکلات شناخته‌شده‌ای با بررسی 229 در محیط‌های هدف خاص وجود دارد، می‌توانید به‌جای آن مُهر زمان ارسال رویداد گزارش‌شده توسط مرورگر را ردیابی کنید.

چون Safari رویداد تأییدکننده Enter keydown را بسیار نزدیک به رویداد compositionend ارسال می‌کند (اغلب در عرض ۵ میلی‌ثانیه، و گاهی اوقات با مُهر زمان keydown کمی زودتر به‌دلیل وارونگی ترتیب تحویل گرداننده)، بررسی تفاوت زمانی بسیار قابل‌اعتماد است و بعید است که در شرایط استاندارد، موارد مثبت کاذب را در صفحه‌کلیدهای مجازی تلفن همراه فعال کند.

let lastCompositionEndAt = null;

textarea.addEventListener('compositionend', (event) => {
  // IMPORTANT: Always use event.timeStamp, not Date.now() or performance.now().
  // Handler-time measurements are vulnerable to drift when the main thread is blocked.
  lastCompositionEndAt = event.timeStamp;
});

textarea.addEventListener('keydown', (event) => {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault();

    if (event.isComposing) {
      return;
    }

    // Block submission if the event occurs within a 50ms window of composition ending.
    // Math.abs handles Safari's inverted event delivery timing bug.
    if (
      lastCompositionEndAt !== null &&
      Math.abs(event.timeStamp - lastCompositionEndAt) < 50
    ) {
      return;
    }

    form.requestSubmit();
  }
});