feat: Disable scrub on non-numeric values (#5502)

ref  #5501

## Description

- [x] disable scrub if value is not a number on a css value input,
originally the idea was that if you scrub

## Steps for reproduction

1. click button
2. expect xyz

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
0000)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
This commit is contained in:
Oleg Isonen authored and GitHub committed 2025-12-10 20:00:46 +01:00
1 parent f1d04acbf3
commit b18315e570
2 files changed
+45 -14

No files matched your search

@@ -32,6 +32,14 @@ type HoverTarget<P> = {
element: HTMLElement | SVGElement;
};
/**
* Scrub hook for visual spacing diagram (box model UI)
* - Works on any HTML/SVG element via hover target
* - Supports multi-property editing with modifier keys (Shift/Alt)
* - Has direction awareness (horizontal/vertical based on property)
* - Returns status object with isActive, properties, and values
* - Used in space.tsx for drag-to-adjust on the visual box model
*/
export const useScrub = <P extends CssProperty>(props: {
value?: StyleValue;
target: HoverTarget<P> | undefined;
@@ -68,6 +76,19 @@ export const useScrub = <P extends CssProperty>(props: {
return;
}
// Don't activate scrub for keyword values
if (props.value?.type === "keyword") {
return;
}
// Don't activate scrub for non-numeric intermediate values
const numericValue = Number.parseFloat(
finalTarget.element.textContent ?? ""
);
if (Number.isNaN(numericValue)) {
return;
}
const property = finalTarget.property;
const handleChange = (event: { value: number }, isEphemeral: boolean) => {
@@ -144,7 +165,7 @@ export const useScrub = <P extends CssProperty>(props: {
);
},
});
}, [finalTarget]);
}, [finalTarget, props.value?.type]);
return {
isActive: activeTarget !== undefined,
@@ -45,7 +45,6 @@ import {
camelCaseProperty,
declarationDescriptions,
isValidDeclaration,
propertiesData,
} from "@webstudio-is/css-data";
import { $selectedInstanceSizes } from "~/shared/nano-states";
import { convertUnits } from "./convert-units";
@@ -60,13 +59,6 @@ import {
import { useEffectEvent } from "~/shared/hook-utils/effect-event";
import { scrollByPointer } from "../scroll-by-pointer";
// We need to enable scrub on properties that can have numeric value.
const canBeNumber = (property: CssProperty, value: CssValueInputValue) => {
const unitGroups = propertiesData[property]?.unitGroups ?? [];
// allow scrubbing css variables with unit value
return unitGroups.length !== 0 || value.type === "unit";
};
// Subjective adjust ment based on how it feels on macbook/trackpad.
// It won't be ideal for everyone with different input devices and preferences.
// Ideally we also need some kind of acceleration setting with 1 value.
@@ -79,6 +71,15 @@ const scrubUnitAcceleration = new Map<Unit, number>([
["number", 1 / 20],
]);
/**
* Scrub hook for numeric input fields
* - Works specifically on <input> elements (returns refs to attach)
* - Handles single property at a time
* - Manages intermediate values during typing
* - Has abort functionality (ESC key restores original)
* - Integrates with input focus/blur/selection behavior
* - Custom shouldHandleEvent for preventing scrub on vars/unit selects
*/
const useScrub = ({
value,
intermediateValue,
@@ -130,14 +131,23 @@ const useScrub = ({
const scrubRefCurrent = scrubRef.current;
// Support only auto keyword to be scrubbable
if (
inputRefCurrent === null ||
scrubRefCurrent === null ||
canBeNumber(property, valueRef.current) === false
) {
if (inputRefCurrent === null || scrubRefCurrent === null) {
return;
}
// Don't activate scrub for keyword values
if (valueRef.current.type === "keyword") {
return;
}
// Don't activate scrub for non-numeric intermediate values
if (valueRef.current.type === "intermediate") {
const numericValue = Number.parseFloat(valueRef.current.value);
if (Number.isNaN(numericValue)) {
return;
}
}
let unit: Unit = defaultUnit ?? "number";
const validateValue = (numericValue: number) => {