diff --git a/.gitignore b/.gitignore
index 68843c871c..56d91db76a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,6 +89,7 @@ storybook-static/
# editor config
.vscode
+.idea
# happo output directory
diff --git a/src/components/forms/RangeInput/RangeInput.test.tsx b/src/components/forms/RangeInput/RangeInput.test.tsx
index 638aaf5468..191e042809 100644
--- a/src/components/forms/RangeInput/RangeInput.test.tsx
+++ b/src/components/forms/RangeInput/RangeInput.test.tsx
@@ -1,5 +1,6 @@
import React from 'react'
-import { render } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { render, screen, waitFor } from '@testing-library/react'
import { RangeInput } from './RangeInput'
@@ -17,25 +18,68 @@ describe('RangeInput component', () => {
expect(rangeElement).toBeInTheDocument()
expect(rangeElement).toHaveAttribute('id', 'range-slider-id')
expect(rangeElement).toHaveAttribute('name', 'rangeName')
+ expect(rangeElement).toHaveAttribute('aria-valuemin', '0')
+ expect(rangeElement).toHaveAttribute('aria-valuemax', '100')
+ expect(rangeElement).toHaveAttribute('aria-valuenow', '50')
expect(rangeElement).toHaveClass('usa-range')
expect(rangeElement).toHaveClass('additional-class')
})
it('renders with custom range values', () => {
+ const min = -15
+ const max = 60
const { queryByTestId } = render(
)
- expect(queryByTestId('range')).toHaveAttribute('min', '0')
- expect(queryByTestId('range')).toHaveAttribute('max', '60')
- expect(queryByTestId('range')).toHaveAttribute('step', '15')
- expect(queryByTestId('range')).toHaveAttribute('value', '45')
+
+ const rangeElement = queryByTestId('range')
+
+ expect(rangeElement).toHaveAttribute('min', '-15')
+ expect(rangeElement).toHaveAttribute('max', '60')
+ expect(rangeElement).toHaveAttribute('aria-valuemin', String(min))
+ expect(rangeElement).toHaveAttribute('aria-valuemax', String(max))
+ expect(rangeElement).toHaveAttribute(
+ 'aria-valuenow',
+ String(min + (max - min) / 2)
+ )
+ expect(rangeElement).toHaveAttribute('step', '15')
+ })
+
+ it('renders with default value', () => {
+ const { queryByTestId } = render(
+
+ )
+
+ expect(queryByTestId('range')).toHaveAttribute('aria-valuenow', '75')
+ })
+
+ it('renders with custom aria values and updates aria-valuenow on keyboard actions', () => {
+ render(
+
+ )
+ const rangeInput = screen.getByTestId('range')
+ expect(rangeInput).toHaveAttribute('aria-valuemin', '12')
+ expect(rangeInput).toHaveAttribute('aria-valuemax', '58')
+ expect(rangeInput).toHaveAttribute('aria-valuenow', '23')
+
+ userEvent.type(rangeInput, '{arrowright}')
+
+ waitFor(() => expect(rangeInput).toHaveAttribute('aria-valuenow', '24'))
+ userEvent.type(rangeInput, '{arrowleft}')
+ userEvent.type(rangeInput, '{arrowleft}')
+ waitFor(() => expect(rangeInput).toHaveAttribute('aria-valuenow', '22'))
})
it('renders with step attribute set to value any', () => {
diff --git a/src/components/forms/RangeInput/RangeInput.tsx b/src/components/forms/RangeInput/RangeInput.tsx
index ebe783cb09..1e95fe3f19 100644
--- a/src/components/forms/RangeInput/RangeInput.tsx
+++ b/src/components/forms/RangeInput/RangeInput.tsx
@@ -4,6 +4,8 @@ import classnames from 'classnames'
interface RangeInputProps {
id: string
name: string
+ min?: number
+ max?: number
inputRef?:
| string
| ((instance: HTMLInputElement | null) => void)
@@ -17,9 +19,33 @@ export const RangeInput = ({
inputRef,
...inputProps
}: RangeInputProps & JSX.IntrinsicElements['input']): React.ReactElement => {
- // Range defaults to min = 0, max = 100, step = 1, and value = (max/2) if not specified.
-
const classes = classnames('usa-range', className)
+ // input range defaults to min = 0, max = 100, step = 1, and value = (max/2) if not specified.
+ const defaultMin = 0
+ const defaultMax = 100
+ const { min, max, defaultValue } = inputProps
+ const rangeMin = min || defaultMin
+ const rangeMax = max || defaultMax
+ const ariaMin = inputProps['aria-valuemin'] || rangeMin
+ const ariaMax = inputProps['aria-valuemax'] || rangeMax
+ const calculatedAriaValueNow =
+ inputProps['aria-valuenow'] ||
+ defaultValue ||
+ (rangeMax < rangeMin ? rangeMin : rangeMin + (rangeMax - rangeMin) / 2)
+ const convertValueType = (
+ value: string | number | readonly string[]
+ ): number | undefined => {
+ if (typeof value === 'number' || typeof value === 'string') {
+ return Number(value)
+ }
+ return undefined
+ }
+ const [ariaValue, setAriaValue] = React.useState(
+ convertValueType(calculatedAriaValueNow)
+ )
+ const onValueChange: React.ChangeEventHandler = (e) => {
+ if (!inputProps['aria-valuenow']) setAriaValue(e.target.valueAsNumber)
+ }
return (
onValueChange(e)}
/>
)
}