Skip to content

Commit 17ce1a7

Browse files
authored
fix: can't separate input when mode=tags and open=false (#507)
* fix: can't separate input when mode=tags and open=false * chore * fix: keydown enter not work on tags mode when closed * chore: use esm in entry * fix: export default * refactor * chore * chore * chore: comments * chore: fix typo
1 parent 78dd42b commit 17ce1a7

File tree

7 files changed

+123
-12
lines changed

7 files changed

+123
-12
lines changed

examples/tags.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,34 @@ const Test: React.FC = () => {
7070
toggle maxTagCount (null)
7171
</button>
7272
</p>
73+
<h2>tags select with open = false</h2>
74+
<div>
75+
<Select
76+
placeholder="placeholder"
77+
mode="tags"
78+
style={{ width: 500 }}
79+
disabled={disabled}
80+
maxTagCount={maxTagCount}
81+
maxTagTextLength={10}
82+
value={value}
83+
onChange={(val: string[], option) => {
84+
console.log('change:', val, option);
85+
setValue(val);
86+
}}
87+
onSelect={(val, option) => {
88+
console.log('selected', val, option);
89+
}}
90+
onDeselect={(val, option) => {
91+
console.log('deselected', val, option);
92+
}}
93+
tokenSeparators={[' ', ',']}
94+
onFocus={() => console.log('focus')}
95+
onBlur={() => console.log('blur')}
96+
open={false}
97+
>
98+
{children}
99+
</Select>
100+
</div>
73101
</div>
74102
);
75103
};

index.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/Selector/MultipleSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const SelectSelector: React.FC<SelectorProps> = props => {
7474
}, []);
7575

7676
// ===================== Search ======================
77-
const inputValue = open ? searchValue : '';
77+
const inputValue = open || mode === 'tags' ? searchValue : '';
7878
const inputEditable: boolean = mode === 'tags' || (open && showSearch);
7979

8080
// We measure width and set to the input immediately

src/Selector/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export interface SelectorProps {
7878
onToggleOpen: (open?: boolean) => void;
7979
/** `onSearch` returns go next step boolean to check if need do toggle open */
8080
onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean;
81+
onSearchSubmit: (searchText: string) => void;
8182
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
8283
onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;
8384

@@ -100,6 +101,7 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
100101
showSearch,
101102

102103
onSearch,
104+
onSearchSubmit,
103105
onToggleOpen,
104106
onInputKeyDown,
105107

@@ -130,6 +132,12 @@ const Selector: React.RefForwardingComponent<RefSelectorProps, SelectorProps> =
130132
onInputKeyDown(event);
131133
}
132134

135+
if (which === KeyCode.ENTER && mode === 'tags' && !compositionStatusRef.current && !open) {
136+
// When menu isn't open, OptionList won't trigger a value change
137+
// So when enter is pressed, the tag's input value should be emitted here to let selector know
138+
onSearchSubmit((event.target as HTMLInputElement).value);
139+
}
140+
133141
if (![KeyCode.SHIFT, KeyCode.TAB, KeyCode.BACKSPACE, KeyCode.ESC].includes(which)) {
134142
onToggleOpen(true);
135143
}

src/generate.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,18 @@ export default function generateSelector<
673673
return ret;
674674
};
675675

676+
// Only triggered when menu is closed & mode is tags
677+
// If menu is open, OptionList will take charge
678+
// If mode isn't tags, press enter is not meaningful when you can't see any option
679+
const onSearchSubmit = (searchText: string) => {
680+
const newRawValues = Array.from(new Set<RawValueType>([...mergedRawValue, searchText]));
681+
triggerChange(newRawValues);
682+
newRawValues.forEach(newRawValue => {
683+
triggerSelect(newRawValue, true, 'input');
684+
});
685+
setInnerSearchValue('');
686+
};
687+
676688
// Close dropdown when disabled change
677689
useEffect(() => {
678690
if (innerOpen && !!disabled) {
@@ -1014,6 +1026,7 @@ export default function generateSelector<
10141026
searchValue={mergedSearchValue}
10151027
activeValue={activeValue}
10161028
onSearch={triggerSearch}
1029+
onSearchSubmit={onSearchSubmit}
10171030
onSelect={onInternalSelectionSelect}
10181031
/>
10191032
</SelectTrigger>

tests/Multiple.test.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,49 @@ describe('Select.Multiple', () => {
7979
expectOpen(wrapper, false);
8080
});
8181

82-
it(`shouldn't separate words when compositing`, () => {
82+
it('tokenize input when mode=tags and open=false', () => {
83+
const handleChange = jest.fn();
84+
const handleSelect = jest.fn();
85+
const wrapper = mount(
86+
<Select
87+
mode="tags"
88+
optionLabelProp="children"
89+
tokenSeparators={[',']}
90+
onChange={handleChange}
91+
onSelect={handleSelect}
92+
open={false}
93+
>
94+
<Option value="0">Zero</Option>
95+
</Select>,
96+
);
97+
98+
wrapper.find('input').simulate('change', {
99+
target: {
100+
value: 'One',
101+
},
102+
});
103+
expect(handleChange).not.toHaveBeenCalled();
104+
105+
handleChange.mockReset();
106+
wrapper.find('input').simulate('change', {
107+
target: {
108+
value: 'One,Two,Three,',
109+
},
110+
});
111+
expect(handleChange).toHaveBeenCalledWith(['One', 'Two', 'Three'], expect.anything());
112+
113+
handleChange.mockReset();
114+
wrapper.find('input').simulate('change', {
115+
target: {
116+
value: 'One,Two,',
117+
},
118+
});
119+
expect(handleChange).toHaveBeenCalledWith(['One', 'Two', 'Three'], expect.anything());
120+
121+
expect(wrapper.find('input').props().value).toBe('');
122+
});
123+
124+
it('shouldn\'t separate words when compositing', () => {
83125
const handleChange = jest.fn();
84126
const handleSelect = jest.fn();
85127
const wrapper = mount(

tests/Tags.test.tsx

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

76-
(wrapper.find('input').instance() as any).focus = jest.fn();
77-
7876
wrapper.find('input').simulate('change', { target: { value: '2,3,4' } });
7977

8078
expect(handleChange).toHaveBeenCalledWith(['2', '3', '4'], expect.anything());
@@ -90,15 +88,13 @@ describe('Select.Tags', () => {
9088
it("shounld't separate words when compositing", () => {
9189
const handleChange = jest.fn();
9290
const handleSelect = jest.fn();
93-
const option2 = <Option value="2">2</Option>;
9491
const wrapper = mount(
9592
<Select mode="tags" tokenSeparators={[',']} onChange={handleChange} onSelect={handleSelect}>
9693
<Option value="1">1</Option>
97-
{option2}
94+
<Option value="2">2</Option>
9895
</Select>,
9996
);
10097

101-
(wrapper.find('input').instance() as any).focus = jest.fn();
10298
wrapper.find('input').simulate('compositionstart');
10399
wrapper.find('input').simulate('change', { target: { value: '2,3,4' } });
104100
expect(handleChange).not.toHaveBeenCalled();
@@ -115,6 +111,35 @@ describe('Select.Tags', () => {
115111
expectOpen(wrapper, false);
116112
});
117113

114+
it('should work when menu is closed', () => {
115+
const handleChange = jest.fn();
116+
const handleSelect = jest.fn();
117+
const wrapper = mount(
118+
<Select
119+
mode="tags"
120+
tokenSeparators={[',']}
121+
onChange={handleChange}
122+
onSelect={handleSelect}
123+
open={false}
124+
>
125+
<Option value="1">1</Option>
126+
<Option value="2">2</Option>
127+
</Select>,
128+
);
129+
wrapper.find('input').simulate('compositionstart');
130+
wrapper.find('input').simulate('change', { target: { value: 'Star Kirby' } });
131+
wrapper.find('input').simulate('keydown', { which: KeyCode.ENTER });
132+
expect(handleChange).not.toHaveBeenCalled();
133+
handleChange.mockReset();
134+
wrapper.find('input').simulate('compositionend');
135+
wrapper.find('input').simulate('keydown', { which: KeyCode.ENTER });
136+
expect(handleChange).toHaveBeenCalledWith(['Star Kirby'], expect.anything());
137+
expect(handleSelect).toHaveBeenCalledTimes(1);
138+
expect(findSelection(wrapper).text()).toEqual('Star Kirby');
139+
expect(wrapper.find('input').props().value).toBe('');
140+
expectOpen(wrapper, false);
141+
});
142+
118143
it('paste content to split', () => {
119144
const onChange = jest.fn();
120145
const wrapper = mount(
@@ -221,8 +246,6 @@ describe('Select.Tags', () => {
221246
};
222247
const wrapper = mount(<Select mode="tags" tokenSeparators={[',']} tagRender={tagRender} />);
223248

224-
(wrapper.find('input').instance() as any).focus = jest.fn();
225-
226249
wrapper.find('input').simulate('change', { target: { value: '1,A,42' } });
227250

228251
expect(wrapper.find('span.A').length).toBe(1);

0 commit comments

Comments
 (0)