From f8b249e6494159a8cb3806dcefe240d7607611f1 Mon Sep 17 00:00:00 2001 From: plind <59729252+plind-dm@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:49:37 -0700 Subject: [PATCH] fix(web): handle IME composition in DelimiterInput (#34660) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../components/__tests__/inputs.spec.tsx | 16 ++++++++++ .../create/step-two/components/inputs.tsx | 29 ++++++++++++++++--- web/eslint-suppressions.json | 3 -- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx b/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx index 28c640cdbe..2c0480e508 100644 --- a/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx +++ b/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx @@ -33,6 +33,22 @@ describe('DelimiterInput', () => { // Tooltip triggers render; component mounts without error expect(screen.getByText(`${ns}.stepTwo.separator`)).toBeInTheDocument() }) + + it('should suppress onChange during IME composition', () => { + const onChange = vi.fn() + const finalValue = 'wu' + render() + const input = screen.getByPlaceholderText(`${ns}.stepTwo.separatorPlaceholder`) + + fireEvent.compositionStart(input) + fireEvent.change(input, { target: { value: 'w' } }) + fireEvent.change(input, { target: { value: finalValue } }) + expect(onChange).not.toHaveBeenCalled() + + fireEvent.compositionEnd(input) + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange.mock.calls[0][0].target.value).toBe(finalValue) + }) }) describe('MaxLengthInput', () => { diff --git a/web/app/components/datasets/create/step-two/components/inputs.tsx b/web/app/components/datasets/create/step-two/components/inputs.tsx index 9d40f511f9..4733852d19 100644 --- a/web/app/components/datasets/create/step-two/components/inputs.tsx +++ b/web/app/components/datasets/create/step-two/components/inputs.tsx @@ -1,6 +1,7 @@ import type { FC, PropsWithChildren, ReactNode } from 'react' import type { InputProps } from '@/app/components/base/input' import type { NumberFieldInputProps, NumberFieldRootProps, NumberFieldSize } from '@/app/components/base/ui/number-field' +import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import Tooltip from '@/app/components/base/tooltip' @@ -16,7 +17,7 @@ import { import { env } from '@/env' const TextLabel: FC = (props) => { - return + return } const FormField: FC> = (props) => { @@ -28,8 +29,11 @@ const FormField: FC> = (props) => { ) } -export const DelimiterInput: FC = (props) => { +export const DelimiterInput: FC = ({ tooltip, onChange, value, ...rest }) => { const { t } = useTranslation() + const isComposing = useRef(false) + const [compositionValue, setCompositionValue] = useState('') + return ( @@ -37,7 +41,7 @@ export const DelimiterInput: FC = (props) => - {props.tooltip || t('stepTwo.separatorTip', { ns: 'datasetCreation' })} + {tooltip || t('stepTwo.separatorTip', { ns: 'datasetCreation' })} )} /> @@ -48,7 +52,24 @@ export const DelimiterInput: FC = (props) => type="text" className="h-9" placeholder={t('stepTwo.separatorPlaceholder', { ns: 'datasetCreation' })!} - {...props} + value={isComposing.current ? compositionValue : value} + onChange={(e) => { + if (isComposing.current) + setCompositionValue(e.target.value) + else + onChange?.(e) + }} + onCompositionStart={() => { + isComposing.current = true + setCompositionValue(String(value ?? '')) + }} + onCompositionEnd={(e) => { + const committed = e.currentTarget.value + isComposing.current = false + setCompositionValue('') + onChange?.({ ...e, target: { ...e.target, value: committed } } as unknown as React.ChangeEvent) + }} + {...rest} /> ) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 7180bd677a..1077b4c1fd 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -4291,9 +4291,6 @@ "app/components/datasets/create/step-two/components/inputs.tsx": { "no-restricted-imports": { "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 1 } }, "app/components/datasets/create/step-two/components/option-card.tsx": {