🐛 Fix resize cursor when selection has mixed values (#12229)

* 🐛 Fix resize cursor when selection has mixed values

* 📎 PR changes

* ✨ Add test
This commit is contained in:
Luis de Dios 2026-10-13 18:47:45 +02:00 committed by GitHub
parent 21ecfb67b7
commit d800b893bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 70 additions and 17 deletions

View File

@ -133,7 +133,7 @@ test("BUG 10001: Negative margins are allowed on the numeric input", async ({
});
await expect(verticalMarginInput).toBeVisible();
await verticalMarginInput.fill("-10");
await verticalMarginInput.press("Enter");
await expect(verticalMarginInput).toHaveValue("-10");
@ -230,3 +230,54 @@ test("BUG 10638 - Invalid padding input on multi-selection must not persist stri
expect(seenLayoutValues).not.toEqual([]);
expect(badLayoutValues).toEqual([]);
});
test("BUG 12227: Numeric inputs only show the scrub cursor when they hold a single value", async ({
page,
}) => {
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile(page);
await workspacePage.mockRPC(
/get\-file\?/,
"workspace/get-file-copy-paste.json",
);
await workspacePage.mockRPC(
"get-file-fragment?file-id=*&fragment-id=*",
"workspace/get-file-copy-paste-fragment.json",
);
await workspacePage.goToWorkspace({
fileId: "870f9f10-87b5-8137-8005-934804124660",
pageId: "870f9f10-87b5-8137-8005-934804124661",
});
await page.getByTestId("layer-item").getByRole("button").first().click();
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
const measuresSection = workspacePage.rightSidebar.getByRole("region", {
name: "shape-measures-section",
});
await expect(measuresSection).toBeVisible();
const widthInput = measuresSection.getByRole("textbox", {
name: "Width",
exact: true,
});
await expect(widthInput).toHaveValue("360");
// The wrapper around the input is the element that carries the cursor.
const widthWrapper = measuresSection
.getByTestId("numeric-input-wrapper")
.filter({ has: page.getByRole("textbox", { name: "Width", exact: true }) });
// A single committed value can be scrubbed.
await expect(widthWrapper).toHaveCSS("cursor", "ew-resize");
// Selecting a second shape with a different width mixes the values.
// The field then has no single value, so it must not look scrubbable.
await workspacePage.layers
.getByTestId("layer-row")
.nth(1)
.click({ modifiers: ["Shift"] });
await expect(widthInput).toHaveAttribute("placeholder", "Mixed");
await expect(widthWrapper).not.toHaveCSS("cursor", "ew-resize");
});

View File

@ -245,6 +245,10 @@
is-token-applied? (and (some? token-applied-name)
(not= :multiple token-applied-name))
scrubbable? (and (some? value)
(not disabled)
(not is-token-applied?))
focused-id* (mf/use-state nil)
focused-id (deref focused-id*)
@ -572,24 +576,22 @@
on-scrub-pointer-down
(mf/use-fn
(mf/deps disabled is-open is-multiple? ref min max nillable default is-token-applied?)
(mf/deps is-open ref min max nillable default scrubbable?)
(fn [event]
(when-not (or disabled is-open is-multiple? is-token-applied?)
(let [has-token (some? (deref token-applied-name*))]
(when-not has-token
(let [client-x (.-clientX event)
parsed (parse-value (str/trim (mf/ref-val raw-value*)) (mf/ref-val last-value*) min max nillable)
start-val (or parsed default 0)]
(mf/set-ref-val! drag-state* :maybe-dragging)
(mf/set-ref-val! drag-start-x* client-x)
(mf/set-ref-val! drag-start-val* start-val)
(dom/capture-pointer event)))))))
(when (and (not is-open) scrubbable?)
(let [client-x (.-clientX event)
parsed (parse-value (str/trim (mf/ref-val raw-value*)) (mf/ref-val last-value*) min max nillable)
start-val (or parsed default 0)]
(mf/set-ref-val! drag-state* :maybe-dragging)
(mf/set-ref-val! drag-start-x* client-x)
(mf/set-ref-val! drag-start-val* start-val)
(dom/capture-pointer event)))))
on-scrub-pointer-move
(mf/use-fn
(mf/deps apply-value update-input step min max on-change-start is-token-applied?)
(mf/deps apply-value update-input step min max on-change-start scrubbable?)
(fn [event]
(when-not is-token-applied?
(when scrubbable?
(let [state (mf/ref-val drag-state*)]
(when (or (= state :maybe-dragging) (= state :dragging))
(let [client-x (.-clientX event)
@ -857,9 +859,9 @@
(mf/with-effect [handle-unmount] handle-unmount)
[:div {:class [class (stl/css-case :input-wrapper true
:resizable (and (not is-token-applied?)
(not disabled)))]
:scrubbable scrubbable?)]
:ref wrapper-ref
:data-testid "numeric-input-wrapper"
:on-pointer-down on-scrub-pointer-down
:on-pointer-move on-scrub-pointer-move
:on-pointer-up on-scrub-pointer-up

View File

@ -22,7 +22,7 @@
inline-size: 100%;
position: relative;
&.resizable {
&.scrubbable {
cursor: ew-resize;
}