Invio con Invio sicuro per IME

Funzionalità web: Eventi della tastiera

Molte interfacce di chat inviano il messaggio quando l'utente preme Enter in un <textarea>.

Questo funziona per gli utenti che digitano con l'input diretto della tastiera latina (ad es. inglese), ma non per gli utenti che digitano con un Input Method Editor (IME) per comporre testo in lingue come giapponese, cinese o coreano.

In questi contesti, il tasto Enter/Return viene utilizzato per confermare il candidato alla conversione dei caratteri corrente. Se il codice JavaScript personalizzato ascolta keydown su Enter e invia immediatamente, il messaggio dell'utente viene inviato mentre sta ancora convertendo i caratteri, il che genera messaggi incompleti, frammentati o errati.

Il controllo composition-active si applica a qualsiasi gestore keydown personalizzato che agisce su Enter, non solo ai campi <textarea> su più righe. I campi <input> a riga singola con la propria gestione di Enter, come le barre di ricerca e di ricerca, i campi di ridenominazione incorporati, i campi delle finestre di dialogo modali e le tavolozze dei comandi, ricevono lo stesso keydown Enter di conferma dell'IME di un <textarea>, con isComposing impostato su true. L'invio implicito nativo di un <form> da un <input> senza un gestore Enter personalizzato non richiede una gestione esplicita, perché i browser sopprimono l'invio implicito quando la pressione del tasto Enter viene utilizzata da un IME.

Strategia di implementazione

Per un elemento <textarea> o <input> con invio personalizzato tramite il tasto Invio, controlla la proprietà nativa isComposing dell'elemento KeyboardEvent prima di inviare i contenuti. Chiama anche event.preventDefault(): l'azione predefinita di Enter inserisce una nuova riga in un <textarea> e in un <input> all'interno di un <form> attiva l'invio implicito del browser, che invierebbe una seconda volta.

<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();
  }
});

Per un <input> a riga singola, utilizza lo stesso gestore senza la condizione !event.shiftKey, che esiste solo perché Shift+Enter inserisce un ritorno a capo in un <textarea>.

Accessibilità e test

  1. Pulsante di invio esplicito: includi sempre un elemento <button type="submit">. Le scorciatoie da tastiera sono ausiliari; non devono sostituire i percorsi di invio dei moduli nativi.
  2. Input accessibili: assicurati che tutti gli elementi <input> e <textarea> siano associati in modo programmatico a un elemento <label> utilizzando attributi id e for corrispondenti.

Strategie di riserva

Nelle versioni recenti di Safari, un problema di ordinamento degli eventi (bug WebKit 165004) ha inviato compositionend ai gestori di script prima della conferma di Enter keydown, anche se gli eventi sottostanti sono stati inviati nell'ordine inverso. Al momento dell'esecuzione del gestore keydown, event.isComposing era già stato reimpostato su false, il che significa che il controllo standard da solo non avrebbe impedito l'invio prematuro.

Se devi supportare la compatibilità cross-browser con le versioni precedenti di Safari e altre piattaforme, adotta una delle seguenti strategie di riserva:

Se abbini event.isComposing a un controllo per event.keyCode === 229, puoi rilevare in modo affidabile la conferma fuori ordine di Safari della pressione del tasto Enter. Poiché questo è nidificato sotto il gate event.key === 'Enter', è protetto dalle tastiere virtuali mobile che potrebbero utilizzare 229 per l'inserimento del layout dei caratteri normali.

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();
  }
});

Strategia 2: soluzione alternativa per la finestra di event.timeStamp (alternativa)

Per i codebase che vietano rigorosamente l'utilizzo di API deprecate come keyCode o se esistono problemi noti con il controllo 229 in ambienti target specifici, puoi monitorare il timestamp di invio dell'evento segnalato dal browser.

Poiché Safari invia l'evento keydown Enter di conferma molto vicino all'evento compositionend (spesso entro 5 ms e a volte con il timestamp keydown leggermente precedente a causa dell'inversione dell'ordine di invio del gestore), il controllo della differenza di tempo è altamente affidabile ed è improbabile che attivi falsi positivi sulle tastiere virtuali mobile in condizioni standard.

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();
  }
});