Skip to content

Commit c45ddce

Browse files
mylukinclaude
andcommitted
fix: iOS Safari Chinese punctuation and emoji input
Fix input handling for Chinese punctuation (,。!?) and emoji on iOS Safari. The issue occurs because: 1. iOS Safari fires keydown (setting _keyDownSeen=true) 2. Then fires input event with ev.composed=true 3. But NO composition events are triggered for punctuation The old condition `(!ev.composed || !this._keyDownSeen)` would reject these inputs because both conditions are true. For emoji, there's a timing issue: 1. compositionend fires → isComposing becomes false 2. input event fires → old fix would accept (duplicate!) 3. setTimeout in CompositionHelper fires → sends emoji (first copy) The fix: 1. Change condition to check `!compositionHelper.isComposing` 2. Also check `!compositionHelper.isSendingComposition` to catch the window between compositionend and setTimeout callback 3. Add public getter for isSendingComposition in CompositionHelper This correctly: - Accepts punctuation input (not composing, not pending) - Rejects input during active composition - Rejects input when CompositionHelper has pending setTimeout - Prevents emoji duplication Fixes #3070, #4486 Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 2521bab commit c45ddce

File tree

5 files changed

+28
-5
lines changed

5 files changed

+28
-5
lines changed

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/browser/CoreBrowserTerminal.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,10 +1241,16 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
12411241
* @param ev The input event to be handled.
12421242
*/
12431243
protected _inputEvent(ev: InputEvent): boolean {
1244-
// Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
1245-
// support reading out character input which can doubling up input characters
1246-
// Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679
1247-
if (ev.data && ev.inputType === 'insertText' && (!ev.composed || !this._keyDownSeen) && !this.optionsService.rawOptions.screenReaderMode) {
1244+
// Handle direct text input (not from composition).
1245+
// We skip input events when:
1246+
// - isComposing: Active composition in progress (e.g., emoji picker, CJK IME)
1247+
// - isSendingComposition: compositionend fired but setTimeout hasn't sent data yet
1248+
// CompositionHelper handles input in these cases to prevent duplicates.
1249+
// When NOT composing/sending, we accept input even if ev.composed=true, which fixes
1250+
// iOS Safari Chinese punctuation input (issue #3070, #4486).
1251+
// Screen reader mode needs the event to bubble for accessibility announcements.
1252+
const compositionHelper = this._compositionHelper!;
1253+
if (ev.data && ev.inputType === 'insertText' && !compositionHelper.isComposing && !compositionHelper.isSendingComposition && !this.optionsService.rawOptions.screenReaderMode) {
12481254
if (this._keyPressHandled) {
12491255
return false;
12501256
}

src/browser/TestUtils.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@ export class MockCompositionHelper implements ICompositionHelper {
344344
public get isComposing(): boolean {
345345
return false;
346346
}
347+
public get isSendingComposition(): boolean {
348+
return false;
349+
}
347350
public compositionstart(): void {
348351
throw new Error('Method not implemented.');
349352
}

src/browser/Types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export type LineData = CharData[];
4040

4141
export interface ICompositionHelper {
4242
readonly isComposing: boolean;
43+
readonly isSendingComposition: boolean;
4344
compositionstart(): void;
4445
compositionupdate(ev: CompositionEvent): void;
4546
compositionend(): void;

src/browser/input/CompositionHelper.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ export class CompositionHelper {
3232

3333
/**
3434
* Whether a composition is in the process of being sent, setting this to false will cancel any
35-
* in-progress composition.
35+
* in-progress composition. This is true between compositionend and when the setTimeout callback
36+
* fires to actually send the data.
3637
*/
3738
private _isSendingComposition: boolean;
39+
public get isSendingComposition(): boolean { return this._isSendingComposition; }
3840

3941
/**
4042
* Data already sent due to keydown event.

0 commit comments

Comments
 (0)