mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(grid): skip cell edit commit during IME composition
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import QueryTimingDetails from "./QueryTimingDetails.vue";
|
||||
import { applyDdlStoragePreference } from "@/lib/sql/ddlStorage";
|
||||
import { applyDdlSearchMarks } from "@/lib/sql/ddlSearchMarks";
|
||||
import { isAiPromptImeCompositionEvent } from "@/lib/ai/aiPromptKeyboard";
|
||||
import DdlStorageToggle from "@/components/objects/DdlStorageToggle.vue";
|
||||
|
||||
import { useUpdateBlocker } from "@/lib/app/updatePreparation";
|
||||
@@ -10818,7 +10819,14 @@ async function onTemporalCellEditorSave() {
|
||||
}
|
||||
}
|
||||
|
||||
const cellEditCompositionActive = ref(false);
|
||||
|
||||
async function onCellEditKeydown(event: KeyboardEvent) {
|
||||
// The composition guard must also cover the save-shortcut and inline bulk
|
||||
// edit branches below: they run before the delegation to onEditKeydown and
|
||||
// reuse the same cell edit input, so an IME Enter would otherwise commit
|
||||
// the bulk fill (#12470).
|
||||
if (isAiPromptImeCompositionEvent(event, cellEditCompositionActive.value)) return;
|
||||
if (isSaveShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -10841,7 +10849,7 @@ async function onCellEditKeydown(event: KeyboardEvent) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
onEditKeydown(event);
|
||||
onEditKeydown(event, cellEditCompositionActive.value);
|
||||
}
|
||||
|
||||
function undoGridChange(): boolean {
|
||||
@@ -14850,6 +14858,8 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
@click.stop
|
||||
@focus="onCellEditTextareaInput"
|
||||
@input="onCellEditTextareaInput"
|
||||
@compositionstart="cellEditCompositionActive = true"
|
||||
@compositionend="cellEditCompositionActive = false"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@paste.stop="onCellEditTextareaPaste"
|
||||
@wheel.stop
|
||||
@@ -14865,6 +14875,8 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@input="onCellEditTextareaInput"
|
||||
@compositionstart="cellEditCompositionActive = true"
|
||||
@compositionend="cellEditCompositionActive = false"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@paste.stop="onCellEditTextareaPaste"
|
||||
/>
|
||||
@@ -15550,6 +15562,8 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
@click.stop
|
||||
@focus="onCellEditTextareaInput"
|
||||
@input="onCellEditTextareaInput"
|
||||
@compositionstart="cellEditCompositionActive = true"
|
||||
@compositionend="cellEditCompositionActive = false"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@paste.stop="onCellEditTextareaPaste"
|
||||
@wheel.stop
|
||||
@@ -15565,6 +15579,8 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@input="onCellEditTextareaInput"
|
||||
@compositionstart="cellEditCompositionActive = true"
|
||||
@compositionend="cellEditCompositionActive = false"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@paste.stop="onCellEditTextareaPaste"
|
||||
/>
|
||||
@@ -15774,6 +15790,8 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
@click.stop
|
||||
@focus="onCellEditTextareaInput"
|
||||
@input="onCellEditTextareaInput"
|
||||
@compositionstart="cellEditCompositionActive = true"
|
||||
@compositionend="cellEditCompositionActive = false"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@paste.stop="onCellEditTextareaPaste"
|
||||
@wheel.stop
|
||||
@@ -15789,6 +15807,8 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@input="onCellEditTextareaInput"
|
||||
@compositionstart="cellEditCompositionActive = true"
|
||||
@compositionend="cellEditCompositionActive = false"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@paste.stop="onCellEditTextareaPaste"
|
||||
/>
|
||||
|
||||
@@ -2339,6 +2339,46 @@ describe("useDataGridEditor saveChanges reload", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("useDataGridEditor IME composition", () => {
|
||||
it("does not commit or cancel the cell while Enter/Escape belongs to an IME composition", () => {
|
||||
const editor = createEditor(undefined, true, undefined, undefined, [["Ada", "hidden", "Lovelace"]]);
|
||||
editor.startEdit(0, 0);
|
||||
editor.editValue.value = "中";
|
||||
|
||||
const composing = { key: "Enter", isComposing: true, keyCode: 229, preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as KeyboardEvent;
|
||||
editor.onEditKeydown(composing);
|
||||
expect(composing.preventDefault).not.toHaveBeenCalled();
|
||||
expect(editor.editingCell.value).not.toBeNull();
|
||||
|
||||
const escape = { key: "Escape", isComposing: true, keyCode: 229, preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as KeyboardEvent;
|
||||
editor.onEditKeydown(escape);
|
||||
expect(escape.preventDefault).not.toHaveBeenCalled();
|
||||
expect(editor.editingCell.value).not.toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the guard for the Process key without keyCode 229", () => {
|
||||
const editor = createEditor(undefined, true, undefined, undefined, [["Ada", "hidden", "Lovelace"]]);
|
||||
editor.startEdit(0, 0);
|
||||
const process = { key: "Process", isComposing: false, keyCode: 13, preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as KeyboardEvent;
|
||||
editor.onEditKeydown(process);
|
||||
expect(process.preventDefault).not.toHaveBeenCalled();
|
||||
expect(editor.editingCell.value).not.toBeNull();
|
||||
});
|
||||
|
||||
it("defers to the composition tracking flag when the keydown itself carries no composition signal", () => {
|
||||
const editor = createEditor(undefined, true, undefined, undefined, [["Ada", "hidden", "Lovelace"]]);
|
||||
editor.startEdit(0, 0);
|
||||
// WebKit fires compositionend before the committing keydown, so the input's
|
||||
// composition tracking ref is the only reliable signal in that window.
|
||||
const enter = { key: "Enter", isComposing: false, keyCode: 13, preventDefault: vi.fn(), stopPropagation: vi.fn() } as unknown as KeyboardEvent;
|
||||
editor.onEditKeydown(enter, true);
|
||||
expect(enter.preventDefault).not.toHaveBeenCalled();
|
||||
expect(editor.editingCell.value).not.toBeNull();
|
||||
editor.onEditKeydown(enter, false);
|
||||
expect(enter.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useDataGridEditor cell edit focus", () => {
|
||||
it("selects the editor value only on the first frame so fast typing is not clobbered (#7336)", async () => {
|
||||
const editor = createEditor(undefined, true, undefined, undefined, [["long json value", "hidden", "last"]]);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { joinedSaveOptions } from "@/lib/dataGrid/joinedRowChanges";
|
||||
import { isAiPromptImeCompositionEvent } from "@/lib/ai/aiPromptKeyboard";
|
||||
import { ref, shallowRef, triggerRef, computed, nextTick, watch, getCurrentInstance, onActivated, onBeforeUnmount, onDeactivated, onMounted, toRaw, type ComputedRef, type Ref } from "vue";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import type { DataGridSaveGuard } from "@/lib/backend/tauri";
|
||||
@@ -1248,7 +1249,11 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
restoreScrollAcrossFrames(restoreScroll);
|
||||
}
|
||||
|
||||
function onEditKeydown(e: KeyboardEvent) {
|
||||
function onEditKeydown(e: KeyboardEvent, compositionActive = false) {
|
||||
// Enter/Escape can also be the key that commits an IME candidate. Never
|
||||
// treat that key as the grid shortcut; otherwise a Chinese/Japanese input
|
||||
// commits the cell before the composition is inserted (#12470).
|
||||
if (isAiPromptImeCompositionEvent(e, compositionActive)) return;
|
||||
const isExpandedTextarea = typeof HTMLTextAreaElement !== "undefined" && e.target instanceof HTMLTextAreaElement && e.target.dataset.expandedCellEditor === "true";
|
||||
if (e.key === "Enter" && (!isExpandedTextarea || e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user