Skip to content

Commit 562b6bb

Browse files
07akioniafc163
andauthored
fix: not seperating words while compositing (#505)
* fix: not seperating words while compositing * test: not seperating words while compositing * chore: revert a useless fix * chore: code styling fix Co-authored-by: 偏右 <[email protected]> Co-authored-by: 偏右 <[email protected]>
1 parent f819066 commit 562b6bb

File tree

7 files changed

+141
-10
lines changed

7 files changed

+141
-10
lines changed

src/Selector/Input.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ interface InputProps {
2222
onMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2323
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2424
onPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
25+
onCompositionStart: React.CompositionEventHandler<
26+
HTMLInputElement | HTMLTextAreaElement | HTMLElement
27+
>;
28+
onCompositionEnd: React.CompositionEventHandler<
29+
HTMLInputElement | HTMLTextAreaElement | HTMLElement
30+
>;
2531
}
2632

2733
const Input: React.RefForwardingComponent<InputRef, InputProps> = (
@@ -40,6 +46,8 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
4046
onMouseDown,
4147
onChange,
4248
onPaste,
49+
onCompositionStart,
50+
onCompositionEnd,
4351
open,
4452
attrs,
4553
},
@@ -53,6 +61,8 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
5361
onKeyDown: onOriginKeyDown,
5462
onChange: onOriginChange,
5563
onMouseDown: onOriginMouseDown,
64+
onCompositionStart: onOriginCompositionStart,
65+
onCompositionEnd: onOriginCompositionEnd,
5666
style,
5767
},
5868
} = inputNode;
@@ -95,6 +105,18 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
95105
onOriginChange(event);
96106
}
97107
},
108+
onCompositionStart(event: React.CompositionEvent<HTMLElement>) {
109+
onCompositionStart(event);
110+
if (onOriginCompositionStart) {
111+
onOriginCompositionStart(event);
112+
}
113+
},
114+
onCompositionEnd(event: React.CompositionEvent<HTMLElement>) {
115+
onCompositionEnd(event);
116+
if (onOriginCompositionEnd) {
117+
onOriginCompositionEnd(event);
118+
}
119+
},
98120
onPaste,
99121
});
100122

src/Selector/MultipleSelector.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ const SelectSelector: React.FC<SelectorProps> = props => {
6060
onInputPaste,
6161
onInputKeyDown,
6262
onInputMouseDown,
63+
onCompositionStart,
64+
onCompositionEnd,
6365
} = props;
6466

6567
const [motionAppear, setMotionAppear] = React.useState(false);
@@ -196,6 +198,8 @@ const SelectSelector: React.FC<SelectorProps> = props => {
196198
onMouseDown={onInputMouseDown}
197199
onChange={onInputChange}
198200
onPaste={onInputPaste}
201+
onCompositionStart={onCompositionStart}
202+
onCompositionEnd={onCompositionEnd}
199203
tabIndex={tabIndex}
200204
attrs={pickAttrs(props, true)}
201205
/>

src/Selector/SingleSelector.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const SingleSelector: React.FC<SelectorProps> = props => {
3333
onInputMouseDown,
3434
onInputChange,
3535
onInputPaste,
36+
onCompositionStart,
37+
onCompositionEnd,
3638
} = props;
3739

3840
const combobox = mode === 'combobox';
@@ -67,6 +69,8 @@ const SingleSelector: React.FC<SelectorProps> = props => {
6769
onMouseDown={onInputMouseDown}
6870
onChange={onInputChange}
6971
onPaste={onInputPaste}
72+
onCompositionStart={onCompositionStart}
73+
onCompositionEnd={onCompositionEnd}
7074
tabIndex={tabIndex}
7175
attrs={pickAttrs(props, true)}
7276
/>

src/Selector/index.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export interface InnerSelectorProps {
3737
onInputMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement>;
3838
onInputChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
3939
onInputPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
40+
onCompositionStart: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
41+
onCompositionEnd: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
4042
}
4143

4244
export interface RefSelectorProps {
@@ -75,7 +77,7 @@ export interface SelectorProps {
7577

7678
onToggleOpen: (open?: boolean) => void;
7779
/** `onSearch` returns go next step boolean to check if need do toggle open */
78-
onSearch: (searchValue: string) => boolean;
80+
onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean;
7981
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
8082
onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
8183

@@ -88,6 +90,7 @@ export interface SelectorProps {
8890

8991
const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> = (props, ref) => {
9092
const inputRef = React.useRef<HTMLInputElement>(null);
93+
const compositionStatusRef = React.useRef<boolean>(false);
9194

9295
const {
9396
prefixCls,
@@ -144,11 +147,19 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
144147
const pasteClearRef = React.useRef(false);
145148

146149
const triggerOnSearch = (value: string) => {
147-
if (onSearch(value) !== false) {
150+
if (onSearch(value, true, compositionStatusRef.current) !== false) {
148151
onToggleOpen(true);
149152
}
150153
};
151154

155+
const onCompositionStart = () => {
156+
compositionStatusRef.current = true;
157+
};
158+
159+
const onCompositionEnd = () => {
160+
compositionStatusRef.current = false;
161+
};
162+
152163
const onInputChange = ({ target: { value } }) => {
153164
if (pasteClearRef.current) {
154165
pasteClearRef.current = false;
@@ -191,7 +202,7 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
191202

192203
if ((mode !== 'combobox' && (!showSearch || !inputMouseDown)) || !open) {
193204
if (open) {
194-
onSearch('');
205+
onSearch('', true, false);
195206
}
196207
onToggleOpen();
197208
}
@@ -204,6 +215,8 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
204215
onInputMouseDown: onInternalInputMouseDown,
205216
onInputChange,
206217
onInputPaste,
218+
onCompositionStart,
219+
onCompositionEnd,
207220
};
208221

209222
const selectNode = multiple ? (

src/generate.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -619,13 +619,15 @@ export default function generateSelector<
619619
);
620620

621621
// ============================= Search =============================
622-
const triggerSearch = (searchText: string, fromTyping: boolean = true) => {
622+
const triggerSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => {
623623
let ret = true;
624624
let newSearchText = searchText;
625625
setActiveValue(null);
626626

627627
// Check if match the `tokenSeparators`
628-
const patchLabels: string[] = getSeparatedContent(searchText, tokenSeparators);
628+
const patchLabels: string[] = isCompositing
629+
? null
630+
: getSeparatedContent(searchText, tokenSeparators);
629631
let patchRawValues: RawValueType[] = patchLabels;
630632

631633
if (mode === 'combobox') {
@@ -681,7 +683,7 @@ export default function generateSelector<
681683
// Close will clean up single mode search text
682684
React.useEffect(() => {
683685
if (!mergedOpen && !isMultiple && mode !== 'combobox') {
684-
triggerSearch('', false);
686+
triggerSearch('', false, false);
685687
}
686688
}, [mergedOpen]);
687689

@@ -776,7 +778,7 @@ export default function generateSelector<
776778
if (mergedSearchValue) {
777779
// `tags` mode should move `searchValue` into values
778780
if (mode === 'tags') {
779-
triggerSearch('', false);
781+
triggerSearch('', false, false);
780782
triggerChange(Array.from(new Set([...mergedRawValue, mergedSearchValue])));
781783
} else if (mode === 'multiple') {
782784
// `multiple` mode only clean the search value but not trigger event
@@ -885,7 +887,7 @@ export default function generateSelector<
885887
}
886888

887889
triggerChange([]);
888-
triggerSearch('', false);
890+
triggerSearch('', false, false);
889891
};
890892

891893
if (!disabled && allowClear && (mergedRawValue.length || mergedSearchValue)) {

tests/Multiple.test.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,64 @@ describe('Select.Multiple', () => {
7979
expectOpen(wrapper, false);
8080
});
8181

82+
it(`shouldn't separate words when compositing`, () => {
83+
const handleChange = jest.fn();
84+
const handleSelect = jest.fn();
85+
const wrapper = mount(
86+
<Select
87+
mode="multiple"
88+
optionLabelProp="children"
89+
tokenSeparators={[',']}
90+
onChange={handleChange}
91+
onSelect={handleSelect}
92+
>
93+
<OptGroup key="group1">
94+
<Option value="1">One</Option>
95+
</OptGroup>
96+
<OptGroup key="group2">
97+
<Option value="2">Two</Option>
98+
</OptGroup>
99+
</Select>,
100+
);
101+
102+
wrapper.find('input').simulate('change', {
103+
target: {
104+
value: 'One',
105+
},
106+
});
107+
expect(handleChange).not.toHaveBeenCalled();
108+
109+
handleChange.mockReset();
110+
wrapper.find('input').simulate('compositionstart');
111+
wrapper.find('input').simulate('change', {
112+
target: {
113+
value: 'One,Two,Three',
114+
},
115+
});
116+
expect(handleChange).not.toHaveBeenCalled();
117+
118+
handleChange.mockReset();
119+
wrapper.find('input').simulate('compositionend');
120+
wrapper.find('input').simulate('change', {
121+
target: {
122+
value: 'One,Two,Three',
123+
},
124+
});
125+
expect(handleChange).toHaveBeenCalledWith(['1', '2'], expect.anything());
126+
127+
handleChange.mockReset();
128+
wrapper.find('input').simulate('change', {
129+
target: {
130+
value: 'One,Two',
131+
},
132+
});
133+
expect(handleChange).toHaveBeenCalledWith(['1', '2'], expect.anything());
134+
135+
expect(wrapper.find('input').props().value).toBe('');
136+
wrapper.update();
137+
expectOpen(wrapper, false);
138+
});
139+
82140
it('focus', () => {
83141
jest.useFakeTimers();
84142
const handleFocus = jest.fn();

tests/Tags.test.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('Select.Tags', () => {
7373
</Select>,
7474
);
7575

76-
wrapper.find('input').instance().focus = jest.fn();
76+
(wrapper.find('input').instance() as any).focus = jest.fn();
7777

7878
wrapper.find('input').simulate('change', { target: { value: '2,3,4' } });
7979

@@ -87,6 +87,34 @@ describe('Select.Tags', () => {
8787
expectOpen(wrapper, false);
8888
});
8989

90+
it("shounld't separate words when compositing", () => {
91+
const handleChange = jest.fn();
92+
const handleSelect = jest.fn();
93+
const option2 = <Option value="2">2</Option>;
94+
const wrapper = mount(
95+
<Select mode="tags" tokenSeparators={[',']} onChange={handleChange} onSelect={handleSelect}>
96+
<Option value="1">1</Option>
97+
{option2}
98+
</Select>,
99+
);
100+
101+
(wrapper.find('input').instance() as any).focus = jest.fn();
102+
wrapper.find('input').simulate('compositionstart');
103+
wrapper.find('input').simulate('change', { target: { value: '2,3,4' } });
104+
expect(handleChange).not.toHaveBeenCalled();
105+
handleChange.mockReset();
106+
wrapper.find('input').simulate('compositionend');
107+
wrapper.find('input').simulate('change', { target: { value: '2,3,4' } });
108+
expect(handleChange).toHaveBeenCalledWith(['2', '3', '4'], expect.anything());
109+
expect(handleSelect).toHaveBeenCalledTimes(3);
110+
expect(handleSelect).toHaveBeenLastCalledWith('4', expect.anything());
111+
expect(findSelection(wrapper).text()).toEqual('2');
112+
expect(findSelection(wrapper, 1).text()).toEqual('3');
113+
expect(findSelection(wrapper, 2).text()).toEqual('4');
114+
expect(wrapper.find('input').props().value).toBe('');
115+
expectOpen(wrapper, false);
116+
});
117+
90118
it('paste content to split', () => {
91119
const onChange = jest.fn();
92120
const wrapper = mount(
@@ -193,7 +221,7 @@ describe('Select.Tags', () => {
193221
};
194222
const wrapper = mount(<Select mode="tags" tokenSeparators={[',']} tagRender={tagRender} />);
195223

196-
wrapper.find('input').instance().focus = jest.fn();
224+
(wrapper.find('input').instance() as any).focus = jest.fn();
197225

198226
wrapper.find('input').simulate('change', { target: { value: '1,A,42' } });
199227

0 commit comments

Comments
 (0)