diff --git a/packages/main/cypress/specs/Tokenizer.cy.tsx b/packages/main/cypress/specs/Tokenizer.cy.tsx index 0dd97b5ce12a..c4d7138539a0 100755 --- a/packages/main/cypress/specs/Tokenizer.cy.tsx +++ b/packages/main/cypress/specs/Tokenizer.cy.tsx @@ -1824,6 +1824,108 @@ describe("Clipboard Operations", () => { // Only the selected token should be copied cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Selected"); }); + + it("should copy focused token text when no tokens are selected", () => { + cy.mount( + + + + + ); + + // Tab into the tokenizer to focus the first token without selecting it + cy.realPress("Tab"); + cy.get("[ui5-token]").eq(0).should("have.prop", "focused", true); + cy.get("[ui5-token]").eq(0).should("have.prop", "selected", false); + + cy.window().then((win) => { + cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite"); + Object.defineProperty(win, "isSecureContext", { value: true, writable: true }); + }); + + cy.realPress(["Control", "c"]); + + cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Focused"); + }); + + it("should not cut token in readonly mode", () => { + cy.mount( + + + + + ); + + cy.realPress("Tab"); + cy.get("[ui5-token]").eq(0).should("have.prop", "focused", true); + + cy.window().then((win) => { + cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite"); + Object.defineProperty(win, "isSecureContext", { value: true, writable: true }); + }); + + cy.realPress(["Control", "x"]); + + // Should copy but not delete in readonly mode + cy.get("@clipboardWrite").should("have.been.calledOnceWith", "ReadonlyToken"); + cy.get("[ui5-token]").should("have.length", 2); + }); + + it("should cut only selected tokens when there are both selected and focused tokens", () => { + cy.mount( + + + + + + ); + + // Focus the third token via keyboard without selecting it + cy.realPress("Tab"); + cy.realPress("ArrowRight"); + cy.realPress("ArrowRight"); + cy.get("[ui5-token]").eq(2).should("have.prop", "focused", true); + cy.get("[ui5-token]").eq(2).should("have.prop", "selected", false); + // First two remain selected + cy.get("[ui5-token]").eq(0).should("have.prop", "selected", true); + cy.get("[ui5-token]").eq(1).should("have.prop", "selected", true); + + cy.window().then((win) => { + cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite"); + Object.defineProperty(win, "isSecureContext", { value: true, writable: true }); + }); + + cy.realPress(["Control", "x"]); + + // Should cut the selected tokens, not the focused one + cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Selected1\r\nSelected2"); + cy.get("[ui5-token]").should("have.length", 1); + cy.get("[ui5-token]").eq(0).should("have.prop", "text", "NotSelected"); + }); + + it("should cut focused token when no tokens are selected", () => { + cy.mount( + + + + + ); + + cy.realPress("Tab"); + cy.get("[ui5-token]").eq(0).should("have.prop", "focused", true); + cy.get("[ui5-token]").eq(0).should("have.prop", "selected", false); + + cy.window().then((win) => { + cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite"); + Object.defineProperty(win, "isSecureContext", { value: true, writable: true }); + }); + + cy.realPress(["Control", "x"]); + + cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Focused"); + cy.get("[ui5-token]").should("have.length", 1); + cy.get("[ui5-token]").eq(0).should("have.prop", "text", "Other"); + }); }); describe("Tokenizer - getFocusDomRef Method", () => { diff --git a/packages/main/src/Tokenizer.ts b/packages/main/src/Tokenizer.ts index 0cdd39e560a1..aeb66ad8311f 100644 --- a/packages/main/src/Tokenizer.ts +++ b/packages/main/src/Tokenizer.ts @@ -721,17 +721,21 @@ class Tokenizer extends UI5Element implements IFormInputElement { const isCut = e.key.toLowerCase() === "x" || isDeleteShift(e); const selectedTokens = this._tokens.filter(token => token.selected); - const focusedToken = selectedTokens.find(token => token.focused); + const focusedToken = this._tokens.find(token => token.focused); + let tokensToCopy = selectedTokens; + if (!tokensToCopy.length && focusedToken) { + tokensToCopy = [focusedToken]; + } - if (isCut) { - const cutResult = this._fillClipboard(ClipboardDataOperation.cut, selectedTokens); + if (isCut && !this.readonly && tokensToCopy.length) { + const cutResult = this._fillClipboard(ClipboardDataOperation.cut, tokensToCopy); - focusedToken && this.deleteToken(focusedToken); + this.deleteToken(tokensToCopy[0]); return cutResult; } - return this._fillClipboard(ClipboardDataOperation.copy, selectedTokens); + return this._fillClipboard(ClipboardDataOperation.copy, tokensToCopy); } if (isCtrl && e.key.toLowerCase() === "i" && this._tokens.length > 0) { @@ -1079,7 +1083,7 @@ class Tokenizer extends UI5Element implements IFormInputElement { } _fillClipboard(shortcutName: ClipboardDataOperation, tokens: Array) { - const tokensTexts = tokens.filter(token => token.selected).map(token => token.text).join("\r\n"); + const tokensTexts = tokens.map(token => token.text).join("\r\n"); // Async clipboard API (works in secure contexts - HTTPS/localhost) if (navigator.clipboard?.writeText && window.isSecureContext) {