diff --git a/packages/main/cypress/specs/MultiComboBox.cy.tsx b/packages/main/cypress/specs/MultiComboBox.cy.tsx
index 2e704bf87590..6dacca7e109e 100644
--- a/packages/main/cypress/specs/MultiComboBox.cy.tsx
+++ b/packages/main/cypress/specs/MultiComboBox.cy.tsx
@@ -3430,6 +3430,54 @@ describe("Keyboard Handling", () => {
.should("have.value", "I");
});
+ it("should deselect all tokens on [Escape] key when focus is on tokenizer", () => {
+ cy.mount(
+
+
+
+
+
+ );
+
+ cy.get("[ui5-multi-combobox]")
+ .shadow()
+ .find("[ui5-tokenizer]")
+ .find("[ui5-token]")
+ .should("have.length", 3);
+
+ cy.get("[ui5-multi-combobox]")
+ .realClick();
+
+ cy.realPress("Backspace");
+
+ cy.get("[ui5-multi-combobox]")
+ .shadow()
+ .find("[ui5-tokenizer]")
+ .find("[ui5-token]")
+ .last()
+ .should("be.focused");
+
+ cy.realPress(["Shift", "Home"]);
+
+ cy.get("[ui5-multi-combobox]")
+ .shadow()
+ .find("[ui5-tokenizer]")
+ .find("[ui5-token]")
+ .each($token => {
+ cy.wrap($token).should("have.attr", "selected");
+ });
+
+ cy.realPress("Escape");
+
+ cy.get("[ui5-multi-combobox]")
+ .shadow()
+ .find("[ui5-tokenizer]")
+ .find("[ui5-token]")
+ .each($token => {
+ cy.wrap($token).should("not.have.attr", "selected");
+ });
+ });
+
it("Selects an item when enter is pressed and value matches a text of an item in the list", () => {
cy.mount(
<>
diff --git a/packages/main/cypress/specs/MultiInput.cy.tsx b/packages/main/cypress/specs/MultiInput.cy.tsx
index 7734b4d81319..6dc87533e408 100644
--- a/packages/main/cypress/specs/MultiInput.cy.tsx
+++ b/packages/main/cypress/specs/MultiInput.cy.tsx
@@ -1502,6 +1502,39 @@ describe("Keyboard handling", () => {
cy.get("@changeSpy")
.should("have.been.calledOnce");
});
+
+ it("should deselect all tokens on [Escape] key", () => {
+ cy.mount(
+
+
+
+
+
+ );
+
+ cy.get("[ui5-multi-input]")
+ .shadow()
+ .find("input")
+ .realClick();
+
+ cy.realPress("Home");
+
+ cy.get("[ui5-token]")
+ .eq(0)
+ .should("be.focused");
+
+ cy.realPress(["Shift", "End"]);
+
+ cy.get("[ui5-token]").each($token => {
+ cy.wrap($token).should("have.attr", "selected");
+ });
+
+ cy.realPress("Escape");
+
+ cy.get("[ui5-token]").each($token => {
+ cy.wrap($token).should("not.have.attr", "selected");
+ });
+ });
});
describe("MultiInput Composition", () => {
diff --git a/packages/main/cypress/specs/Tokenizer.cy.tsx b/packages/main/cypress/specs/Tokenizer.cy.tsx
index 0dd97b5ce12a..c8495e8af866 100755
--- a/packages/main/cypress/specs/Tokenizer.cy.tsx
+++ b/packages/main/cypress/specs/Tokenizer.cy.tsx
@@ -1590,6 +1590,104 @@ describe("Keyboard Handling", () => {
.eq(1)
.should("have.attr", "selected");
});
+
+ it("should deselect all tokens on [Escape] key", () => {
+ cy.get("[ui5-token]")
+ .eq(0)
+ .as("firstToken");
+
+ cy.get("[ui5-token]")
+ .eq(1)
+ .as("secondToken");
+
+ cy.get("[ui5-token]")
+ .eq(2)
+ .as("thirdToken");
+
+ cy.get("@firstToken")
+ .realClick();
+
+ cy.realPress(["Shift", "End"]);
+
+ cy.get("@firstToken")
+ .should("have.attr", "selected");
+
+ cy.get("@secondToken")
+ .should("have.attr", "selected");
+
+ cy.get("@thirdToken")
+ .should("have.attr", "selected");
+
+ cy.realPress("Escape");
+
+ cy.get("@firstToken")
+ .should("not.have.attr", "selected");
+
+ cy.get("@secondToken")
+ .should("not.have.attr", "selected");
+
+ cy.get("@thirdToken")
+ .should("not.have.attr", "selected");
+ });
+
+ it("should fire selection-change event on [Escape] when tokens are selected", () => {
+ cy.mount(
+
+
+
+
+
+ );
+
+ cy.get("[ui5-token]")
+ .eq(0)
+ .realClick();
+
+ cy.get("@selectionChange")
+ .should("have.been.called");
+
+ cy.get("@selectionChange")
+ .invoke("resetHistory");
+
+ cy.realPress("Escape");
+
+ cy.get("@selectionChange")
+ .should("have.been.calledOnce");
+
+ cy.get("@selectionChange")
+ .its("firstCall.args.0.detail.tokens")
+ .should("have.length", 0);
+ });
+
+ it("should not fire selection-change on [Escape] when no tokens are selected", () => {
+ cy.mount(
+
+
+
+
+ );
+
+ cy.get("[ui5-token]")
+ .eq(0)
+ .realClick();
+
+ cy.get("@selectionChange")
+ .invoke("resetHistory");
+
+ cy.realPress("Space");
+
+ cy.get("[ui5-token]")
+ .eq(0)
+ .should("not.have.attr", "selected");
+
+ cy.get("@selectionChange")
+ .invoke("resetHistory");
+
+ cy.realPress("Escape");
+
+ cy.get("@selectionChange")
+ .should("not.have.been.called");
+ });
});
describe("Clipboard Operations", () => {
diff --git a/packages/main/src/Tokenizer.ts b/packages/main/src/Tokenizer.ts
index 0cdd39e560a1..6a4f96c00f1e 100644
--- a/packages/main/src/Tokenizer.ts
+++ b/packages/main/src/Tokenizer.ts
@@ -716,6 +716,10 @@ class Tokenizer extends UI5Element implements IFormInputElement {
_onkeydown(e: KeyboardEvent) {
const isCtrl = !!(e.metaKey || e.ctrlKey);
+ if (isEscape(e)) {
+ return this._deselectAllTokens();
+ }
+
if ((isCtrl && ["c", "x"].includes(e.key.toLowerCase())) || isDeleteShift(e) || isInsertCtrl(e)) {
e.preventDefault();
@@ -1070,6 +1074,17 @@ class Tokenizer extends UI5Element implements IFormInputElement {
}
}
+ _deselectAllTokens() {
+ const hadSelection = this._selectedTokens.length > 0;
+ this._tokens.forEach(token => { token.selected = false; });
+
+ if (hadSelection) {
+ this.fireDecoratorEvent("selection-change", {
+ tokens: [],
+ });
+ }
+ }
+
get hasTokens() {
return this._tokens.length > 0;
}