Skip to content

Commit 77c8cc5

Browse files
authored
chore: Support multiple line paste (#475)
* support onPaste * update test case
1 parent bdbcdf6 commit 77c8cc5

File tree

6 files changed

+64
-21
lines changed

6 files changed

+64
-21
lines changed

examples/tags.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const Test: React.FC = () => {
4444
onDeselect={(val, option) => {
4545
console.log('deselected', val, option);
4646
}}
47-
tokenSeparators={[' ', ',']}
47+
tokenSeparators={[' ', ',', '\n']}
4848
onFocus={() => console.log('focus')}
4949
onBlur={() => console.log('blur')}
5050
>

src/Selector/Input.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface InputProps {
1919
onKeyDown: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2020
onMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2121
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
22+
onPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement | HTMLElement>;
2223
}
2324

2425
const Input: React.RefForwardingComponent<InputRef, InputProps> = (
@@ -36,6 +37,7 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
3637
onKeyDown,
3738
onMouseDown,
3839
onChange,
40+
onPaste,
3941
open,
4042
},
4143
ref,
@@ -88,6 +90,7 @@ const Input: React.RefForwardingComponent<InputRef, InputProps> = (
8890
onOriginChange(event);
8991
}
9092
},
93+
onPaste,
9194
});
9295

9396
return inputNode;

src/Selector/MultipleSelector.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const SelectSelector: React.FC<SelectorProps> = ({
5555

5656
onSelect,
5757
onInputChange,
58+
onInputPaste,
5859
onInputKeyDown,
5960
onInputMouseDown,
6061
}) => {
@@ -191,6 +192,7 @@ const SelectSelector: React.FC<SelectorProps> = ({
191192
onKeyDown={onInputKeyDown}
192193
onMouseDown={onInputMouseDown}
193194
onChange={onInputChange}
195+
onPaste={onInputPaste}
194196
tabIndex={tabIndex}
195197
/>
196198

src/Selector/SingleSelector.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const SingleSelector: React.FC<SelectorProps> = ({
3030
onInputKeyDown,
3131
onInputMouseDown,
3232
onInputChange,
33+
onInputPaste,
3334
}) => {
3435
const combobox = mode === 'combobox';
3536
const inputEditable = combobox || (showSearch && open);
@@ -62,6 +63,7 @@ const SingleSelector: React.FC<SelectorProps> = ({
6263
onKeyDown={onInputKeyDown}
6364
onMouseDown={onInputMouseDown}
6465
onChange={onInputChange}
66+
onPaste={onInputPaste}
6567
tabIndex={tabIndex}
6668
/>
6769
</span>

src/Selector/index.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface InnerSelectorProps {
3636
onInputKeyDown: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
3737
onInputMouseDown: React.MouseEventHandler<HTMLInputElement | HTMLTextAreaElement>;
3838
onInputChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
39+
onInputPaste: React.ClipboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
3940
}
4041

4142
export interface RefSelectorProps {
@@ -139,12 +140,37 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
139140
setInputMouseDown(true);
140141
};
141142

142-
const onInputChange = ({ target: { value } }) => {
143+
// When paste come, ignore next onChange
144+
const pasteClearRef = React.useRef(false);
145+
146+
const triggerOnSearch = (value: string) => {
143147
if (onSearch(value) !== false) {
144148
onToggleOpen(true);
145149
}
146150
};
147151

152+
const onInputChange = ({ target: { value } }) => {
153+
if (pasteClearRef.current) {
154+
pasteClearRef.current = false;
155+
return;
156+
}
157+
158+
triggerOnSearch(value);
159+
};
160+
161+
const onInputPaste: React.ClipboardEventHandler = e => {
162+
const { clipboardData } = e;
163+
const value = clipboardData.getData('text');
164+
165+
// Block next onChange
166+
pasteClearRef.current = true;
167+
setTimeout(() => {
168+
pasteClearRef.current = false;
169+
});
170+
171+
triggerOnSearch(value);
172+
};
173+
148174
// ====================== Focus ======================
149175
// Should focus input if click the selector
150176
const onClick = ({ target }) => {
@@ -170,6 +196,7 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
170196
onInputKeyDown: onInternalInputKeyDown,
171197
onInputMouseDown: onInternalInputMouseDown,
172198
onInputChange,
199+
onInputPaste,
173200
};
174201

175202
const selectNode = multiple ? (

tests/Tags.test.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@ import openControlledTest from './shared/openControlledTest';
1313
import removeSelectedTest from './shared/removeSelectedTest';
1414
import renderTest from './shared/renderTest';
1515
import throwOptionValue from './shared/throwOptionValue';
16-
import {
17-
injectRunAllTimers,
18-
findSelection,
19-
expectOpen,
20-
toggleOpen,
21-
} from './utils/common';
16+
import { injectRunAllTimers, findSelection, expectOpen, toggleOpen } from './utils/common';
2217

2318
describe('Select.Tags', () => {
2419
injectRunAllTimers(jest);
@@ -72,12 +67,7 @@ describe('Select.Tags', () => {
7267
const handleSelect = jest.fn();
7368
const option2 = <Option value="2">2</Option>;
7469
const wrapper = mount(
75-
<Select
76-
mode="tags"
77-
tokenSeparators={[',']}
78-
onChange={handleChange}
79-
onSelect={handleSelect}
80-
>
70+
<Select mode="tags" tokenSeparators={[',']} onChange={handleChange} onSelect={handleSelect}>
8171
<Option value="1">1</Option>
8272
{option2}
8373
</Select>,
@@ -87,10 +77,7 @@ describe('Select.Tags', () => {
8777

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

90-
expect(handleChange).toHaveBeenCalledWith(
91-
['2', '3', '4'],
92-
expect.anything(),
93-
);
80+
expect(handleChange).toHaveBeenCalledWith(['2', '3', '4'], expect.anything());
9481
expect(handleSelect).toHaveBeenCalledTimes(3);
9582
expect(handleSelect).toHaveBeenLastCalledWith('4', expect.anything());
9683
expect(findSelection(wrapper).text()).toEqual('2');
@@ -100,6 +87,30 @@ describe('Select.Tags', () => {
10087
expectOpen(wrapper, false);
10188
});
10289

90+
it('paste content to split', () => {
91+
const onChange = jest.fn();
92+
const wrapper = mount(
93+
<Select mode="tags" tokenSeparators={[' ', '\n']} onChange={onChange}>
94+
<Option value="1">1</Option>
95+
</Select>,
96+
);
97+
98+
wrapper.find('input').simulate('paste', {
99+
clipboardData: {
100+
getData: () => `
101+
light
102+
bamboo
103+
`,
104+
},
105+
});
106+
wrapper.find('input').simulate('change', {
107+
target: { value: ' light bamboo ' },
108+
});
109+
110+
expect(onChange).toHaveBeenCalledWith(['light', 'bamboo'], expect.anything());
111+
expect(onChange).toHaveBeenCalledTimes(1);
112+
});
113+
103114
it('renders unlisted item in value', () => {
104115
const wrapper = mount(
105116
<Select mode="tags" value="3" open>
@@ -180,9 +191,7 @@ describe('Select.Tags', () => {
180191
</span>
181192
);
182193
};
183-
const wrapper = mount(
184-
<Select mode="tags" tokenSeparators={[',']} tagRender={tagRender} />,
185-
);
194+
const wrapper = mount(<Select mode="tags" tokenSeparators={[',']} tagRender={tagRender} />);
186195

187196
wrapper.find('input').instance().focus = jest.fn();
188197

0 commit comments

Comments
 (0)