@oxy.so/bloom 3.0.1 → 3.1.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 (269) hide show
  1. package/docs/adoption-matrix.mdx +266 -0
  2. package/docs/checkbox.mdx +16 -0
  3. package/docs/composition.mdx +88 -16
  4. package/docs/date-picker.mdx +11 -0
  5. package/docs/field.mdx +35 -4
  6. package/docs/file-upload.mdx +13 -0
  7. package/docs/input-group.mdx +22 -0
  8. package/docs/input-otp.mdx +14 -0
  9. package/docs/layout.mdx +34 -0
  10. package/docs/migrating-to-3.0.mdx +43 -4
  11. package/docs/phone-input.mdx +17 -0
  12. package/docs/radio.mdx +16 -0
  13. package/docs/rating.mdx +14 -0
  14. package/docs/segmented-control.mdx +16 -4
  15. package/docs/select.mdx +22 -0
  16. package/docs/slider.mdx +14 -0
  17. package/docs/stepper.mdx +3 -1
  18. package/docs/switch.mdx +14 -0
  19. package/docs/text-field.mdx +14 -0
  20. package/docs/textarea.mdx +20 -0
  21. package/lib/commonjs/bottom-sheet/BottomSheetBase.js +11 -4
  22. package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
  23. package/lib/commonjs/checkbox/Checkbox.js +24 -8
  24. package/lib/commonjs/checkbox/Checkbox.js.map +1 -1
  25. package/lib/commonjs/date-picker/TimeField.js +18 -6
  26. package/lib/commonjs/date-picker/TimeField.js.map +1 -1
  27. package/lib/commonjs/dialog/DialogContent.js +36 -29
  28. package/lib/commonjs/dialog/DialogContent.js.map +1 -1
  29. package/lib/commonjs/field/index.js +7 -0
  30. package/lib/commonjs/field/index.js.map +1 -1
  31. package/lib/commonjs/field/membership.js +105 -0
  32. package/lib/commonjs/field/membership.js.map +1 -0
  33. package/lib/commonjs/file-upload/FileUpload.js +17 -3
  34. package/lib/commonjs/file-upload/FileUpload.js.map +1 -1
  35. package/lib/commonjs/index.js +21 -0
  36. package/lib/commonjs/index.js.map +1 -1
  37. package/lib/commonjs/index.web.js +21 -0
  38. package/lib/commonjs/index.web.js.map +1 -1
  39. package/lib/commonjs/input-group/InputGroup.js +18 -5
  40. package/lib/commonjs/input-group/InputGroup.js.map +1 -1
  41. package/lib/commonjs/input-otp/InputOtp.js +20 -8
  42. package/lib/commonjs/input-otp/InputOtp.js.map +1 -1
  43. package/lib/commonjs/layout/index.js +7 -0
  44. package/lib/commonjs/layout/index.js.map +1 -1
  45. package/lib/commonjs/layout/screen-scope.js +61 -0
  46. package/lib/commonjs/layout/screen-scope.js.map +1 -0
  47. package/lib/commonjs/phone-input/PhoneInput.js +20 -8
  48. package/lib/commonjs/phone-input/PhoneInput.js.map +1 -1
  49. package/lib/commonjs/radio/Radio.js +35 -9
  50. package/lib/commonjs/radio/Radio.js.map +1 -1
  51. package/lib/commonjs/rating/RatingInput.js +14 -6
  52. package/lib/commonjs/rating/RatingInput.js.map +1 -1
  53. package/lib/commonjs/segmented-control/SegmentedControl.js +38 -8
  54. package/lib/commonjs/segmented-control/SegmentedControl.js.map +1 -1
  55. package/lib/commonjs/select/Select.js +15 -3
  56. package/lib/commonjs/select/Select.js.map +1 -1
  57. package/lib/commonjs/select/Select.web.js +13 -5
  58. package/lib/commonjs/select/Select.web.js.map +1 -1
  59. package/lib/commonjs/slider/Slider.js +17 -3
  60. package/lib/commonjs/slider/Slider.js.map +1 -1
  61. package/lib/commonjs/stepper/Stepper.js +16 -7
  62. package/lib/commonjs/stepper/Stepper.js.map +1 -1
  63. package/lib/commonjs/styles/styled-primitives.js.map +1 -1
  64. package/lib/commonjs/switch/Switch.js +23 -8
  65. package/lib/commonjs/switch/Switch.js.map +1 -1
  66. package/lib/commonjs/text-field/TextField.js +19 -4
  67. package/lib/commonjs/text-field/TextField.js.map +1 -1
  68. package/lib/commonjs/textarea/Textarea.js +39 -15
  69. package/lib/commonjs/textarea/Textarea.js.map +1 -1
  70. package/lib/module/bottom-sheet/BottomSheetBase.js +8 -1
  71. package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
  72. package/lib/module/checkbox/Checkbox.js +24 -8
  73. package/lib/module/checkbox/Checkbox.js.map +1 -1
  74. package/lib/module/date-picker/TimeField.js +19 -5
  75. package/lib/module/date-picker/TimeField.js.map +1 -1
  76. package/lib/module/dialog/DialogContent.js +37 -30
  77. package/lib/module/dialog/DialogContent.js.map +1 -1
  78. package/lib/module/field/index.js +1 -0
  79. package/lib/module/field/index.js.map +1 -1
  80. package/lib/module/field/membership.js +101 -0
  81. package/lib/module/field/membership.js.map +1 -0
  82. package/lib/module/file-upload/FileUpload.js +17 -3
  83. package/lib/module/file-upload/FileUpload.js.map +1 -1
  84. package/lib/module/index.js +2 -2
  85. package/lib/module/index.js.map +1 -1
  86. package/lib/module/index.web.js +2 -2
  87. package/lib/module/index.web.js.map +1 -1
  88. package/lib/module/input-group/InputGroup.js +19 -4
  89. package/lib/module/input-group/InputGroup.js.map +1 -1
  90. package/lib/module/input-otp/InputOtp.js +21 -7
  91. package/lib/module/input-otp/InputOtp.js.map +1 -1
  92. package/lib/module/layout/index.js +4 -1
  93. package/lib/module/layout/index.js.map +1 -1
  94. package/lib/module/layout/screen-scope.js +57 -0
  95. package/lib/module/layout/screen-scope.js.map +1 -0
  96. package/lib/module/phone-input/PhoneInput.js +22 -9
  97. package/lib/module/phone-input/PhoneInput.js.map +1 -1
  98. package/lib/module/radio/Radio.js +35 -9
  99. package/lib/module/radio/Radio.js.map +1 -1
  100. package/lib/module/rating/RatingInput.js +15 -5
  101. package/lib/module/rating/RatingInput.js.map +1 -1
  102. package/lib/module/segmented-control/SegmentedControl.js +38 -8
  103. package/lib/module/segmented-control/SegmentedControl.js.map +1 -1
  104. package/lib/module/select/Select.js +15 -3
  105. package/lib/module/select/Select.js.map +1 -1
  106. package/lib/module/select/Select.web.js +14 -4
  107. package/lib/module/select/Select.web.js.map +1 -1
  108. package/lib/module/slider/Slider.js +17 -3
  109. package/lib/module/slider/Slider.js.map +1 -1
  110. package/lib/module/stepper/Stepper.js +17 -6
  111. package/lib/module/stepper/Stepper.js.map +1 -1
  112. package/lib/module/styles/styled-primitives.js.map +1 -1
  113. package/lib/module/switch/Switch.js +23 -8
  114. package/lib/module/switch/Switch.js.map +1 -1
  115. package/lib/module/text-field/TextField.js +19 -4
  116. package/lib/module/text-field/TextField.js.map +1 -1
  117. package/lib/module/textarea/Textarea.js +39 -15
  118. package/lib/module/textarea/Textarea.js.map +1 -1
  119. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  120. package/lib/typescript/commonjs/checkbox/Checkbox.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/checkbox/types.d.ts +5 -0
  122. package/lib/typescript/commonjs/checkbox/types.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/date-picker/TimeField.d.ts +1 -1
  124. package/lib/typescript/commonjs/date-picker/TimeField.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/date-picker/types.d.ts +6 -3
  126. package/lib/typescript/commonjs/date-picker/types.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/dialog/DialogContent.d.ts.map +1 -1
  128. package/lib/typescript/commonjs/field/index.d.ts +2 -0
  129. package/lib/typescript/commonjs/field/index.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/field/membership.d.ts +60 -0
  131. package/lib/typescript/commonjs/field/membership.d.ts.map +1 -0
  132. package/lib/typescript/commonjs/file-upload/FileUpload.d.ts +1 -1
  133. package/lib/typescript/commonjs/file-upload/FileUpload.d.ts.map +1 -1
  134. package/lib/typescript/commonjs/floating/types.d.ts +13 -0
  135. package/lib/typescript/commonjs/floating/types.d.ts.map +1 -1
  136. package/lib/typescript/commonjs/index.d.ts +3 -3
  137. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/index.web.d.ts +3 -3
  139. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/input-group/InputGroup.d.ts +1 -1
  141. package/lib/typescript/commonjs/input-group/InputGroup.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/input-otp/InputOtp.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/input-otp/types.d.ts +7 -1
  144. package/lib/typescript/commonjs/input-otp/types.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/layout/index.d.ts +4 -1
  146. package/lib/typescript/commonjs/layout/index.d.ts.map +1 -1
  147. package/lib/typescript/commonjs/layout/screen-scope.d.ts +40 -0
  148. package/lib/typescript/commonjs/layout/screen-scope.d.ts.map +1 -0
  149. package/lib/typescript/commonjs/phone-input/PhoneInput.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/radio/Radio.d.ts +1 -1
  151. package/lib/typescript/commonjs/radio/Radio.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/radio/types.d.ts +11 -3
  153. package/lib/typescript/commonjs/radio/types.d.ts.map +1 -1
  154. package/lib/typescript/commonjs/rating/RatingInput.d.ts +1 -1
  155. package/lib/typescript/commonjs/rating/RatingInput.d.ts.map +1 -1
  156. package/lib/typescript/commonjs/rating/types.d.ts +6 -3
  157. package/lib/typescript/commonjs/rating/types.d.ts.map +1 -1
  158. package/lib/typescript/commonjs/segmented-control/SegmentedControl.d.ts +18 -2
  159. package/lib/typescript/commonjs/segmented-control/SegmentedControl.d.ts.map +1 -1
  160. package/lib/typescript/commonjs/select/Select.d.ts.map +1 -1
  161. package/lib/typescript/commonjs/select/Select.web.d.ts.map +1 -1
  162. package/lib/typescript/commonjs/select/types.d.ts +8 -1
  163. package/lib/typescript/commonjs/select/types.d.ts.map +1 -1
  164. package/lib/typescript/commonjs/slider/Slider.d.ts.map +1 -1
  165. package/lib/typescript/commonjs/stepper/Stepper.d.ts +1 -1
  166. package/lib/typescript/commonjs/stepper/Stepper.d.ts.map +1 -1
  167. package/lib/typescript/commonjs/stepper/types.d.ts +8 -1
  168. package/lib/typescript/commonjs/stepper/types.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/styles/styled-primitives.d.ts +16 -0
  170. package/lib/typescript/commonjs/styles/styled-primitives.d.ts.map +1 -1
  171. package/lib/typescript/commonjs/switch/Switch.d.ts.map +1 -1
  172. package/lib/typescript/commonjs/switch/types.d.ts +7 -0
  173. package/lib/typescript/commonjs/switch/types.d.ts.map +1 -1
  174. package/lib/typescript/commonjs/text-field/TextField.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/textarea/Textarea.d.ts +1 -1
  176. package/lib/typescript/commonjs/textarea/Textarea.d.ts.map +1 -1
  177. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  178. package/lib/typescript/module/checkbox/Checkbox.d.ts.map +1 -1
  179. package/lib/typescript/module/checkbox/types.d.ts +5 -0
  180. package/lib/typescript/module/checkbox/types.d.ts.map +1 -1
  181. package/lib/typescript/module/date-picker/TimeField.d.ts +1 -1
  182. package/lib/typescript/module/date-picker/TimeField.d.ts.map +1 -1
  183. package/lib/typescript/module/date-picker/types.d.ts +6 -3
  184. package/lib/typescript/module/date-picker/types.d.ts.map +1 -1
  185. package/lib/typescript/module/dialog/DialogContent.d.ts.map +1 -1
  186. package/lib/typescript/module/field/index.d.ts +2 -0
  187. package/lib/typescript/module/field/index.d.ts.map +1 -1
  188. package/lib/typescript/module/field/membership.d.ts +60 -0
  189. package/lib/typescript/module/field/membership.d.ts.map +1 -0
  190. package/lib/typescript/module/file-upload/FileUpload.d.ts +1 -1
  191. package/lib/typescript/module/file-upload/FileUpload.d.ts.map +1 -1
  192. package/lib/typescript/module/floating/types.d.ts +13 -0
  193. package/lib/typescript/module/floating/types.d.ts.map +1 -1
  194. package/lib/typescript/module/index.d.ts +3 -3
  195. package/lib/typescript/module/index.d.ts.map +1 -1
  196. package/lib/typescript/module/index.web.d.ts +3 -3
  197. package/lib/typescript/module/index.web.d.ts.map +1 -1
  198. package/lib/typescript/module/input-group/InputGroup.d.ts +1 -1
  199. package/lib/typescript/module/input-group/InputGroup.d.ts.map +1 -1
  200. package/lib/typescript/module/input-otp/InputOtp.d.ts.map +1 -1
  201. package/lib/typescript/module/input-otp/types.d.ts +7 -1
  202. package/lib/typescript/module/input-otp/types.d.ts.map +1 -1
  203. package/lib/typescript/module/layout/index.d.ts +4 -1
  204. package/lib/typescript/module/layout/index.d.ts.map +1 -1
  205. package/lib/typescript/module/layout/screen-scope.d.ts +40 -0
  206. package/lib/typescript/module/layout/screen-scope.d.ts.map +1 -0
  207. package/lib/typescript/module/phone-input/PhoneInput.d.ts.map +1 -1
  208. package/lib/typescript/module/radio/Radio.d.ts +1 -1
  209. package/lib/typescript/module/radio/Radio.d.ts.map +1 -1
  210. package/lib/typescript/module/radio/types.d.ts +11 -3
  211. package/lib/typescript/module/radio/types.d.ts.map +1 -1
  212. package/lib/typescript/module/rating/RatingInput.d.ts +1 -1
  213. package/lib/typescript/module/rating/RatingInput.d.ts.map +1 -1
  214. package/lib/typescript/module/rating/types.d.ts +6 -3
  215. package/lib/typescript/module/rating/types.d.ts.map +1 -1
  216. package/lib/typescript/module/segmented-control/SegmentedControl.d.ts +18 -2
  217. package/lib/typescript/module/segmented-control/SegmentedControl.d.ts.map +1 -1
  218. package/lib/typescript/module/select/Select.d.ts.map +1 -1
  219. package/lib/typescript/module/select/Select.web.d.ts.map +1 -1
  220. package/lib/typescript/module/select/types.d.ts +8 -1
  221. package/lib/typescript/module/select/types.d.ts.map +1 -1
  222. package/lib/typescript/module/slider/Slider.d.ts.map +1 -1
  223. package/lib/typescript/module/stepper/Stepper.d.ts +1 -1
  224. package/lib/typescript/module/stepper/Stepper.d.ts.map +1 -1
  225. package/lib/typescript/module/stepper/types.d.ts +8 -1
  226. package/lib/typescript/module/stepper/types.d.ts.map +1 -1
  227. package/lib/typescript/module/styles/styled-primitives.d.ts +16 -0
  228. package/lib/typescript/module/styles/styled-primitives.d.ts.map +1 -1
  229. package/lib/typescript/module/switch/Switch.d.ts.map +1 -1
  230. package/lib/typescript/module/switch/types.d.ts +7 -0
  231. package/lib/typescript/module/switch/types.d.ts.map +1 -1
  232. package/lib/typescript/module/text-field/TextField.d.ts.map +1 -1
  233. package/lib/typescript/module/textarea/Textarea.d.ts +1 -1
  234. package/lib/typescript/module/textarea/Textarea.d.ts.map +1 -1
  235. package/package.json +2 -1
  236. package/src/bottom-sheet/BottomSheetBase.tsx +6 -1
  237. package/src/checkbox/Checkbox.tsx +24 -8
  238. package/src/checkbox/types.ts +5 -0
  239. package/src/date-picker/TimeField.tsx +14 -3
  240. package/src/date-picker/types.ts +6 -3
  241. package/src/dialog/DialogContent.tsx +6 -2
  242. package/src/field/index.ts +2 -0
  243. package/src/field/membership.ts +164 -0
  244. package/src/file-upload/FileUpload.tsx +14 -3
  245. package/src/floating/types.ts +13 -0
  246. package/src/index.ts +9 -2
  247. package/src/index.web.ts +9 -2
  248. package/src/input-group/InputGroup.tsx +13 -2
  249. package/src/input-otp/InputOtp.tsx +19 -5
  250. package/src/input-otp/types.ts +7 -1
  251. package/src/layout/index.ts +4 -1
  252. package/src/layout/screen-scope.tsx +54 -0
  253. package/src/phone-input/PhoneInput.tsx +20 -8
  254. package/src/radio/Radio.tsx +32 -9
  255. package/src/radio/types.ts +11 -3
  256. package/src/rating/RatingInput.tsx +10 -3
  257. package/src/rating/types.ts +6 -3
  258. package/src/segmented-control/SegmentedControl.tsx +51 -8
  259. package/src/select/Select.tsx +14 -2
  260. package/src/select/Select.web.tsx +11 -2
  261. package/src/select/types.ts +8 -1
  262. package/src/slider/Slider.tsx +13 -3
  263. package/src/stepper/Stepper.tsx +12 -4
  264. package/src/stepper/types.ts +8 -1
  265. package/src/styles/styled-primitives.ts +16 -0
  266. package/src/switch/Switch.tsx +22 -9
  267. package/src/switch/types.ts +7 -0
  268. package/src/text-field/TextField.tsx +16 -4
  269. package/src/textarea/Textarea.tsx +38 -15
@@ -59,6 +59,22 @@ export interface WebAriaProps {
59
59
  * currently needs; the union is the ARIA one, narrowed to what is spellable.
60
60
  */
61
61
  'aria-haspopup'?: 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';
62
+ /**
63
+ * The id list of the elements that DESCRIBE this control — a field's hint or
64
+ * its error message (`field/context.ts`).
65
+ *
66
+ * Here for the same reason as `aria-haspopup`: React Native types no such prop
67
+ * (there is no `accessibilityState` entry for it to fold into) while
68
+ * react-native-web forwards it, so web gets the association and native is
69
+ * unaffected. A control that needs the description READ on native carries it
70
+ * in its accessible name instead.
71
+ */
72
+ 'aria-describedby'?: string;
73
+ /**
74
+ * Whether the control's current value is invalid. Same platform split as
75
+ * `aria-describedby`.
76
+ */
77
+ 'aria-invalid'?: boolean;
62
78
  }
63
79
  /**
64
80
  * `ref` is on the PUBLISHED type and deliberately not on the one handed to
@@ -1 +1 @@
1
- {"version":3,"file":"styled-primitives.d.ts","sourceRoot":"","sources":["../../../../src/styles/styled-primitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EACL,KAAK,EAEL,IAAI,EACJ,IAAI,EACJ,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAGtB;;;;;;GAMG;AACH,KAAK,mBAAmB,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAAG;IACzD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;CACnE;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GACpD,YAAY,GAAG;IACb,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;CACjB,CAAC;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,SAAS,GAAG;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;CAAE,CAEpE,CAAC;AAEH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,SAAS,GAAG;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;CAAE,CAEpE,CAAC;AAEH,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,UAAU,GAAG;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAA;CAAE,CAEvE,CAAC;AAEH,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,oBAAoB,CAG/D,CAAC"}
1
+ {"version":3,"file":"styled-primitives.d.ts","sourceRoot":"","sources":["../../../../src/styles/styled-primitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EACL,KAAK,EAEL,IAAI,EACJ,IAAI,EACJ,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAGtB;;;;;;GAMG;AACH,KAAK,mBAAmB,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAAG;IACzD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClE;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GACpD,YAAY,GAAG;IACb,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;CACjB,CAAC;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,SAAS,GAAG;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;CAAE,CAEpE,CAAC;AAEH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,SAAS,GAAG;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;CAAE,CAEpE,CAAC;AAEH,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,UAAU,GAAG;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAA;CAAE,CAEvE,CAAC;AAEH,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,oBAAoB,CAG/D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../../../src/switch/Switch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAOzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAkI3C,eAAO,MAAM,MAAM,4FAAwB,CAAC"}
1
+ {"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../../../src/switch/Switch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAQzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AA8I3C,eAAO,MAAM,MAAM,4FAAwB,CAAC"}
@@ -25,8 +25,15 @@ export interface SwitchProps {
25
25
  * are not the same rule.)
26
26
  *
27
27
  * Omitting it warns once in development; see `use-accessible-name-warning`.
28
+ * Inside a `Field` the field's label supplies the name, so a switch in a
29
+ * labelled field needs nothing here and does not warn.
28
30
  */
29
31
  accessibilityLabel?: string;
32
+ /**
33
+ * The switch's id. Inside a `Field` the field supplies one, so the label's
34
+ * `htmlFor` points at the control on web.
35
+ */
36
+ nativeID?: string;
30
37
  testID?: string;
31
38
  }
32
39
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/switch/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,8CAA8C;IAC9C,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,mBAAmB;IACnB,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/switch/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,8CAA8C;IAC9C,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,mBAAmB;IACnB,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TextField.d.ts","sourceRoot":"","sources":["../../../../src/text-field/TextField.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,kBAAkB,EAQxB,MAAM,cAAc,CAAC;AAOtB,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAmBL,KAAK,aAAa,EACnB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACf,MAAM,SAAS,CAAC;AAkCjB;;;;;;;;GAQG;AACH,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AACD,eAAO,MAAM,qBAAqB,4DAAyD,CAAC;AAiD5F,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAS,EAAE,aAAqB,EAChC,QAAQ,EAAE,YAAoB,EAC9B,IAAI,EAAE,QAAmB,EACzB,MAA0B,EAC1B,YAAY,EACZ,KAAK,GACN,EAAE,cAAc,2CA6EhB;AAsFD,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,KAAK,EACL,YAAY,EACZ,OAAO,EACP,MAAM,EACN,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,aAAqB,EACrB,GAAG,IAAI,EACR,EAAE,mBAAmB,2CA8LrB;AAmKD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,QAAQ,EACR,QAAgB,EAChB,OAAe,EACf,KAAK,GACN,EAAE,mBAAmB,2CA6BrB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,SAAiB,EACjB,QAAQ,EACR,KAAK,GACN,EAAE,kBAAkB,2CAkBpB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAoB,EAAE,EAAE,kBAAkB,2CA4BrF;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,KAAK,EACL,iBAAiB,EACjB,KAAK,GACN,EAAE,KAAK,CAAC,iBAAiB,CACxB,aAAa,GAAG;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;CAC7D,CACF,2CAyBA;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,2CAqCA"}
1
+ {"version":3,"file":"TextField.d.ts","sourceRoot":"","sources":["../../../../src/text-field/TextField.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,kBAAkB,EAQxB,MAAM,cAAc,CAAC;AAOtB,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAmBL,KAAK,aAAa,EACnB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACf,MAAM,SAAS,CAAC;AAoCjB;;;;;;;;GAQG;AACH,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AACD,eAAO,MAAM,qBAAqB,4DAAyD,CAAC;AAiD5F,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAS,EAAE,aAAqB,EAChC,QAAQ,EAAE,YAAoB,EAC9B,IAAI,EAAE,QAAQ,EACd,MAA0B,EAC1B,YAAY,EACZ,KAAK,GACN,EAAE,cAAc,2CAuFhB;AAsFD,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,KAAK,EACL,YAAY,EACZ,OAAO,EACP,MAAM,EACN,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,aAAqB,EACrB,GAAG,IAAI,EACR,EAAE,mBAAmB,2CA8LrB;AAmKD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,QAAQ,EACR,QAAgB,EAChB,OAAe,EACf,KAAK,GACN,EAAE,mBAAmB,2CA6BrB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,SAAiB,EACjB,QAAQ,EACR,KAAK,GACN,EAAE,kBAAkB,2CAkBpB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAoB,EAAE,EAAE,kBAAkB,2CA4BrF;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,KAAK,EACL,iBAAiB,EACjB,KAAK,GACN,EAAE,KAAK,CAAC,iBAAiB,CACxB,aAAa,GAAG;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;CAC7D,CACF,2CAyBA;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,2CAqCA"}
@@ -16,5 +16,5 @@ import type { TextareaProps } from './types';
16
16
  * `maxHeight`; on web the same is `field-sizing: content`, which shrinks back
17
17
  * as well as growing (browsers without it keep the `rows` height and scroll).
18
18
  */
19
- export declare function Textarea({ label, hint, placeholder, value, defaultValue, onChangeText, onFocus, onBlur, size, rows, autoResize, maxRows, resize, maxLength, showCount, isInvalid, disabled, required, tooltip, style, inputStyle, inputRef, accessibilityLabel, testID, ...rest }: TextareaProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function Textarea({ label, hint, placeholder, value, defaultValue, onChangeText, onFocus, onBlur, size: sizeProp, rows, autoResize, maxRows, resize, maxLength, showCount, isInvalid, disabled, required, tooltip, style, inputStyle, inputRef, accessibilityLabel, nativeID, testID, ...rest }: TextareaProps): import("react/jsx-runtime").JSX.Element;
20
20
  //# sourceMappingURL=Textarea.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Textarea.d.ts","sourceRoot":"","sources":["../../../../src/textarea/Textarea.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAO7C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,WAAW,EACX,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,MAAM,EACN,IAAe,EACf,IAAQ,EACR,UAAkB,EAClB,OAAO,EACP,MAAmB,EACnB,SAAS,EACT,SAAiB,EACjB,SAAiB,EACjB,QAAgB,EAChB,QAAgB,EAChB,OAAe,EACf,KAAK,EACL,UAAU,EACV,QAAQ,EACR,kBAAkB,EAClB,MAAM,EACN,GAAG,IAAI,EACR,EAAE,aAAa,2CA8If"}
1
+ {"version":3,"file":"Textarea.d.ts","sourceRoot":"","sources":["../../../../src/textarea/Textarea.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAO7C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,WAAW,EACX,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,MAAM,EACN,IAAI,EAAE,QAAQ,EACd,IAAQ,EACR,UAAkB,EAClB,OAAO,EACP,MAAmB,EACnB,SAAS,EACT,SAAiB,EACjB,SAAiB,EACjB,QAAgB,EAChB,QAAgB,EAChB,OAAe,EACf,KAAK,EACL,UAAU,EACV,QAAQ,EACR,kBAAkB,EAClB,QAAQ,EACR,MAAM,EACN,GAAG,IAAI,EACR,EAAE,aAAa,2CAkKf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/bloom",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "packageManager": "bun@1.3.14",
5
5
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
6
6
  "main": "lib/commonjs/index.js",
@@ -2623,6 +2623,7 @@
2623
2623
  "generate:theme-css": "bun scripts/generate-theme-css.ts",
2624
2624
  "generate:theme-golden": "bun scripts/generate-theme-golden.ts",
2625
2625
  "generate:tokens-json": "bun scripts/generate-design-tokens-json.ts",
2626
+ "generate:adoption-matrix": "bun scripts/generate-adoption-matrix.ts",
2626
2627
  "prebuild": "node scripts/generate-platform-exports.mjs && bun scripts/generate-theme-css.ts && bun scripts/generate-design-tokens-json.ts",
2627
2628
  "build": "bob build",
2628
2629
  "postbuild": "node scripts/verify-package.mjs && node scripts/verify-build-freshness.mjs --worktree",
@@ -14,6 +14,7 @@ import { Gesture, GestureDetector, type GestureType } from 'react-native-gesture
14
14
  import { adoptStyleSheet, dropStyleSheet } from '../styles/adopt-style-sheet';
15
15
  import type { WebCssStyle } from '../styles/web-view-style';
16
16
  import { SurfaceLevelProvider, surfaceFillVars } from '../styles/surface-levels';
17
+ import { ScreenScope } from '../layout';
17
18
  import { Z_INDEX } from '../styles/z-index';
18
19
  import Animated, {
19
20
  type AnimatedStyle,
@@ -650,9 +651,13 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
650
651
  // — web keeps React context across a portal — would tell its content it is
651
652
  // sitting on the panel's card, and in-sheet chrome would paint the column's
652
653
  // colour on top of the sheet's.
654
+ // ...and it is its own SCREEN for the same reason: a header inside the sheet
655
+ // must claim the sheet's top edge rather than the page's, and chrome inside
656
+ // it must follow the sheet's scroller rather than `window.scrollY`
657
+ // (`layout/screen-scope.tsx`).
653
658
  const surfaceChildren = (
654
659
  <SurfaceLevelProvider level={0} fill={colors.background}>
655
- {children}
660
+ <ScreenScope>{children}</ScreenScope>
656
661
  </SurfaceLevelProvider>
657
662
  );
658
663
 
@@ -14,6 +14,7 @@ import {
14
14
  checkboxLabelLineHeight,
15
15
  resolveCheckboxPaint,
16
16
  } from './shared';
17
+ import { useFieldMembership } from '../field/membership';
17
18
  import type { CheckboxProps } from './types';
18
19
 
19
20
  /**
@@ -89,9 +90,21 @@ const CheckboxComponent: React.FC<CheckboxProps> = ({
89
90
  style,
90
91
  labelStyle,
91
92
  accessibilityLabel,
93
+ nativeID,
92
94
  testID,
93
95
  }) => {
94
96
  const theme = useTheme();
97
+ // The label is ADJACENT — the words beside the box ARE the control, so they
98
+ // name it and a `Field` around it only supplies what is missing. `disabled`
99
+ // is the other direction: the field's constrains, so it is OR-ed in.
100
+ const field = useFieldMembership({
101
+ accessibilityLabel,
102
+ label,
103
+ labelPlacement: 'adjacent',
104
+ disabled,
105
+ nativeID,
106
+ });
107
+ const isDisabled = field.disabled;
95
108
  useInteractiveWebCss(STYLE_ID, BLOOM_CHECKBOX_CSS);
96
109
  useInteractiveWebCss(CHECKBOX_GLYPH_STYLE_ID, CHECKBOX_GLYPH_CSS);
97
110
  const { state: hovered, onIn: onHoverIn, onOut: onHoverOut } = useInteractionState();
@@ -100,13 +113,13 @@ const CheckboxComponent: React.FC<CheckboxProps> = ({
100
113
  const { state: pressed, onIn: onPressIn, onOut: onPressOut } = useInteractionState();
101
114
  const sizeConfig = CHECKBOX_SIZE_CONFIG[size];
102
115
  const paint = useMemo(() => resolveCheckboxPaint(theme, color), [theme, color]);
103
- const highlighted = !disabled && (hovered || pressed);
116
+ const highlighted = !isDisabled && (hovered || pressed);
104
117
 
105
118
  const handlePress = useCallback(() => {
106
- if (!disabled) {
119
+ if (!isDisabled) {
107
120
  onCheckedChange(!checked);
108
121
  }
109
- }, [checked, disabled, onCheckedChange]);
122
+ }, [checked, isDisabled, onCheckedChange]);
110
123
 
111
124
  const hasText = Boolean(label || description);
112
125
 
@@ -135,11 +148,14 @@ const CheckboxComponent: React.FC<CheckboxProps> = ({
135
148
  : {})}
136
149
  style={[rowStyle, style]}
137
150
  onPress={handlePress}
138
- onPressIn={disabled ? undefined : onPressIn}
139
- onPressOut={disabled ? undefined : onPressOut}
151
+ onPressIn={isDisabled ? undefined : onPressIn}
152
+ onPressOut={isDisabled ? undefined : onPressOut}
140
153
  onHoverIn={onHoverIn}
141
154
  onHoverOut={onHoverOut}
142
- disabled={disabled}
155
+ disabled={isDisabled}
156
+ nativeID={field.nativeID}
157
+ aria-describedby={field.describedBy}
158
+ aria-invalid={field.invalid || undefined}
143
159
  accessibilityRole="checkbox"
144
160
  // `aria-checked`, not `accessibilityState`: react-native-web's
145
161
  // `createDOMProps` never reads `accessibilityState`, so a checkbox that
@@ -149,7 +165,7 @@ const CheckboxComponent: React.FC<CheckboxProps> = ({
149
165
  // this one prop is the spelling both platforms honour. `disabled`
150
166
  // travels on the `disabled` prop above, which both platforms map.
151
167
  aria-checked={indeterminate ? 'mixed' : checked}
152
- accessibilityLabel={accessibilityLabel ?? label}
168
+ accessibilityLabel={field.accessibilityLabel}
153
169
  hitSlop={{ top: slop, bottom: slop, left: slop, right: slop }}
154
170
  testID={testID}
155
171
  >
@@ -157,7 +173,7 @@ const CheckboxComponent: React.FC<CheckboxProps> = ({
157
173
  size={size}
158
174
  checked={checked}
159
175
  indeterminate={indeterminate}
160
- disabled={disabled}
176
+ disabled={isDisabled}
161
177
  highlighted={highlighted}
162
178
  paint={paint}
163
179
  // Centre the box on the label's first line box.
@@ -22,6 +22,11 @@ export interface CheckboxProps {
22
22
  style?: StyleProp<ViewStyle>;
23
23
  labelStyle?: StyleProp<TextStyle>;
24
24
  accessibilityLabel?: string;
25
+ /**
26
+ * The control's id. Inside a `Field` the field supplies one, so the label's
27
+ * `htmlFor` points at this control on web.
28
+ */
29
+ nativeID?: string;
25
30
  testID?: string;
26
31
  }
27
32
 
@@ -9,6 +9,8 @@ import { TYPE_SCALE } from '../typography';
9
9
  import { resolveCalendarPalette } from './palette';
10
10
  import { formatTime, parseTime, snapTime, stepTime } from './time';
11
11
  import type { TimeFieldProps, TimeFieldSize } from './types';
12
+ import { useInheritedControl } from '../control-surface';
13
+ import { useFieldMembership } from '../field/membership';
12
14
 
13
15
  /**
14
16
  * A time of day, typed. `DatePicker` is the day; this is the hour and minute,
@@ -60,8 +62,8 @@ function TimeFieldComponent({
60
62
  max,
61
63
  step = 1,
62
64
  hourFormat = '24h',
63
- size = 'medium',
64
- disabled = false,
65
+ size: sizeProp,
66
+ disabled: disabledProp = false,
65
67
  accessibilityLabel,
66
68
  placeholder,
67
69
  width,
@@ -69,6 +71,12 @@ function TimeFieldComponent({
69
71
  testID,
70
72
  }: TimeFieldProps) {
71
73
  const theme = useTheme();
74
+ // `"--:--"` names nothing, so the name is a prop — or, inside a `Field`, the
75
+ // field's label. The density contract supplies the size the same way: a
76
+ // container can ask for `small` once instead of on every control.
77
+ const size = useInheritedControl('density', sizeProp, 'medium');
78
+ const field = useFieldMembership({ accessibilityLabel, disabled: disabledProp });
79
+ const disabled = field.disabled;
72
80
  const palette = useMemo(() => resolveCalendarPalette(theme), [theme]);
73
81
  const config = SIZE_CONFIG[size];
74
82
 
@@ -150,7 +158,10 @@ function TimeFieldComponent({
150
158
  placeholder={placeholder ?? (hourFormat === '12h' ? '--:-- --' : '--:--')}
151
159
  placeholderTextColor={palette.secondaryText}
152
160
  inputMode={hourFormat === '12h' ? 'text' : 'numeric'}
153
- accessibilityLabel={accessibilityLabel}
161
+ nativeID={field.nativeID}
162
+ accessibilityLabel={field.accessibilityLabel}
163
+ aria-describedby={field.describedBy}
164
+ aria-invalid={field.invalid || undefined}
154
165
  aria-disabled={disabled || undefined}
155
166
  style={[FIELD_TRANSITION, fieldStyle, style]}
156
167
  testID={testID}
@@ -207,10 +207,13 @@ export interface TimeFieldProps {
207
207
  size?: TimeFieldSize;
208
208
  disabled?: boolean;
209
209
  /**
210
- * The NAME of the time ("Viewing time", "Check-in"). Required: the field
211
- * draws no label of its own, and `"--:--"` names nothing.
210
+ * The NAME of the time ("Viewing time", "Check-in").
211
+ *
212
+ * The field draws no label of its own and `"--:--"` names nothing, so this or
213
+ * an enclosing `Field`'s label is the only route to a name. With neither it
214
+ * warns once in development (`hooks/use-accessible-name-warning.ts`).
212
215
  */
213
- accessibilityLabel: string;
216
+ accessibilityLabel?: string;
214
217
  /** The text while empty. Default `"--:--"`, or `"--:-- --"` in 12h. */
215
218
  placeholder?: string;
216
219
  /** Fixed width. Default: 104 at `medium`, 96 at `small`. */
@@ -4,6 +4,7 @@ import { View, type GestureResponderEvent } from 'react-native';
4
4
  import { Text } from '../typography';
5
5
 
6
6
  import { useTheme } from '../theme/use-theme';
7
+ import { ScreenScope } from '../layout';
7
8
  import { Button } from '../button';
8
9
  import type { ButtonVariant } from '../button/types';
9
10
  import { useDialogContext } from './context';
@@ -40,7 +41,10 @@ export function DialogBody({
40
41
  }) {
41
42
  const theme = useTheme();
42
43
  return (
43
- <>
44
+ // A dialog is its own screen: chrome inside it claims THIS surface's top
45
+ // edge and follows THIS surface's scroller, not the page's underneath
46
+ // (`layout/screen-scope.tsx`).
47
+ <ScreenScope>
44
48
  {title ? (
45
49
  // Bloom's `Text`, not react-native's: the raw one renders in whatever
46
50
  // font the platform picks and takes no theme colour, so every dialog
@@ -74,7 +78,7 @@ export function DialogBody({
74
78
  ) : null}
75
79
  {children}
76
80
  {actions && actions.length > 0 ? <ActionRow actions={actions} /> : null}
77
- </>
81
+ </ScreenScope>
78
82
  );
79
83
  }
80
84
 
@@ -1,5 +1,7 @@
1
1
  export { Field } from './Field';
2
2
  export { useFieldControl } from './context';
3
+ export { useFieldMembership } from './membership';
3
4
 
4
5
  export type { FieldControlValue } from './context';
6
+ export type { FieldLabelPlacement, FieldMembership, FieldMembershipInput } from './membership';
5
7
  export type { FieldProps } from './types';
@@ -0,0 +1,164 @@
1
+ /**
2
+ * ONE resolution of field membership, for every control that opts in.
3
+ *
4
+ * `useFieldControl()` hands a control the field's ids and state; applying them
5
+ * is four separate decisions, and each has a direction that is silently wrong:
6
+ *
7
+ * the NAME defers to the field (`??`) — a control inside a labelled
8
+ * field must not announce nothing
9
+ * the CONSTRAINTS combine with the field (`||`) — `disabled` and `invalid`
10
+ * restrict, so a control cannot opt out
11
+ * the ID is the field's (`??` the caller's own, which wins)
12
+ * `describedBy` is a LIST, so the two are joined rather than one chosen
13
+ *
14
+ * Fifteen families resolving that by hand is fifteen chances to write `??` where
15
+ * `||` belongs — and the failure is silent in both directions: a control that
16
+ * re-enables itself inside a disabled field looks fine and a field-labelled
17
+ * control that announces nothing looks fine too. So it is a function, for the
18
+ * same reason `useInheritedControl` is one.
19
+ *
20
+ * `TextFieldInput` is the one family that resolves the same rule inline, because
21
+ * it also reconciles the text-field shell's own context and its standalone
22
+ * branch. It is not exempt from the RULE: `FieldMembership.test.tsx` asserts the
23
+ * observable properties for every family, so the two implementations cannot
24
+ * disagree about what a field means without going red.
25
+ *
26
+ * ## The name, and why placement decides it
27
+ *
28
+ * A control's own label is either STACKED above it (`TextField`, `Textarea`:
29
+ * the field's label and the control's label are two spellings of one thing, and
30
+ * a `Field` around it should win) or ADJACENT to it (a checkbox's caption, a
31
+ * radio's option text: the words the user reads AS the control, which the
32
+ * announced name must match). Getting that backwards makes a screen reader say
33
+ * one thing while the eye reads another, so it is a declared property of the
34
+ * family rather than a guess this hook could make.
35
+ *
36
+ * A control with neither — `Switch`, `Slider`, a progress bar — can be named by
37
+ * NOTHING but the field, which is the case this hook exists for.
38
+ */
39
+ import { useMemo } from 'react';
40
+
41
+ import { useFieldControl } from './context';
42
+
43
+ /** Where the control renders its own label, which decides which name wins. */
44
+ export type FieldLabelPlacement = 'stacked' | 'adjacent';
45
+
46
+ export interface FieldMembershipInput {
47
+ /** The name the caller wrote on the control. Always wins. */
48
+ accessibilityLabel?: string;
49
+ /** The control's own label, when it has one. */
50
+ label?: string;
51
+ /**
52
+ * Whether that label sits above the control (`stacked`, the default — the
53
+ * field's label is the same thing said twice, so the field wins) or beside it
54
+ * as the control's visible text (`adjacent` — it wins, so the announced name
55
+ * and the words on screen cannot disagree).
56
+ */
57
+ labelPlacement?: FieldLabelPlacement;
58
+ /** The control's own `disabled`. Combined with the field's, never replaced. */
59
+ disabled?: boolean;
60
+ /** The control's own invalid state. Combined with the field's. */
61
+ invalid?: boolean;
62
+ /** The control's own `required`. Combined with the field's. */
63
+ required?: boolean;
64
+ /** An id the caller wrote on the control. Outranks the field's. */
65
+ nativeID?: string;
66
+ /** An `aria-describedby` the caller wrote. JOINED with the field's, not replaced. */
67
+ describedBy?: string;
68
+ }
69
+
70
+ export interface FieldMembership {
71
+ /** Whether there is an enclosing `Field` at all. */
72
+ inField: boolean;
73
+ /** The id to render, or `undefined` when neither side has one. */
74
+ nativeID?: string;
75
+ /** The accessible name to render, or `undefined` when nothing names it. */
76
+ accessibilityLabel?: string;
77
+ /** The `aria-describedby` id list, or `undefined` when there is nothing to describe it. */
78
+ describedBy?: string;
79
+ /** The effective disabled state: the control's OR the field's. */
80
+ disabled: boolean;
81
+ /** The effective invalid state: the control's OR the field's. */
82
+ invalid: boolean;
83
+ /** The effective required state: the control's OR the field's. */
84
+ required: boolean;
85
+ }
86
+
87
+ /**
88
+ * Resolve what an enclosing `Field` contributes to one control.
89
+ *
90
+ * ```tsx
91
+ * const field = useFieldMembership({ accessibilityLabel, label, labelPlacement: 'adjacent', disabled });
92
+ * <Pressable
93
+ * nativeID={field.nativeID}
94
+ * accessibilityLabel={field.accessibilityLabel}
95
+ * aria-describedby={field.describedBy}
96
+ * aria-invalid={field.invalid || undefined}
97
+ * disabled={field.disabled}
98
+ * />
99
+ * ```
100
+ *
101
+ * Outside a `Field` it returns the control's own values unchanged, so a control
102
+ * can call it unconditionally.
103
+ */
104
+ export function useFieldMembership(input: FieldMembershipInput = {}): FieldMembership {
105
+ const field = useFieldControl();
106
+ const {
107
+ accessibilityLabel,
108
+ label,
109
+ labelPlacement = 'stacked',
110
+ disabled = false,
111
+ invalid = false,
112
+ required = false,
113
+ nativeID,
114
+ describedBy,
115
+ } = input;
116
+
117
+ const fieldLabel = field?.labelText;
118
+ const fieldDescribedBy = field?.describedBy;
119
+ const fieldControlId = field?.controlId;
120
+ const fieldDisabled = field?.disabled === true;
121
+ const fieldInvalid = field?.invalid === true;
122
+ const fieldRequired = field?.required === true;
123
+
124
+ return useMemo<FieldMembership>(() => {
125
+ const own = label ?? undefined;
126
+ const name =
127
+ accessibilityLabel ??
128
+ (labelPlacement === 'adjacent' ? (own ?? fieldLabel) : (fieldLabel ?? own));
129
+
130
+ // An id LIST: a control that already points at its own hint keeps it, and
131
+ // the field's error is added rather than swapped in. Duplicates are
132
+ // dropped — an id named twice is read twice by some screen readers.
133
+ const described = [describedBy, fieldDescribedBy].filter(
134
+ (id): id is string => typeof id === 'string' && id.length > 0,
135
+ );
136
+ const uniqueDescribed = Array.from(new Set(described.flatMap((ids) => ids.split(/\s+/))));
137
+
138
+ return {
139
+ inField: field != null,
140
+ nativeID: nativeID ?? fieldControlId,
141
+ accessibilityLabel: name,
142
+ describedBy: uniqueDescribed.length > 0 ? uniqueDescribed.join(' ') : undefined,
143
+ disabled: disabled || fieldDisabled,
144
+ invalid: invalid || fieldInvalid,
145
+ required: required || fieldRequired,
146
+ };
147
+ }, [
148
+ field,
149
+ accessibilityLabel,
150
+ label,
151
+ labelPlacement,
152
+ disabled,
153
+ invalid,
154
+ required,
155
+ nativeID,
156
+ describedBy,
157
+ fieldLabel,
158
+ fieldDescribedBy,
159
+ fieldControlId,
160
+ fieldDisabled,
161
+ fieldInvalid,
162
+ fieldRequired,
163
+ ]);
164
+ }
@@ -92,6 +92,7 @@ type StaggerState = 'shown' | 'hiding' | 'hidden';
92
92
 
93
93
  import { formatFileSize } from './shared';
94
94
  import { DISABLED_OPACITY } from '../styles/tokens';
95
+ import { useFieldMembership } from '../field/membership';
95
96
 
96
97
  export { formatFileSize };
97
98
 
@@ -445,12 +446,19 @@ const FileUploadComponent = function FileUpload({
445
446
  maxBytes = DEFAULT_MAX_BYTES,
446
447
  renderFileIcon,
447
448
  labels: labelOverrides,
448
- disabled = false,
449
- accessibilityLabel = 'Upload a file',
449
+ disabled: disabledProp = false,
450
+ accessibilityLabel,
450
451
  style,
451
452
  testID,
452
453
  }: FileUploadProps) {
453
454
  const theme = useTheme();
455
+ // The drop zone draws a prompt but names itself with a prop, so a `Field`
456
+ // around it is the other place the name can come from — and the field's
457
+ // `disabled` blocks the picker whatever the caller passed. `'Upload a file'`
458
+ // stays the last resort rather than a default that would outrank a label the
459
+ // user can read.
460
+ const field = useFieldMembership({ accessibilityLabel, disabled: disabledProp });
461
+ const disabled = field.disabled;
454
462
  useInteractiveWebCss(STYLE_ID, BLOOM_FILE_UPLOAD_CSS);
455
463
  const reducedMotion = useReducedMotion();
456
464
  const paint = useMemo(() => resolveFileUploadPaint(theme), [theme]);
@@ -644,7 +652,10 @@ const FileUploadComponent = function FileUpload({
644
652
  ref={rootRef}
645
653
  {...webDataSet({ bloomFileUpload: busy ? 'busy' : 'idle' })}
646
654
  accessibilityRole="button"
647
- accessibilityLabel={accessibilityLabel}
655
+ nativeID={field.nativeID}
656
+ accessibilityLabel={field.accessibilityLabel ?? 'Upload a file'}
657
+ aria-describedby={field.describedBy}
658
+ aria-invalid={field.invalid || undefined}
648
659
  aria-busy={busy}
649
660
  disabled={disabled}
650
661
  focusable={!blocked}
@@ -162,6 +162,19 @@ export interface TriggerHandleProps {
162
162
  accessibilityLabel?: string;
163
163
  accessibilityRole?: 'button';
164
164
  'aria-expanded'?: boolean;
165
+ /**
166
+ * The trigger's id, so an enclosing `Field`'s label can point at it.
167
+ *
168
+ * Only a trigger that IS a form control sets it — `Select` does; a `Popover`
169
+ * or a `DropdownMenu` inside a field is not the field's control and must not
170
+ * take its id. `TriggerSlot` therefore does not read the field context
171
+ * itself; each family decides whether it is the control.
172
+ */
173
+ nativeID?: string;
174
+ /** The field's hint or error, for a trigger that is a form control. */
175
+ 'aria-describedby'?: string;
176
+ /** The field's invalid state, for a trigger that is a form control. */
177
+ 'aria-invalid'?: boolean;
165
178
  /**
166
179
  * What this trigger OPENS. Set by each family from its own constant in
167
180
  * `floating/constants.ts`, never defaulted here — the five families open three
package/src/index.ts CHANGED
@@ -167,6 +167,7 @@ export {
167
167
  useTopEdgeInset,
168
168
  ScrollOffsetProvider,
169
169
  useScrollOffset,
170
+ ScreenScope,
170
171
  } from './layout';
171
172
  export { Fab } from './fab';
172
173
  export type { FabProps, FabVariant, FabSize, FabPlacement } from './fab';
@@ -282,8 +283,14 @@ export * from './date-picker';
282
283
  export { Search } from './search';
283
284
  export { Label } from './label';
284
285
  export type { LabelProps } from './label';
285
- export { Field } from './field';
286
- export type { FieldProps } from './field';
286
+ export { Field, useFieldControl, useFieldMembership } from './field';
287
+ export type {
288
+ FieldControlValue,
289
+ FieldLabelPlacement,
290
+ FieldMembership,
291
+ FieldMembershipInput,
292
+ FieldProps,
293
+ } from './field';
287
294
  export { InputGroup, InputGroupAddon } from './input-group';
288
295
  export type { InputGroupProps, InputGroupAddonProps } from './input-group';
289
296
  export { RangeSlider, Slider } from './slider';
package/src/index.web.ts CHANGED
@@ -172,6 +172,7 @@ export {
172
172
  useTopEdgeInset,
173
173
  ScrollOffsetProvider,
174
174
  useScrollOffset,
175
+ ScreenScope,
175
176
  } from './layout';
176
177
  export { Fab } from './fab/index.web';
177
178
  export type { FabProps, FabVariant, FabSize, FabPlacement } from './fab/index.web';
@@ -287,8 +288,14 @@ export * from './date-picker';
287
288
  export { Search } from './search';
288
289
  export { Label } from './label';
289
290
  export type { LabelProps } from './label';
290
- export { Field } from './field';
291
- export type { FieldProps } from './field';
291
+ export { Field, useFieldControl, useFieldMembership } from './field';
292
+ export type {
293
+ FieldControlValue,
294
+ FieldLabelPlacement,
295
+ FieldMembership,
296
+ FieldMembershipInput,
297
+ FieldProps,
298
+ } from './field';
292
299
  export { InputGroup, InputGroupAddon } from './input-group';
293
300
  export type { InputGroupProps, InputGroupAddonProps } from './input-group';
294
301
  export { RangeSlider, Slider } from './slider';
@@ -24,6 +24,7 @@ import {
24
24
  } from '../text-field/shared';
25
25
  import { TextFieldGroupContext } from '../text-field/TextField';
26
26
  import type { InputGroupAddonProps, InputGroupProps } from './types';
27
+ import { useFieldMembership } from '../field/membership';
27
28
 
28
29
  /**
29
30
  * The group is the input shell (`base/input/input.tsx`) with its adornment
@@ -149,12 +150,21 @@ InputGroupAddon.displayName = 'InputGroupAddon';
149
150
  */
150
151
  const InputGroupComponent = function InputGroup({
151
152
  children,
152
- isInvalid = false,
153
- disabled = false,
153
+ isInvalid: isInvalidProp = false,
154
+ disabled: disabledProp = false,
154
155
  size = 'md',
155
156
  style,
156
157
  testID,
157
158
  }: InputGroupProps) {
159
+ // A group holding a control and its addons is ONE control as far as a `Field`
160
+ // is concerned: the field's `disabled` and invalid state reach every member
161
+ // through the group's own contexts, including an addon's `Button`, which reads
162
+ // neither the field nor the text-field group. The group publishes no name — it
163
+ // does not know which of its children is the control the label points at, so
164
+ // that stays the field's `nativeID` and the input's to apply.
165
+ const member = useFieldMembership({ disabled: disabledProp, invalid: isInvalidProp });
166
+ const disabled = member.disabled;
167
+ const isInvalid = member.invalid;
158
168
  const cfg = SIZE_CONFIG[size];
159
169
  const { state: focused, onIn: onFocus, onOut: onBlur } = useInteractionState();
160
170
  const { state: hovered, onIn: onHoverIn, onOut: onHoverOut } =
@@ -211,6 +221,7 @@ const InputGroupComponent = function InputGroup({
211
221
  // react-native-web has no `disabled` prop to derive the attribute from
212
222
  // and never reads `accessibilityState`. React Native folds it back.
213
223
  aria-disabled={disabled || undefined}
224
+ aria-invalid={isInvalid || undefined}
214
225
  // Capture focus bubbling from a nested input so the whole chrome
215
226
  // reflects focus — RN-web bubbles focus/blur, native does not but a
216
227
  // nested TextInput's own focus ring is sufficient there.