@cdx-ui/primitives 0.0.1-beta.140 → 0.0.1-beta.142

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 (433) hide show
  1. package/lib/commonjs/calendar/CalendarGrid.js +33 -0
  2. package/lib/commonjs/calendar/CalendarGrid.js.map +1 -0
  3. package/lib/commonjs/calendar/CalendarRoot.js +95 -0
  4. package/lib/commonjs/calendar/CalendarRoot.js.map +1 -0
  5. package/lib/commonjs/calendar/context.js +17 -0
  6. package/lib/commonjs/calendar/context.js.map +1 -0
  7. package/lib/commonjs/calendar/index.js +27 -0
  8. package/lib/commonjs/calendar/index.js.map +1 -0
  9. package/lib/commonjs/calendar/types.js +6 -0
  10. package/lib/commonjs/calendar/types.js.map +1 -0
  11. package/lib/commonjs/checkbox/CheckboxRoot.web.js +91 -28
  12. package/lib/commonjs/checkbox/CheckboxRoot.web.js.map +1 -1
  13. package/lib/commonjs/checkbox/useCheckboxRoot.js +33 -19
  14. package/lib/commonjs/checkbox/useCheckboxRoot.js.map +1 -1
  15. package/lib/commonjs/date-field/DateFieldHiddenInput.js +148 -0
  16. package/lib/commonjs/date-field/DateFieldHiddenInput.js.map +1 -0
  17. package/lib/commonjs/date-field/DateFieldInput.js +122 -0
  18. package/lib/commonjs/date-field/DateFieldInput.js.map +1 -0
  19. package/lib/commonjs/date-field/DateFieldRoot.js +85 -0
  20. package/lib/commonjs/date-field/DateFieldRoot.js.map +1 -0
  21. package/lib/commonjs/date-field/DateFieldSegment.js +92 -0
  22. package/lib/commonjs/date-field/DateFieldSegment.js.map +1 -0
  23. package/lib/commonjs/date-field/context.js +19 -0
  24. package/lib/commonjs/date-field/context.js.map +1 -0
  25. package/lib/commonjs/date-field/index.js +34 -0
  26. package/lib/commonjs/date-field/index.js.map +1 -0
  27. package/lib/commonjs/date-field/segmentIndex.js +23 -0
  28. package/lib/commonjs/date-field/segmentIndex.js.map +1 -0
  29. package/lib/commonjs/date-field/segmentKeyboard.js +169 -0
  30. package/lib/commonjs/date-field/segmentKeyboard.js.map +1 -0
  31. package/lib/commonjs/date-field/types.js +6 -0
  32. package/lib/commonjs/date-field/types.js.map +1 -0
  33. package/lib/commonjs/date-field/useDateFieldSegmentState.js +381 -0
  34. package/lib/commonjs/date-field/useDateFieldSegmentState.js.map +1 -0
  35. package/lib/commonjs/date-field/useRangeDateFieldBridge.js +67 -0
  36. package/lib/commonjs/date-field/useRangeDateFieldBridge.js.map +1 -0
  37. package/lib/commonjs/date-picker/DatePickerRoot.js +123 -0
  38. package/lib/commonjs/date-picker/DatePickerRoot.js.map +1 -0
  39. package/lib/commonjs/date-picker/context.js +17 -0
  40. package/lib/commonjs/date-picker/context.js.map +1 -0
  41. package/lib/commonjs/date-picker/index.js +26 -0
  42. package/lib/commonjs/date-picker/index.js.map +1 -0
  43. package/lib/commonjs/date-range-picker/DateRangePickerRoot.js +144 -0
  44. package/lib/commonjs/date-range-picker/DateRangePickerRoot.js.map +1 -0
  45. package/lib/commonjs/date-range-picker/context.js +23 -0
  46. package/lib/commonjs/date-range-picker/context.js.map +1 -0
  47. package/lib/commonjs/date-range-picker/index.js +26 -0
  48. package/lib/commonjs/date-range-picker/index.js.map +1 -0
  49. package/lib/commonjs/date-time-field/DateTimeFieldHiddenInput.js +150 -0
  50. package/lib/commonjs/date-time-field/DateTimeFieldHiddenInput.js.map +1 -0
  51. package/lib/commonjs/date-time-field/DateTimeFieldInput.js +104 -0
  52. package/lib/commonjs/date-time-field/DateTimeFieldInput.js.map +1 -0
  53. package/lib/commonjs/date-time-field/DateTimeFieldRoot.js +159 -0
  54. package/lib/commonjs/date-time-field/DateTimeFieldRoot.js.map +1 -0
  55. package/lib/commonjs/date-time-field/DateTimeFieldSegment.js +87 -0
  56. package/lib/commonjs/date-time-field/DateTimeFieldSegment.js.map +1 -0
  57. package/lib/commonjs/date-time-field/context.js +17 -0
  58. package/lib/commonjs/date-time-field/context.js.map +1 -0
  59. package/lib/commonjs/date-time-field/index.js +40 -0
  60. package/lib/commonjs/date-time-field/index.js.map +1 -0
  61. package/lib/commonjs/date-time-field/useDateTimeFieldSegmentState.js +564 -0
  62. package/lib/commonjs/date-time-field/useDateTimeFieldSegmentState.js.map +1 -0
  63. package/lib/commonjs/date-time-list/DateTimeListRoot.js +418 -0
  64. package/lib/commonjs/date-time-list/DateTimeListRoot.js.map +1 -0
  65. package/lib/commonjs/date-time-list/context.js +26 -0
  66. package/lib/commonjs/date-time-list/context.js.map +1 -0
  67. package/lib/commonjs/date-time-list/index.js +62 -0
  68. package/lib/commonjs/date-time-list/index.js.map +1 -0
  69. package/lib/commonjs/date-time-picker/DateTimePickerRoot.js +134 -0
  70. package/lib/commonjs/date-time-picker/DateTimePickerRoot.js.map +1 -0
  71. package/lib/commonjs/date-time-picker/context.js +17 -0
  72. package/lib/commonjs/date-time-picker/context.js.map +1 -0
  73. package/lib/commonjs/date-time-picker/index.js +26 -0
  74. package/lib/commonjs/date-time-picker/index.js.map +1 -0
  75. package/lib/commonjs/dialog/DialogRoot.js +1 -1
  76. package/lib/commonjs/dialog/DialogRoot.js.map +1 -1
  77. package/lib/commonjs/index.js +148 -0
  78. package/lib/commonjs/index.js.map +1 -1
  79. package/lib/commonjs/month-calendar/MonthCalendarCell.js +64 -0
  80. package/lib/commonjs/month-calendar/MonthCalendarCell.js.map +1 -0
  81. package/lib/commonjs/month-calendar/MonthCalendarRoot.js +103 -0
  82. package/lib/commonjs/month-calendar/MonthCalendarRoot.js.map +1 -0
  83. package/lib/commonjs/month-calendar/MonthCalendarYearCell.js +63 -0
  84. package/lib/commonjs/month-calendar/MonthCalendarYearCell.js.map +1 -0
  85. package/lib/commonjs/month-calendar/context.js +17 -0
  86. package/lib/commonjs/month-calendar/context.js.map +1 -0
  87. package/lib/commonjs/month-calendar/index.js +34 -0
  88. package/lib/commonjs/month-calendar/index.js.map +1 -0
  89. package/lib/commonjs/month-picker/MonthPickerRoot.js +118 -0
  90. package/lib/commonjs/month-picker/MonthPickerRoot.js.map +1 -0
  91. package/lib/commonjs/month-picker/context.js +17 -0
  92. package/lib/commonjs/month-picker/context.js.map +1 -0
  93. package/lib/commonjs/month-picker/index.js +26 -0
  94. package/lib/commonjs/month-picker/index.js.map +1 -0
  95. package/lib/commonjs/overlay/useAnchorPosition.js +27 -16
  96. package/lib/commonjs/overlay/useAnchorPosition.js.map +1 -1
  97. package/lib/commonjs/overlay/useDismissOverlay.web.js +10 -2
  98. package/lib/commonjs/overlay/useDismissOverlay.web.js.map +1 -1
  99. package/lib/commonjs/overlay/useOverlayPosition.js +5 -2
  100. package/lib/commonjs/overlay/useOverlayPosition.js.map +1 -1
  101. package/lib/commonjs/portal/portal.js +59 -2
  102. package/lib/commonjs/portal/portal.js.map +1 -1
  103. package/lib/commonjs/radio/RadioGroup.js +65 -16
  104. package/lib/commonjs/radio/RadioGroup.js.map +1 -1
  105. package/lib/commonjs/radio/RadioRoot.web.js +114 -26
  106. package/lib/commonjs/radio/RadioRoot.web.js.map +1 -1
  107. package/lib/commonjs/radio/context.js.map +1 -1
  108. package/lib/commonjs/radio/useRadioGroupNavigation.js +79 -0
  109. package/lib/commonjs/radio/useRadioGroupNavigation.js.map +1 -0
  110. package/lib/commonjs/radio/useRadioRoot.js +14 -13
  111. package/lib/commonjs/radio/useRadioRoot.js.map +1 -1
  112. package/lib/commonjs/range-calendar/RangeCalendarRoot.js +148 -0
  113. package/lib/commonjs/range-calendar/RangeCalendarRoot.js.map +1 -0
  114. package/lib/commonjs/range-calendar/context.js +17 -0
  115. package/lib/commonjs/range-calendar/context.js.map +1 -0
  116. package/lib/commonjs/range-calendar/index.js +20 -0
  117. package/lib/commonjs/range-calendar/index.js.map +1 -0
  118. package/lib/commonjs/select/SelectRoot.js +1 -1
  119. package/lib/commonjs/select/SelectRoot.js.map +1 -1
  120. package/lib/commonjs/utils/accessibleLabel.js +23 -0
  121. package/lib/commonjs/utils/accessibleLabel.js.map +1 -0
  122. package/lib/commonjs/utils/createPortal.web.js +21 -0
  123. package/lib/commonjs/utils/createPortal.web.js.map +1 -0
  124. package/lib/module/calendar/CalendarGrid.js +29 -0
  125. package/lib/module/calendar/CalendarGrid.js.map +1 -0
  126. package/lib/module/calendar/CalendarRoot.js +91 -0
  127. package/lib/module/calendar/CalendarRoot.js.map +1 -0
  128. package/lib/module/calendar/context.js +12 -0
  129. package/lib/module/calendar/context.js.map +1 -0
  130. package/lib/module/calendar/index.js +6 -0
  131. package/lib/module/calendar/index.js.map +1 -0
  132. package/lib/module/calendar/types.js +4 -0
  133. package/lib/module/calendar/types.js.map +1 -0
  134. package/lib/module/checkbox/CheckboxRoot.web.js +92 -29
  135. package/lib/module/checkbox/CheckboxRoot.web.js.map +1 -1
  136. package/lib/module/checkbox/useCheckboxRoot.js +34 -20
  137. package/lib/module/checkbox/useCheckboxRoot.js.map +1 -1
  138. package/lib/module/date-field/DateFieldHiddenInput.js +146 -0
  139. package/lib/module/date-field/DateFieldHiddenInput.js.map +1 -0
  140. package/lib/module/date-field/DateFieldInput.js +118 -0
  141. package/lib/module/date-field/DateFieldInput.js.map +1 -0
  142. package/lib/module/date-field/DateFieldRoot.js +81 -0
  143. package/lib/module/date-field/DateFieldRoot.js.map +1 -0
  144. package/lib/module/date-field/DateFieldSegment.js +88 -0
  145. package/lib/module/date-field/DateFieldSegment.js.map +1 -0
  146. package/lib/module/date-field/context.js +15 -0
  147. package/lib/module/date-field/context.js.map +1 -0
  148. package/lib/module/date-field/index.js +7 -0
  149. package/lib/module/date-field/index.js.map +1 -0
  150. package/lib/module/date-field/segmentIndex.js +18 -0
  151. package/lib/module/date-field/segmentIndex.js.map +1 -0
  152. package/lib/module/date-field/segmentKeyboard.js +157 -0
  153. package/lib/module/date-field/segmentKeyboard.js.map +1 -0
  154. package/lib/module/date-field/types.js +4 -0
  155. package/lib/module/date-field/types.js.map +1 -0
  156. package/lib/module/date-field/useDateFieldSegmentState.js +378 -0
  157. package/lib/module/date-field/useDateFieldSegmentState.js.map +1 -0
  158. package/lib/module/date-field/useRangeDateFieldBridge.js +63 -0
  159. package/lib/module/date-field/useRangeDateFieldBridge.js.map +1 -0
  160. package/lib/module/date-picker/DatePickerRoot.js +119 -0
  161. package/lib/module/date-picker/DatePickerRoot.js.map +1 -0
  162. package/lib/module/date-picker/context.js +12 -0
  163. package/lib/module/date-picker/context.js.map +1 -0
  164. package/lib/module/date-picker/index.js +5 -0
  165. package/lib/module/date-picker/index.js.map +1 -0
  166. package/lib/module/date-range-picker/DateRangePickerRoot.js +140 -0
  167. package/lib/module/date-range-picker/DateRangePickerRoot.js.map +1 -0
  168. package/lib/module/date-range-picker/context.js +19 -0
  169. package/lib/module/date-range-picker/context.js.map +1 -0
  170. package/lib/module/date-range-picker/index.js +5 -0
  171. package/lib/module/date-range-picker/index.js.map +1 -0
  172. package/lib/module/date-time-field/DateTimeFieldHiddenInput.js +148 -0
  173. package/lib/module/date-time-field/DateTimeFieldHiddenInput.js.map +1 -0
  174. package/lib/module/date-time-field/DateTimeFieldInput.js +100 -0
  175. package/lib/module/date-time-field/DateTimeFieldInput.js.map +1 -0
  176. package/lib/module/date-time-field/DateTimeFieldRoot.js +154 -0
  177. package/lib/module/date-time-field/DateTimeFieldRoot.js.map +1 -0
  178. package/lib/module/date-time-field/DateTimeFieldSegment.js +83 -0
  179. package/lib/module/date-time-field/DateTimeFieldSegment.js.map +1 -0
  180. package/lib/module/date-time-field/context.js +12 -0
  181. package/lib/module/date-time-field/context.js.map +1 -0
  182. package/lib/module/date-time-field/index.js +7 -0
  183. package/lib/module/date-time-field/index.js.map +1 -0
  184. package/lib/module/date-time-field/useDateTimeFieldSegmentState.js +561 -0
  185. package/lib/module/date-time-field/useDateTimeFieldSegmentState.js.map +1 -0
  186. package/lib/module/date-time-list/DateTimeListRoot.js +416 -0
  187. package/lib/module/date-time-list/DateTimeListRoot.js.map +1 -0
  188. package/lib/module/date-time-list/context.js +20 -0
  189. package/lib/module/date-time-list/context.js.map +1 -0
  190. package/lib/module/date-time-list/index.js +5 -0
  191. package/lib/module/date-time-list/index.js.map +1 -0
  192. package/lib/module/date-time-picker/DateTimePickerRoot.js +130 -0
  193. package/lib/module/date-time-picker/DateTimePickerRoot.js.map +1 -0
  194. package/lib/module/date-time-picker/context.js +12 -0
  195. package/lib/module/date-time-picker/context.js.map +1 -0
  196. package/lib/module/date-time-picker/index.js +5 -0
  197. package/lib/module/date-time-picker/index.js.map +1 -0
  198. package/lib/module/dialog/DialogRoot.js +1 -1
  199. package/lib/module/dialog/DialogRoot.js.map +1 -1
  200. package/lib/module/index.js +11 -2
  201. package/lib/module/index.js.map +1 -1
  202. package/lib/module/month-calendar/MonthCalendarCell.js +60 -0
  203. package/lib/module/month-calendar/MonthCalendarCell.js.map +1 -0
  204. package/lib/module/month-calendar/MonthCalendarRoot.js +99 -0
  205. package/lib/module/month-calendar/MonthCalendarRoot.js.map +1 -0
  206. package/lib/module/month-calendar/MonthCalendarYearCell.js +59 -0
  207. package/lib/module/month-calendar/MonthCalendarYearCell.js.map +1 -0
  208. package/lib/module/month-calendar/context.js +12 -0
  209. package/lib/module/month-calendar/context.js.map +1 -0
  210. package/lib/module/month-calendar/index.js +7 -0
  211. package/lib/module/month-calendar/index.js.map +1 -0
  212. package/lib/module/month-picker/MonthPickerRoot.js +114 -0
  213. package/lib/module/month-picker/MonthPickerRoot.js.map +1 -0
  214. package/lib/module/month-picker/context.js +12 -0
  215. package/lib/module/month-picker/context.js.map +1 -0
  216. package/lib/module/month-picker/index.js +5 -0
  217. package/lib/module/month-picker/index.js.map +1 -0
  218. package/lib/module/overlay/useAnchorPosition.js +27 -16
  219. package/lib/module/overlay/useAnchorPosition.js.map +1 -1
  220. package/lib/module/overlay/useDismissOverlay.web.js +9 -2
  221. package/lib/module/overlay/useDismissOverlay.web.js.map +1 -1
  222. package/lib/module/overlay/useOverlayPosition.js +5 -2
  223. package/lib/module/overlay/useOverlayPosition.js.map +1 -1
  224. package/lib/module/portal/portal.js +59 -2
  225. package/lib/module/portal/portal.js.map +1 -1
  226. package/lib/module/radio/RadioGroup.js +68 -19
  227. package/lib/module/radio/RadioGroup.js.map +1 -1
  228. package/lib/module/radio/RadioRoot.web.js +117 -29
  229. package/lib/module/radio/RadioRoot.web.js.map +1 -1
  230. package/lib/module/radio/context.js.map +1 -1
  231. package/lib/module/radio/useRadioGroupNavigation.js +74 -0
  232. package/lib/module/radio/useRadioGroupNavigation.js.map +1 -0
  233. package/lib/module/radio/useRadioRoot.js +14 -13
  234. package/lib/module/radio/useRadioRoot.js.map +1 -1
  235. package/lib/module/range-calendar/RangeCalendarRoot.js +144 -0
  236. package/lib/module/range-calendar/RangeCalendarRoot.js.map +1 -0
  237. package/lib/module/range-calendar/context.js +12 -0
  238. package/lib/module/range-calendar/context.js.map +1 -0
  239. package/lib/module/range-calendar/index.js +5 -0
  240. package/lib/module/range-calendar/index.js.map +1 -0
  241. package/lib/module/select/SelectRoot.js +1 -1
  242. package/lib/module/select/SelectRoot.js.map +1 -1
  243. package/lib/module/utils/accessibleLabel.js +18 -0
  244. package/lib/module/utils/accessibleLabel.js.map +1 -0
  245. package/lib/module/utils/createPortal.web.js +17 -0
  246. package/lib/module/utils/createPortal.web.js.map +1 -0
  247. package/lib/typescript/calendar/CalendarGrid.d.ts +7 -0
  248. package/lib/typescript/calendar/CalendarGrid.d.ts.map +1 -0
  249. package/lib/typescript/calendar/CalendarRoot.d.ts +10 -0
  250. package/lib/typescript/calendar/CalendarRoot.d.ts.map +1 -0
  251. package/lib/typescript/calendar/context.d.ts +18 -0
  252. package/lib/typescript/calendar/context.d.ts.map +1 -0
  253. package/lib/typescript/calendar/index.d.ts +5 -0
  254. package/lib/typescript/calendar/index.d.ts.map +1 -0
  255. package/lib/typescript/calendar/types.d.ts +29 -0
  256. package/lib/typescript/calendar/types.d.ts.map +1 -0
  257. package/lib/typescript/checkbox/CheckboxRoot.web.d.ts +8 -0
  258. package/lib/typescript/checkbox/CheckboxRoot.web.d.ts.map +1 -1
  259. package/lib/typescript/checkbox/useCheckboxRoot.d.ts +7 -18
  260. package/lib/typescript/checkbox/useCheckboxRoot.d.ts.map +1 -1
  261. package/lib/typescript/date-field/DateFieldHiddenInput.d.ts +4 -0
  262. package/lib/typescript/date-field/DateFieldHiddenInput.d.ts.map +1 -0
  263. package/lib/typescript/date-field/DateFieldInput.d.ts +8 -0
  264. package/lib/typescript/date-field/DateFieldInput.d.ts.map +1 -0
  265. package/lib/typescript/date-field/DateFieldRoot.d.ts +10 -0
  266. package/lib/typescript/date-field/DateFieldRoot.d.ts.map +1 -0
  267. package/lib/typescript/date-field/DateFieldSegment.d.ts +9 -0
  268. package/lib/typescript/date-field/DateFieldSegment.d.ts.map +1 -0
  269. package/lib/typescript/date-field/context.d.ts +33 -0
  270. package/lib/typescript/date-field/context.d.ts.map +1 -0
  271. package/lib/typescript/date-field/index.d.ts +9 -0
  272. package/lib/typescript/date-field/index.d.ts.map +1 -0
  273. package/lib/typescript/date-field/segmentIndex.d.ts +7 -0
  274. package/lib/typescript/date-field/segmentIndex.d.ts.map +1 -0
  275. package/lib/typescript/date-field/segmentKeyboard.d.ts +23 -0
  276. package/lib/typescript/date-field/segmentKeyboard.d.ts.map +1 -0
  277. package/lib/typescript/date-field/types.d.ts +38 -0
  278. package/lib/typescript/date-field/types.d.ts.map +1 -0
  279. package/lib/typescript/date-field/useDateFieldSegmentState.d.ts +26 -0
  280. package/lib/typescript/date-field/useDateFieldSegmentState.d.ts.map +1 -0
  281. package/lib/typescript/date-field/useRangeDateFieldBridge.d.ts +4 -0
  282. package/lib/typescript/date-field/useRangeDateFieldBridge.d.ts.map +1 -0
  283. package/lib/typescript/date-picker/DatePickerRoot.d.ts +28 -0
  284. package/lib/typescript/date-picker/DatePickerRoot.d.ts.map +1 -0
  285. package/lib/typescript/date-picker/context.d.ts +25 -0
  286. package/lib/typescript/date-picker/context.d.ts.map +1 -0
  287. package/lib/typescript/date-picker/index.d.ts +5 -0
  288. package/lib/typescript/date-picker/index.d.ts.map +1 -0
  289. package/lib/typescript/date-range-picker/DateRangePickerRoot.d.ts +43 -0
  290. package/lib/typescript/date-range-picker/DateRangePickerRoot.d.ts.map +1 -0
  291. package/lib/typescript/date-range-picker/context.d.ts +42 -0
  292. package/lib/typescript/date-range-picker/context.d.ts.map +1 -0
  293. package/lib/typescript/date-range-picker/index.d.ts +4 -0
  294. package/lib/typescript/date-range-picker/index.d.ts.map +1 -0
  295. package/lib/typescript/date-time-field/DateTimeFieldHiddenInput.d.ts +4 -0
  296. package/lib/typescript/date-time-field/DateTimeFieldHiddenInput.d.ts.map +1 -0
  297. package/lib/typescript/date-time-field/DateTimeFieldInput.d.ts +10 -0
  298. package/lib/typescript/date-time-field/DateTimeFieldInput.d.ts.map +1 -0
  299. package/lib/typescript/date-time-field/DateTimeFieldRoot.d.ts +24 -0
  300. package/lib/typescript/date-time-field/DateTimeFieldRoot.d.ts.map +1 -0
  301. package/lib/typescript/date-time-field/DateTimeFieldSegment.d.ts +10 -0
  302. package/lib/typescript/date-time-field/DateTimeFieldSegment.d.ts.map +1 -0
  303. package/lib/typescript/date-time-field/context.d.ts +27 -0
  304. package/lib/typescript/date-time-field/context.d.ts.map +1 -0
  305. package/lib/typescript/date-time-field/index.d.ts +8 -0
  306. package/lib/typescript/date-time-field/index.d.ts.map +1 -0
  307. package/lib/typescript/date-time-field/useDateTimeFieldSegmentState.d.ts +26 -0
  308. package/lib/typescript/date-time-field/useDateTimeFieldSegmentState.d.ts.map +1 -0
  309. package/lib/typescript/date-time-list/DateTimeListRoot.d.ts +42 -0
  310. package/lib/typescript/date-time-list/DateTimeListRoot.d.ts.map +1 -0
  311. package/lib/typescript/date-time-list/context.d.ts +29 -0
  312. package/lib/typescript/date-time-list/context.d.ts.map +1 -0
  313. package/lib/typescript/date-time-list/index.d.ts +4 -0
  314. package/lib/typescript/date-time-list/index.d.ts.map +1 -0
  315. package/lib/typescript/date-time-picker/DateTimePickerRoot.d.ts +36 -0
  316. package/lib/typescript/date-time-picker/DateTimePickerRoot.d.ts.map +1 -0
  317. package/lib/typescript/date-time-picker/context.d.ts +25 -0
  318. package/lib/typescript/date-time-picker/context.d.ts.map +1 -0
  319. package/lib/typescript/date-time-picker/index.d.ts +4 -0
  320. package/lib/typescript/date-time-picker/index.d.ts.map +1 -0
  321. package/lib/typescript/dialog/types.d.ts +2 -1
  322. package/lib/typescript/dialog/types.d.ts.map +1 -1
  323. package/lib/typescript/index.d.ts +11 -2
  324. package/lib/typescript/index.d.ts.map +1 -1
  325. package/lib/typescript/month-calendar/MonthCalendarCell.d.ts +9 -0
  326. package/lib/typescript/month-calendar/MonthCalendarCell.d.ts.map +1 -0
  327. package/lib/typescript/month-calendar/MonthCalendarRoot.d.ts +30 -0
  328. package/lib/typescript/month-calendar/MonthCalendarRoot.d.ts.map +1 -0
  329. package/lib/typescript/month-calendar/MonthCalendarYearCell.d.ts +9 -0
  330. package/lib/typescript/month-calendar/MonthCalendarYearCell.d.ts.map +1 -0
  331. package/lib/typescript/month-calendar/context.d.ts +20 -0
  332. package/lib/typescript/month-calendar/context.d.ts.map +1 -0
  333. package/lib/typescript/month-calendar/index.d.ts +7 -0
  334. package/lib/typescript/month-calendar/index.d.ts.map +1 -0
  335. package/lib/typescript/month-picker/MonthPickerRoot.d.ts +29 -0
  336. package/lib/typescript/month-picker/MonthPickerRoot.d.ts.map +1 -0
  337. package/lib/typescript/month-picker/context.d.ts +23 -0
  338. package/lib/typescript/month-picker/context.d.ts.map +1 -0
  339. package/lib/typescript/month-picker/index.d.ts +4 -0
  340. package/lib/typescript/month-picker/index.d.ts.map +1 -0
  341. package/lib/typescript/overlay/useAnchorPosition.d.ts.map +1 -1
  342. package/lib/typescript/overlay/useDismissOverlay.web.d.ts.map +1 -1
  343. package/lib/typescript/overlay/useOverlayPosition.d.ts.map +1 -1
  344. package/lib/typescript/portal/portal.d.ts.map +1 -1
  345. package/lib/typescript/radio/RadioGroup.d.ts.map +1 -1
  346. package/lib/typescript/radio/RadioRoot.web.d.ts +8 -0
  347. package/lib/typescript/radio/RadioRoot.web.d.ts.map +1 -1
  348. package/lib/typescript/radio/context.d.ts +6 -1
  349. package/lib/typescript/radio/context.d.ts.map +1 -1
  350. package/lib/typescript/radio/types.d.ts +7 -0
  351. package/lib/typescript/radio/types.d.ts.map +1 -1
  352. package/lib/typescript/radio/useRadioGroupNavigation.d.ts +17 -0
  353. package/lib/typescript/radio/useRadioGroupNavigation.d.ts.map +1 -0
  354. package/lib/typescript/radio/useRadioRoot.d.ts +4 -18
  355. package/lib/typescript/radio/useRadioRoot.d.ts.map +1 -1
  356. package/lib/typescript/range-calendar/RangeCalendarRoot.d.ts +34 -0
  357. package/lib/typescript/range-calendar/RangeCalendarRoot.d.ts.map +1 -0
  358. package/lib/typescript/range-calendar/context.d.ts +26 -0
  359. package/lib/typescript/range-calendar/context.d.ts.map +1 -0
  360. package/lib/typescript/range-calendar/index.d.ts +5 -0
  361. package/lib/typescript/range-calendar/index.d.ts.map +1 -0
  362. package/lib/typescript/select/types.d.ts +2 -1
  363. package/lib/typescript/select/types.d.ts.map +1 -1
  364. package/lib/typescript/utils/accessibleLabel.d.ts +13 -0
  365. package/lib/typescript/utils/accessibleLabel.d.ts.map +1 -0
  366. package/lib/typescript/utils/createPortal.web.d.ts +17 -0
  367. package/lib/typescript/utils/createPortal.web.d.ts.map +1 -0
  368. package/package.json +3 -2
  369. package/src/calendar/CalendarGrid.tsx +29 -0
  370. package/src/calendar/CalendarRoot.tsx +138 -0
  371. package/src/calendar/context.tsx +28 -0
  372. package/src/calendar/index.ts +4 -0
  373. package/src/calendar/types.ts +30 -0
  374. package/src/checkbox/CheckboxRoot.web.tsx +105 -29
  375. package/src/checkbox/useCheckboxRoot.ts +37 -15
  376. package/src/date-field/DateFieldHiddenInput.tsx +152 -0
  377. package/src/date-field/DateFieldInput.tsx +138 -0
  378. package/src/date-field/DateFieldRoot.tsx +117 -0
  379. package/src/date-field/DateFieldSegment.tsx +97 -0
  380. package/src/date-field/context.tsx +48 -0
  381. package/src/date-field/index.ts +8 -0
  382. package/src/date-field/segmentIndex.tsx +17 -0
  383. package/src/date-field/segmentKeyboard.ts +207 -0
  384. package/src/date-field/types.ts +40 -0
  385. package/src/date-field/useDateFieldSegmentState.ts +455 -0
  386. package/src/date-field/useRangeDateFieldBridge.ts +78 -0
  387. package/src/date-picker/DatePickerRoot.tsx +186 -0
  388. package/src/date-picker/context.tsx +34 -0
  389. package/src/date-picker/index.ts +4 -0
  390. package/src/date-range-picker/DateRangePickerRoot.tsx +236 -0
  391. package/src/date-range-picker/context.tsx +48 -0
  392. package/src/date-range-picker/index.ts +8 -0
  393. package/src/date-time-field/DateTimeFieldHiddenInput.tsx +156 -0
  394. package/src/date-time-field/DateTimeFieldInput.tsx +119 -0
  395. package/src/date-time-field/DateTimeFieldRoot.tsx +209 -0
  396. package/src/date-time-field/DateTimeFieldSegment.tsx +95 -0
  397. package/src/date-time-field/context.tsx +41 -0
  398. package/src/date-time-field/index.ts +7 -0
  399. package/src/date-time-field/useDateTimeFieldSegmentState.ts +672 -0
  400. package/src/date-time-list/DateTimeListRoot.tsx +530 -0
  401. package/src/date-time-list/context.tsx +45 -0
  402. package/src/date-time-list/index.ts +16 -0
  403. package/src/date-time-picker/DateTimePickerRoot.tsx +210 -0
  404. package/src/date-time-picker/context.tsx +33 -0
  405. package/src/date-time-picker/index.ts +7 -0
  406. package/src/dialog/DialogRoot.tsx +1 -1
  407. package/src/dialog/types.ts +2 -1
  408. package/src/index.ts +21 -2
  409. package/src/month-calendar/MonthCalendarCell.tsx +53 -0
  410. package/src/month-calendar/MonthCalendarRoot.tsx +153 -0
  411. package/src/month-calendar/MonthCalendarYearCell.tsx +54 -0
  412. package/src/month-calendar/context.tsx +30 -0
  413. package/src/month-calendar/index.ts +6 -0
  414. package/src/month-picker/MonthPickerRoot.tsx +183 -0
  415. package/src/month-picker/context.tsx +31 -0
  416. package/src/month-picker/index.ts +3 -0
  417. package/src/overlay/useAnchorPosition.ts +25 -11
  418. package/src/overlay/useDismissOverlay.web.ts +10 -2
  419. package/src/overlay/useOverlayPosition.ts +5 -2
  420. package/src/portal/portal.tsx +79 -2
  421. package/src/radio/RadioGroup.tsx +84 -20
  422. package/src/radio/RadioRoot.web.tsx +136 -31
  423. package/src/radio/context.tsx +6 -1
  424. package/src/radio/types.ts +6 -0
  425. package/src/radio/useRadioGroupNavigation.ts +104 -0
  426. package/src/radio/useRadioRoot.ts +18 -11
  427. package/src/range-calendar/RangeCalendarRoot.tsx +213 -0
  428. package/src/range-calendar/context.tsx +33 -0
  429. package/src/range-calendar/index.ts +4 -0
  430. package/src/select/SelectRoot.tsx +1 -1
  431. package/src/select/types.ts +2 -1
  432. package/src/utils/accessibleLabel.ts +22 -0
  433. package/src/utils/createPortal.web.ts +18 -0
@@ -0,0 +1,183 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ type ComponentRef,
9
+ type ReactNode,
10
+ } from 'react';
11
+ import { View, type ViewProps } from 'react-native';
12
+ import type { DateComponentLabels, MonthId } from '@cdx-ui/formatters';
13
+ import { resolveDateComponentLabels } from '@cdx-ui/formatters';
14
+ import { mergeRefs, useControllableState } from '@cdx-ui/utils';
15
+ import { DateFieldRoot } from '../date-field/DateFieldRoot';
16
+ import { Slot } from '../slot';
17
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
18
+ import type { PickerSurfaceType } from '../date-picker/context';
19
+ import { MonthPickerContext } from './context';
20
+
21
+ export interface MonthPickerRootProps extends Omit<ViewProps, 'children'> {
22
+ asChild?: boolean;
23
+ children?: ReactNode;
24
+ className?: string;
25
+ value?: MonthId;
26
+ defaultValue?: MonthId;
27
+ /** Called with the new `MonthId`, or `undefined` when the selection is cleared. */
28
+ onValueChange?: (value: MonthId | undefined) => void;
29
+ /** Controlled open state. Use `defaultOpen` for the uncontrolled variant. */
30
+ isOpen?: boolean;
31
+ defaultOpen?: boolean;
32
+ onOpenChange?: (open: boolean) => void;
33
+ isDisabled?: boolean;
34
+ isReadOnly?: boolean;
35
+ isInvalid?: boolean;
36
+ isRequired?: boolean;
37
+ /** Earliest selectable month, applied to the embedded calendar. Build with `monthId(...)`. */
38
+ minValue?: MonthId;
39
+ /** Latest selectable month, applied to the embedded calendar. Build with `monthId(...)`. */
40
+ maxValue?: MonthId;
41
+ name?: string;
42
+ locale?: string;
43
+ labels?: Partial<DateComponentLabels>;
44
+ }
45
+
46
+ export const MonthPickerRoot = forwardRef<ComponentRef<typeof View>, MonthPickerRootProps>(
47
+ (
48
+ {
49
+ asChild,
50
+ children,
51
+ value: valueProp,
52
+ defaultValue,
53
+ onValueChange,
54
+ isOpen: openProp,
55
+ defaultOpen = false,
56
+ onOpenChange,
57
+ isDisabled: disabled = false,
58
+ isReadOnly,
59
+ isInvalid,
60
+ isRequired,
61
+ minValue,
62
+ maxValue,
63
+ locale = 'en-US',
64
+ labels: labelsProp,
65
+ ...props
66
+ },
67
+ ref,
68
+ ) => {
69
+ const labels = useMemo(() => resolveDateComponentLabels(labelsProp), [labelsProp]);
70
+ const [value, setValue] = useControllableState<MonthId | undefined>({
71
+ prop: valueProp,
72
+ defaultProp: defaultValue,
73
+ onChange: onValueChange,
74
+ });
75
+
76
+ const [open, setOpenState] = useControllableState<boolean>({
77
+ prop: openProp,
78
+ defaultProp: defaultOpen,
79
+ onChange: onOpenChange,
80
+ });
81
+
82
+ const [draftValue, setDraftValue] = useState<MonthId | undefined>(value);
83
+
84
+ const surfacesRef = useRef<Set<PickerSurfaceType>>(new Set());
85
+ const anchorRef = useRef<ComponentRef<typeof View>>(null);
86
+ const triggerRef = useRef<ComponentRef<typeof View>>(null);
87
+
88
+ const setOpen = useCallback(
89
+ (next: boolean) => {
90
+ if (disabled && next) {
91
+ return;
92
+ }
93
+ if (next) {
94
+ setDraftValue(value);
95
+ }
96
+ setOpenState(next);
97
+ },
98
+ [disabled, setOpenState, value],
99
+ );
100
+
101
+ const registerSurface = useCallback((type: PickerSurfaceType) => {
102
+ if (surfacesRef.current.has(type)) {
103
+ return;
104
+ }
105
+ surfacesRef.current.add(type);
106
+ if (surfacesRef.current.size > 1) {
107
+ console.warn(
108
+ '[Forge UI] MonthPicker: multiple presentation surfaces detected. Use only one of Popover, Dialog, or BottomSheet.',
109
+ );
110
+ }
111
+ }, []);
112
+
113
+ useEffect(() => {
114
+ setDraftValue(value);
115
+ }, [value]);
116
+
117
+ useEffect(() => {
118
+ if (!open) {
119
+ surfacesRef.current.clear();
120
+ }
121
+ }, [open]);
122
+
123
+ const contextValue = useMemo(
124
+ () => ({
125
+ value,
126
+ onValueChange: setValue,
127
+ draftValue,
128
+ setDraftValue,
129
+ open: open ?? false,
130
+ setOpen,
131
+ disabled,
132
+ minValue,
133
+ maxValue,
134
+ locale,
135
+ labels,
136
+ registerSurface,
137
+ anchorRef,
138
+ triggerRef,
139
+ }),
140
+ [
141
+ value,
142
+ setValue,
143
+ draftValue,
144
+ open,
145
+ setOpen,
146
+ disabled,
147
+ minValue,
148
+ maxValue,
149
+ locale,
150
+ labels,
151
+ registerSurface,
152
+ ],
153
+ );
154
+
155
+ const Comp = asChild ? Slot : View;
156
+
157
+ return (
158
+ <MonthPickerContext.Provider value={contextValue}>
159
+ <Comp
160
+ ref={mergeRefs(ref, anchorRef)}
161
+ {...props}
162
+ {...mergeDataAttributes(props, dataAttributes({ disabled, open }))}
163
+ >
164
+ <DateFieldRoot
165
+ value={value}
166
+ onValueChange={(next) => setValue(next as MonthId | undefined)}
167
+ mode="month"
168
+ locale={locale}
169
+ labels={labelsProp}
170
+ isDisabled={disabled}
171
+ isReadOnly={isReadOnly}
172
+ isInvalid={isInvalid}
173
+ isRequired={isRequired}
174
+ >
175
+ {children}
176
+ </DateFieldRoot>
177
+ </Comp>
178
+ </MonthPickerContext.Provider>
179
+ );
180
+ },
181
+ );
182
+
183
+ MonthPickerRoot.displayName = 'MonthPickerRoot';
@@ -0,0 +1,31 @@
1
+ import { createContext, useContext, type RefObject } from 'react';
2
+ import type { View } from 'react-native';
3
+ import type { DateComponentLabels, MonthId } from '@cdx-ui/formatters';
4
+ import type { PickerSurfaceType } from '../date-picker/context';
5
+
6
+ export interface MonthPickerContextValue {
7
+ value?: MonthId;
8
+ onValueChange?: (value: MonthId | undefined) => void;
9
+ draftValue?: MonthId;
10
+ setDraftValue: (value: MonthId | undefined) => void;
11
+ open: boolean;
12
+ setOpen: (open: boolean) => void;
13
+ disabled: boolean;
14
+ minValue?: MonthId;
15
+ maxValue?: MonthId;
16
+ locale: string;
17
+ labels: DateComponentLabels;
18
+ registerSurface: (type: PickerSurfaceType) => void;
19
+ anchorRef: RefObject<View | null>;
20
+ triggerRef: RefObject<View | null>;
21
+ }
22
+
23
+ export const MonthPickerContext = createContext<MonthPickerContextValue | null>(null);
24
+
25
+ export function useMonthPickerContext(): MonthPickerContextValue {
26
+ const ctx = useContext(MonthPickerContext);
27
+ if (!ctx) {
28
+ throw new Error('MonthPicker compound components must be used within MonthPickerRoot');
29
+ }
30
+ return ctx;
31
+ }
@@ -0,0 +1,3 @@
1
+ export { MonthPickerRoot } from './MonthPickerRoot';
2
+ export type { MonthPickerRootProps } from './MonthPickerRoot';
3
+ export { MonthPickerContext, useMonthPickerContext, type MonthPickerContextValue } from './context';
@@ -78,21 +78,35 @@ export function useAnchorPosition(
78
78
 
79
79
  let cancelled = false;
80
80
 
81
- if (Platform.OS === 'web') {
82
- measureAnchor();
83
- } else {
84
- const anchor = anchorRef.current;
85
- if (!anchor) {
86
- return;
81
+ const runMeasure = () => {
82
+ if (!cancelled) {
83
+ measureAnchor();
87
84
  }
85
+ };
86
+
87
+ runMeasure();
88
88
 
89
- anchor.measureInWindow((x: number, y: number, width: number, height: number) => {
90
- if (!cancelled) {
91
- setLayoutIfChanged(setLayout, { x, y, width, height });
92
- }
93
- });
89
+ if (Platform.OS === 'web') {
90
+ const frame = requestAnimationFrame(runMeasure);
91
+ return () => {
92
+ cancelled = true;
93
+ cancelAnimationFrame(frame);
94
+ };
94
95
  }
95
96
 
97
+ const anchor = anchorRef.current;
98
+ if (!anchor) {
99
+ return () => {
100
+ cancelled = true;
101
+ };
102
+ }
103
+
104
+ anchor.measureInWindow((x: number, y: number, width: number, height: number) => {
105
+ if (!cancelled) {
106
+ setLayoutIfChanged(setLayout, { x, y, width, height });
107
+ }
108
+ });
109
+
96
110
  return () => {
97
111
  cancelled = true;
98
112
  };
@@ -1,6 +1,14 @@
1
1
  import type { RefObject } from 'react';
2
2
  import { useEffect, useRef } from 'react';
3
3
 
4
+ function refContainsNode(ref: RefObject<unknown>, target: Node): boolean {
5
+ const current = ref.current as { contains?: (node: Node) => boolean } | null;
6
+ if (!current || typeof current.contains !== 'function') {
7
+ return false;
8
+ }
9
+ return current.contains(target);
10
+ }
11
+
4
12
  /**
5
13
  * Dismisses an overlay when the user clicks/presses outside of it or when
6
14
  * focus moves to an element outside the overlay via keyboard.
@@ -24,11 +32,11 @@ export function useDismissOverlay(
24
32
  }
25
33
 
26
34
  const isInsideOverlay = (target: Node) => {
27
- if (contentRef.current?.contains?.(target)) {
35
+ if (refContainsNode(contentRef, target)) {
28
36
  return true;
29
37
  }
30
38
  for (const ref of excludeRefsRef.current) {
31
- if (ref.current?.contains?.(target)) {
39
+ if (refContainsNode(ref, target)) {
32
40
  return true;
33
41
  }
34
42
  }
@@ -37,6 +37,9 @@ export const OverlayInsetsProvider = OverlayInsetsContext.Provider;
37
37
  /** Minimum gap (px) between the overlay edge and the viewport/safe-area boundary. */
38
38
  const SCREEN_EDGE_PADDING = 8;
39
39
 
40
+ /** Gap between anchor and popover content. */
41
+ const POPOVER_GAP = 4;
42
+
40
43
  /** Minimum usable space (px) below the anchor before the overlay flips above. */
41
44
  const MIN_CONTENT_HEIGHT = 200;
42
45
 
@@ -71,7 +74,7 @@ export function useOverlayPosition(anchorLayout: AnchorLayout | null): OverlayPo
71
74
  const maxHeight = Math.max(0, spaceBelow);
72
75
  return {
73
76
  style: {
74
- top: anchorBottom,
77
+ top: anchorBottom + POPOVER_GAP,
75
78
  left: anchorLayout.x,
76
79
  width: anchorLayout.width,
77
80
  maxHeight,
@@ -84,7 +87,7 @@ export function useOverlayPosition(anchorLayout: AnchorLayout | null): OverlayPo
84
87
  const maxHeight = Math.max(0, spaceAbove);
85
88
  return {
86
89
  style: {
87
- bottom: viewportHeight - anchorLayout.y,
90
+ bottom: viewportHeight - anchorLayout.y + POPOVER_GAP,
88
91
  left: anchorLayout.x,
89
92
  width: anchorLayout.width,
90
93
  maxHeight,
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect, useSyncExternalStore } from 'react';
3
3
  import { Platform, type View, type ViewStyle } from 'react-native';
4
+ import { createPortalFn } from '../utils/createPortal';
4
5
 
5
6
  const DEFAULT_PORTAL_HOST = 'FORGE_UI_DEFAULT_HOST_NAME';
6
7
 
@@ -17,12 +18,67 @@ const store: PortalStore = {
17
18
  listeners: new Set(),
18
19
  };
19
20
 
21
+ /** Tracks mounted PortalHost instances per host name — only the first mount renders portals. */
22
+ const hostStackStore = {
23
+ stacks: new Map<string, symbol[]>(),
24
+ listeners: new Set<() => void>(),
25
+ };
26
+
27
+ let hasWarnedMultiplePortalHosts = false;
28
+
20
29
  // --------------------------------------------------
21
30
 
22
31
  function emit() {
23
32
  for (const cb of store.listeners) cb();
24
33
  }
25
34
 
35
+ function emitHostStack() {
36
+ for (const cb of hostStackStore.listeners) cb();
37
+ }
38
+
39
+ function getHostStackSnapshot() {
40
+ return hostStackStore.stacks;
41
+ }
42
+
43
+ function subscribeHostStack(cb: () => void) {
44
+ hostStackStore.listeners.add(cb);
45
+ return () => {
46
+ hostStackStore.listeners.delete(cb);
47
+ };
48
+ }
49
+
50
+ function pushPortalHost(name: string, id: symbol) {
51
+ const stacks = new Map(hostStackStore.stacks);
52
+ const stack = [...(stacks.get(name) ?? [])];
53
+ stack.push(id);
54
+ stacks.set(name, stack);
55
+ hostStackStore.stacks = stacks;
56
+
57
+ if (__DEV__ && !hasWarnedMultiplePortalHosts && stack.length > 1) {
58
+ hasWarnedMultiplePortalHosts = true;
59
+ console.warn(
60
+ '[Forge UI] Multiple PortalHost instances detected for the same host name. ' +
61
+ 'Only the first mounted PortalHost renders portal content. ' +
62
+ 'Mount a single PortalHost near your app root (ForgeDesignProvider does this automatically).',
63
+ );
64
+ }
65
+
66
+ emitHostStack();
67
+ }
68
+
69
+ function popPortalHost(name: string, id: symbol) {
70
+ const stacks = new Map(hostStackStore.stacks);
71
+ const stack = (stacks.get(name) ?? []).filter((entry) => entry !== id);
72
+ stacks.set(name, stack);
73
+ hostStackStore.stacks = stacks;
74
+ emitHostStack();
75
+ }
76
+
77
+ function isActivePortalHost(name: string, id: symbol) {
78
+ const stack = hostStackStore.stacks.get(name) ?? [];
79
+ return stack.length > 0 && stack[0] === id;
80
+ }
81
+
26
82
  function getSnapshot() {
27
83
  return store.map;
28
84
  }
@@ -55,10 +111,31 @@ function removePortal(hostName: string, name: string) {
55
111
  // --------------------------------------------------
56
112
 
57
113
  export function PortalHost({ name = DEFAULT_PORTAL_HOST }: { name?: string }) {
114
+ const hostId = React.useRef(Symbol()).current;
115
+
116
+ useEffect(() => {
117
+ pushPortalHost(name, hostId);
118
+ return () => {
119
+ popPortalHost(name, hostId);
120
+ };
121
+ }, [name, hostId]);
122
+
123
+ useSyncExternalStore(subscribeHostStack, getHostStackSnapshot, getHostStackSnapshot);
124
+
58
125
  const map = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
59
126
  const portalMap = map.get(name) ?? new Map<string, React.ReactNode>();
60
- if (portalMap.size === 0) return null;
61
- return <>{Array.from(portalMap.values())}</>;
127
+
128
+ if (!isActivePortalHost(name, hostId) || portalMap.size === 0) {
129
+ return null;
130
+ }
131
+
132
+ const content = <>{Array.from(portalMap.values())}</>;
133
+
134
+ if (Platform.OS === 'web' && createPortalFn && typeof document !== 'undefined') {
135
+ return createPortalFn(content, document.body);
136
+ }
137
+
138
+ return content;
62
139
  }
63
140
 
64
141
  // --------------------------------------------------
@@ -1,12 +1,13 @@
1
- import { forwardRef } from 'react';
2
- import { View, type ViewProps } from 'react-native';
3
- import { useFormControlContext } from '@cdx-ui/utils';
1
+ import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
2
+ import { Platform, View, type ViewProps } from 'react-native';
3
+ import { composeEventHandlers, useFormControl } from '@cdx-ui/utils';
4
4
  import { useRadioGroup } from '@react-native-aria/radio';
5
5
  import { useRadioGroupState } from '@react-stately/radio';
6
6
  import { Slot } from '../slot';
7
7
  import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
8
8
  import { RadioGroupContext } from './context';
9
- import type { IRadioGroupProps } from './types';
9
+ import type { IRadioGroupProps, RegisteredRadio } from './types';
10
+ import { useRadioGroupNavigation } from './useRadioGroupNavigation';
10
11
 
11
12
  export interface RadioGroupRootProps extends IRadioGroupProps {
12
13
  asChild?: boolean;
@@ -16,30 +17,86 @@ export interface RadioGroupRootProps extends IRadioGroupProps {
16
17
 
17
18
  export const RadioGroup = forwardRef<View, RadioGroupRootProps>(
18
19
  ({ asChild, children, className, style, ...props }, ref) => {
19
- const formControlContext = useFormControlContext();
20
+ const formControlProps = useFormControl({
21
+ isDisabled: props.isDisabled,
22
+ isRequired: props.isRequired,
23
+ isInvalid: props.isInvalid,
24
+ isReadOnly: props.isReadOnly,
25
+ });
20
26
 
21
- const combinedProps = {
22
- ...formControlContext,
23
- ...props,
24
- };
27
+ const isDisabled = props.isDisabled ?? Boolean(formControlProps.disabled);
28
+ const isInvalid = props.isInvalid ?? Boolean(formControlProps['aria-invalid']);
29
+ const isRequired = props.isRequired ?? Boolean(formControlProps.required);
30
+ const isReadOnly = props.isReadOnly ?? Boolean(formControlProps.readOnly);
31
+ const name = props.name ?? formControlProps.name;
32
+ const ariaLabel = props['aria-label'];
33
+ const ariaLabelledBy = props['aria-labelledby'] ?? formControlProps['aria-labelledby'];
34
+ const ariaDescribedBy = formControlProps['aria-describedby'];
25
35
 
26
36
  const radioGroupState = useRadioGroupState({
27
- ...combinedProps,
28
- validationState: combinedProps.isInvalid ? 'invalid' : 'valid',
37
+ name,
38
+ value: props.value,
39
+ defaultValue: props.defaultValue,
40
+ onChange: props.onChange,
41
+ isDisabled,
42
+ isInvalid,
43
+ isRequired,
44
+ isReadOnly,
45
+ validationState: isInvalid ? 'invalid' : 'valid',
29
46
  });
30
47
 
31
48
  const { radioGroupProps } = useRadioGroup(
32
49
  {
33
- ...combinedProps,
34
- 'aria-label': combinedProps['aria-label'],
50
+ 'aria-label': ariaLabel,
51
+ 'aria-labelledby': ariaLabelledBy,
52
+ isDisabled,
35
53
  },
36
54
  radioGroupState,
37
55
  );
38
56
 
39
- const isDisabled = combinedProps.isDisabled ?? false;
40
- const isInvalid = combinedProps.isInvalid ?? false;
41
- const isRequired = combinedProps.isRequired ?? false;
42
- const isReadOnly = combinedProps.isReadOnly ?? false;
57
+ const [focusedValue, setFocusedValue] = useState<string | undefined>(undefined);
58
+ // Identity of getRadios depends on this so roving tabIndex recomputes after register/unregister.
59
+ const [registrationEpoch, setRegistrationEpoch] = useState(0);
60
+ const radiosRef = useRef<Map<string, RegisteredRadio>>(new Map());
61
+
62
+ useEffect(() => {
63
+ setFocusedValue((prev) =>
64
+ prev !== undefined && prev !== radioGroupState.selectedValue ? undefined : prev,
65
+ );
66
+ }, [radioGroupState.selectedValue]);
67
+
68
+ const registerRadio = useCallback((value: string, entry: RegisteredRadio) => {
69
+ radiosRef.current.set(value, entry);
70
+ setRegistrationEpoch((epoch) => epoch + 1);
71
+ }, []);
72
+
73
+ const unregisterRadio = useCallback((value: string) => {
74
+ radiosRef.current.delete(value);
75
+ setRegistrationEpoch((epoch) => epoch + 1);
76
+ }, []);
77
+
78
+ const getRadios = useCallback(
79
+ () => Array.from(radiosRef.current.entries()),
80
+ [registrationEpoch],
81
+ );
82
+
83
+ const { handleKeyDown } = useRadioGroupNavigation({
84
+ getRadios,
85
+ focusedValue,
86
+ setFocusedValue,
87
+ selectedValue: radioGroupState.selectedValue,
88
+ selectValue: (value: string) => radioGroupState.setSelectedValue(value),
89
+ });
90
+
91
+ const webProps =
92
+ Platform.OS === 'web'
93
+ ? {
94
+ onKeyDown: composeEventHandlers(
95
+ (props as { onKeyDown?: (e: KeyboardEvent) => void }).onKeyDown,
96
+ handleKeyDown,
97
+ ),
98
+ }
99
+ : {};
43
100
 
44
101
  const Comp = asChild ? Slot : View;
45
102
 
@@ -55,18 +112,25 @@ export const RadioGroup = forwardRef<View, RadioGroupRootProps>(
55
112
  selectValue: (value: string) => radioGroupState.setSelectedValue(value),
56
113
  },
57
114
  radioGroupState,
58
- name: combinedProps.name,
115
+ name,
116
+ focusedValue,
117
+ setFocusedValue,
118
+ registerRadio,
119
+ unregisterRadio,
120
+ getRadios,
59
121
  }}
60
122
  >
61
123
  <Comp
62
124
  {...radioGroupProps}
63
- aria-label={combinedProps['aria-label']}
64
- aria-labelledby={combinedProps['aria-labelledby']}
125
+ aria-label={ariaLabel}
126
+ aria-labelledby={ariaLabelledBy}
127
+ aria-describedby={ariaDescribedBy}
65
128
  aria-required={isRequired || undefined}
66
129
  aria-readonly={isReadOnly || undefined}
67
130
  className={className}
68
131
  style={style}
69
132
  ref={ref}
133
+ {...webProps}
70
134
  {...mergeDataAttributes(
71
135
  props,
72
136
  dataAttributes({