From e03c002efd45a0d7a8434d116b5883c2a7cee418 Mon Sep 17 00:00:00 2001 From: gijigae Date: Sat, 6 Dec 2025 17:26:35 +0900 Subject: [PATCH] Fix Japanese IME input handling for tags and form fields When typing in Japanese, the Enter key is used to confirm kanji conversion during IME composition. Previously, pressing Enter would immediately save the tag, which prevented users from completing the conversion. Changes: - Add isComposing check to navigable_list_controller.js Enter handler - Add IME composition tracking to form_controller.js - Add compositionstart/compositionend event handlers to tag, step, and reaction input fields - Add preventComposingSubmit action to prevent form submission during composition This fix supports all IME input methods (Japanese, Chinese, Korean, etc.) using standard browser composition events. --- app/javascript/controllers/form_controller.js | 17 +++++++++++++++++ .../controllers/navigable_list_controller.js | 3 +++ app/views/cards/comments/reactions/new.html.erb | 4 ++-- app/views/cards/display/perma/_steps.html.erb | 4 ++-- app/views/cards/taggings/new.html.erb | 4 ++-- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/javascript/controllers/form_controller.js b/app/javascript/controllers/form_controller.js index f917bd092d..beca1f08f3 100644 --- a/app/javascript/controllers/form_controller.js +++ b/app/javascript/controllers/form_controller.js @@ -8,10 +8,21 @@ export default class extends Controller { debounceTimeout: { type: Number, default: 300 } } + #isComposing = false + initialize() { this.debouncedSubmit = debounce(this.debouncedSubmit.bind(this), this.debounceTimeoutValue) } + // IME Composition tracking + compositionStart() { + this.#isComposing = true + } + + compositionEnd() { + this.#isComposing = false + } + submit() { this.element.requestSubmit() } @@ -32,6 +43,12 @@ export default class extends Controller { } } + preventComposingSubmit(event) { + if (this.#isComposing) { + event.preventDefault() + } + } + debouncedSubmit(event) { this.submit(event) } diff --git a/app/javascript/controllers/navigable_list_controller.js b/app/javascript/controllers/navigable_list_controller.js index 4af794c4b4..bd5e876659 100644 --- a/app/javascript/controllers/navigable_list_controller.js +++ b/app/javascript/controllers/navigable_list_controller.js @@ -245,6 +245,9 @@ export default class extends Controller { } }, Enter(event) { + // Skip handling during IME composition (e.g., Japanese input) + if (event.isComposing) { return } + if (event.shiftKey) { this.#toggleCurrentItem(event) } else { diff --git a/app/views/cards/comments/reactions/new.html.erb b/app/views/cards/comments/reactions/new.html.erb index 38c6daf988..8cc5dda73c 100644 --- a/app/views/cards/comments/reactions/new.html.erb +++ b/app/views/cards/comments/reactions/new.html.erb @@ -2,14 +2,14 @@ <%= form_with model: [ @comment.card, @comment, Reaction.new ], class: "reaction reaction__form expanded", html: { aria: { label: "New reaction" } }, - data: { controller: "form reaction-emoji", turbo_frame: dom_id(@comment, :reacting), action: "keydown.esc->form#cancel submit->form#preventEmptySubmit" } do |form| %> + data: { controller: "form reaction-emoji", turbo_frame: dom_id(@comment, :reacting), action: "keydown.esc->form#cancel submit->form#preventEmptySubmit submit->form#preventComposingSubmit" } do |form| %> <%= render "cards/comments/reactions/menu", comment: @comment %> diff --git a/app/views/cards/display/perma/_steps.html.erb b/app/views/cards/display/perma/_steps.html.erb index 36ce68fc51..9f65dec2a8 100644 --- a/app/views/cards/display/perma/_steps.html.erb +++ b/app/views/cards/display/perma/_steps.html.erb @@ -4,8 +4,8 @@ <% unless card.closed? %>
  • - <%= form_with model: [card, Step.new], url: card_steps_path(card), class: "min-width", data: { controller: "form", action: "submit->form#preventEmptySubmit turbo:submit-end->form#reset" } do |form| %> - <%= form.text_field :content, class: "input step__content hide-focus-ring", placeholder: "Add a step…", autocomplete: "off", data: { form_target: "input", "1p-ignore": "true" }, aria: { label: "Add a step" } %> + <%= form_with model: [card, Step.new], url: card_steps_path(card), class: "min-width", data: { controller: "form", action: "submit->form#preventEmptySubmit submit->form#preventComposingSubmit turbo:submit-end->form#reset" } do |form| %> + <%= form.text_field :content, class: "input step__content hide-focus-ring", placeholder: "Add a step…", autocomplete: "off", data: { form_target: "input", "1p-ignore": "true", action: "compositionstart->form#compositionStart compositionend->form#compositionEnd" }, aria: { label: "Add a step" } %> <% end %>
  • <% end %> diff --git a/app/views/cards/taggings/new.html.erb b/app/views/cards/taggings/new.html.erb index 644b29d2b0..b8ff47df6e 100644 --- a/app/views/cards/taggings/new.html.erb +++ b/app/views/cards/taggings/new.html.erb @@ -11,10 +11,10 @@ <%= form_with url: card_taggings_path(@card), id: dom_id(@card, :tags_form), - data: { controller: "form", action: "submit->form#preventEmptySubmit" }, + data: { controller: "form", action: "submit->form#preventEmptySubmit submit->form#preventComposingSubmit" }, class: "flex flex-column gap-half full-width margin-block-half" do |form| %> <%= form.text_field :tag_title, placeholder: @tags.any? ? "Add a new tag or filter…" : "Name this tag…", class: "input txt-small full-width", - autocomplete: "off", autofocus: true, data: { filter_target: "input", form_target: "input", action: "input->filter#filter" } %> + autocomplete: "off", autofocus: true, data: { filter_target: "input", form_target: "input", action: "input->filter#filter compositionstart->form#compositionStart compositionend->form#compositionEnd" } %> <% end %>