@react-spectrum/button 3.15.0 → 3.16.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 (180) hide show
  1. package/dist/ar-AE.main.js +6 -0
  2. package/dist/ar-AE.main.js.map +1 -0
  3. package/dist/ar-AE.mjs +8 -0
  4. package/dist/ar-AE.module.js +8 -0
  5. package/dist/ar-AE.module.js.map +1 -0
  6. package/dist/bg-BG.main.js +6 -0
  7. package/dist/bg-BG.main.js.map +1 -0
  8. package/dist/bg-BG.mjs +8 -0
  9. package/dist/bg-BG.module.js +8 -0
  10. package/dist/bg-BG.module.js.map +1 -0
  11. package/dist/cs-CZ.main.js +6 -0
  12. package/dist/cs-CZ.main.js.map +1 -0
  13. package/dist/cs-CZ.mjs +8 -0
  14. package/dist/cs-CZ.module.js +8 -0
  15. package/dist/cs-CZ.module.js.map +1 -0
  16. package/dist/da-DK.main.js +6 -0
  17. package/dist/da-DK.main.js.map +1 -0
  18. package/dist/da-DK.mjs +8 -0
  19. package/dist/da-DK.module.js +8 -0
  20. package/dist/da-DK.module.js.map +1 -0
  21. package/dist/de-DE.main.js +6 -0
  22. package/dist/de-DE.main.js.map +1 -0
  23. package/dist/de-DE.mjs +8 -0
  24. package/dist/de-DE.module.js +8 -0
  25. package/dist/de-DE.module.js.map +1 -0
  26. package/dist/el-GR.main.js +6 -0
  27. package/dist/el-GR.main.js.map +1 -0
  28. package/dist/el-GR.mjs +8 -0
  29. package/dist/el-GR.module.js +8 -0
  30. package/dist/el-GR.module.js.map +1 -0
  31. package/dist/en-US.main.js +6 -0
  32. package/dist/en-US.main.js.map +1 -0
  33. package/dist/en-US.mjs +8 -0
  34. package/dist/en-US.module.js +8 -0
  35. package/dist/en-US.module.js.map +1 -0
  36. package/dist/es-ES.main.js +6 -0
  37. package/dist/es-ES.main.js.map +1 -0
  38. package/dist/es-ES.mjs +8 -0
  39. package/dist/es-ES.module.js +8 -0
  40. package/dist/es-ES.module.js.map +1 -0
  41. package/dist/et-EE.main.js +6 -0
  42. package/dist/et-EE.main.js.map +1 -0
  43. package/dist/et-EE.mjs +8 -0
  44. package/dist/et-EE.module.js +8 -0
  45. package/dist/et-EE.module.js.map +1 -0
  46. package/dist/fi-FI.main.js +6 -0
  47. package/dist/fi-FI.main.js.map +1 -0
  48. package/dist/fi-FI.mjs +8 -0
  49. package/dist/fi-FI.module.js +8 -0
  50. package/dist/fi-FI.module.js.map +1 -0
  51. package/dist/fr-FR.main.js +6 -0
  52. package/dist/fr-FR.main.js.map +1 -0
  53. package/dist/fr-FR.mjs +8 -0
  54. package/dist/fr-FR.module.js +8 -0
  55. package/dist/fr-FR.module.js.map +1 -0
  56. package/dist/he-IL.main.js +6 -0
  57. package/dist/he-IL.main.js.map +1 -0
  58. package/dist/he-IL.mjs +8 -0
  59. package/dist/he-IL.module.js +8 -0
  60. package/dist/he-IL.module.js.map +1 -0
  61. package/dist/hr-HR.main.js +6 -0
  62. package/dist/hr-HR.main.js.map +1 -0
  63. package/dist/hr-HR.mjs +8 -0
  64. package/dist/hr-HR.module.js +8 -0
  65. package/dist/hr-HR.module.js.map +1 -0
  66. package/dist/hu-HU.main.js +6 -0
  67. package/dist/hu-HU.main.js.map +1 -0
  68. package/dist/hu-HU.mjs +8 -0
  69. package/dist/hu-HU.module.js +8 -0
  70. package/dist/hu-HU.module.js.map +1 -0
  71. package/dist/import.mjs +82 -180
  72. package/dist/it-IT.main.js +6 -0
  73. package/dist/it-IT.main.js.map +1 -0
  74. package/dist/it-IT.mjs +8 -0
  75. package/dist/it-IT.module.js +8 -0
  76. package/dist/it-IT.module.js.map +1 -0
  77. package/dist/ja-JP.main.js +6 -0
  78. package/dist/ja-JP.main.js.map +1 -0
  79. package/dist/ja-JP.mjs +8 -0
  80. package/dist/ja-JP.module.js +8 -0
  81. package/dist/ja-JP.module.js.map +1 -0
  82. package/dist/ko-KR.main.js +6 -0
  83. package/dist/ko-KR.main.js.map +1 -0
  84. package/dist/ko-KR.mjs +8 -0
  85. package/dist/ko-KR.module.js +8 -0
  86. package/dist/ko-KR.module.js.map +1 -0
  87. package/dist/lt-LT.main.js +6 -0
  88. package/dist/lt-LT.main.js.map +1 -0
  89. package/dist/lt-LT.mjs +8 -0
  90. package/dist/lt-LT.module.js +8 -0
  91. package/dist/lt-LT.module.js.map +1 -0
  92. package/dist/lv-LV.main.js +6 -0
  93. package/dist/lv-LV.main.js.map +1 -0
  94. package/dist/lv-LV.mjs +8 -0
  95. package/dist/lv-LV.module.js +8 -0
  96. package/dist/lv-LV.module.js.map +1 -0
  97. package/dist/main.css +1771 -1
  98. package/dist/main.css.map +1 -0
  99. package/dist/main.js +80 -178
  100. package/dist/main.js.map +1 -1
  101. package/dist/module.js +82 -180
  102. package/dist/module.js.map +1 -1
  103. package/dist/nb-NO.main.js +6 -0
  104. package/dist/nb-NO.main.js.map +1 -0
  105. package/dist/nb-NO.mjs +8 -0
  106. package/dist/nb-NO.module.js +8 -0
  107. package/dist/nb-NO.module.js.map +1 -0
  108. package/dist/nl-NL.main.js +6 -0
  109. package/dist/nl-NL.main.js.map +1 -0
  110. package/dist/nl-NL.mjs +8 -0
  111. package/dist/nl-NL.module.js +8 -0
  112. package/dist/nl-NL.module.js.map +1 -0
  113. package/dist/pl-PL.main.js +6 -0
  114. package/dist/pl-PL.main.js.map +1 -0
  115. package/dist/pl-PL.mjs +8 -0
  116. package/dist/pl-PL.module.js +8 -0
  117. package/dist/pl-PL.module.js.map +1 -0
  118. package/dist/pt-BR.main.js +6 -0
  119. package/dist/pt-BR.main.js.map +1 -0
  120. package/dist/pt-BR.mjs +8 -0
  121. package/dist/pt-BR.module.js +8 -0
  122. package/dist/pt-BR.module.js.map +1 -0
  123. package/dist/pt-PT.main.js +6 -0
  124. package/dist/pt-PT.main.js.map +1 -0
  125. package/dist/pt-PT.mjs +8 -0
  126. package/dist/pt-PT.module.js +8 -0
  127. package/dist/pt-PT.module.js.map +1 -0
  128. package/dist/ro-RO.main.js +6 -0
  129. package/dist/ro-RO.main.js.map +1 -0
  130. package/dist/ro-RO.mjs +8 -0
  131. package/dist/ro-RO.module.js +8 -0
  132. package/dist/ro-RO.module.js.map +1 -0
  133. package/dist/ru-RU.main.js +6 -0
  134. package/dist/ru-RU.main.js.map +1 -0
  135. package/dist/ru-RU.mjs +8 -0
  136. package/dist/ru-RU.module.js +8 -0
  137. package/dist/ru-RU.module.js.map +1 -0
  138. package/dist/sk-SK.main.js +6 -0
  139. package/dist/sk-SK.main.js.map +1 -0
  140. package/dist/sk-SK.mjs +8 -0
  141. package/dist/sk-SK.module.js +8 -0
  142. package/dist/sk-SK.module.js.map +1 -0
  143. package/dist/sl-SI.main.js +6 -0
  144. package/dist/sl-SI.main.js.map +1 -0
  145. package/dist/sl-SI.mjs +8 -0
  146. package/dist/sl-SI.module.js +8 -0
  147. package/dist/sl-SI.module.js.map +1 -0
  148. package/dist/sr-SP.main.js +6 -0
  149. package/dist/sr-SP.main.js.map +1 -0
  150. package/dist/sr-SP.mjs +8 -0
  151. package/dist/sr-SP.module.js +8 -0
  152. package/dist/sr-SP.module.js.map +1 -0
  153. package/dist/sv-SE.main.js +6 -0
  154. package/dist/sv-SE.main.js.map +1 -0
  155. package/dist/sv-SE.mjs +8 -0
  156. package/dist/sv-SE.module.js +8 -0
  157. package/dist/sv-SE.module.js.map +1 -0
  158. package/dist/tr-TR.main.js +6 -0
  159. package/dist/tr-TR.main.js.map +1 -0
  160. package/dist/tr-TR.mjs +8 -0
  161. package/dist/tr-TR.module.js +8 -0
  162. package/dist/tr-TR.module.js.map +1 -0
  163. package/dist/types.d.ts.map +1 -1
  164. package/dist/uk-UA.main.js +6 -0
  165. package/dist/uk-UA.main.js.map +1 -0
  166. package/dist/uk-UA.mjs +8 -0
  167. package/dist/uk-UA.module.js +8 -0
  168. package/dist/uk-UA.module.js.map +1 -0
  169. package/dist/zh-CN.main.js +6 -0
  170. package/dist/zh-CN.main.js.map +1 -0
  171. package/dist/zh-CN.mjs +8 -0
  172. package/dist/zh-CN.module.js +8 -0
  173. package/dist/zh-CN.module.js.map +1 -0
  174. package/dist/zh-TW.main.js +6 -0
  175. package/dist/zh-TW.main.js.map +1 -0
  176. package/dist/zh-TW.mjs +8 -0
  177. package/dist/zh-TW.module.js +8 -0
  178. package/dist/zh-TW.module.js.map +1 -0
  179. package/package.json +11 -11
  180. package/src/Button.tsx +52 -12
package/src/Button.tsx CHANGED
@@ -22,14 +22,14 @@ import {FocusableRef} from '@react-types/shared';
22
22
  import {FocusRing} from '@react-aria/focus';
23
23
  // @ts-ignore
24
24
  import intlMessages from '../intl/*.json';
25
- import {mergeProps} from '@react-aria/utils';
25
+ import {isAppleDevice, isFirefox, mergeProps, useId} from '@react-aria/utils';
26
26
  import {ProgressCircle} from '@react-spectrum/progress';
27
27
  import React, {ElementType, ReactElement, useEffect, useState} from 'react';
28
28
  import {SpectrumButtonProps} from '@react-types/button';
29
29
  import styles from '@adobe/spectrum-css-temp/components/button/vars.css';
30
30
  import {Text} from '@react-spectrum/text';
31
31
  import {useButton} from '@react-aria/button';
32
- import {useHover} from '@react-aria/interactions';
32
+ import {useFocus, useHover} from '@react-aria/interactions';
33
33
  import {useLocalizedStringFormatter} from '@react-aria/i18n';
34
34
  import {useProviderProps} from '@react-spectrum/provider';
35
35
 
@@ -67,11 +67,20 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
67
67
  let domRef = useFocusableRef(ref);
68
68
  let {buttonProps, isPressed} = useButton(props, domRef);
69
69
  let {hoverProps, isHovered} = useHover({isDisabled});
70
- let stringFormatter = useLocalizedStringFormatter(intlMessages);
70
+ let [isFocused, onFocusChange] = useState(false);
71
+ let {focusProps} = useFocus({onFocusChange, isDisabled});
72
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/button');
71
73
  let {styleProps} = useStyleProps(otherProps);
72
74
  let hasLabel = useHasChild(`.${styles['spectrum-Button-label']}`, domRef);
73
75
  let hasIcon = useHasChild(`.${styles['spectrum-Icon']}`, domRef);
76
+ // an aria label will block children and their labels from being read, this is undesirable for pending state
77
+ let hasAriaLabel = !!buttonProps['aria-label'] || !!buttonProps['aria-labelledby'];
74
78
  let [isProgressVisible, setIsProgressVisible] = useState(false);
79
+ let backupButtonId = useId();
80
+ let buttonId = buttonProps.id || backupButtonId;
81
+ let iconId = useId();
82
+ let textId = useId();
83
+ let spinnerId = useId();
75
84
 
76
85
  useEffect(() => {
77
86
  let timeout: ReturnType<typeof setTimeout>;
@@ -98,17 +107,26 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
98
107
  staticColor = 'white';
99
108
  }
100
109
 
110
+ const isPendingAriaLiveLabel = `${hasAriaLabel ? buttonProps['aria-label'] : ''} ${stringFormatter.format('pending')}`.trim();
111
+ const isPendingAriaLiveLabelledby = hasAriaLabel ? (buttonProps['aria-labelledby']?.replace(buttonId, spinnerId) ?? spinnerId) : `${hasIcon ? iconId : ''} ${hasLabel ? textId : ''} ${spinnerId}`.trim();
112
+
113
+ let ariaLive: 'off' | 'polite' | 'assertive' = 'polite';
114
+ if (isAppleDevice() && (!hasAriaLabel || isFirefox())) {
115
+ ariaLive = 'off';
116
+ }
101
117
  return (
102
118
  <FocusRing focusRingClass={classNames(styles, 'focus-ring')} autoFocus={autoFocus}>
103
119
  <Element
104
120
  {...styleProps}
105
- {...mergeProps(buttonProps, hoverProps)}
121
+ {...mergeProps(buttonProps, hoverProps, focusProps)}
122
+ id={buttonId}
106
123
  ref={domRef}
107
124
  data-variant={variant}
108
125
  data-style={style}
109
126
  data-static-color={staticColor || undefined}
110
- aria-disabled={isPending || undefined}
111
- aria-live={isPending ? 'polite' : undefined}
127
+ aria-disabled={isPending ? 'true' : undefined}
128
+ aria-label={isPending ? isPendingAriaLiveLabel : buttonProps['aria-label']}
129
+ aria-labelledby={isPending ? isPendingAriaLiveLabelledby : buttonProps['aria-labelledby']}
112
130
  className={
113
131
  classNames(
114
132
  styles,
@@ -126,22 +144,44 @@ function Button<T extends ElementType = 'button'>(props: SpectrumButtonProps<T>,
126
144
  <SlotProvider
127
145
  slots={{
128
146
  icon: {
147
+ id: iconId,
129
148
  size: 'S',
130
149
  UNSAFE_className: classNames(styles, 'spectrum-Icon')
131
150
  },
132
151
  text: {
152
+ id: textId,
133
153
  UNSAFE_className: classNames(styles, 'spectrum-Button-label')
134
154
  }
135
155
  }}>
136
- {isProgressVisible && <ProgressCircle
137
- aria-label={stringFormatter.format('loading')}
138
- isIndeterminate
139
- size="S"
140
- UNSAFE_className={classNames(styles, 'spectrum-Button-circleLoader')}
141
- staticColor={staticColor} />}
142
156
  {typeof children === 'string'
143
157
  ? <Text>{children}</Text>
144
158
  : children}
159
+ {isPending && (
160
+ <div
161
+ aria-hidden="true"
162
+ style={{visibility: isProgressVisible ? 'visible' : 'hidden'}}
163
+ className={classNames(styles, 'spectrum-Button-circleLoader')}>
164
+ <ProgressCircle
165
+ aria-label={isPendingAriaLiveLabel}
166
+ isIndeterminate
167
+ size="S"
168
+ staticColor={staticColor} />
169
+ </div>
170
+ )}
171
+ {isPending &&
172
+ <>
173
+ <div aria-live={isFocused ? ariaLive : 'off'}>
174
+ {isProgressVisible &&
175
+ <div role="img" aria-labelledby={isPendingAriaLiveLabelledby} />
176
+ }
177
+ </div>
178
+ {/* Adding the element here with the same labels as the button itself causes aria-live to pick up the change in Safari.
179
+ Safari with VO unfortunately doesn't announce changes to *all* of its labels specifically for button
180
+ https://a11ysupport.io/tests/tech__html__button-name-change#assertion-aria-aria-label_attribute-convey_name_change-html-button_element-vo_macos-safari
181
+ The aria-live may cause extra announcements in other browsers. */}
182
+ <div id={spinnerId} role="img" aria-label={isPendingAriaLiveLabel} />
183
+ </>
184
+ }
145
185
  </SlotProvider>
146
186
  </Element>
147
187
  </FocusRing>