diff --git a/packages/core/src/components/tag-input/tag-input.md b/packages/core/src/components/tag-input/tag-input.md index b3a748ae635..aae86e4ceb3 100644 --- a/packages/core/src/components/tag-input/tag-input.md +++ b/packages/core/src/components/tag-input/tag-input.md @@ -25,7 +25,8 @@ new items. A `separator` prop is supported to allow multiple items to be added at once; the default splits on commas and newlines. **Tags can be removed** by clicking their -buttons, or by pressing backspace repeatedly. +buttons, or by pressing either backspace or delete repeatedly. +Pressing delete mimics the behavior of deleting in a text editor, where trying to delete at the end of the line will do nothing. Arrow keys can also be used to focus on a particular tag before removing it. The cursor must be at the beginning of the text input for these interactions. diff --git a/packages/core/src/components/tag-input/tagInput.tsx b/packages/core/src/components/tag-input/tagInput.tsx index 84824cad93f..10f3ecd6225 100644 --- a/packages/core/src/components/tag-input/tagInput.tsx +++ b/packages/core/src/components/tag-input/tagInput.tsx @@ -405,6 +405,8 @@ export class TagInput extends AbstractPureComponent2) { + const { activeIndex } = this.state; + if (this.isValidIndex(activeIndex)) { + event.stopPropagation(); + this.removeIndexFromValues(activeIndex); + } + } + /** Remove the item at the given index by invoking `onRemove` and `onChange` accordingly. */ private removeIndexFromValues(index: number) { const { onChange, onRemove, values } = this.props; diff --git a/packages/core/test/tag-input/tagInputTests.tsx b/packages/core/test/tag-input/tagInputTests.tsx index d7fe8e08f0c..c452b3f9783 100644 --- a/packages/core/test/tag-input/tagInputTests.tsx +++ b/packages/core/test/tag-input/tagInputTests.tsx @@ -315,6 +315,33 @@ describe("", () => { assert.sameMembers(onRemove.args[0], [VALUES[1], 1]); }); + it("pressing left arrow key navigates active item and delete removes it", () => { + const onRemove = sinon.spy(); + const wrapper = mount(); + // select and remove middle item + wrapper + .find("input") + .simulate("keydown", { which: Keys.ARROW_LEFT }) + .simulate("keydown", { which: Keys.ARROW_LEFT }) + .simulate("keydown", { which: Keys.DELETE }); + + // in this case we're not moving into the previous item but + // we rather "take the place" of the item we just removed + assert.equal(wrapper.state("activeIndex"), 1); + assert.isTrue(onRemove.calledOnce); + assert.sameMembers(onRemove.args[0], [VALUES[1], 1]); + }); + + it("pressing delete with no selection does nothing", () => { + const onRemove = sinon.spy(); + const wrapper = mount(); + + wrapper.find("input").simulate("keydown", { which: Keys.DELETE }); + + assert.equal(wrapper.state("activeIndex"), -1); + assert.isTrue(onRemove.notCalled); + }); + it("pressing right arrow key in initial state does nothing", () => { const wrapper = mount(); wrapper.find("input").simulate("keydown", { which: Keys.ARROW_RIGHT });