Quellcode durchsuchen

Merge pull request #16885 from ShirasawaSama/patch-5

fix: fix Safari IME composition bug (#16615)
Tim Jaeryang Baek vor 1 Monat
Ursprung
Commit
e3207f35d7
1 geänderte Dateien mit 32 neuen und 26 gelöschten Zeilen
  1. 32 26
      src/lib/components/chat/MessageInput.svelte

+ 32 - 26
src/lib/components/chat/MessageInput.svelte

@@ -417,6 +417,30 @@
 	let recording = false;
 
 	let isComposing = false;
+	// Safari has a bug where compositionend is not triggered correctly #16615
+	// when using the virtual keyboard on iOS.
+	let compositionEndedAt = -2e8;
+	const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
+	function inOrNearComposition(event: Event) {
+		if (isComposing) {
+			return true;
+		}
+		// See https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/.
+		// On Japanese input method editors (IMEs), the Enter key is used to confirm character
+		// selection. On Safari, when Enter is pressed, compositionend and keydown events are
+		// emitted. The keydown event triggers newline insertion, which we don't want.
+		// This method returns true if the keydown event should be ignored.
+		// We only ignore it once, as pressing Enter a second time *should* insert a newline.
+		// Furthermore, the keydown event timestamp must be close to the compositionEndedAt timestamp.
+		// This guards against the case where compositionend is triggered without the keyboard
+		// (e.g. character confirmation may be done with the mouse), and keydown is triggered
+		// afterwards- we wouldn't want to ignore the keydown event in this case.
+		if (isSafari && Math.abs(event.timeStamp - compositionEndedAt) < 500) {
+			compositionEndedAt = -2e8;
+			return true;
+		}
+		return false;
+	}
 
 	let chatInputContainerElement;
 	let chatInputElement;
@@ -1169,19 +1193,9 @@
 														return res;
 													}}
 													oncompositionstart={() => (isComposing = true)}
-													oncompositionend={() => {
-														const isSafari = /^((?!chrome|android).)*safari/i.test(
-															navigator.userAgent
-														);
-
-														if (isSafari) {
-															// Safari has a bug where compositionend is not triggered correctly #16615
-															// when using the virtual keyboard on iOS.
-															// We use a timeout to ensure that the composition is ended after a short delay.
-															setTimeout(() => (isComposing = false));
-														} else {
-															isComposing = false;
-														}
+													oncompositionend={(e) => {
+														compositionEndedAt = e.timeStamp;
+														isComposing = false;
 													}}
 													on:keydown={async (e) => {
 														e = e.detail.event;
@@ -1290,7 +1304,7 @@
 																	navigator.msMaxTouchPoints > 0
 																)
 															) {
-																if (isComposing) {
+																if (inOrNearComposition(e)) {
 																	return;
 																}
 
@@ -1393,17 +1407,9 @@
 												command = getCommand();
 											}}
 											on:compositionstart={() => (isComposing = true)}
-											on:compositionend={() => {
-												const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
-
-												if (isSafari) {
-													// Safari has a bug where compositionend is not triggered correctly #16615
-													// when using the virtual keyboard on iOS.
-													// We use a timeout to ensure that the composition is ended after a short delay.
-													setTimeout(() => (isComposing = false));
-												} else {
-													isComposing = false;
-												}
+											oncompositionend={(e) => {
+												compositionEndedAt = e.timeStamp;
+												isComposing = false;
 											}}
 											on:keydown={async (e) => {
 												const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
@@ -1523,7 +1529,7 @@
 															navigator.msMaxTouchPoints > 0
 														)
 													) {
-														if (isComposing) {
+														if (inOrNearComposition(e)) {
 															return;
 														}