diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index c55aa9cf77..619bc5e2b8 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -721,7 +721,7 @@ (or (nil? last-space-left) (> (dm/number open-pos) (dm/number last-space-left))) (or (nil? first-space-right) (< (dm/number close-pos) (dm/number first-space-right))))))) -(defn- build-result +(defn build-result "Builds the result map for `insert-ref` by replacing the substring of `value` between `prefix-end` and `suffix-start` with a formatted reference `{name}`. Returns a map with: diff --git a/frontend/playwright/ui/specs/tokens/crud.spec.js b/frontend/playwright/ui/specs/tokens/crud.spec.js index 05fd55fe06..3ce9047f3c 100644 --- a/frontend/playwright/ui/specs/tokens/crud.spec.js +++ b/frontend/playwright/ui/specs/tokens/crud.spec.js @@ -113,6 +113,80 @@ test.describe("Tokens - creation", () => { ).toBeVisible(); }); + test("User creates a token referencing another via combobox, replacing existing text", async ({ + page, + }) => { + const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page, { + flags: ["enable-token-combobox", "enable-feature-token-input"], + }); + + const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); + + const addTokenButton = tokensTabPanel.getByRole("button", { + name: "Add Token: Border Radius", + }); + + await addTokenButton.click(); + await expect(tokensUpdateCreateModal).toBeVisible(); + + const nameField = tokensUpdateCreateModal.getByLabel("Name"); + const valueField = tokensUpdateCreateModal.getByRole("combobox", { + name: "Value", + }); + const submitButton = tokensUpdateCreateModal.getByRole("button", { + name: "Save", + }); + + // Create first token + await nameField.fill("my-token"); + await valueField.fill("1 + 2"); + await expect( + tokensUpdateCreateModal.getByText("Resolved value: 3"), + ).toBeVisible(); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + + await unfoldTokenType(tokensTabPanel, "border radius"); + await expect( + tokensTabPanel.getByRole("button", { name: "my-token" }), + ).toBeEnabled(); + + // Create second token — type text first, then replace it via combobox + await addTokenButton.click(); + + await nameField.fill("my-token-2"); + await valueField.fill("4 + 4"); + await expect( + tokensUpdateCreateModal.getByText("Resolved value: 8"), + ).toBeVisible(); + + const toggleDropdownButton = tokensUpdateCreateModal.getByRole("button", { + name: "Open token list", + }); + await toggleDropdownButton.click(); + + const option = page.getByRole("option", { name: "my-token" }); + await expect(option).toBeVisible(); + await option.click(); + + // The typed text should have been replaced by {my-token} + await expect(valueField).toHaveValue("{my-token}"); + await expect( + tokensUpdateCreateModal.getByText("Resolved value: 3"), + ).toBeVisible(); + + // Original text must no longer be present + await expect(valueField).not.toHaveValue("some text to replace"); + + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + + await unfoldTokenType(tokensTabPanel, "border radius"); + await expect( + tokensTabPanel.getByRole("button", { name: "my-token-2" }), + ).toBeEnabled(); + }); + test("User creates dimensions token", async ({ page }) => { await testTokenCreationFlow(page, { tokenLabel: "Dimensions", diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs index e62729b77d..c69d877aaf 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs @@ -147,12 +147,14 @@ toggle-dropdown (mf/use-fn (mf/deps is-open) - (fn [event] + (fn [event & [select-text?]] (dom/prevent-default event) (swap! is-open* not) (reset! selected-id* (get-selected-id)) - (let [input-node (mf/ref-val ref)] - (dom/focus! input-node)))) + (when select-text? + (let [input-node (mf/ref-val ref)] + (dom/select-text! input-node) + (dom/focus! input-node))))) resolve-stream (mf/with-memo [token] @@ -264,7 +266,7 @@ :tab-index "-1" :aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown") :on-mouse-down dom/prevent-default - :on-click toggle-dropdown}]))}) + :on-click #(toggle-dropdown % true)}]))}) props (if (or extra-error (and error touched?)) (mf/spread-props props {:hint-type "error" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox_navigation.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox_navigation.cljs index 3508833797..be078ed2da 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox_navigation.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox_navigation.cljs @@ -65,7 +65,7 @@ ;; Dropdown closed with options: open and focus first (seq focusables) (do - (toggle-dropdown event) + (toggle-dropdown event false) (when get-selected-id (get-selected-id)) (reset! focused-id* (first-focusable-id focusables))) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/token_parsing.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/token_parsing.cljs index 1c317ff3e3..b06486b9aa 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/token_parsing.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/token_parsing.cljs @@ -50,9 +50,12 @@ (defn select-option-by-id [id options-ref input-node value] (let [cursor (dom/selection-start input-node) + sel-end (dom/selection-end input-node) options (mf/ref-val options-ref) options (if (delay? options) @options options) option (get-option options id) name (:name option)] - (cto/insert-ref value cursor name))) \ No newline at end of file + (if (= cursor sel-end) + (cto/insert-ref value cursor name) + (cto/build-result value cursor sel-end name)))) \ No newline at end of file diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index b80a385ad5..f4be22b6c9 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -299,6 +299,11 @@ (when (some? node) (.-selectionStart node))) +(defn selection-end + [^js node] + (when (some? node) + (.-selectionEnd node))) + (defn set-selection-range! [^js node start end] (when (some? node)