Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add format prop to RAC Slider to support arbitrary aria-valuetext. #6411

Open
wants to merge 236 commits into
base: main
Choose a base branch
from

Conversation

robin-kestrel
Copy link

@robin-kestrel robin-kestrel commented May 18, 2024

Closes #6402

The aria-valuetext attribute specifies the human-readable representation of a numeric value. RAC components that allow users to manipulate numeric values should provide a means of setting aria-valuetext on the appropriate underlying element.

Currently, RAC sliders are implemented using one input element per thumb. The Slider component takes a formatOptions prop; the aria-valuetext of each input is computed as

Intl.NumberFormat(currentLocale, formatOptions).format(value)

(The formatted values of all the thumbs in a slider are also used to set the default content of the SliderOutput component; this default can be overriden by providing children to the output component, whereas the aria-valuetext of each thumb presently can't be.)

The current approach is probably flexible enough for most use cases, but falls somewhat short of the spec, which gives the following example:

Authors SHOULD only set the aria-valuetext attribute when the rendered value cannot be meaningfully represented as a number. For example, a slider may have rendered values of small, medium, and large. In this case, the values of aria-valuenow could range from 1 through 3, which indicate the position of each value in the value space, but the aria-valuetext would be one of the strings: small, medium, or large.

Currently, there is no way to implement this in RAC.

This PR adds a format prop to Slider that accepts either a NumberFormatOptions object (the same as the current formatOptions prop) or a function with the signature (value: number, locale: string) => string. If format is an object, then the behavior will be the same as if formatOptions were specified with that object. If format is a function, then that function will be used to set the aria-valuetext for each input, as well as the default content of SliderOutput.

This approach makes only a modest change to the code, and enables many use cases. A few examples:

  • Mapping values to strings, as in the spec's small, medium, and large example.

  • Representing numerical values in ways not supported by Intl.NumberFormat: time intervals (e.g. 2h15m), magnifications (e.g. 10x), fractions (e.g. ½), etc.

  • Providing multiple representations of a single value, such as a temperature represented in both degrees Celsius and Fahrenheit.

  • Implementing non-linear scaling. It is currently almost possible to implement a non-linear scale by wrapping the existing Slider component, e.g.

    const [value, setValue] = useState(0);
    
    <Slider
      value={Math.log10(value)}
      onChange={(value) => setValue(10 ** value)}
      {...etc}
    >
      {/* ... */}
      <SliderOutput>{value}</SliderOutput>
      {/* ... */}
    </Slider>;

    This is a working logarithmically scaled slider in every respect except the incorrect aria-valuetext on the underlying input.

    (There is an existing issue, custom scale for useSlider #1830, addressing non-linear scaling; a design was approved two years ago, but never implemented. This PR provides an alternative solution to the problem.)

I've included a storybook entry for testing the new functionality. I'm happy to add tests and documentation updates, but I wanted to get some feedback on this PR before I committed to that work.

Edit: I noticed after creating this PR that Spectrum sliders already specify a getValueLabel function for customizing a slider's output; as implemented, it does not correctly set aria-valuetext, as can be observed by turning on a screen reader and looking at the "donuts to buy" example in the documentation. If this PR is merged, then Spectrum could be modified to have getValueLabel correctly propagate its computed label to aria-valuetext. I'd also be happy to change the name of the prop in this PR to getValueLabel for consistency with Spectrum, if the maintainers prefer.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

http://localhost:9003/?path=/story/react-aria-components--slider-custom-format&args=showAlternative:!true&providerSwitcher-express=false&strict=true

celsius-fahrenheit-slider.mov

Reid Barber and others added 24 commits July 24, 2024 19:13
* Keep focus inside of ComboboxInput
* add script to test docs for warnings/errors

* get links before testing

* update yarn.lock

* update script to get latest browser binaries first

* support running with webkit and firefox

* exit process at the end

---------

Co-authored-by: Robert Snow <[email protected]>
Add missing prop to controlled switch
…cificity (adobe#6829)

* fix Combobox quiet readonly style specificity

makes the nonfocused style more specific so it wins over the disabled style but still doesnt win over the active style

* make search field styles win over textfield styles

* increase combobox invalid style specificity
@snowystinger
Copy link
Member

snowystinger commented Aug 9, 2024

Discussed in the team today, we have a number of opposing opinions and have not settled on the API we'd like for this yet. Thank you for your patience.

Some requirements we have so far:

  • It should be easy to "do the right thing"
  • Whatever value is in the output element should be the aria-valuetext as well by default
  • We don't want to send this through the useNumberFormatter or deprecate formatOptions
  • There should only be one way to accomplish SliderOutput formatting/aria-valuetext

Some other options we talked about

  • use a getValueLabel like we do in RSP, this leads us to multiple ways to accomplish the same task for what is rendered into "SliderOutput"
  • add a prop on the Thumbs, people will likely miss this and we can't make it required nor do we want to
  • reuse the function passed to SliderOutput through a registration context pattern, however, we allow ReactNode here and that isn't compatible with aria-valuetext's type of string.
  • use textContent and use a layout effect to grab it and set it on the inputs, might be a render or two behind, would ignore most formatting

Something to keep an eye out for, when making a range such as https://react-spectrum.adobe.com/react-aria/Slider.html#reusable-wrappers but with labels like this getValueLabel example https://react-spectrum.adobe.com/react-spectrum/Slider.html#labeling would the valuetext end up as "20 - 30 donuts" or "20 donuts - 30 donuts"?

I'll be marking this as "on hold" until we can get the API resolved.

@robin-kestrel
Copy link
Author

Glad to hear it’s being discussed.

Some requirements we have so far: […]

  • We don't want to send this through the useNumberFormatter or deprecate formatOptions

  • There should only be one way to accomplish SliderOutput formatting/aria-valuetext

Aren’t these requirements in direct contradiction? If there needs to be only one way to set the label, and you’re committed to keeping the existing formatOptions/NumberFormat mechanism for setting the label, and you want to make it possible to set the label to values other than those that can be handled by the existing mechanism… Maybe I’m missing something, but I can’t see what design could possibly satisfy all of those contraints.

Some other options we talked about [...]

  • reuse the function passed to SliderOutput through a

I think part of a sentence got cut off?

@snowystinger
Copy link
Member

Aren’t these requirements in direct contradiction?

Yep, this is most of the source of our conflict

@devongovett devongovett force-pushed the main branch 2 times, most recently from a79adcf to 3013156 Compare September 30, 2024 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sliders aria-valuetext should match output element