react-native icon indicating copy to clipboard operation
react-native copied to clipboard

[w3c] ☂️ Web Props umbrella issue

Open necolas opened this issue 2 years ago • 17 comments

Add support for Web props to core components

Contingent on RFC feedback. This is the umbrella issue for basic React DOM / Web props support on React Native components, as described in this proposal: "RFC: Reduce fragmentation across platforms".

Each of the tasks listed below can be tackled with individual PRs that link back to this issue. Not every task has a known solution or implementation yet, so feel free to discuss implementation details in the comments. Each new prop should take priority over any existing equivalents.

Common props

These props should be supported by core components, i.e., <Image>, <View>, <Text>, <TextInput>, etc.

Prop aliases

Accessibility State. https://github.com/facebook/react-native/pull/34524

Accessibility Value. https://github.com/facebook/react-native/pull/34535

Prop equivalents

  • [x] Add aria-hidden. https://github.com/facebook/react-native/pull/34552
  • [x] Add aria-live. https://github.com/facebook/react-native/pull/34555
  • [x] Add role. https://github.com/facebook/react-native/pull/34538#issuecomment-1232918518 & https://github.com/facebook/react-native/pull/34976
    • [x] Alias for accessibilityRole but with full list of ARIA roles allowed.
    • [x] Map role='slider' to accessibilityRole='adjustable'
    • [x] Map role='img' to accessibilityRole='image'
    • [x] Map role='presentation' to accessibilityRole='none'
    • [x] Map role='summary' to accessibilityRole='region'
    • [ ] Add role='option' support.
  • [x] Add tabIndex. https://github.com/facebook/react-native/pull/34486
    • [x] Only support for 0 and -1 values only.
    • [x] Map tabIndex={0} to focusable={true} (Android)
    • [x] Map tabIndex={-1} to focusable={false} (Android)

Example:

<View
  aria-hidden={true}
  aria-busy={false}
  aria-label="Accessibility label"
  aria-valuetext="Middle"
  id="native-id"
  role="slider"
  tabIndex={0}
>

// same as

<View
  accessibilityElementsHidden={true}
  accessibilityLabel="Accessibility label"
  accessibilityRole="adjustable"
  accessibilityState={{ busy: false }}
  accessibilityValue={{ text: "Middle" }}
  focusable={true}
  importantforAccessibility='no-hide-descendants'
  nativeId="native-id"
>

<Image> props

These props should be supported by <Image>.

  • [x] Add alt. https://github.com/facebook/react-native/pull/34550
    • [x] Support alternative text support.
  • [x] Add tintColor prop to replace non-standard style.tintColor. The tintColor style is currently forwarded to a native prop and should instead be exposed as a prop so that React Native for Web does not have to deopt styles for Image rendering. https://github.com/facebook/react-native/pull/34534#issuecomment-1232902094

These props are inter-related:crossOrigin and referrerPolicy should only apply if src or srcSet are defined; and src should be ignored if a valid srcSet is defined. The user-provided source prop should be ignored if a valid src or srcSet prop is also defined.

https://github.com/facebook/react-native/pull/34481

  • [x] Add crossOrigin.
    • [x] Potentially map crossOrigin='use-credentials' to source.headers['Access-Control-Allow-Credentials'] = true.
  • [x] Add height.
  • [x] Add referrerPolicy.
    • [x] Potentially map referrerPolicy to source.headers['Referrer-Policy'] = referrerPolicy.
  • [x] Add src.
    • [x] Map src to source.uri.
  • [x] Add srcSet.
    • [x] Map srcSet='path1 x1, path1 x2' to source=[{ uri: path1, scale: 1 }, { uri: path2, scale: 2 }].
  • [x] Add width.

Example mapping to source:

const { crossOrigin, height, referrerPolicy, src, srcSet, width } = props;
let source = null;
if (src != null) {
  const headers = {};
  if (crossOrigin === 'use-credentials') {
    headers['Access-Control-Allow-Credentials'] = true;
  }
  if (referrerPolicy != null) {
    headers['Referrer-Policy'] = referrerPolicy;
  }
  nextProps.progressiveRenderingEnabled = true;
  source = { headers, height, uri: src, width };
}
if (srcSet != null) {
  source = [];
  const srcList = srcSet.split(', ');
  srcList.forEach((src) => {
    const [uri, xscale] = src.split(' ');
    const scale = parseInt(xscale.split('x')[0], 10);
    const headers = {};
    if (crossOrigin === 'use-credentials') {
      headers['Access-Control-Allow-Credentials'] = true;
    }
    if (referrerPolicy != null) {
      headers['Referrer-Policy'] = referrerPolicy;
    }
    source.push({ headers, height, scale, uri, width });
  });
}

Example:

<Image
  alt="Alternative text"
  crossOrigin="use-credentials"
  height={300}
  referrerPolicy="origin"
  srcSet="https://image.png 1x, https://image2.png 2x"
  width={500}
>

// same as

<Image
  source={[
    {
      uri: "https://image.png",
      scale: 1,
      height: 300,
      headers: {
        'Access-Control-Allow-Credentials': true,
        'Referrer-Policy': 'origin'
      },
      width: 500
    }
    {
      uri: "https://image2.png",
      scale: 2,
      height: 300,
      headers: {
        'Access-Control-Allow-Credentials': true,
        'Referrer-Policy': 'origin'
      },
      width: 500
    }
  ]}
>

<TextInput> props

These props should be supported by <TextInput>.

  • [x] Redefine autoComplete. https://github.com/facebook/react-native/pull/34523
  • [x] Add enterKeyHint. https://github.com/facebook/react-native/pull/34482
    • [x] Map to equivalent returnKeyType values.
    • [x] Map enterKeyHint === 'enter' to returnKeyType = 'default'
    • [x] Map enterKeyHint === 'done' to returnKeyType = 'done'
    • [x] Map enterKeyHint === 'go' to returnKeyType = 'go'
    • [x] Map enterKeyHint === 'next' to returnKeyType = 'next'
    • [x] Map enterKeyHint === 'previous' to returnKeyType = 'previous'
    • [x] Map enterKeyHint === 'search' to returnKeyType = 'search'
    • [x] Map enterKeyHint === 'send' to returnKeyType = 'send'
  • [x] Add inputMode. https://github.com/facebook/react-native/pull/34460
    • [x] Map to equivalent keyboardType values.
    • [x] Map inputMode === 'none' to keyboardType = 'none'
    • [x] Map inputMode === 'text' to keyboardType = 'text'
    • [x] Map inputMode === 'decimal' to keyboardType = 'decimal-pad'
    • [x] Map inputMode === 'email' to keyboardType = 'email-address'
    • [x] Map inputMode === 'numeric' to keyboardType = 'numeric'
    • [x] Map inputMode === 'search' to keyboardType = 'search'
    • [x] Map inputMode === 'tel'to keyboardType = 'phone-pad'
    • [x] Map inputMode === 'url' to keyboardType = 'url'
  • [x] Add readOnly. https://github.com/facebook/react-native/pull/34444
    • [x] Map readOnly={false} to editable={true}.
    • [x] Map readOnly={true} to editable={false}.
  • [x] Add rows (for multiline={true}). https://github.com/facebook/react-native/pull/34488

Example autoComplete mapping:

// https://reactnative.dev/docs/textinput#autocomplete-android
const autoCompleteAndroid = {
 // web: android
  'address-line1': 'postal-address-region',
  'address-line2': 'postal-address-locality',
  bday: 'birthdate-full',
  'bday-day': 'birthdate-day',
  'bday-month': 'birthdate-month',
  'bday-year': 'birthdate-year',
  'cc-csc': 'cc-csc',
  'cc-exp': 'cc-exp',
  'cc-exp-month': 'cc-exp-month',
  'cc-exp-year': 'cc-exp-year',
  'cc-number': 'cc-number',
  country: 'postal-address-country',
  'current-password': 'password',
  email: 'email',
  name: 'name',
  'additional-name': 'name-middle',
  'family-name': 'name-family',
  'given-name': 'name-given',
  'honorific-prefix': 'namePrefix',
  'honorific-suffix': 'nameSuffix',
  'new-password': 'password-new',
  off: 'off',
  'one-time-code': 'sms-otp',
  'postal-code': 'postal-code',
  sex: 'gender',
  'street-address': 'street-address',
  tel: 'tel',
  'tel-country-code': 'tel-country-code',
  'tel-national': 'tel-national',
  username: 'username'
};

// https://reactnative.dev/docs/textinput#textcontenttype-ios
const autoCompleteIOS = {
 // web: ios
  'address-line1': 'streetAddressLine1',
  'address-line2': 'streetAddressLine2',
  'cc-number': 'creditCardNumber',
  'current-password': 'password',
  country: 'countryName',
  email: 'emailAddress',
  name: 'name',
  'additional-name': 'middleName',
  'family-name': 'familyName',
  'given-name': 'givenName',
  nickname: 'nickname',
  'honorific-prefix': 'name-prefix',
  'honorific-suffix': 'name-suffix',
  'new-password': 'newPassword',
  off: 'none',
  'one-time-code': 'oneTimeCode',
  organization: 'organizationName',
  'organization-title': 'jobTitle',
  'postal-code': 'postalCode',
  'street-address': 'fullStreetAddress',
  tel: 'telephoneNumber',
  url: 'URL',
  username: 'username'
};

Examples:

<TextArea
  autoComplete="email"
  inputMode="email"
/>

// same as

<TextArea
  autoComplete="email"
  keyboardType="email-address"
  textContentType="emailAddress"
/>
<TextArea
  multiline
  readOnly={true}
  rows={3}
/>

// same as

<TextArea
  editable={false}
  multiline
  numberOfLines={3}
/>

Documentation

  • [x] Update react-native-website docs to include all these props. @gabrieldonadel has several PRs open for individual props, which we may want to consolidate into a single PR and add remaining props, so it can be merged in one commit.

necolas avatar Aug 15 '22 18:08 necolas

See companion issue related to styles https://github.com/facebook/react-native/issues/34425

necolas avatar Aug 15 '22 18:08 necolas

I'd love to help out. This would be my first real open source contribution. Is there anything I should be aware of in order to get started?

talon-himself avatar Aug 17 '22 00:08 talon-himself

@necolas I've just opened a PR adding the readOnly prop to the TextInput component (https://github.com/facebook/react-native/pull/34444), please let me know if this is what was expected or if it should be implemented using a different approach

gabrieldonadel avatar Aug 18 '22 04:08 gabrieldonadel

@necolas just FYI I've opened a PR tackling the Add tabIndex task here https://github.com/facebook/react-native/pull/34486

gabrieldonadel avatar Aug 26 '22 04:08 gabrieldonadel

We have PRs either merged or open for every prop listed here except aria-hidden, aria-live, and alt. Big thanks to everyone who has contributed patches so far. This work is also going to indirectly help make React Native for Web better.

If you're still looking to contribute to this wider effort to bridge the gap between React Native and React DOM, we also have some styling tasks https://github.com/facebook/react-native/issues/34425

necolas avatar Aug 31 '22 19:08 necolas

The following would also be great to add:

  • mask
  • clip-path
  • shape-outside
  • filter
  • mix-blend-mode

nandorojo avatar Sep 02 '22 00:09 nandorojo

The following would also be great to add

Presumably you meant to post this on the styles umbrella issue?

There has to be an idea of how to implement these features though. Almost everything I've listed to date has already been bridged in RNWeb or a shim I wrote internally, so there's not a lot of new native work needed. Once we have added support for the easy props and styles, people can start looking into how to implement features that require new native code to be written.

necolas avatar Sep 02 '22 01:09 necolas

Presumably you meant to post this on the styles umbrella issue?

Ah, yep, posted on the wrong one.

Once we have added support for the easy props and styles, people can start looking into how to implement features that require new native code to be written.

Sounds good.

nandorojo avatar Sep 02 '22 01:09 nandorojo

I also want to set expectations: for at least the next 6 months, Meta is unlikely to be able to commit engineers to work on new native features to expand CSS support (with the exception of flexbox-related changes to Yoga). But if contributors who want these CSS features can develop and test them, we will help to integrate the code and give credit where it is due. Thanks!

necolas avatar Sep 02 '22 02:09 necolas

Question: what about data- attributes?

some headless web UI libraries make huge use of them to attach some "state" to DOM, I'm worried that they are not transferrable.

Are those supported?

asherccohen avatar Sep 02 '22 06:09 asherccohen

One more task has been added, which is to update the react-native-website docs to include all these new props once all the PRs have been merged.

necolas avatar Sep 03 '22 20:09 necolas

I think adding aria-labelledby aliases for accessibilityLabelledBy should be part of this Issue as well.

Viraj-10 avatar Sep 17 '22 07:09 Viraj-10

Good idea

necolas avatar Sep 17 '22 19:09 necolas

I think role support is missing from Text (which appears to support accessibilityRole): https://github.com/facebook/react-native/blob/main/Libraries/Text/TextProps.js

This logic in View may need to be moved to a shared place so it can be used in Text too

necolas avatar Sep 28 '22 21:09 necolas

I think role="option" is missing

efoken avatar Oct 19 '22 11:10 efoken

I think role support is missing from Text (which appears to support accessibilityRole): https://github.com/facebook/react-native/blob/main/Libraries/Text/TextProps.js

This logic in View may need to be moved to a shared place so it can be used in Text too

Hi @necolas, just FYI I've opened a PR (https://github.com/facebook/react-native/pull/34976) a couple days ago tackling this

gabrieldonadel avatar Oct 19 '22 15:10 gabrieldonadel

I think role="option" is missing

Good spot. I'll add one more task to add option.

necolas avatar Oct 26 '22 18:10 necolas

Good spot. I'll add one more task to add option.

@necolas what should we map the role option to? Should we add this new value natively on accessibilityRole as well?

gabrieldonadel avatar Oct 29 '22 14:10 gabrieldonadel

It maps to nothing AFAICT, but needs to be included in the type for role

necolas avatar Oct 29 '22 16:10 necolas

It maps to nothing AFAICT, but needs to be included in the type for role

Got it, I've just opened a PR for that https://github.com/facebook/react-native/pull/35137

gabrieldonadel avatar Oct 30 '22 19:10 gabrieldonadel

Hi @necolas, I saw that the Update [react-native-website](https://github.com/facebook/react-native-website/) docs to include all these props task was marked as completed. I believe some props are still missing tho, is someone from Meta going to work on this or is it still pending?

gabrieldonadel avatar Oct 30 '22 19:10 gabrieldonadel

My mistake. Unchecked it again

necolas avatar Oct 31 '22 01:10 necolas

All the props listed here have been implemented. Thanks so much to everyone who helped here, especially @gabrieldonadel for helping from start to end!

I'm going complete some updates to RNWeb so that it allows these props too.

And then I will probably create a "Part 2" issue with a few more props from the RFC. I'll also be updating the RFC with more details about the Event and Element APIs, which will likely form new umbrella issues.

necolas avatar Oct 31 '22 18:10 necolas

If you have suggestions for more W3C props we could implement, please comment on "RFC: Reduce fragmentation across platforms". I will start to publish updates to that RFC in the coming weeks. Thanks

necolas avatar Nov 08 '22 02:11 necolas

👋 Been closely following along this work and just want to thank all of you for pushing this effort forward!

Quick question: are there plans to align implementation/typescript values for accessibility props? I'm seeing react-native uses boolean while @types/react uses Booleanish which allows boolean | 'true' | 'false'

kmartinezmedia avatar Feb 23 '23 05:02 kmartinezmedia

@necolas I noticed that Touchable* components doesn't have accessibilityLabelledBy & aria-labelledby props, is it expected ?

retyui avatar Apr 05 '24 13:04 retyui