Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/Selector/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ interface InputProps {
onMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
onPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
onCompositionStart: React.CompositionEventHandler<
HTMLInputElement | HTMLTextAreaElement | HTMLElement
>;
onCompositionEnd: React.CompositionEventHandler<
HTMLInputElement | HTMLTextAreaElement | HTMLElement
>;
}

const Input: React.RefForwardingComponent<InputRef, InputProps> = (
Expand All @@ -40,6 +46,8 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
onMouseDown,
onChange,
onPaste,
onCompositionStart,
onCompositionEnd,
open,
attrs,
},
Expand All @@ -53,6 +61,8 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
onKeyDown: onOriginKeyDown,
onChange: onOriginChange,
onMouseDown: onOriginMouseDown,
onCompositionStart: onOriginCompositionStart,
onCompositionEnd: onOriginCompositionEnd,
style,
},
} = inputNode;
Expand Down Expand Up @@ -95,6 +105,18 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
onOriginChange(event);
}
},
onCompositionStart(event: React.CompositionEvent<HTMLElement>) {
onCompositionStart(event);
if (onOriginCompositionStart) {
onOriginCompositionStart(event);
}
},
onCompositionEnd(event: React.CompositionEvent<HTMLElement>) {
onCompositionEnd(event);
if (onOriginCompositionEnd) {
onOriginCompositionEnd(event);
}
},
onPaste,
});

Expand Down
4 changes: 4 additions & 0 deletions src/Selector/MultipleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ const SelectSelector: React.FC<SelectorProps> = props => {
onInputPaste,
onInputKeyDown,
onInputMouseDown,
onCompositionStart,
onCompositionEnd,
} = props;

const [motionAppear, setMotionAppear] = React.useState(false);
Expand Down Expand Up @@ -196,6 +198,8 @@ const SelectSelector: React.FC<SelectorProps> = props => {
onMouseDown={onInputMouseDown}
onChange={onInputChange}
onPaste={onInputPaste}
onCompositionStart={onCompositionStart}
onCompositionEnd={onCompositionEnd}
tabIndex={tabIndex}
attrs={pickAttrs(props, true)}
/>
Expand Down
4 changes: 4 additions & 0 deletions src/Selector/SingleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const SingleSelector: React.FC<SelectorProps> = props => {
onInputMouseDown,
onInputChange,
onInputPaste,
onCompositionStart,
onCompositionEnd,
} = props;

const combobox = mode === 'combobox';
Expand Down Expand Up @@ -67,6 +69,8 @@ const SingleSelector: React.FC<SelectorProps> = props => {
onMouseDown={onInputMouseDown}
onChange={onInputChange}
onPaste={onInputPaste}
onCompositionStart={onCompositionStart}
onCompositionEnd={onCompositionEnd}
tabIndex={tabIndex}
attrs={pickAttrs(props, true)}
/>
Expand Down
19 changes: 16 additions & 3 deletions src/Selector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface InnerSelectorProps {
onInputMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onInputChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onInputPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onCompositionStart: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onCompositionEnd: React.CompositionEventHandler<HTMLInputElement | HTMLTextAreaElement>;
}

export interface RefSelectorProps {
Expand Down Expand Up @@ -75,7 +77,7 @@ export interface SelectorProps {

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

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

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

const {
prefixCls,
Expand Down Expand Up @@ -144,11 +147,19 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
const pasteClearRef = React.useRef(false);

const triggerOnSearch = (value: string) => {
if (onSearch(value) !== false) {
if (onSearch(value, true, compositionStatusRef.current) !== false) {
onToggleOpen(true);
}
};

const onCompositionStart = () => {
compositionStatusRef.current = true;
};

const onCompositionEnd = () => {
compositionStatusRef.current = false;
};

const onInputChange = ({ target: { value } }) => {
if (pasteClearRef.current) {
pasteClearRef.current = false;
Expand Down Expand Up @@ -191,7 +202,7 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =

if ((mode !== 'combobox' && (!showSearch || !inputMouseDown)) || !open) {
if (open) {
onSearch('');
onSearch('', true, false);
}
onToggleOpen();
}
Expand All @@ -204,6 +215,8 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
onInputMouseDown: onInternalInputMouseDown,
onInputChange,
onInputPaste,
onCompositionStart,
onCompositionEnd,
};

const selectNode = multiple ? (
Expand Down
12 changes: 7 additions & 5 deletions src/generate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -619,13 +619,15 @@ export default function generateSelector<
);

// ============================= Search =============================
const triggerSearch = (searchText: string, fromTyping: boolean = true) => {
const triggerSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => {
let ret = true;
let newSearchText = searchText;
setActiveValue(null);

// Check if match the `tokenSeparators`
const patchLabels: string[] = getSeparatedContent(searchText, tokenSeparators);
const patchLabels: string[] = isCompositing
? null
: getSeparatedContent(searchText, tokenSeparators);
let patchRawValues: RawValueType[] = patchLabels;

if (mode === 'combobox') {
Expand Down Expand Up @@ -681,7 +683,7 @@ export default function generateSelector<
// Close will clean up single mode search text
React.useEffect(() => {
if (!mergedOpen && !isMultiple && mode !== 'combobox') {
triggerSearch('', false);
triggerSearch('', false, false);
}
}, [mergedOpen]);

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

triggerChange([]);
triggerSearch('', false);
triggerSearch('', false, false);
};

if (!disabled && allowClear && (mergedRawValue.length || mergedSearchValue)) {
Expand Down
58 changes: 58 additions & 0 deletions tests/Multiple.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,64 @@ describe('Select.Multiple', () => {
expectOpen(wrapper, false);
});

it(`shouldn't separate words when compositing`, () => {
const handleChange = jest.fn();
const handleSelect = jest.fn();
const wrapper = mount(
<Select
mode="multiple"
optionLabelProp="children"
tokenSeparators={[',']}
onChange={handleChange}
onSelect={handleSelect}
>
<OptGroup key="group1">
<Option value="1">One</Option>
</OptGroup>
<OptGroup key="group2">
<Option value="2">Two</Option>
</OptGroup>
</Select>,
);

wrapper.find('input').simulate('change', {
target: {
value: 'One',
},
});
expect(handleChange).not.toHaveBeenCalled();

handleChange.mockReset();
wrapper.find('input').simulate('compositionstart');
wrapper.find('input').simulate('change', {
target: {
value: 'One,Two,Three',
},
});
expect(handleChange).not.toHaveBeenCalled();

handleChange.mockReset();
wrapper.find('input').simulate('compositionend');
wrapper.find('input').simulate('change', {
target: {
value: 'One,Two,Three',
},
});
expect(handleChange).toHaveBeenCalledWith(['1', '2'], expect.anything());

handleChange.mockReset();
wrapper.find('input').simulate('change', {
target: {
value: 'One,Two',
},
});
expect(handleChange).toHaveBeenCalledWith(['1', '2'], expect.anything());

expect(wrapper.find('input').props().value).toBe('');
wrapper.update();
expectOpen(wrapper, false);
});

it('focus', () => {
jest.useFakeTimers();
const handleFocus = jest.fn();
Expand Down
32 changes: 30 additions & 2 deletions tests/Tags.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('Select.Tags', () => {
</Select>,
);

wrapper.find('input').instance().focus = jest.fn();
(wrapper.find('input').instance() as any).focus = jest.fn();

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

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

it("shounld't separate words when compositing", () => {
const handleChange = jest.fn();
const handleSelect = jest.fn();
const option2 = <Option value="2">2</Option>;
const wrapper = mount(
<Select mode="tags" tokenSeparators={[',']} onChange={handleChange} onSelect={handleSelect}>
<Option value="1">1</Option>
{option2}
</Select>,
);

(wrapper.find('input').instance() as any).focus = jest.fn();
wrapper.find('input').simulate('compositionstart');
wrapper.find('input').simulate('change', { target: { value: '2,3,4' } });
expect(handleChange).not.toHaveBeenCalled();
handleChange.mockReset();
wrapper.find('input').simulate('compositionend');
wrapper.find('input').simulate('change', { target: { value: '2,3,4' } });
expect(handleChange).toHaveBeenCalledWith(['2', '3', '4'], expect.anything());
expect(handleSelect).toHaveBeenCalledTimes(3);
expect(handleSelect).toHaveBeenLastCalledWith('4', expect.anything());
expect(findSelection(wrapper).text()).toEqual('2');
expect(findSelection(wrapper, 1).text()).toEqual('3');
expect(findSelection(wrapper, 2).text()).toEqual('4');
expect(wrapper.find('input').props().value).toBe('');
expectOpen(wrapper, false);
});

it('paste content to split', () => {
const onChange = jest.fn();
const wrapper = mount(
Expand Down Expand Up @@ -193,7 +221,7 @@ describe('Select.Tags', () => {
};
const wrapper = mount(<Select mode="tags" tokenSeparators={[',']} tagRender={tagRender} />);

wrapper.find('input').instance().focus = jest.fn();
(wrapper.find('input').instance() as any).focus = jest.fn();

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

Expand Down