mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
feat(web): enhance HomeHero component to support IME composition handling
- Added a `composingRef` to track IME composition state, preventing submission during text composition. - Implemented `isImeComposing` function to check if the input is currently being composed. - Updated event handlers to ensure that submissions and plugin selections are blocked while composing. - Added tests to verify that submissions and plugin picks do not occur during IME composition, improving user experience for non-Latin input methods. This update enhances the input handling in the HomeHero component, ensuring a smoother experience for users utilizing IME for text input.
This commit is contained in:
parent
c36609c47d
commit
f7a13c7b15
2 changed files with 76 additions and 1 deletions
|
|
@ -7,7 +7,8 @@
|
|||
// composed with the recent-projects strip and plugins section
|
||||
// without owning their data lifecycles.
|
||||
|
||||
import { forwardRef, useMemo, useState } from 'react';
|
||||
import { forwardRef, useMemo, useRef, useState } from 'react';
|
||||
import type { KeyboardEvent as ReactKeyboardEvent } from 'react';
|
||||
import type { InstalledPluginRecord } from '@open-design/contracts';
|
||||
import { Icon } from './Icon';
|
||||
import {
|
||||
|
|
@ -59,6 +60,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
|
|||
ref,
|
||||
) {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const composingRef = useRef(false);
|
||||
const canSubmit = prompt.trim().length > 0 && !submitDisabled;
|
||||
const placeholder = activePluginTitle
|
||||
? 'Edit the example query or write your own…'
|
||||
|
|
@ -135,7 +137,14 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
|
|||
onPromptChange(e.target.value);
|
||||
setSelectedIndex(0);
|
||||
}}
|
||||
onCompositionStart={() => {
|
||||
composingRef.current = true;
|
||||
}}
|
||||
onCompositionEnd={() => {
|
||||
composingRef.current = false;
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isImeComposing(e, composingRef.current)) return;
|
||||
if (pickerOpen && pickerOptions.length > 0) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
|
|
@ -292,6 +301,11 @@ function replaceMentionToken(value: string, mention: PluginMention): string | nu
|
|||
return next.length > 0 ? next : null;
|
||||
}
|
||||
|
||||
function isImeComposing(event: ReactKeyboardEvent<HTMLTextAreaElement>, composing: boolean): boolean {
|
||||
const nativeEvent = event.nativeEvent as KeyboardEvent & { keyCode?: number };
|
||||
return composing || nativeEvent.isComposing || nativeEvent.keyCode === 229;
|
||||
}
|
||||
|
||||
function getPluginSourceLabel(plugin: InstalledPluginRecord): string {
|
||||
return plugin.sourceKind === 'bundled' ? 'Community' : 'My plugin';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,4 +72,65 @@ describe('HomeHero plugin picker', () => {
|
|||
'Make',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not submit while an IME composition is confirming text with Enter', () => {
|
||||
const onSubmit = vi.fn();
|
||||
render(
|
||||
<HomeHero
|
||||
prompt="做一个中文官网"
|
||||
onPromptChange={() => undefined}
|
||||
onSubmit={onSubmit}
|
||||
activePluginTitle={null}
|
||||
activeChipId={null}
|
||||
onClearActivePlugin={() => undefined}
|
||||
pluginOptions={[]}
|
||||
pluginsLoading={false}
|
||||
pendingPluginId={null}
|
||||
pendingChipId={null}
|
||||
onPickPlugin={() => undefined}
|
||||
onPickChip={() => undefined}
|
||||
contextItemCount={0}
|
||||
error={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = screen.getByTestId('home-hero-input');
|
||||
fireEvent.compositionStart(input);
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.compositionEnd(input);
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not pick a plugin while an IME composition is active', () => {
|
||||
const onPickPlugin = vi.fn();
|
||||
const onSubmit = vi.fn();
|
||||
render(
|
||||
<HomeHero
|
||||
prompt="Make @sam"
|
||||
onPromptChange={() => undefined}
|
||||
onSubmit={onSubmit}
|
||||
activePluginTitle={null}
|
||||
activeChipId={null}
|
||||
onClearActivePlugin={() => undefined}
|
||||
pluginOptions={[makePlugin('sample-plugin', 'Sample Plugin')]}
|
||||
pluginsLoading={false}
|
||||
pendingPluginId={null}
|
||||
pendingChipId={null}
|
||||
onPickPlugin={onPickPlugin}
|
||||
onPickChip={() => undefined}
|
||||
contextItemCount={0}
|
||||
error={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = screen.getByTestId('home-hero-input');
|
||||
fireEvent.compositionStart(input);
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
expect(onPickPlugin).not.toHaveBeenCalled();
|
||||
expect(onSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue