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
28 changes: 28 additions & 0 deletions examples/tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,34 @@ const Test: React.FC = () => {
toggle maxTagCount (null)
</button>
</p>
<h2>tags select with open = false</h2>
<div>
<Select
placeholder="placeholder"
mode="tags"
style={{ width: 500 }}
disabled={disabled}
maxTagCount={maxTagCount}
maxTagTextLength={10}
value={value}
onChange={(val: string[], option) => {
console.log('change:', val, option);
setValue(val);
}}
onSelect={(val, option) => {
console.log('selected', val, option);
}}
onDeselect={(val, option) => {
console.log('deselected', val, option);
}}
tokenSeparators={[' ', ',']}
onFocus={() => console.log('focus')}
onBlur={() => console.log('blur')}
open={false}
>
{children}
</Select>
</div>
</div>
);
};
Expand Down
3 changes: 0 additions & 3 deletions index.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/Selector/MultipleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const SelectSelector: React.FC<SelectorProps> = props => {
}, []);

// ===================== Search ======================
const inputValue = open ? searchValue : '';
const inputValue = open || mode === 'tags' ? searchValue : '';
const inputEditable: boolean = mode === 'tags' || (open && showSearch);

// We measure width and set to the input immediately
Expand Down
8 changes: 8 additions & 0 deletions src/Selector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface SelectorProps {
onToggleOpen: (open?: boolean) => void;
/** `onSearch` returns go next step boolean to check if need do toggle open */
onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean;
onSearchSubmit: (searchText: string) => void;
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement>;

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

onSearch,
onSearchSubmit,
onToggleOpen,
onInputKeyDown,

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

if (which === KeyCode.ENTER && mode === 'tags' && !compositionStatusRef.current && !open) {
// When menu isn't open, OptionList won't trigger a value change
// So when enter is pressed, the tag's input value should be emitted here to let selector know
onSearchSubmit((event.target as HTMLInputElement).value);
}

if (![KeyCode.SHIFT, KeyCode.TAB, KeyCode.BACKSPACE, KeyCode.ESC].includes(which)) {
onToggleOpen(true);
}
Expand Down
13 changes: 13 additions & 0 deletions src/generate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,18 @@ export default function generateSelector<
return ret;
};

// Only triggered when menu is closed & mode is tags
// If menu is open, OptionList will take charge
// If mode isn't tags, press enter is not meaningful when you can't see any option
const onSearchSubmit = (searchText: string) => {
const newRawValues = Array.from(new Set<RawValueType>([...mergedRawValue, searchText]));
triggerChange(newRawValues);
newRawValues.forEach(newRawValue => {
triggerSelect(newRawValue, true, 'input');
});
setInnerSearchValue('');
};

// Close dropdown when disabled change
useEffect(() => {
if (innerOpen && !!disabled) {
Expand Down Expand Up @@ -1014,6 +1026,7 @@ export default function generateSelector<
searchValue={mergedSearchValue}
activeValue={activeValue}
onSearch={triggerSearch}
onSearchSubmit={onSearchSubmit}
onSelect={onInternalSelectionSelect}
/>
</SelectTrigger>
Expand Down
44 changes: 43 additions & 1 deletion tests/Multiple.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,49 @@ describe('Select.Multiple', () => {
expectOpen(wrapper, false);
});

it(`shouldn't separate words when compositing`, () => {
it('tokenize input when mode=tags and open=false', () => {
const handleChange = jest.fn();
const handleSelect = jest.fn();
const wrapper = mount(
<Select
mode="tags"
optionLabelProp="children"
tokenSeparators={[',']}
onChange={handleChange}
onSelect={handleSelect}
open={false}
>
<Option value="0">Zero</Option>
</Select>,
);

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

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

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

expect(wrapper.find('input').props().value).toBe('');
});

it('shouldn\'t separate words when compositing', () => {
const handleChange = jest.fn();
const handleSelect = jest.fn();
const wrapper = mount(
Expand Down
37 changes: 30 additions & 7 deletions tests/Tags.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ describe('Select.Tags', () => {
</Select>,
);

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

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

expect(handleChange).toHaveBeenCalledWith(['2', '3', '4'], expect.anything());
Expand All @@ -90,15 +88,13 @@ describe('Select.Tags', () => {
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}
<Option value="2">2</Option>
</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();
Expand All @@ -115,6 +111,35 @@ describe('Select.Tags', () => {
expectOpen(wrapper, false);
});

it('should work when menu is closed', () => {
const handleChange = jest.fn();
const handleSelect = jest.fn();
const wrapper = mount(
<Select
mode="tags"
tokenSeparators={[',']}
onChange={handleChange}
onSelect={handleSelect}
open={false}
>
<Option value="1">1</Option>
<Option value="2">2</Option>
</Select>,
);
wrapper.find('input').simulate('compositionstart');
wrapper.find('input').simulate('change', { target: { value: 'Star Kirby' } });
wrapper.find('input').simulate('keydown', { which: KeyCode.ENTER });
expect(handleChange).not.toHaveBeenCalled();
handleChange.mockReset();
wrapper.find('input').simulate('compositionend');
wrapper.find('input').simulate('keydown', { which: KeyCode.ENTER });
expect(handleChange).toHaveBeenCalledWith(['Star Kirby'], expect.anything());
expect(handleSelect).toHaveBeenCalledTimes(1);
expect(findSelection(wrapper).text()).toEqual('Star Kirby');
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 @@ -221,8 +246,6 @@ describe('Select.Tags', () => {
};
const wrapper = mount(<Select mode="tags" tokenSeparators={[',']} tagRender={tagRender} />);

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

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

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