Skip to content

Commit

Permalink
fix(number-field): precision handling with floating point offsets and…
Browse files Browse the repository at this point in the history
… value snapping (#468)
  • Loading branch information
dev-rb authored Aug 25, 2024
1 parent cfb0a01 commit 746432c
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 5 deletions.
65 changes: 60 additions & 5 deletions packages/core/src/number-field/number-field-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import {
type ValidationState,
access,
createGenerateId,
getPrecision,
mergeDefaultProps,
mergeRefs,
snapValueToStep,
} from "@kobalte/utils";
import {
type JSX,
Expand Down Expand Up @@ -308,11 +310,33 @@ export function NumberFieldRoot<T extends ValidComponent = "div">(
batch(() => {
let newValue = rawValue;

if (rawValue % 1 === 0) {
newValue += offset;
} else {
if (offset > 0) newValue = Math.ceil(newValue);
else newValue = Math.floor(newValue);
const operation = offset > 0 ? "+" : "-";
const localStep = Math.abs(offset);
// If there was no min or max provided, don't use our default values
// use NaN instead to help with the calculation which will use 0
// instead for a NaN value
const min =
props.minValue === undefined ? Number.NaN : context.minValue();
const max =
props.maxValue === undefined ? Number.NaN : context.maxValue();

// Try to snap the value to the nearest step
newValue = snapValueToStep(rawValue, min, max, localStep);

// If the value didn't change in the direction we wanted to,
// then add the step and snap that value
if (
!(
(operation === "+" && newValue > rawValue) ||
(operation === "-" && newValue < rawValue)
)
) {
newValue = snapValueToStep(
handleDecimalOperation(operation, rawValue, localStep),
min,
max,
localStep,
);
}

context.setValue(newValue);
Expand Down Expand Up @@ -352,3 +376,34 @@ export function NumberFieldRoot<T extends ValidComponent = "div">(
</FormControlContext.Provider>
);
}

function handleDecimalOperation(
operator: "-" | "+",
value1: number,
value2: number,
): number {
let result = operator === "+" ? value1 + value2 : value1 - value2;
if (
Number.isFinite(value1) &&
Number.isFinite(value2) &&
(value2 % 1 !== 0 || value1 % 1 !== 0)
) {
const offsetPrecision = getPrecision(value2);
const valuePrecision = getPrecision(value1);

const multiplier = 10 ** Math.max(offsetPrecision, valuePrecision);

const multipliedOffset = Math.round(value2 * multiplier);
const multipliedValue = Math.round(value1 * multiplier);

const next =
operator === "+"
? multipliedValue + multipliedOffset
: multipliedValue - multipliedOffset;

// Undo multiplier to get the new value
result = next / multiplier;
}

return result;
}
11 changes: 11 additions & 0 deletions packages/utils/src/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,14 @@ export function snapValueToStep(

return snappedValue;
}

export const getPrecision = (n: number) => {
let e = 1;
let precision = 0;
while (Math.round(n * e) / e !== n) {
e *= 10;
precision++;
}

return precision;
};

0 comments on commit 746432c

Please sign in to comment.