adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
12.13k stars 1.06k forks source link

Add "form" attribute to all components that render an <input>, <textarea> or <select> #4117

Open tchak opened 1 year ago

tchak commented 1 year ago

🙋 Feature Request

Add "form" attribute to all components that render an <input>, <textarea> or <select>.

🤔 Expected Behavior

Be able to set form attribute on the internal <input>, <textarea> or <select>.

😯 Current Behavior

form attribute is not exposed.

💁 Possible Solution

I have tried to use ref and set form attribute in an useEffect. In some cases I am also using data-testid to gain access to underlying DOM/input and set form attribute in an useEffect. Neither is a good solution.

Another solution is to not use internal <input> (not set name on the component) and always use an external hidden <input>.

🔦 Context

It is more practical some time to render form and fields in separate parts of the DOM. For example, in cases when I want each field to have its own form. I can still group fields with one fieldset and put forms outside while referencing them from the inputs.

wojtekmaj commented 4 months ago

I nearly ripped my eyes out writing this but here's a workaround if you're in an urgent need of a fix like myself:

        ref={
          // Workaround for react-aria-components not supporting form attribute
          // See https://github.com/adobe/react-spectrum/issues/4117
          form
            ? (ref) => {
                if (!ref) {
                  return;
                }

                // A hidden section rendered by react-aria-components
                const nextSibling = ref.nextSibling;

                if (!nextSibling || !(nextSibling instanceof HTMLElement)) {
                  return;
                }

                // A hidden input rendered by react-aria-components
                const formElement = nextSibling.querySelector(`[name="${name}"]`);

                if (!formElement) {
                  return;
                }

                formElement.setAttribute('form', form);
              }
            : undefined
        }
snowystinger commented 4 months ago

For components where you provide an Input you can access it through the ref to that element https://stackblitz.com/edit/vitejs-vite-28q1zf?file=package.json,src%2FApp.tsx&terminal=dev

For the others, I think we could consider exposing an inputRef on the component or adding it to the ref via a useImperativeHandle.

If we do expose an inputRef then we'll have to determine how to deal with components that are implemented with multiple inputs, such as DatePicker or ColorArea.

mxp-qk commented 3 months ago

Hey,

I used to rewire the name and form props with the react-aria hooks, but since this PR I switch to RAC and it works great (👏) except for this two uses cases :

For the second use case, here is an example with a TextField, a NumberField and a Select.

I tried with the form attribute on the <Input /> element but it land on the "aria input version" not the hidden one. The form attribute only purpose, if I'm correct, is to associate the input to the given form and should solve both use case. I cannot find a way to be sure it won't impact assistive technology.

vincerubinetti commented 2 months ago

Related https://github.com/adobe/react-spectrum/discussions/6262

I've taken to just reimplementing these hidden inputs myself. Here's an example, but keep in mind that this is only to be used in FormData. It doesn't replace the hidden inputs RAC adds for accessibility, e.g. for slider thumbs.

<input
  className="sr-only"
  type="checkbox"
  aria-hidden={true}
  tabIndex={-1}
  value={isSelected ? checkedValue : uncheckedValue}
  checked={!(required && !isSelected)}
  required={required}
  name={name}
  form={form}
  onChange={() => null}
/>

Cannot believe that the supposedly golden standard component library overlooks this.