@@ -2,8 +2,10 @@ import {
2
2
type ValidationState ,
3
3
access ,
4
4
createGenerateId ,
5
+ getPrecision ,
5
6
mergeDefaultProps ,
6
7
mergeRefs ,
8
+ snapValueToStep ,
7
9
} from "@kobalte/utils" ;
8
10
import {
9
11
type JSX ,
@@ -308,11 +310,33 @@ export function NumberFieldRoot<T extends ValidComponent = "div">(
308
310
batch ( ( ) => {
309
311
let newValue = rawValue ;
310
312
311
- if ( rawValue % 1 === 0 ) {
312
- newValue += offset ;
313
- } else {
314
- if ( offset > 0 ) newValue = Math . ceil ( newValue ) ;
315
- else newValue = Math . floor ( newValue ) ;
313
+ const operation = offset > 0 ? "+" : "-" ;
314
+ const localStep = Math . abs ( offset ) ;
315
+ // If there was no min or max provided, don't use our default values
316
+ // use NaN instead to help with the calculation which will use 0
317
+ // instead for a NaN value
318
+ const min =
319
+ props . minValue === undefined ? Number . NaN : context . minValue ( ) ;
320
+ const max =
321
+ props . maxValue === undefined ? Number . NaN : context . maxValue ( ) ;
322
+
323
+ // Try to snap the value to the nearest step
324
+ newValue = snapValueToStep ( rawValue , min , max , localStep ) ;
325
+
326
+ // If the value didn't change in the direction we wanted to,
327
+ // then add the step and snap that value
328
+ if (
329
+ ! (
330
+ ( operation === "+" && newValue > rawValue ) ||
331
+ ( operation === "-" && newValue < rawValue )
332
+ )
333
+ ) {
334
+ newValue = snapValueToStep (
335
+ handleDecimalOperation ( operation , rawValue , localStep ) ,
336
+ min ,
337
+ max ,
338
+ localStep ,
339
+ ) ;
316
340
}
317
341
318
342
context . setValue ( newValue ) ;
@@ -352,3 +376,34 @@ export function NumberFieldRoot<T extends ValidComponent = "div">(
352
376
</ FormControlContext . Provider >
353
377
) ;
354
378
}
379
+
380
+ function handleDecimalOperation (
381
+ operator : "-" | "+" ,
382
+ value1 : number ,
383
+ value2 : number ,
384
+ ) : number {
385
+ let result = operator === "+" ? value1 + value2 : value1 - value2 ;
386
+ if (
387
+ Number . isFinite ( value1 ) &&
388
+ Number . isFinite ( value2 ) &&
389
+ ( value2 % 1 !== 0 || value1 % 1 !== 0 )
390
+ ) {
391
+ const offsetPrecision = getPrecision ( value2 ) ;
392
+ const valuePrecision = getPrecision ( value1 ) ;
393
+
394
+ const multiplier = 10 ** Math . max ( offsetPrecision , valuePrecision ) ;
395
+
396
+ const multipliedOffset = Math . round ( value2 * multiplier ) ;
397
+ const multipliedValue = Math . round ( value1 * multiplier ) ;
398
+
399
+ const next =
400
+ operator === "+"
401
+ ? multipliedValue + multipliedOffset
402
+ : multipliedValue - multipliedOffset ;
403
+
404
+ // Undo multiplier to get the new value
405
+ result = next / multiplier ;
406
+ }
407
+
408
+ return result ;
409
+ }
0 commit comments