الإرسال الآمن باستخدام مفتاح Enter في محرّر أسلوب الإدخال (IME)

ميزات الويب: أحداث لوحة المفاتيح

تُرسل العديد من واجهات المحادثة رسائلها عندما يضغط المستخدم على Enter في <textarea>.

تعمل هذه الطريقة مع المستخدمين الذين يكتبون باستخدام لوحة مفاتيح لاتينية مباشرة (مثل الإنجليزية)، ولكنّها لا تعمل مع المستخدمين الذين يكتبون باستخدام "محرر أسلوب الإدخال" (IME) لإنشاء نص بلغات مثل اليابانية أو الصينية أو الكورية.

في هذه السياقات، يُستخدَم مفتاح Enter/Return لتأكيد الحرف المرشّح الحالي للتحويل. إذا كان رمز JavaScript المخصّص يستمع إلى keydown على Enter ويُرسل الرسالة فورًا، يتم إرسال رسالة المستخدم أثناء تحويله للأحرف، ما يؤدي إلى رسائل غير مكتملة أو مجزّأة أو غير صحيحة.

ينطبق التحقّق من نشاط التركيب على أي معالج keydown مخصّص يعمل على Enter، وليس فقط على حقول <textarea> المتعدّدة الأسطر. تتلقّى حقول <input> المكوّنة من سطر واحد والتي تتضمّن معالجة Enter الخاصة بها، مثل أشرطة البحث والعثور وحقول إعادة التسمية المضمّنة وحقول مربّعات الحوار المشروطة ولوحات الأوامر، مفتاح Enter keydown نفسه لتأكيد IME مثل <textarea>، مع ضبط isComposing على true. لا يتطلّب الإرسال الضمني الأصلي لـ <form> من <input> بدون معالج Enter مخصّص معالجة صريحة، لأنّ المتصفّحات تمنع الإرسال الضمني عندما يستهلك محرِّر أسلوب الإدخال ضغطة مفتاح Enter.

استراتيجية التنفيذ

بالنسبة إلى <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> مرتبطة آليًا بعنصر <label> باستخدام سمتَي id وfor المتطابقتَين.

استراتيجيات احتياطية

في الإصدارات الحديثة من Safari، تسبّبت مشكلة في ترتيب الأحداث (خطأ WebKit‏ 165004) في تسليم compositionend إلى معالِجات النصوص البرمجية قبل تأكيد Enter keydown، على الرغم من إرسال الأحداث الأساسية بترتيب عكسي. عند تشغيل معالج keydown، كان قد تمت إعادة ضبط event.isComposing على false، ما يعني أنّ عملية التحقّق العادية وحدها كانت ستفشل في منع الإرسال المبكر.

إذا كنت بحاجة إلى إتاحة التوافق مع الإصدارات القديمة من Safari والمنصات الأخرى، يمكنك استخدام إحدى استراتيجيات الاحتياط التالية:

By pairing event.isComposing with a check for event.keyCode === 229, you can reliably catch Safari's out-of-order confirming 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();
  }
});

الاستراتيجية 2: الحل البديل لمهلة event.timeStamp (بديل)

بالنسبة إلى قواعد الرموز البرمجية التي تمنع بشكل صارم استخدام واجهات برمجة التطبيقات المتوقّفة نهائيًا مثل keyCode، أو إذا كانت هناك مشاكل معروفة في عملية التحقّق من 229 في بيئات مستهدَفة معيّنة، يمكنك بدلاً من ذلك تتبُّع الطابع الزمني لإرسال الأحداث الذي يبلّغ عنه المتصفّح.

بما أنّ Safari يرسل حدث Enter keydown للتأكيد في وقت قريب جدًا من حدث compositionend (غالبًا في غضون 5 ملي ثانية، وأحيانًا يكون الطابع الزمني لـ 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();
  }
});