Skip to content

Commit

Permalink
fix: fix RangeInput value rounding when step is less than 1 (#346)
Browse files Browse the repository at this point in the history
* fix: fix rounding of range values

* chore: changeset
  • Loading branch information
sampotts authored Jan 13, 2021
1 parent faebd72 commit b8d528b
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 23 deletions.
9 changes: 9 additions & 0 deletions .changeset/shaggy-oranges-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@sajari/react-components': patch
'@sajari/react-hooks': patch
'@sajari/react-sdk-utils': patch
'@sajari/react-search-ui': patch
'@sajari/server': patch
---

Fix RangeInput value rounding when step is less than 1
31 changes: 17 additions & 14 deletions packages/components/src/RangeInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AriaTextFieldOptions, useTextField } from '@react-aria/textfield';
import { __DEV__, clamp, closest, formatNumber, getStylesObject, noop, round } from '@sajari/react-sdk-utils';
import { __DEV__, clamp, closest, formatNumber, getStylesObject, noop, roundToStep } from '@sajari/react-sdk-utils';
import * as React from 'react';
import { useRanger } from 'react-ranger';

Expand Down Expand Up @@ -46,7 +46,7 @@ const RangeInput = React.forwardRef((props: RangeInputProps, ref?: React.Ref<HTM
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setValue = (newValue: RangeValue, fireOnChange = false) => {
// Round values to nearest step
const values = newValue.map((v) => round(v, step)) as RangeValue;
const values = newValue.map((v) => roundToStep(v, step)) as RangeValue;

setRange(values);
onInput(values);
Expand Down Expand Up @@ -79,16 +79,16 @@ const RangeInput = React.forwardRef((props: RangeInputProps, ref?: React.Ref<HTM
const trackRef = React.useRef<HTMLDivElement>(null);

const handleRangeInputChange = (left: boolean) => (v: string | number) => {
const updatedValue = Number(v);
const isNumeric = Number.isNaN(updatedValue);
const val = Number(v);
const isNaN = Number.isNaN(val);
let newValue: number[] | null = null;

if (isSingleHandle) {
newValue = [isNumeric ? min : updatedValue];
newValue = [isNaN || val < min ? min : val];
} else if (left) {
newValue = [isNumeric ? min : updatedValue, high];
newValue = [isNaN || val < min ? min : val, high];
} else {
newValue = [low, isNumeric ? max : updatedValue];
newValue = [low, isNaN || val > max ? max : val];
}

if (!newValue) {
Expand All @@ -98,15 +98,18 @@ const RangeInput = React.forwardRef((props: RangeInputProps, ref?: React.Ref<HTM
setValue(newValue as RangeValue, true);
};

// Format a value to be presented in the UI
const formatValue = (input: number) => {
// Format a value to be presented in the UI as a label
const formatLabel = (input: number) => {
if (format === 'price') {
return formatNumber(input, { style: 'currency', currency }).replace('.00', '');
}

return input.toLocaleString(language);
};

// Format a value for the input
const formatInputValue = (input: number) => input.toFixed(format === 'price' ? 2 : 0).replace('.00', '');

const handleSwitchRange = () => {
if (isSingleHandle) {
return;
Expand All @@ -125,7 +128,7 @@ const RangeInput = React.forwardRef((props: RangeInputProps, ref?: React.Ref<HTM
// Calculate percentage
const clientRect = trackRef.current.getBoundingClientRect();
const percent = clamp((100 / clientRect.width) * (event.clientX - clientRect.left), 0, 100);
const newValue = round((max - min) * (percent / 100), step);
const newValue = roundToStep((max - min) * (percent / 100), step);

if (i === 1 && !isSingleHandle) {
// Determine closest handle if clicking in center section
Expand All @@ -149,15 +152,15 @@ const RangeInput = React.forwardRef((props: RangeInputProps, ref?: React.Ref<HTM
const leftInputProps = {
...inputProps,
className: inputClassName,
value: low.toString(),
value: formatInputValue(low),
onChange: handleRangeInputChange(true),
label: 'Range input left bound',
};

const rightInputProps = {
...inputProps,
className: inputClassName,
value: (isSingleHandle ? 0 : high).toString(),
value: formatInputValue(isSingleHandle ? 0 : high),
onChange: handleRangeInputChange(false),
label: 'Range input right bound',
};
Expand Down Expand Up @@ -204,7 +207,7 @@ const RangeInput = React.forwardRef((props: RangeInputProps, ref?: React.Ref<HTM
css={styles.tickItem}
disableDefaultStyles={disableDefaultStyles}
>
{formatValue(tickValue)}
{formatLabel(tickValue)}
</Text>
);
})}
Expand All @@ -229,7 +232,7 @@ const RangeInput = React.forwardRef((props: RangeInputProps, ref?: React.Ref<HTM

{handles.map(({ value: handleValue, active, getHandleProps }) => (
<Handle
data-value={formatValue(handleValue)}
data-value={formatLabel(handleValue)}
activeClassName={handleActiveClassName}
className={handleClassName}
disableDefaultStyles={disableDefaultStyles}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class RangeFilterBuilder {
initial,
min = 0,
max = aggregate ? 0 : 100,
formatter = (value: Range) => value.map((v) => round(v, 1)) as Range,
formatter = (value: Range) => value.map((v) => round(v, 2)) as Range,
}: RangeFilterOptions) {
if (typeof initial === 'undefined') {
this.initial = aggregate ? null : [min, max];
Expand Down
21 changes: 13 additions & 8 deletions packages/utils/src/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,21 @@ export function getDecimalPlaces(value: number): number {

/**
* Round to the nearest step
* @param number
* @param step
* @param input - the number to round
* @param places - how many decimal places to round to
*/
export function round(number: number, step: number): number {
if (step < 1) {
const places = getDecimalPlaces(step);
return parseFloat(number.toFixed(places));
}
export function round(input: number, places: number): number {
return parseFloat(input.toFixed(places));
}

return Math.round(number / step) * step;
/**
* Round to the nearest step
* @param input - the number to round
* @param step - the step to round to
*/
export function roundToStep(input: number, step: number): number {
const places = getDecimalPlaces(step);
return round(Math.round(input / step) * step, places);
}

interface FormatNumberOptions extends Intl.NumberFormatOptions {
Expand Down

0 comments on commit b8d528b

Please sign in to comment.