@telus-uds/components-base 1.14.3 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +15 -2
  2. package/__tests17__/A11yText/A11yText.test.jsx +34 -0
  3. package/__tests17__/ActivityIndicator/ActivityIndicator.test.jsx +68 -0
  4. package/__tests17__/ActivityIndicator/__snapshots__/ActivityIndicator.test.jsx.snap +299 -0
  5. package/__tests17__/Box/Box.test.jsx +111 -0
  6. package/__tests17__/Button/Button.test.jsx +86 -0
  7. package/__tests17__/Button/ButtonBase.test.jsx +82 -0
  8. package/__tests17__/Button/ButtonGroup.test.jsx +347 -0
  9. package/__tests17__/Button/ButtonLink.test.jsx +61 -0
  10. package/__tests17__/Card/Card.test.jsx +63 -0
  11. package/__tests17__/Carousel/Carousel.test.jsx +128 -0
  12. package/__tests17__/Carousel/CarouselTabs.test.jsx +142 -0
  13. package/__tests17__/Checkbox/Checkbox.test.jsx +94 -0
  14. package/__tests17__/Checkbox/CheckboxGroup.test.jsx +246 -0
  15. package/__tests17__/Divider/Divider.test.jsx +91 -0
  16. package/__tests17__/ExpandCollapse/ExpandCollapse.test.jsx +109 -0
  17. package/__tests17__/Feedback/Feedback.test.jsx +42 -0
  18. package/__tests17__/FlexGrid/Col.test.jsx +261 -0
  19. package/__tests17__/FlexGrid/FlexGrid.test.jsx +136 -0
  20. package/__tests17__/FlexGrid/Row.test.jsx +273 -0
  21. package/__tests17__/HorizontalScroll/HorizontalScroll.test.jsx +165 -0
  22. package/__tests17__/Icon/Icon.test.jsx +61 -0
  23. package/__tests17__/IconButton/IconButton.test.jsx +52 -0
  24. package/__tests17__/InputLabel/InputLabel.test.jsx +28 -0
  25. package/__tests17__/InputLabel/__snapshots__/InputLabel.test.jsx.snap +3 -0
  26. package/__tests17__/InputSupports/InputSupports.test.jsx +60 -0
  27. package/__tests17__/Link/Link.test.jsx +63 -0
  28. package/__tests17__/Link/TextButton.test.jsx +35 -0
  29. package/__tests17__/List/List.test.jsx +82 -0
  30. package/__tests17__/Modal/Modal.test.jsx +47 -0
  31. package/__tests17__/Notification/Notification.test.jsx +20 -0
  32. package/__tests17__/Pagination/Pagination.test.jsx +160 -0
  33. package/__tests17__/Progress/Progress.test.jsx +79 -0
  34. package/__tests17__/Radio/Radio.test.jsx +87 -0
  35. package/__tests17__/Radio/RadioGroup.test.jsx +220 -0
  36. package/__tests17__/RadioCard/RadioCard.test.jsx +87 -0
  37. package/__tests17__/RadioCard/RadioCardGroup.test.jsx +246 -0
  38. package/__tests17__/Search/Search.test.jsx +87 -0
  39. package/__tests17__/Select/Select.test.jsx +94 -0
  40. package/__tests17__/SideNav/SideNav.test.jsx +110 -0
  41. package/__tests17__/Skeleton/Skeleton.test.jsx +61 -0
  42. package/__tests17__/SkipLink/SkipLink.test.jsx +61 -0
  43. package/__tests17__/Spacer/Spacer.test.jsx +63 -0
  44. package/__tests17__/StackView/StackView.test.jsx +211 -0
  45. package/__tests17__/StackView/StackWrap.test.jsx +47 -0
  46. package/__tests17__/StackView/getStackedContent.test.jsx +295 -0
  47. package/__tests17__/StepTracker/StepTracker.test.jsx +108 -0
  48. package/__tests17__/Tabs/Tabs.test.jsx +49 -0
  49. package/__tests17__/Tags/Tags.test.jsx +327 -0
  50. package/__tests17__/TextInput/TextArea.test.jsx +35 -0
  51. package/__tests17__/TextInput/TextInputBase.test.jsx +125 -0
  52. package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +80 -0
  53. package/__tests17__/ThemeProvider/useThemeTokens.test.jsx +514 -0
  54. package/__tests17__/ThemeProvider/utils/theme-tokens.test.js +41 -0
  55. package/__tests17__/ToggleSwitch/ToggleSwitch.test.jsx +82 -0
  56. package/__tests17__/ToggleSwitch/ToggleSwitchGroup.test.jsx +192 -0
  57. package/__tests17__/Tooltip/Tooltip.test.jsx +65 -0
  58. package/__tests17__/Tooltip/getTooltipPosition.test.js +79 -0
  59. package/__tests17__/Typography/typography.test.jsx +90 -0
  60. package/__tests17__/utils/children.test.jsx +128 -0
  61. package/__tests17__/utils/containUniqueFields.test.js +25 -0
  62. package/__tests17__/utils/input.test.js +375 -0
  63. package/__tests17__/utils/props.test.js +36 -0
  64. package/__tests17__/utils/semantics.test.jsx +34 -0
  65. package/__tests17__/utils/useCopy.test.js +42 -0
  66. package/__tests17__/utils/useResponsiveProp.test.jsx +202 -0
  67. package/__tests17__/utils/useSpacingScale.test.jsx +273 -0
  68. package/__tests17__/utils/useUniqueId.test.js +31 -0
  69. package/component-docs.json +85 -438
  70. package/lib/A11yInfoProvider/index.js +14 -5
  71. package/lib/Button/ButtonGroup.js +3 -2
  72. package/lib/Checkbox/Checkbox.js +9 -6
  73. package/lib/ExpandCollapse/Control.js +6 -5
  74. package/lib/ExpandCollapse/Panel.js +5 -4
  75. package/lib/List/ListItem.js +10 -236
  76. package/lib/List/ListItemBase.js +162 -0
  77. package/lib/List/ListItemContent.js +85 -0
  78. package/lib/List/ListItemMark.js +158 -0
  79. package/lib/List/PressableListItemBase.js +147 -0
  80. package/lib/Notification/Notification.js +2 -1
  81. package/lib/Pagination/Pagination.js +4 -3
  82. package/lib/Radio/Radio.js +9 -6
  83. package/lib/RadioCard/RadioCard.js +9 -6
  84. package/lib/Tabs/Tabs.js +12 -3
  85. package/lib/Tags/Tags.js +3 -3
  86. package/lib/TextInput/TextInput.js +5 -4
  87. package/lib/ViewportProvider/useViewportListener.js +11 -5
  88. package/lib/utils/hasOwnProperty.js +18 -0
  89. package/lib/utils/props/a11yProps.js +212 -45
  90. package/lib/utils/props/getPropSelector.js +47 -5
  91. package/lib/utils/useResponsiveProp.js +5 -3
  92. package/lib/utils/withLinkRouter.js +3 -5
  93. package/lib-module/A11yInfoProvider/index.js +14 -4
  94. package/lib-module/Button/ButtonGroup.js +3 -2
  95. package/lib-module/Checkbox/Checkbox.js +9 -6
  96. package/lib-module/ExpandCollapse/Control.js +6 -5
  97. package/lib-module/ExpandCollapse/Panel.js +5 -4
  98. package/lib-module/List/ListItem.js +13 -235
  99. package/lib-module/List/ListItemBase.js +139 -0
  100. package/lib-module/List/ListItemContent.js +66 -0
  101. package/lib-module/List/ListItemMark.js +143 -0
  102. package/lib-module/List/PressableListItemBase.js +117 -0
  103. package/lib-module/Notification/Notification.js +2 -1
  104. package/lib-module/Pagination/Pagination.js +5 -3
  105. package/lib-module/Radio/Radio.js +9 -6
  106. package/lib-module/RadioCard/RadioCard.js +9 -6
  107. package/lib-module/Tabs/Tabs.js +13 -4
  108. package/lib-module/Tags/Tags.js +3 -3
  109. package/lib-module/TextInput/TextInput.js +5 -4
  110. package/lib-module/ViewportProvider/useViewportListener.js +10 -4
  111. package/lib-module/utils/hasOwnProperty.js +11 -0
  112. package/lib-module/utils/props/a11yProps.js +210 -45
  113. package/lib-module/utils/props/getPropSelector.js +44 -5
  114. package/lib-module/utils/useResponsiveProp.js +3 -4
  115. package/lib-module/utils/withLinkRouter.js +3 -5
  116. package/package.json +11 -16
  117. package/src/A11yInfoProvider/index.jsx +20 -4
  118. package/src/Button/ButtonGroup.jsx +4 -2
  119. package/src/Checkbox/Checkbox.jsx +7 -3
  120. package/src/ExpandCollapse/Control.jsx +8 -5
  121. package/src/ExpandCollapse/Panel.jsx +7 -5
  122. package/src/List/ListItem.jsx +12 -191
  123. package/src/List/ListItemBase.jsx +118 -0
  124. package/src/List/ListItemContent.jsx +52 -0
  125. package/src/List/ListItemMark.jsx +99 -0
  126. package/src/List/PressableListItemBase.jsx +102 -0
  127. package/src/Notification/Notification.jsx +1 -1
  128. package/src/Pagination/Pagination.jsx +6 -1
  129. package/src/Radio/Radio.jsx +7 -3
  130. package/src/RadioCard/RadioCard.jsx +7 -3
  131. package/src/Tabs/Tabs.jsx +19 -2
  132. package/src/Tags/Tags.jsx +3 -3
  133. package/src/TextInput/TextInput.jsx +4 -4
  134. package/src/ViewportProvider/useViewportListener.js +10 -5
  135. package/src/utils/hasOwnProperty.js +11 -0
  136. package/src/utils/props/a11yProps.js +168 -55
  137. package/src/utils/props/getPropSelector.js +45 -4
  138. package/src/utils/useResponsiveProp.js +3 -3
  139. package/src/utils/withLinkRouter.jsx +1 -3
@@ -0,0 +1,99 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { View, StyleSheet } from 'react-native'
5
+
6
+ export const tokenTypes = {
7
+ itemIconSize: PropTypes.number.isRequired,
8
+ itemIconColor: PropTypes.string.isRequired,
9
+ listGutter: PropTypes.number.isRequired,
10
+ iconMarginTop: PropTypes.number.isRequired
11
+ }
12
+
13
+ const selectItemIconTokens = ({ itemIconSize, itemIconColor }) => ({
14
+ size: itemIconSize,
15
+ color: itemIconColor
16
+ })
17
+
18
+ const selectSideItemContainerStyles = ({ listGutter, iconMarginTop }) => ({
19
+ marginTop: iconMarginTop,
20
+ marginRight: listGutter
21
+ })
22
+
23
+ // Align bullets with the top line of text the same way icons are aligned
24
+ const selectBulletPositioningStyles = ({ itemIconSize }) => ({
25
+ width: itemIconSize,
26
+ height: itemIconSize
27
+ })
28
+
29
+ const selectBulletStyles = ({ itemBulletWidth, itemBulletHeight, itemBulletColor }) => ({
30
+ width: itemBulletWidth,
31
+ height: itemBulletHeight,
32
+ borderRadius: itemBulletHeight / 2,
33
+ backgroundColor: itemBulletColor
34
+ })
35
+
36
+ const selectBulletContainerStyles = ({ itemBulletContainerWidth, itemBulletContainerAlign }) => ({
37
+ width: itemBulletContainerWidth,
38
+ alignItems: itemBulletContainerAlign
39
+ })
40
+
41
+ /**
42
+ * Subcomponent used within ListItem and similar components for rendering bullets or icons
43
+ * that sit alongside a ListIconContent in a { flexDirection: row } container.
44
+ *
45
+ * It's the responsibility of themes to make sure that the supplied tokens align the
46
+ * icon or bullet nicely against the first line of text in a ListIconContent.
47
+ */
48
+ const ListItemMark = ({ icon, iconColor, iconSize, tokens = {} }) => {
49
+ const IconComponent = icon || <></>
50
+
51
+ const themeTokens = typeof tokens === 'function' ? tokens() : tokens
52
+
53
+ const sideItemContainerStyles = selectSideItemContainerStyles(themeTokens)
54
+
55
+ if (icon) {
56
+ const iconTokens = selectItemIconTokens(themeTokens)
57
+ return (
58
+ <View style={sideItemContainerStyles}>
59
+ <IconComponent size={iconSize || iconTokens.size} color={iconColor || iconTokens.color} />
60
+ </View>
61
+ )
62
+ }
63
+
64
+ const itemBulletContainerStyles = selectBulletContainerStyles(themeTokens)
65
+ const itemBulletStyles = selectBulletStyles(themeTokens)
66
+ const itemBulletPositioningStyles = selectBulletPositioningStyles(themeTokens)
67
+ return (
68
+ <View style={[sideItemContainerStyles, itemBulletContainerStyles]}>
69
+ <View style={[staticStyles.bulletPositioning, itemBulletPositioningStyles]}>
70
+ <View style={itemBulletStyles} testID="unordered-item-bullet" />
71
+ </View>
72
+ </View>
73
+ )
74
+ }
75
+
76
+ ListItemMark.propTypes = {
77
+ tokens: PropTypes.shape(tokenTypes).isRequired,
78
+ /**
79
+ * Renders side item icon
80
+ */
81
+ icon: PropTypes.elementType,
82
+ /**
83
+ * Will set display icon color
84
+ */
85
+ iconColor: PropTypes.string,
86
+ /**
87
+ * Allow the user define the icon size
88
+ */
89
+ iconSize: PropTypes.number
90
+ }
91
+
92
+ const staticStyles = StyleSheet.create({
93
+ bulletPositioning: {
94
+ alignItems: 'center',
95
+ justifyContent: 'center'
96
+ }
97
+ })
98
+
99
+ export default ListItemMark
@@ -0,0 +1,102 @@
1
+ import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import ABBPropTypes from 'airbnb-prop-types'
4
+
5
+ import { Pressable, StyleSheet, Platform } from 'react-native'
6
+
7
+ import {
8
+ resolvePressableTokens,
9
+ clickProps,
10
+ linkProps,
11
+ hrefAttrsProp,
12
+ withLinkRouter
13
+ } from '../utils'
14
+
15
+ import ListItemBase from './ListItemBase'
16
+ import ListItemContent, { tokenTypes as contentTokenTypes } from './ListItemContent'
17
+ import ListItemMark, { tokenTypes as markTokenTypes } from './ListItemMark'
18
+
19
+ const selectPressableStyles = ({
20
+ backgroundColor,
21
+ paddingLeft,
22
+ paddingRight,
23
+ paddingTop,
24
+ paddingBottom,
25
+ interItemMargin
26
+ }) => ({
27
+ backgroundColor,
28
+ paddingLeft,
29
+ paddingRight,
30
+ paddingTop,
31
+ paddingBottom,
32
+ marginBottom: interItemMargin,
33
+ ...Platform.select({ web: { outline: 'none' } })
34
+ })
35
+
36
+ const PressableListItemBase = forwardRef(
37
+ ({ href, tokens, icon, children, listItemRef, ...rest }, ref) => {
38
+ const { onPress, ...props } = clickProps.toPressProps(rest)
39
+ const { hrefAttrs, rest: listItemProps } = hrefAttrsProp.bundle(props)
40
+ const handlePress = linkProps.handleHref({ href, onPress })
41
+
42
+ return (
43
+ <ListItemBase ref={listItemRef} tokens={tokens()} {...listItemProps}>
44
+ {({ isLastItem }) => {
45
+ const getTokens = (pressableState) =>
46
+ resolvePressableTokens(tokens, pressableState, { last: isLastItem })
47
+ const getPressableStyle = (pressableState) => [
48
+ staticStyles.itemContainer,
49
+ selectPressableStyles(getTokens(pressableState))
50
+ ]
51
+ return (
52
+ <Pressable
53
+ onPress={handlePress}
54
+ href={href}
55
+ hrefAttrs={hrefAttrs}
56
+ style={getPressableStyle}
57
+ ref={ref}
58
+ >
59
+ {(pressableState) => {
60
+ const themeTokens = getTokens(pressableState)
61
+ return (
62
+ <>
63
+ <ListItemMark icon={icon} tokens={themeTokens} />
64
+ <ListItemContent tokens={themeTokens}>
65
+ {typeof children === 'function' ? children(pressableState) : children}
66
+ </ListItemContent>
67
+ </>
68
+ )
69
+ }}
70
+ </Pressable>
71
+ )
72
+ }}
73
+ </ListItemBase>
74
+ )
75
+ }
76
+ )
77
+
78
+ PressableListItemBase.displayName = 'PressableListItemBase'
79
+
80
+ const staticStyles = StyleSheet.create({
81
+ itemContainer: {
82
+ flexDirection: 'row',
83
+ flex: 1
84
+ },
85
+ tokens: {
86
+ ...contentTokenTypes,
87
+ ...markTokenTypes
88
+ }
89
+ })
90
+
91
+ PressableListItemBase.propTypes = {
92
+ href: PropTypes.string,
93
+ onPress: PropTypes.func,
94
+ // TODO - type this better, maybe import the subcomponent token types and run it through util
95
+ // eslint-disable-next-line react/forbid-prop-types
96
+ tokens: PropTypes.any,
97
+ icon: PropTypes.elementType,
98
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
99
+ listItemRef: ABBPropTypes.ref()
100
+ }
101
+
102
+ export default withLinkRouter(PressableListItemBase)
@@ -112,7 +112,7 @@ const Notification = forwardRef(
112
112
  const textStyles = selectTextStyles(themeTokens, themeOptions)
113
113
 
114
114
  const content = wrapStringsInText(
115
- typeof children === 'function' ? children({ textStyles }) : children,
115
+ typeof children === 'function' ? children({ textStyles, variant }) : children,
116
116
  { style: textStyles }
117
117
  )
118
118
 
@@ -103,12 +103,17 @@ const Pagination = forwardRef(
103
103
  label={buttonLabel}
104
104
  copy={copy}
105
105
  isActive={isItemActive(itemIndex)}
106
+ key={buttonLabel}
106
107
  />
107
108
  )
108
109
  }
109
110
 
110
111
  if (shouldRenderEllipsis(itemIndex)) {
111
- return <Text style={ellipsisTextStyles}>...</Text>
112
+ return (
113
+ <Text key="..." style={ellipsisTextStyles}>
114
+ ...
115
+ </Text>
116
+ )
112
117
  }
113
118
 
114
119
  return null
@@ -151,15 +151,19 @@ const Radio = forwardRef(
151
151
  const uniqueId = useUniqueId('radio')
152
152
  const inputId = id ?? uniqueId
153
153
 
154
+ const selectedProps = selectProps({
155
+ accessibilityRole: 'radio',
156
+ accessibilityState: { checked: isChecked, disabled: inactive },
157
+ ...rest
158
+ })
159
+
154
160
  return (
155
161
  <Pressable
156
162
  ref={ref}
157
163
  disabled={inactive}
158
164
  onKeyDown={handleKeyDown}
159
165
  onPress={handleChange}
160
- accessibilityRole="radio"
161
- accessibilityState={{ checked: isChecked, disabled: inactive }}
162
- {...selectProps(rest)}
166
+ {...selectedProps}
163
167
  >
164
168
  {({ focused: focus, hovered: hover, pressed }) => {
165
169
  const stateTokens = getTokens({ focus, hover, pressed })
@@ -98,6 +98,12 @@ const RadioCard = forwardRef(
98
98
  const getCardTokens = (cardState) => selectPressableCardTokens(getTokens(cardState))
99
99
  const { themeOptions } = useTheme()
100
100
 
101
+ const selectedProps = selectProps({
102
+ accessibilityRole: 'radio',
103
+ accessibilityState: { checked: isChecked, disabled: inactive },
104
+ ...rest
105
+ })
106
+
101
107
  return (
102
108
  <PressableCardBase
103
109
  ref={ref}
@@ -105,9 +111,7 @@ const RadioCard = forwardRef(
105
111
  checked={isChecked}
106
112
  tokens={getCardTokens}
107
113
  onPress={handleChange}
108
- accessibilityRole="radio"
109
- accessibilityState={{ checked: isChecked, disabled: inactive }}
110
- {...selectProps(rest)}
114
+ {...selectedProps}
111
115
  >
112
116
  {(cardState) => {
113
117
  const { radioSpace, contentSpace, ...themeTokens } = getTokens(cardState)
package/src/Tabs/Tabs.jsx CHANGED
@@ -6,6 +6,8 @@ import StackView from '../StackView'
6
6
  import {
7
7
  a11yProps,
8
8
  getTokensPropType,
9
+ focusHandlerProps,
10
+ pressProps,
9
11
  selectSystemProps,
10
12
  useHash,
11
13
  useInputValue,
@@ -20,6 +22,12 @@ import HorizontalScroll, {
20
22
  import TabsItem from './TabsItem'
21
23
 
22
24
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
25
+ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
26
+ a11yProps,
27
+ focusHandlerProps,
28
+ pressProps,
29
+ viewProps
30
+ ])
23
31
 
24
32
  const { selectHorizontalScrollTokens, useItemPositions } = horizontalScrollUtils
25
33
 
@@ -99,15 +107,22 @@ const Tabs = forwardRef(
99
107
  label,
100
108
  id,
101
109
  accessibilityRole = defaultTabItemAccessibiltyRole,
110
+ onPress,
102
111
  ref: itemRef,
103
112
  LinkRouter: ItemLinkRouter = LinkRouter,
104
- linkRouterProps: itemLinkRouterProps
113
+ linkRouterProps: itemLinkRouterProps,
114
+ ...itemRest
105
115
  },
106
116
  index
107
117
  ) => {
108
118
  const itemId = id ?? label
109
119
  const isSelected = Boolean(currentValue && currentValue === itemId)
110
- const handlePress = (event) => setValue(itemId, event)
120
+ const handlePress = (event) => {
121
+ if (typeof onPress === 'function') onPress(event)
122
+ setValue(itemId, event)
123
+ }
124
+
125
+ const itemProps = selectItemProps(itemRest)
111
126
  return (
112
127
  <TabsItem
113
128
  ref={itemRef}
@@ -122,6 +137,7 @@ const Tabs = forwardRef(
122
137
  accessibilityRole={accessibilityRole}
123
138
  LinkRouter={ItemLinkRouter}
124
139
  linkRouterProps={{ ...linkRouterProps, ...itemLinkRouterProps }}
140
+ {...itemProps}
125
141
  >
126
142
  {label}
127
143
  </TabsItem>
@@ -140,6 +156,7 @@ Tabs.propTypes = {
140
156
  ...withLinkRouter.PropTypes,
141
157
  items: PropTypes.arrayOf(
142
158
  PropTypes.shape({
159
+ ...selectedItemPropTypes,
143
160
  ...withLinkRouter.PropTypes,
144
161
  href: PropTypes.string,
145
162
  label: PropTypes.string,
package/src/Tags/Tags.jsx CHANGED
@@ -127,12 +127,12 @@ const Tags = forwardRef(
127
127
  toggleOneValue(id, event)
128
128
  }
129
129
 
130
- const itemProps = {
130
+ const itemProps = selectItemProps({
131
131
  accessibilityState: { checked: isSelected },
132
132
  accessibilityRole: itemA11yRole,
133
133
  ...a11yProps.getPositionInSet(items.length, index),
134
- ...selectItemProps(itemRest)
135
- }
134
+ ...itemRest
135
+ })
136
136
 
137
137
  return (
138
138
  <ButtonBase
@@ -41,7 +41,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
41
41
  * supported props and <a href="https://reactnative.dev/docs/textinput" target="_blank">React Native Web documentation</a> for
42
42
  * their implementation on the web.
43
43
  */
44
- const TextInput = forwardRef(({ tokens, variant = {}, ...rest }, ref) => {
44
+ const TextInput = forwardRef(({ tokens, nativeID, variant = {}, ...rest }, ref) => {
45
45
  const { supportsProps, ...selectedProps } = selectProps(rest)
46
46
 
47
47
  const inputProps = {
@@ -51,9 +51,9 @@ const TextInput = forwardRef(({ tokens, variant = {}, ...rest }, ref) => {
51
51
  }
52
52
 
53
53
  return (
54
- <InputSupports nativeID={selectedProps.nativeID} {...supportsProps}>
55
- {({ inputId, ...props }) => (
56
- <TextInputBase ref={ref} {...inputProps} nativeID={inputId} {...props} />
54
+ <InputSupports nativeID={nativeID} {...supportsProps}>
55
+ {({ inputId, ...propsFromInputSupports }) => (
56
+ <TextInputBase ref={ref} nativeID={inputId} {...propsFromInputSupports} {...inputProps} />
57
57
  )}
58
58
  </InputSupports>
59
59
  )
@@ -28,11 +28,16 @@ const useViewportListenerCSR = (setViewport) => {
28
28
  const onChange = ({ window }) => setViewport(viewports.select(window.width))
29
29
  const listener = Dimensions.addEventListener('change', onChange)
30
30
 
31
- // From RN 0.65.0, Dimensions.removeEventListener is deprecated for `remove` on addEventListener return value;
32
- // however, that is not available in RN <=0.64.X, therefore not in any Expo release as of 2021 (Expo SDK 43).
33
- return listener?.remove || (() => Dimensions.removeEventListener('change', onChange))
34
-
35
- // setViewport is a function from `useState` so it is stable and won't make the effect re-run
31
+ return () => {
32
+ if (typeof listener?.remove === 'function') {
33
+ // Can't just return listener.remove because listener.emitter disappears, causing an internal error.
34
+ // See https://github.com/facebook/react-native/issues/34508
35
+ listener.remove()
36
+ // From RN 0.65.0, Dimensions.removeEventListener is deprecated for `remove` on addEventListener return value
37
+ } else if (typeof Dimensions.removeEventListener === 'function') {
38
+ Dimensions.removeEventListener('change', onChange)
39
+ }
40
+ }
36
41
  }, [setViewport])
37
42
  }
38
43
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Linter disallows object instance prototype methods like someObject.hasOwnProperty(key),
3
+ * but we can use this instead.
4
+ *
5
+ * @param {object} object
6
+ * @param {String} key
7
+ * @returns {Boolean}
8
+ */
9
+ export default function hasOwnProperty(object, key) {
10
+ return Object.prototype.hasOwnProperty.call(object, key)
11
+ }
@@ -40,62 +40,164 @@ const nativeA11yPropTypes = {
40
40
  onAccessibilityTap: PropTypes.func
41
41
  }
42
42
 
43
- const a11yPropTypes = {
44
- ...nativeA11yPropTypes,
43
+ const a11yPropTypes = Platform.select({
45
44
  // React Native Web adds many a11y props that alias aria-* attributes
46
45
  // Types based on https://necolas.github.io/react-native-web/docs/accessibility/
47
- accessibilityActiveDescendant: PropTypes.string,
48
- accessibilityAtomic: PropTypes.bool,
49
- accessibilityAutoComplete: PropTypes.string,
50
- accessibilityBusy: PropTypes.bool,
51
- accessibilityChecked: PropTypes.oneOf([true, false, 'mixed']),
52
- accessibilityColumnCount: PropTypes.number,
53
- accessibilityColumnIndex: PropTypes.number,
54
- accessibilityColumnSpan: PropTypes.number,
55
- accessibilityControls: PropTypes.oneOfType([
56
- PropTypes.string,
57
- PropTypes.arrayOf(PropTypes.string)
58
- ]),
59
- accessibilityCurrent: PropTypes.oneOf([true, false, 'page', 'step', 'location', 'date', 'time']),
60
- accessibilityDescribedBy: PropTypes.oneOfType([
61
- PropTypes.string,
62
- PropTypes.arrayOf(PropTypes.string)
63
- ]),
64
- accessibilityDetails: PropTypes.string,
65
- accessibilityDisabled: PropTypes.bool,
66
- accessibilityErrorMessage: PropTypes.string,
67
- accessibilityExpanded: PropTypes.bool,
68
- accessibilityFlowTo: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
69
- accessibilityHasPopup: PropTypes.string,
70
- accessibilityHidden: PropTypes.bool,
71
- accessibilityInvalid: PropTypes.bool,
72
- accessibilityKeyShortcuts: PropTypes.string,
73
- accessibilityLabelledBy: PropTypes.oneOfType([
74
- PropTypes.string,
75
- PropTypes.arrayOf(PropTypes.string)
76
- ]),
77
- accessibilityLevel: PropTypes.number,
78
- accessibilityModal: PropTypes.bool,
79
- accessibilityMultiline: PropTypes.bool,
80
- accessibilityMultiSelectable: PropTypes.bool,
81
- accessibilityOrientation: PropTypes.oneOf(['horizontal', 'vertical']),
82
- accessibilityOwns: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
83
- accessibilityPlaceholder: PropTypes.string,
84
- accessibilityPosInSet: PropTypes.number,
85
- accessibilityPressed: PropTypes.bool,
86
- accessibilityReadOnly: PropTypes.bool,
87
- accessibilityRequired: PropTypes.bool,
88
- accessibilityRoleDescription: PropTypes.string,
89
- accessibilityRowCount: PropTypes.number,
90
- accessibilityRowIndex: PropTypes.number,
91
- accessibilityRowSpan: PropTypes.number,
92
- accessibilitySelected: PropTypes.bool,
93
- accessibilitySetSize: PropTypes.number,
94
- accessibilitySort: PropTypes.oneOf(['ascending', 'descending', 'none', 'other']),
95
- accessibilityValueMax: PropTypes.number,
96
- accessibilityValueMin: PropTypes.number,
97
- accessibilityValueNow: PropTypes.number,
98
- accessibilityValueText: PropTypes.string
46
+ web: {
47
+ ...nativeA11yPropTypes,
48
+ accessibilityActiveDescendant: PropTypes.string,
49
+ accessibilityAtomic: PropTypes.bool,
50
+ accessibilityAutoComplete: PropTypes.string,
51
+ accessibilityBusy: PropTypes.bool,
52
+ accessibilityChecked: PropTypes.oneOf([true, false, 'mixed']),
53
+ accessibilityColumnCount: PropTypes.number,
54
+ accessibilityColumnIndex: PropTypes.number,
55
+ accessibilityColumnSpan: PropTypes.number,
56
+ accessibilityControls: PropTypes.oneOfType([
57
+ PropTypes.string,
58
+ PropTypes.arrayOf(PropTypes.string)
59
+ ]),
60
+ accessibilityCurrent: PropTypes.oneOf([
61
+ true,
62
+ false,
63
+ 'page',
64
+ 'step',
65
+ 'location',
66
+ 'date',
67
+ 'time'
68
+ ]),
69
+ accessibilityDescribedBy: PropTypes.oneOfType([
70
+ PropTypes.string,
71
+ PropTypes.arrayOf(PropTypes.string)
72
+ ]),
73
+ accessibilityDetails: PropTypes.string,
74
+ accessibilityDisabled: PropTypes.bool,
75
+ accessibilityErrorMessage: PropTypes.string,
76
+ accessibilityExpanded: PropTypes.bool,
77
+ accessibilityFlowTo: PropTypes.oneOfType([
78
+ PropTypes.string,
79
+ PropTypes.arrayOf(PropTypes.string)
80
+ ]),
81
+ accessibilityHasPopup: PropTypes.string,
82
+ accessibilityHidden: PropTypes.bool,
83
+ accessibilityInvalid: PropTypes.bool,
84
+ accessibilityKeyShortcuts: PropTypes.string,
85
+ accessibilityLabelledBy: PropTypes.oneOfType([
86
+ PropTypes.string,
87
+ PropTypes.arrayOf(PropTypes.string)
88
+ ]),
89
+ accessibilityLevel: PropTypes.number,
90
+ accessibilityModal: PropTypes.bool,
91
+ accessibilityMultiline: PropTypes.bool,
92
+ accessibilityMultiSelectable: PropTypes.bool,
93
+ accessibilityOrientation: PropTypes.oneOf(['horizontal', 'vertical']),
94
+ accessibilityOwns: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
95
+ accessibilityPlaceholder: PropTypes.string,
96
+ accessibilityPosInSet: PropTypes.number,
97
+ accessibilityPressed: PropTypes.bool,
98
+ accessibilityReadOnly: PropTypes.bool,
99
+ accessibilityRequired: PropTypes.bool,
100
+ accessibilityRoleDescription: PropTypes.string,
101
+ accessibilityRowCount: PropTypes.number,
102
+ accessibilityRowIndex: PropTypes.number,
103
+ accessibilityRowSpan: PropTypes.number,
104
+ accessibilitySelected: PropTypes.bool,
105
+ accessibilitySetSize: PropTypes.number,
106
+ accessibilitySort: PropTypes.oneOf(['ascending', 'descending', 'none', 'other']),
107
+ accessibilityValueMax: PropTypes.number,
108
+ accessibilityValueMin: PropTypes.number,
109
+ accessibilityValueNow: PropTypes.number,
110
+ accessibilityValueText: PropTypes.string
111
+ },
112
+ // Ignore web-only props in native builds
113
+ default: nativeA11yPropTypes
114
+ })
115
+
116
+ // These RNW-only props only exist in RNW >=0.18. Catch them and map them according to platform
117
+ // so all props work on RN, RNW >=0.18 and RNW <=0.18, regardless of which they were written for:
118
+ // - On native, bundle them into objects, like `accessibilityValue: { max: 100 }`
119
+ // - On web, split them into both of:
120
+ // - The appropriate aria-* attr, like `aria-valuenow`, which will work regardless of RNW version
121
+ // - The corresponding RNW >=0.18 prop, like `accessibilityValueNow`, which in some cases does more
122
+ // than just add the aria-* (e.g. `accessibilityDisabled` adds `disabled` if element supports it,
123
+ // and future releases might add more features here).
124
+ const rwnPropMappings = {
125
+ // Former accessibilityValue props.
126
+ accessibilityValueMax: (value) =>
127
+ Platform.select({
128
+ web: { 'aria-valuemax': value, accessibilityValueMax: value },
129
+ default: { accessibilityValue: { max: value } }
130
+ }),
131
+ accessibilityValueMin: (value) =>
132
+ Platform.select({
133
+ web: { 'aria-valuemin': value, accessibilityValueMin: value },
134
+ default: { accessibilityValue: { min: value } }
135
+ }),
136
+ accessibilityValueNow: (value) =>
137
+ Platform.select({
138
+ web: { 'aria-valuenow': value, accessibilityValueNow: value },
139
+ default: { accessibilityValue: { now: value } }
140
+ }),
141
+ accessibilityValueText: (value) =>
142
+ Platform.select({
143
+ web: { 'aria-valuetext': value, accessibilityValueText: value },
144
+ default: { accessibilityValue: { text: value } }
145
+ }),
146
+
147
+ // Former accessibilityState props
148
+ accessibilityBusy: (value) =>
149
+ Platform.select({
150
+ web: { 'aria-busy': value, accessibilityBusy: value },
151
+ default: { accessibilityState: { busy: value } }
152
+ }),
153
+ accessibilityChecked: (value) =>
154
+ Platform.select({
155
+ web: { 'aria-checked': value, accessibilityChecked: value },
156
+ default: { accessibilityState: { checked: value } }
157
+ }),
158
+ accessibilityDisabled: (value) =>
159
+ Platform.select({
160
+ web: {
161
+ 'aria-disabled': value,
162
+ // RNW >= 0.18 maps `accessibilityDisabled` to `disabled` attr if element supports it
163
+ accessibilityDisabled: value,
164
+ // As of RNW 0.18.9, Pressable doesn't support `accessibilityDisabled`, only `disabled`,
165
+ // but everything else supports `accessibilityDisabled` but not `disabled`.
166
+ disabled: value
167
+ },
168
+ default: { accessibilityState: { disabled: value } }
169
+ }),
170
+ accessibilityExpanded: (value) =>
171
+ Platform.select({
172
+ web: { 'aria-expanded': value, accessibilityExpanded: value },
173
+ default: { accessibilityState: { expanded: value } }
174
+ }),
175
+ accessibilitySelected: (value) =>
176
+ Platform.select({
177
+ web: { 'aria-selected': value, accessibilitySelected: value },
178
+ default: { accessibilityState: { selected: value } }
179
+ })
180
+ }
181
+ if (Platform.OS === 'web') {
182
+ const mapIfDefined = (value, fn) => (value === undefined ? undefined : fn(value))
183
+
184
+ // On Web only, these React Native object props need manual mapping in RNW >=0.18
185
+ // which dropped support for the React Native shape of these props.
186
+ // Re-use our RNW 0.18 prop mappings to support both RNW <0.18 (aria-*) and
187
+ // new features added in >=0.18 (e.g. for accessibilityDisabled).
188
+ rwnPropMappings.accessibilityValue = ({ max, min, now, text } = {}) => ({
189
+ ...mapIfDefined(max, rwnPropMappings.accessibilityValueMax),
190
+ ...mapIfDefined(min, rwnPropMappings.accessibilityValueMin),
191
+ ...mapIfDefined(now, rwnPropMappings.accessibilityValueNow),
192
+ ...mapIfDefined(text, rwnPropMappings.accessibilityValueText)
193
+ })
194
+ rwnPropMappings.accessibilityState = ({ busy, checked, disabled, expanded, selected } = {}) => ({
195
+ ...mapIfDefined(busy, rwnPropMappings.accessibilityBusy),
196
+ ...mapIfDefined(checked, rwnPropMappings.accessibilityChecked),
197
+ ...mapIfDefined(disabled, rwnPropMappings.accessibilityDisabled),
198
+ ...mapIfDefined(expanded, rwnPropMappings.accessibilityExpanded),
199
+ ...mapIfDefined(selected, rwnPropMappings.accessibilitySelected)
200
+ })
99
201
  }
100
202
 
101
203
  export default {
@@ -110,7 +212,18 @@ export default {
110
212
  * Where components accept React Native a11y props, pass { ...rest } from its props to this,
111
213
  * then spread the returned object into the component's props (usually its outer container).
112
214
  */
113
- select: getPropSelector(a11yPropTypes, /^aria-/),
215
+ select: getPropSelector(
216
+ // Allow all React Native accessibility props
217
+ a11yPropTypes,
218
+ // Allow any `aria-*` attribute on web; ignore them on native
219
+ Platform.OS === 'web' && /^aria-/,
220
+ // For the props added and deprecated in React Native Web 0.18, convert them to
221
+ // a form that is platform-appropriate and RNW-version safe
222
+ (key, value) => {
223
+ const rnwPropMapper = rwnPropMappings[key]
224
+ return rnwPropMapper ? rnwPropMapper(value) : undefined
225
+ }
226
+ ),
114
227
  /**
115
228
  * Use this to disable focus for elements which are visually hidden but still rendered.
116
229
  */