Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] feat(TagInput): handle delete to remove items #3993

Merged
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
3 changes: 2 additions & 1 deletion packages/core/src/components/tag-input/tag-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <span class="@ns-icon-standard @ns-icon-cross"></span>
buttons, or by pressing <kbd>backspace</kbd> repeatedly.
buttons, or by pressing either <kbd>backspace</kbd> or <kbd>delete</kbd> repeatedly.
Pressing <kbd>delete</kbd> 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.

Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/components/tag-input/tagInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ export class TagInput extends AbstractPureComponent2<ITagInputProps, ITagInputSt
}
} else if (event.which === Keys.BACKSPACE) {
this.handleBackspaceToRemove(event);
} else if (event.which === Keys.DELETE) {
this.handleDeleteToRemove(event);
}
}

Expand Down Expand Up @@ -450,6 +452,14 @@ export class TagInput extends AbstractPureComponent2<ITagInputProps, ITagInputSt
}
}

private handleDeleteToRemove(event: React.KeyboardEvent<HTMLInputElement>) {
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;
Expand Down
27 changes: 27 additions & 0 deletions packages/core/test/tag-input/tagInputTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,33 @@ describe("<TagInput>", () => {
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(<TagInput onRemove={onRemove} values={VALUES} />);
// 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(<TagInput onRemove={onRemove} values={VALUES} />);

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(<TagInput values={VALUES} />);
wrapper.find("input").simulate("keydown", { which: Keys.ARROW_RIGHT });
Expand Down