@cdx-ui/components 0.2.2 → 0.2.4

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 (373) hide show
  1. package/AGENTS.md +24 -0
  2. package/README.md +6 -2
  3. package/coverage.svg +1 -1
  4. package/docs/components/accordion/content.md +538 -0
  5. package/docs/components/alert/content.md +203 -0
  6. package/docs/components/alert-dialog/content.md +319 -0
  7. package/docs/components/avatar/content.md +368 -0
  8. package/docs/components/avatar/design.md +202 -0
  9. package/docs/components/badge/content.md +235 -0
  10. package/docs/components/bottom-sheet/content.md +351 -0
  11. package/docs/components/bottom-sheet/design.md +249 -0
  12. package/docs/components/breadcrumbs/content.md +169 -0
  13. package/docs/components/button/content.md +398 -0
  14. package/docs/components/button/design.md +258 -0
  15. package/docs/components/calendar/content.md +137 -0
  16. package/docs/components/card/content.md +283 -0
  17. package/docs/components/carousel/content.md +416 -0
  18. package/docs/components/checkbox/content.md +236 -0
  19. package/docs/components/checkbox/design.md +214 -0
  20. package/docs/components/chip/content.md +439 -0
  21. package/docs/components/chip/design.md +418 -0
  22. package/docs/components/color-area/content.md +86 -0
  23. package/docs/components/color-field/content.md +134 -0
  24. package/docs/components/color-picker/content.md +208 -0
  25. package/docs/components/color-slider/content.md +120 -0
  26. package/docs/components/color-swatch/content.md +64 -0
  27. package/docs/components/color-swatch-picker/content.md +126 -0
  28. package/docs/components/content.md +97 -0
  29. package/docs/components/currency-input/content.md +205 -0
  30. package/docs/components/data-grid/content.md +522 -0
  31. package/docs/components/date-field/content.md +147 -0
  32. package/docs/components/date-picker/content.md +360 -0
  33. package/docs/components/date-range-picker/content.md +301 -0
  34. package/docs/components/date-time-field/content.md +157 -0
  35. package/docs/components/date-time-list/content.md +105 -0
  36. package/docs/components/date-time-picker/content.md +272 -0
  37. package/docs/components/dialog/content.md +436 -0
  38. package/docs/components/dialog/design.md +192 -0
  39. package/docs/components/divider/content.md +112 -0
  40. package/docs/components/drawer/content.md +436 -0
  41. package/docs/components/field/content.md +277 -0
  42. package/docs/components/form/content.md +62 -0
  43. package/docs/components/full-screen-dialog/content.md +881 -0
  44. package/docs/components/heading/content.md +55 -0
  45. package/docs/components/icon/content.md +190 -0
  46. package/docs/components/icon-button/content.md +200 -0
  47. package/docs/components/icon-button/design.md +165 -0
  48. package/docs/components/image/content.md +104 -0
  49. package/docs/components/input/content.md +281 -0
  50. package/docs/components/input/design.md +389 -0
  51. package/docs/components/insurance-disclosure/content.md +103 -0
  52. package/docs/components/link/content.md +180 -0
  53. package/docs/components/link/design.md +160 -0
  54. package/docs/components/list-item/content.md +695 -0
  55. package/docs/components/menu/content.md +1247 -0
  56. package/docs/components/month-calendar/content.md +84 -0
  57. package/docs/components/month-picker/content.md +273 -0
  58. package/docs/components/otp-input/content.md +327 -0
  59. package/docs/components/progress-bar/content.md +87 -0
  60. package/docs/components/progress-circle/content.md +176 -0
  61. package/docs/components/progress-segmented/content.md +119 -0
  62. package/docs/components/quick-action-button/content.md +154 -0
  63. package/docs/components/quick-action-button/design.md +170 -0
  64. package/docs/components/radio/content.md +224 -0
  65. package/docs/components/range-calendar/content.md +123 -0
  66. package/docs/components/reorderable-list/content.md +309 -0
  67. package/docs/components/scoped-theme/content.md +47 -0
  68. package/docs/components/scroll-shadow/content.md +686 -0
  69. package/docs/components/select/content.md +400 -0
  70. package/docs/components/select/design.md +301 -0
  71. package/docs/components/skeleton/content.md +103 -0
  72. package/docs/components/slider/content.md +423 -0
  73. package/docs/components/stack/content.md +161 -0
  74. package/docs/components/switch/content.md +150 -0
  75. package/docs/components/switch/design.md +208 -0
  76. package/docs/components/table/content.md +548 -0
  77. package/docs/components/table-pagination/content.md +177 -0
  78. package/docs/components/tabs/content.md +292 -0
  79. package/docs/components/text/content.md +142 -0
  80. package/docs/components/textarea/content.md +222 -0
  81. package/docs/components/tile/content.md +703 -0
  82. package/docs/components/toast/content.md +456 -0
  83. package/docs/components/toggle-button/content.md +505 -0
  84. package/docs/components/tooltip/content.md +350 -0
  85. package/docs/components/virtualized-list/content.md +250 -0
  86. package/docs/foundations/content.md +9 -0
  87. package/docs/foundations/expressions/icons/content.md +19 -0
  88. package/docs/foundations/styles/tokens/all-tokens/content.md +192 -0
  89. package/docs/foundations/styles/tokens/what-are-tokens/content.md +33 -0
  90. package/docs/foundations/styles/tokens/what-are-tokens/design.md +33 -0
  91. package/docs/getting-started/content.md +4 -0
  92. package/docs/getting-started/designers/content.md +13 -0
  93. package/docs/getting-started/designers/design.md +13 -0
  94. package/docs/getting-started/developers/accessibility/content.md +31 -0
  95. package/docs/getting-started/developers/content.md +612 -0
  96. package/docs/getting-started/developers/styling/content.md +193 -0
  97. package/docs/llms.txt +193 -0
  98. package/docs/patterns/account-management/content.md +75 -0
  99. package/docs/patterns/authentication/content.md +52 -0
  100. package/docs/patterns/content.md +10 -0
  101. package/docs/patterns/dashboard/content.md +39 -0
  102. package/docs/patterns/empty-states/content.md +31 -0
  103. package/docs/patterns/forms/content.md +109 -0
  104. package/docs/patterns/onboarding/content.md +69 -0
  105. package/docs/patterns/payments/content.md +35 -0
  106. package/docs/patterns/settings/content.md +72 -0
  107. package/docs/resources/content.md +9 -0
  108. package/docs/resources/figma-libraries/content.md +42 -0
  109. package/docs/resources/release-notes/0.0.1-beta.118/content.md +27 -0
  110. package/docs/resources/release-notes/0.0.1-beta.122/content.md +21 -0
  111. package/docs/resources/release-notes/0.0.1-beta.124/content.md +16 -0
  112. package/docs/resources/release-notes/0.0.1-beta.126/content.md +10 -0
  113. package/docs/resources/release-notes/0.0.1-beta.127/content.md +14 -0
  114. package/docs/resources/release-notes/0.0.1-beta.128/content.md +9 -0
  115. package/docs/resources/release-notes/0.0.1-beta.129/content.md +18 -0
  116. package/docs/resources/release-notes/0.0.1-beta.130/content.md +18 -0
  117. package/docs/resources/release-notes/0.0.1-beta.133/content.md +7 -0
  118. package/docs/resources/release-notes/0.0.1-beta.134/content.md +17 -0
  119. package/docs/resources/release-notes/0.0.1-beta.136/content.md +7 -0
  120. package/docs/resources/release-notes/0.0.1-beta.137/content.md +10 -0
  121. package/docs/resources/release-notes/0.0.1-beta.138/content.md +7 -0
  122. package/docs/resources/release-notes/0.0.1-beta.139/content.md +13 -0
  123. package/docs/resources/release-notes/0.0.1-beta.140/content.md +26 -0
  124. package/docs/resources/release-notes/0.0.1-beta.141/content.md +63 -0
  125. package/docs/resources/release-notes/0.0.1-beta.142/content.md +7 -0
  126. package/docs/resources/release-notes/0.0.1-beta.143/content.md +12 -0
  127. package/docs/resources/release-notes/0.0.1-beta.144/content.md +7 -0
  128. package/docs/resources/release-notes/0.0.1-beta.145/content.md +7 -0
  129. package/docs/resources/release-notes/0.0.1-beta.147/content.md +10 -0
  130. package/docs/resources/release-notes/0.0.1-beta.149/content.md +10 -0
  131. package/docs/resources/release-notes/0.0.1-beta.150/content.md +13 -0
  132. package/docs/resources/release-notes/0.0.1-beta.152/content.md +7 -0
  133. package/docs/resources/release-notes/0.0.1-beta.154/content.md +15 -0
  134. package/docs/resources/release-notes/0.0.1-beta.156/content.md +7 -0
  135. package/docs/resources/release-notes/0.0.1-beta.158/content.md +9 -0
  136. package/docs/resources/release-notes/0.0.1-beta.159/content.md +11 -0
  137. package/docs/resources/release-notes/0.0.1-beta.160/content.md +7 -0
  138. package/docs/resources/release-notes/0.0.1-beta.161/content.md +7 -0
  139. package/docs/resources/release-notes/0.0.1-beta.163/content.md +7 -0
  140. package/docs/resources/release-notes/0.0.1-beta.164/content.md +9 -0
  141. package/docs/resources/release-notes/0.0.1-beta.166/content.md +17 -0
  142. package/docs/resources/release-notes/0.0.1-beta.167/content.md +7 -0
  143. package/docs/resources/release-notes/0.0.1-beta.168/content.md +7 -0
  144. package/docs/resources/release-notes/0.0.1-beta.169/content.md +7 -0
  145. package/docs/resources/release-notes/0.0.1-beta.171/content.md +16 -0
  146. package/docs/resources/release-notes/0.0.1-beta.172/content.md +24 -0
  147. package/docs/resources/release-notes/0.0.1-beta.173/content.md +15 -0
  148. package/docs/resources/release-notes/0.0.1-beta.174/content.md +7 -0
  149. package/docs/resources/release-notes/0.0.1-beta.176/content.md +32 -0
  150. package/docs/resources/release-notes/0.0.1-beta.179/content.md +11 -0
  151. package/docs/resources/release-notes/0.0.1-beta.182/content.md +13 -0
  152. package/docs/resources/release-notes/0.0.1-beta.185/content.md +7 -0
  153. package/docs/resources/release-notes/0.0.1-beta.187/content.md +27 -0
  154. package/docs/resources/release-notes/0.1.1/content.md +23 -0
  155. package/docs/resources/release-notes/0.1.2/content.md +7 -0
  156. package/docs/resources/release-notes/0.1.4/content.md +19 -0
  157. package/docs/resources/release-notes/0.1.5/content.md +7 -0
  158. package/docs/resources/release-notes/0.1.6/content.md +7 -0
  159. package/docs/resources/release-notes/0.2.0/content.md +31 -0
  160. package/docs/resources/release-notes/0.2.1/content.md +17 -0
  161. package/docs/resources/release-notes/0.2.2/content.md +11 -0
  162. package/docs/resources/release-notes/0.2.3/content.md +15 -0
  163. package/docs/resources/release-notes/content.md +64 -0
  164. package/lib/commonjs/components/Checkbox/index.js.map +1 -1
  165. package/lib/commonjs/components/DateTimeList/index.js +11 -26
  166. package/lib/commonjs/components/DateTimeList/index.js.map +1 -1
  167. package/lib/commonjs/components/DateTimeList/styles.js +2 -12
  168. package/lib/commonjs/components/DateTimeList/styles.js.map +1 -1
  169. package/lib/commonjs/components/Heading/index.js.map +1 -1
  170. package/lib/commonjs/components/Icon/index.js.map +1 -1
  171. package/lib/commonjs/components/ListItem/index.js +170 -9
  172. package/lib/commonjs/components/ListItem/index.js.map +1 -1
  173. package/lib/commonjs/components/ListItem/styles.js +38 -2
  174. package/lib/commonjs/components/ListItem/styles.js.map +1 -1
  175. package/lib/commonjs/components/Menu/index.js +11 -5
  176. package/lib/commonjs/components/Menu/index.js.map +1 -1
  177. package/lib/commonjs/components/RangeCalendar/index.js.map +1 -1
  178. package/lib/commonjs/components/ReorderableList/ReorderableListShared.js +91 -0
  179. package/lib/commonjs/components/ReorderableList/ReorderableListShared.js.map +1 -0
  180. package/lib/commonjs/components/ReorderableList/index.js +135 -0
  181. package/lib/commonjs/components/ReorderableList/index.js.map +1 -0
  182. package/lib/commonjs/components/ReorderableList/index.web.js +233 -0
  183. package/lib/commonjs/components/ReorderableList/index.web.js.map +1 -0
  184. package/lib/commonjs/components/ReorderableList/labels.js +47 -0
  185. package/lib/commonjs/components/ReorderableList/labels.js.map +1 -0
  186. package/lib/commonjs/components/ReorderableList/reorderItems.js +16 -0
  187. package/lib/commonjs/components/ReorderableList/reorderItems.js.map +1 -0
  188. package/lib/commonjs/components/ReorderableList/rowContext.js +42 -0
  189. package/lib/commonjs/components/ReorderableList/rowContext.js.map +1 -0
  190. package/lib/commonjs/components/ReorderableList/types.js +6 -0
  191. package/lib/commonjs/components/ReorderableList/types.js.map +1 -0
  192. package/lib/commonjs/components/ReorderableList/useReorder.js +131 -0
  193. package/lib/commonjs/components/ReorderableList/useReorder.js.map +1 -0
  194. package/lib/commonjs/components/ReorderableList/webDnd.js +64 -0
  195. package/lib/commonjs/components/ReorderableList/webDnd.js.map +1 -0
  196. package/lib/commonjs/components/ScopedTheme/index.js.map +1 -1
  197. package/lib/commonjs/components/ScrollShadow/gradient.js +29 -0
  198. package/lib/commonjs/components/ScrollShadow/gradient.js.map +1 -0
  199. package/lib/commonjs/components/ScrollShadow/index.js +171 -44
  200. package/lib/commonjs/components/ScrollShadow/index.js.map +1 -1
  201. package/lib/commonjs/components/ScrollShadow/styles.js +58 -14
  202. package/lib/commonjs/components/ScrollShadow/styles.js.map +1 -1
  203. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js +63 -0
  204. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  205. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +48 -8
  206. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
  207. package/lib/commonjs/components/Skeleton/index.js.map +1 -1
  208. package/lib/commonjs/components/TablePagination/index.js.map +1 -1
  209. package/lib/commonjs/components/Tabs/index.js +16 -23
  210. package/lib/commonjs/components/Tabs/index.js.map +1 -1
  211. package/lib/commonjs/components/Tabs/styles.js +1 -10
  212. package/lib/commonjs/components/Tabs/styles.js.map +1 -1
  213. package/lib/commonjs/components/Text/index.js.map +1 -1
  214. package/lib/commonjs/components/Tile/styles.js +1 -1
  215. package/lib/commonjs/components/Tile/styles.js.map +1 -1
  216. package/lib/commonjs/components/index.js.map +1 -1
  217. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js +20 -5
  218. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js.map +1 -1
  219. package/lib/commonjs/reorderable-list.js +17 -0
  220. package/lib/commonjs/reorderable-list.js.map +1 -0
  221. package/lib/module/components/Checkbox/index.js.map +1 -1
  222. package/lib/module/components/DateTimeList/index.js +12 -27
  223. package/lib/module/components/DateTimeList/index.js.map +1 -1
  224. package/lib/module/components/DateTimeList/styles.js +1 -11
  225. package/lib/module/components/DateTimeList/styles.js.map +1 -1
  226. package/lib/module/components/Heading/index.js.map +1 -1
  227. package/lib/module/components/Icon/index.js.map +1 -1
  228. package/lib/module/components/ListItem/index.js +163 -14
  229. package/lib/module/components/ListItem/index.js.map +1 -1
  230. package/lib/module/components/ListItem/styles.js +37 -1
  231. package/lib/module/components/ListItem/styles.js.map +1 -1
  232. package/lib/module/components/Menu/index.js +11 -5
  233. package/lib/module/components/Menu/index.js.map +1 -1
  234. package/lib/module/components/RangeCalendar/index.js.map +1 -1
  235. package/lib/module/components/ReorderableList/ReorderableListShared.js +86 -0
  236. package/lib/module/components/ReorderableList/ReorderableListShared.js.map +1 -0
  237. package/lib/module/components/ReorderableList/index.js +130 -0
  238. package/lib/module/components/ReorderableList/index.js.map +1 -0
  239. package/lib/module/components/ReorderableList/index.web.js +229 -0
  240. package/lib/module/components/ReorderableList/index.web.js.map +1 -0
  241. package/lib/module/components/ReorderableList/labels.js +41 -0
  242. package/lib/module/components/ReorderableList/labels.js.map +1 -0
  243. package/lib/module/components/ReorderableList/reorderItems.js +12 -0
  244. package/lib/module/components/ReorderableList/reorderItems.js.map +1 -0
  245. package/lib/module/components/ReorderableList/rowContext.js +38 -0
  246. package/lib/module/components/ReorderableList/rowContext.js.map +1 -0
  247. package/lib/module/components/ReorderableList/types.js +4 -0
  248. package/lib/module/components/ReorderableList/types.js.map +1 -0
  249. package/lib/module/components/ReorderableList/useReorder.js +123 -0
  250. package/lib/module/components/ReorderableList/useReorder.js.map +1 -0
  251. package/lib/module/components/ReorderableList/webDnd.js +59 -0
  252. package/lib/module/components/ReorderableList/webDnd.js.map +1 -0
  253. package/lib/module/components/ScopedTheme/index.js.map +1 -1
  254. package/lib/module/components/ScrollShadow/gradient.js +24 -0
  255. package/lib/module/components/ScrollShadow/gradient.js.map +1 -0
  256. package/lib/module/components/ScrollShadow/index.js +170 -48
  257. package/lib/module/components/ScrollShadow/index.js.map +1 -1
  258. package/lib/module/components/ScrollShadow/styles.js +57 -13
  259. package/lib/module/components/ScrollShadow/styles.js.map +1 -1
  260. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js +57 -0
  261. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  262. package/lib/module/components/ScrollShadow/useScrollShadow.js +46 -8
  263. package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
  264. package/lib/module/components/Skeleton/index.js.map +1 -1
  265. package/lib/module/components/TablePagination/index.js.map +1 -1
  266. package/lib/module/components/Tabs/index.js +17 -24
  267. package/lib/module/components/Tabs/index.js.map +1 -1
  268. package/lib/module/components/Tabs/styles.js +0 -9
  269. package/lib/module/components/Tabs/styles.js.map +1 -1
  270. package/lib/module/components/Text/index.js.map +1 -1
  271. package/lib/module/components/Tile/styles.js +1 -1
  272. package/lib/module/components/Tile/styles.js.map +1 -1
  273. package/lib/module/components/index.js.map +1 -1
  274. package/lib/module/figma/ListItem.SectionHeader.figma.js +20 -5
  275. package/lib/module/figma/ListItem.SectionHeader.figma.js.map +1 -1
  276. package/lib/module/reorderable-list.js +4 -0
  277. package/lib/module/reorderable-list.js.map +1 -0
  278. package/lib/typescript/components/Checkbox/index.d.ts +22 -0
  279. package/lib/typescript/components/Checkbox/index.d.ts.map +1 -1
  280. package/lib/typescript/components/DateTimeList/index.d.ts.map +1 -1
  281. package/lib/typescript/components/DateTimeList/styles.d.ts +1 -4
  282. package/lib/typescript/components/DateTimeList/styles.d.ts.map +1 -1
  283. package/lib/typescript/components/Heading/index.d.ts +20 -2
  284. package/lib/typescript/components/Heading/index.d.ts.map +1 -1
  285. package/lib/typescript/components/Icon/index.d.ts +2 -0
  286. package/lib/typescript/components/Icon/index.d.ts.map +1 -1
  287. package/lib/typescript/components/ListItem/index.d.ts +28 -5
  288. package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
  289. package/lib/typescript/components/ListItem/styles.d.ts +6 -1
  290. package/lib/typescript/components/ListItem/styles.d.ts.map +1 -1
  291. package/lib/typescript/components/Menu/index.d.ts +116 -4
  292. package/lib/typescript/components/Menu/index.d.ts.map +1 -1
  293. package/lib/typescript/components/RangeCalendar/index.d.ts +2 -0
  294. package/lib/typescript/components/RangeCalendar/index.d.ts.map +1 -1
  295. package/lib/typescript/components/ReorderableList/ReorderableListShared.d.ts +31 -0
  296. package/lib/typescript/components/ReorderableList/ReorderableListShared.d.ts.map +1 -0
  297. package/lib/typescript/components/ReorderableList/index.d.ts +8 -0
  298. package/lib/typescript/components/ReorderableList/index.d.ts.map +1 -0
  299. package/lib/typescript/components/ReorderableList/index.web.d.ts +8 -0
  300. package/lib/typescript/components/ReorderableList/index.web.d.ts.map +1 -0
  301. package/lib/typescript/components/ReorderableList/labels.d.ts +16 -0
  302. package/lib/typescript/components/ReorderableList/labels.d.ts.map +1 -0
  303. package/lib/typescript/components/ReorderableList/reorderItems.d.ts +3 -0
  304. package/lib/typescript/components/ReorderableList/reorderItems.d.ts.map +1 -0
  305. package/lib/typescript/components/ReorderableList/rowContext.d.ts +57 -0
  306. package/lib/typescript/components/ReorderableList/rowContext.d.ts.map +1 -0
  307. package/lib/typescript/components/ReorderableList/types.d.ts +75 -0
  308. package/lib/typescript/components/ReorderableList/types.d.ts.map +1 -0
  309. package/lib/typescript/components/ReorderableList/useReorder.d.ts +31 -0
  310. package/lib/typescript/components/ReorderableList/useReorder.d.ts.map +1 -0
  311. package/lib/typescript/components/ReorderableList/webDnd.d.ts +27 -0
  312. package/lib/typescript/components/ReorderableList/webDnd.d.ts.map +1 -0
  313. package/lib/typescript/components/ScopedTheme/index.d.ts +6 -1
  314. package/lib/typescript/components/ScopedTheme/index.d.ts.map +1 -1
  315. package/lib/typescript/components/ScrollShadow/gradient.d.ts +7 -0
  316. package/lib/typescript/components/ScrollShadow/gradient.d.ts.map +1 -0
  317. package/lib/typescript/components/ScrollShadow/index.d.ts +57 -15
  318. package/lib/typescript/components/ScrollShadow/index.d.ts.map +1 -1
  319. package/lib/typescript/components/ScrollShadow/styles.d.ts +8 -2
  320. package/lib/typescript/components/ScrollShadow/styles.d.ts.map +1 -1
  321. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts +13 -0
  322. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts.map +1 -0
  323. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts +15 -2
  324. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
  325. package/lib/typescript/components/Skeleton/index.d.ts +3 -0
  326. package/lib/typescript/components/Skeleton/index.d.ts.map +1 -1
  327. package/lib/typescript/components/TablePagination/index.d.ts +54 -0
  328. package/lib/typescript/components/TablePagination/index.d.ts.map +1 -1
  329. package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
  330. package/lib/typescript/components/Tabs/styles.d.ts +0 -7
  331. package/lib/typescript/components/Tabs/styles.d.ts.map +1 -1
  332. package/lib/typescript/components/Text/index.d.ts +12 -2
  333. package/lib/typescript/components/Text/index.d.ts.map +1 -1
  334. package/lib/typescript/components/Tile/styles.d.ts.map +1 -1
  335. package/lib/typescript/components/ToggleButton/styles.d.ts +1 -1
  336. package/lib/typescript/components/index.d.ts +1 -0
  337. package/lib/typescript/components/index.d.ts.map +1 -1
  338. package/lib/typescript/figma/ListItem.SectionHeader.figma.d.ts.map +1 -1
  339. package/lib/typescript/reorderable-list.d.ts +2 -0
  340. package/lib/typescript/reorderable-list.d.ts.map +1 -0
  341. package/package.json +37 -8
  342. package/src/components/Checkbox/index.tsx +22 -0
  343. package/src/components/DateTimeList/index.tsx +13 -26
  344. package/src/components/DateTimeList/styles.ts +1 -10
  345. package/src/components/Heading/index.tsx +10 -1
  346. package/src/components/Icon/index.tsx +2 -0
  347. package/src/components/ListItem/index.tsx +232 -15
  348. package/src/components/ListItem/styles.ts +41 -0
  349. package/src/components/Menu/index.tsx +128 -12
  350. package/src/components/RangeCalendar/index.tsx +2 -0
  351. package/src/components/ReorderableList/ReorderableListShared.tsx +113 -0
  352. package/src/components/ReorderableList/index.tsx +162 -0
  353. package/src/components/ReorderableList/index.web.tsx +254 -0
  354. package/src/components/ReorderableList/labels.ts +52 -0
  355. package/src/components/ReorderableList/reorderItems.ts +16 -0
  356. package/src/components/ReorderableList/rowContext.tsx +72 -0
  357. package/src/components/ReorderableList/types.ts +80 -0
  358. package/src/components/ReorderableList/useReorder.ts +159 -0
  359. package/src/components/ReorderableList/webDnd.ts +60 -0
  360. package/src/components/ScopedTheme/index.tsx +6 -1
  361. package/src/components/ScrollShadow/gradient.ts +48 -0
  362. package/src/components/ScrollShadow/index.tsx +300 -69
  363. package/src/components/ScrollShadow/styles.ts +45 -28
  364. package/src/components/ScrollShadow/useComposedScrollHandler.ts +58 -0
  365. package/src/components/ScrollShadow/useScrollShadow.ts +72 -10
  366. package/src/components/Skeleton/index.tsx +3 -0
  367. package/src/components/TablePagination/index.tsx +54 -0
  368. package/src/components/Tabs/index.tsx +25 -35
  369. package/src/components/Tabs/styles.ts +0 -9
  370. package/src/components/Text/index.tsx +6 -1
  371. package/src/components/Tile/styles.ts +4 -1
  372. package/src/components/index.ts +5 -0
  373. package/src/reorderable-list.ts +1 -0
@@ -0,0 +1,254 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ type KeyboardEvent as ReactKeyboardEvent,
10
+ type ReactElement,
11
+ type Ref,
12
+ } from 'react';
13
+ import { ScrollView, Text, View, type ViewProps, type ViewStyle } from 'react-native';
14
+ import {
15
+ DndContext,
16
+ KeyboardSensor,
17
+ PointerSensor,
18
+ closestCenter,
19
+ useDndContext,
20
+ useSensor,
21
+ useSensors,
22
+ type DragEndEvent,
23
+ } from '@dnd-kit/core';
24
+ import {
25
+ SortableContext,
26
+ sortableKeyboardCoordinates,
27
+ useSortable,
28
+ verticalListSortingStrategy,
29
+ } from '@dnd-kit/sortable';
30
+ import { CSS } from '@dnd-kit/utilities';
31
+ import { cn } from '@cdx-ui/utils';
32
+ import { ReorderableListShared, type ReorderableRowInfo } from './ReorderableListShared';
33
+ import { reorderItems } from './reorderItems';
34
+ import { ReorderableRowProvider, type ReorderableRowHandle } from './rowContext';
35
+ import type { ReorderableListProps } from './types';
36
+ import { useReorderableRowValue, useReorderController, type ReorderController } from './useReorder';
37
+ import { createReorderAnnouncements, resolveDragEnd } from './webDnd';
38
+
39
+ export type {
40
+ ReorderableListLabels,
41
+ ReorderableListProps,
42
+ ReorderableListRenderItemInfo,
43
+ } from './types';
44
+
45
+ const handleTouchStyle = { touchAction: 'none' } as ViewStyle;
46
+ const passThrough: ReorderableRowHandle['wrap'] = (host) => host;
47
+
48
+ /**
49
+ * react-native-web's `Pressable` stops Enter/Space `keydown` from bubbling, so when the handle is a
50
+ * pressable control (`asChild` with an `IconButton`) the drop key never reaches dnd-kit's document
51
+ * listener. While dragging, take the key at the handle and hand it to the document directly.
52
+ */
53
+ function forwardDropKeyToDocument(event: ReactKeyboardEvent) {
54
+ const { key, code } = event.nativeEvent;
55
+ if (key !== 'Enter' && key !== ' ' && code !== 'Enter' && code !== 'Space') return;
56
+ event.preventDefault();
57
+ event.stopPropagation();
58
+ const { ownerDocument } = event.currentTarget as unknown as Node;
59
+ ownerDocument?.dispatchEvent(new KeyboardEvent('keydown', { key, code, cancelable: true }));
60
+ }
61
+
62
+ interface SortableRowProps<T> {
63
+ info: ReorderableRowInfo<T>;
64
+ renderItem: ReorderableListProps<T>['renderItem'];
65
+ controller: ReorderController<T>;
66
+ }
67
+
68
+ function SortableRow<T>({ info, renderItem, controller }: SortableRowProps<T>) {
69
+ const { item, index, count, key } = info;
70
+ const {
71
+ attributes,
72
+ listeners,
73
+ setNodeRef,
74
+ setActivatorNodeRef,
75
+ transform,
76
+ transition,
77
+ isDragging,
78
+ } = useSortable({ id: key });
79
+ const { activatorEvent } = useDndContext();
80
+
81
+ const nodeRef = useRef<HTMLElement | null>(null);
82
+ const swallowNextClickRef = useRef(false);
83
+
84
+ const setRowRef = useCallback(
85
+ (node: View | null) => {
86
+ const element = node as unknown as HTMLElement | null;
87
+ nodeRef.current = element;
88
+ setNodeRef(element);
89
+ },
90
+ [setNodeRef],
91
+ );
92
+
93
+ // dnd-kit's shift/drop animation is written to the row's DOM node; React never owns these keys.
94
+ useLayoutEffect(() => {
95
+ const node = nodeRef.current;
96
+ if (!node) return;
97
+ node.style.transform = transform ? (CSS.Translate.toString({ ...transform, x: 0 }) ?? '') : '';
98
+ node.style.transition = transition ?? '';
99
+ }, [transform, transition]);
100
+
101
+ // A pointer drag ends with a click on the row; swallow that one click so a pressable row does
102
+ // not fire after a drop or cancel. Scoped to this row, and reset by the next pointerdown.
103
+ useEffect(() => {
104
+ if (isDragging && activatorEvent?.type.startsWith('pointer')) {
105
+ swallowNextClickRef.current = true;
106
+ }
107
+ }, [activatorEvent, isDragging]);
108
+
109
+ useEffect(() => {
110
+ const node = nodeRef.current;
111
+ if (!node) return undefined;
112
+ const onClick = (event: MouseEvent) => {
113
+ if (!swallowNextClickRef.current) return;
114
+ swallowNextClickRef.current = false;
115
+ event.stopPropagation();
116
+ event.preventDefault();
117
+ };
118
+ const onPointerDown = () => {
119
+ swallowNextClickRef.current = false;
120
+ };
121
+ node.addEventListener('click', onClick, true);
122
+ node.addEventListener('pointerdown', onPointerDown, true);
123
+ return () => {
124
+ node.removeEventListener('click', onClick, true);
125
+ node.removeEventListener('pointerdown', onPointerDown, true);
126
+ };
127
+ }, []);
128
+
129
+ const handle = useMemo<ReorderableRowHandle>(() => {
130
+ const { tabIndex, role, ...aria } = attributes;
131
+ return {
132
+ props: {
133
+ ...aria,
134
+ role: role as ViewProps['role'],
135
+ tabIndex: tabIndex === 0 || tabIndex === -1 ? tabIndex : undefined,
136
+ style: handleTouchStyle,
137
+ ...listeners,
138
+ onKeyDownCapture: isDragging ? forwardDropKeyToDocument : undefined,
139
+ },
140
+ ref: (node) => setActivatorNodeRef(node as unknown as HTMLElement | null),
141
+ wrap: passThrough,
142
+ };
143
+ }, [attributes, isDragging, listeners, setActivatorNodeRef]);
144
+
145
+ const rowValue = useReorderableRowValue({
146
+ index,
147
+ count,
148
+ isDragging,
149
+ handle,
150
+ labels: controller.labels,
151
+ moveItem: controller.moveItem,
152
+ });
153
+
154
+ return (
155
+ <ReorderableRowProvider value={rowValue}>
156
+ <View ref={setRowRef} className={cn(isDragging && 'relative z-20')}>
157
+ {renderItem({ item, index, isDragging })}
158
+ </View>
159
+ </ReorderableRowProvider>
160
+ );
161
+ }
162
+
163
+ function ReorderableListInner<T>(
164
+ {
165
+ data,
166
+ keyExtractor,
167
+ renderItem,
168
+ onReorder,
169
+ onReorderIndices,
170
+ getItemLabel,
171
+ labels,
172
+ ...rest
173
+ }: ReorderableListProps<T>,
174
+ ref: Ref<ScrollView>,
175
+ ) {
176
+ const [announcement, setAnnouncement] = useState('');
177
+
178
+ const controller = useReorderController({
179
+ data,
180
+ onReorder,
181
+ onReorderIndices,
182
+ getItemLabel,
183
+ labels,
184
+ announce: setAnnouncement,
185
+ });
186
+
187
+ const sortableIds = useMemo(
188
+ () => data.map((item, index) => keyExtractor(item, index)),
189
+ [data, keyExtractor],
190
+ );
191
+
192
+ const sensors = useSensors(
193
+ useSensor(PointerSensor, {
194
+ activationConstraint: { distance: 4 },
195
+ }),
196
+ useSensor(KeyboardSensor, {
197
+ coordinateGetter: sortableKeyboardCoordinates,
198
+ }),
199
+ );
200
+
201
+ const { describe, labels: resolvedLabels } = controller;
202
+ const accessibility = useMemo(
203
+ () => ({
204
+ announcements: createReorderAnnouncements(sortableIds, describe),
205
+ screenReaderInstructions: { draggable: resolvedLabels.screenReaderInstructions },
206
+ }),
207
+ [describe, resolvedLabels, sortableIds],
208
+ );
209
+
210
+ const handleDragEnd = useCallback(
211
+ ({ active, over }: DragEndEvent) => {
212
+ const move = resolveDragEnd(sortableIds, active.id, over?.id);
213
+ if (!move) return;
214
+ onReorder(reorderItems(data, move.from, move.to));
215
+ onReorderIndices?.(move.from, move.to);
216
+ },
217
+ [data, onReorder, onReorderIndices, sortableIds],
218
+ );
219
+
220
+ const renderRow = useCallback(
221
+ (info: ReorderableRowInfo<T>) => (
222
+ <SortableRow info={info} renderItem={renderItem} controller={controller} />
223
+ ),
224
+ [controller, renderItem],
225
+ );
226
+
227
+ return (
228
+ <DndContext
229
+ accessibility={accessibility}
230
+ collisionDetection={closestCenter}
231
+ onDragEnd={handleDragEnd}
232
+ sensors={sensors}
233
+ >
234
+ <SortableContext items={sortableIds} strategy={verticalListSortingStrategy}>
235
+ <ReorderableListShared
236
+ ref={ref}
237
+ data={data}
238
+ keyExtractor={keyExtractor}
239
+ renderRow={renderRow}
240
+ {...rest}
241
+ />
242
+ </SortableContext>
243
+ <View aria-live="polite" className="sr-only">
244
+ <Text>{announcement}</Text>
245
+ </View>
246
+ </DndContext>
247
+ );
248
+ }
249
+
250
+ export const ReorderableList = forwardRef(ReorderableListInner) as <T>(
251
+ props: ReorderableListProps<T> & { ref?: Ref<ScrollView> },
252
+ ) => ReactElement | null;
253
+
254
+ (ReorderableList as { displayName?: string }).displayName = 'ReorderableList';
@@ -0,0 +1,52 @@
1
+ import type { ReorderableListLabels } from './types';
2
+
3
+ export const DEFAULT_REORDERABLE_LIST_LABELS: ReorderableListLabels = {
4
+ item: 'item {position}',
5
+ pickedUp: 'Picked up {item}. Position {position} of {count}.',
6
+ movedTo: 'Moved {item} to position {position} of {count}.',
7
+ dropped: 'Dropped {item} at position {position} of {count}.',
8
+ cancelled: 'Reorder cancelled. {item} returned to position {position} of {count}.',
9
+ screenReaderInstructions:
10
+ 'To pick up an item, press Space or Enter. Use the arrow keys to move it, then press Space or Enter to drop it, or Escape to cancel.',
11
+ moveUp: 'Move up',
12
+ moveDown: 'Move down',
13
+ moveToTop: 'Move to top',
14
+ moveToBottom: 'Move to bottom',
15
+ };
16
+
17
+ export function resolveReorderableListLabels(
18
+ partial?: Partial<ReorderableListLabels>,
19
+ ): ReorderableListLabels {
20
+ return { ...DEFAULT_REORDERABLE_LIST_LABELS, ...partial };
21
+ }
22
+
23
+ export type ReorderMessageKey = 'pickedUp' | 'movedTo' | 'dropped' | 'cancelled';
24
+
25
+ export interface ReorderMessageInput<T> {
26
+ labels: ReorderableListLabels;
27
+ getItemLabel?: (item: T, index: number) => string;
28
+ item: T;
29
+ /** Zero-based position of the item before the move — names the item. */
30
+ fromIndex: number;
31
+ /** Zero-based position announced in the message. */
32
+ position: number;
33
+ count: number;
34
+ }
35
+
36
+ function fill(template: string, values: Record<string, string>): string {
37
+ return template.replace(/\{(\w+)\}/g, (match, name: string) => values[name] ?? match);
38
+ }
39
+
40
+ export function formatReorderMessage<T>(
41
+ key: ReorderMessageKey,
42
+ { labels, getItemLabel, item, fromIndex, position, count }: ReorderMessageInput<T>,
43
+ ): string {
44
+ const itemName = getItemLabel
45
+ ? getItemLabel(item, fromIndex)
46
+ : fill(labels.item, { position: String(fromIndex + 1) });
47
+ return fill(labels[key], {
48
+ item: itemName,
49
+ position: String(position + 1),
50
+ count: String(count),
51
+ });
52
+ }
@@ -0,0 +1,16 @@
1
+ /** Immutable insert-strategy move. No-ops when indices are equal or out of range. */
2
+ export function reorderItems<T>(data: T[], fromIndex: number, toIndex: number): T[] {
3
+ if (
4
+ fromIndex === toIndex ||
5
+ fromIndex < 0 ||
6
+ toIndex < 0 ||
7
+ fromIndex >= data.length ||
8
+ toIndex >= data.length
9
+ ) {
10
+ return data;
11
+ }
12
+
13
+ const next = data.slice();
14
+ next.splice(toIndex, 0, ...next.splice(fromIndex, 1));
15
+ return next;
16
+ }
@@ -0,0 +1,72 @@
1
+ import { createContext, useContext, type ReactElement, type RefCallback } from 'react';
2
+ import type {
3
+ AccessibilityActionEvent,
4
+ AccessibilityActionInfo,
5
+ StyleProp,
6
+ View,
7
+ ViewProps,
8
+ ViewStyle,
9
+ } from 'react-native';
10
+
11
+ /**
12
+ * Props that make a host element the row's drag activator.
13
+ *
14
+ * - **Web:** dnd-kit `attributes` (`role`, `tabIndex`, `aria-*`), `listeners` (`onPointerDown`,
15
+ * `onKeyDown`), and `style: { touchAction: 'none' }`. Some keys (`aria-roledescription`,
16
+ * `aria-describedby`) are outside `ViewProps`, hence the index signature.
17
+ * - **Native:** empty — the gesture is attached by `wrap`.
18
+ */
19
+ export type ReorderableHandleHostProps = ViewProps & Readonly<Record<string, unknown>>;
20
+
21
+ /**
22
+ * Binds a handle element to the row's drag gesture without adding a focusable wrapper.
23
+ *
24
+ * Render one host (a `View`, or the consumer child under `asChild` via `Slot`), then:
25
+ * 1. spread `props` onto it before consumer props (handlers compose through `Slot`),
26
+ * 2. compose `ref` with the host's own ref,
27
+ * 3. return `wrap(host)`.
28
+ *
29
+ * Web: `ref` is dnd-kit's `setActivatorNodeRef` and `wrap` returns the host unchanged.
30
+ * Native: `ref` is `undefined` and `wrap` places the host inside `Sortable.Handle`, which must
31
+ * wrap because react-native-sortables only attaches its handle gesture through that component. It
32
+ * renders a non-accessible `View`, so the host stays the single focusable element; pass layout
33
+ * styles that must sit on the outer box (flex, alignSelf, margin) as `wrap`'s second argument.
34
+ */
35
+ export interface ReorderableRowHandle {
36
+ props: ReorderableHandleHostProps;
37
+ ref: RefCallback<View> | undefined;
38
+ wrap: (host: ReactElement, style?: StyleProp<ViewStyle>) => ReactElement;
39
+ }
40
+
41
+ /** Per-row reorder state and actions, provided to every row rendered by `ReorderableList`. */
42
+ export interface ReorderableRowContextValue {
43
+ /** `true` while this row is dragged. */
44
+ isDragging: boolean;
45
+ /** Zero-based position of the row in `data`. */
46
+ index: number;
47
+ /** Number of rows in the list. */
48
+ count: number;
49
+ /**
50
+ * Moves this row to `toIndex` (clamped to the list), calls `onReorder`/`onReorderIndices`, and
51
+ * announces the new position. No-op when the position does not change.
52
+ */
53
+ move: (toIndex: number) => void;
54
+ /**
55
+ * `Move up` / `Move down` / `Move to top` / `Move to bottom`, omitting those that do not apply at
56
+ * the ends. Spread with `onAccessibilityAction` onto the handle host. Native screen readers
57
+ * expose them as custom actions; react-native-web drops both props, and web keyboard users
58
+ * reorder through dnd-kit's keyboard sensor on the handle instead.
59
+ */
60
+ accessibilityActions: AccessibilityActionInfo[];
61
+ onAccessibilityAction: (event: AccessibilityActionEvent) => void;
62
+ handle: ReorderableRowHandle;
63
+ }
64
+
65
+ const ReorderableRowContext = createContext<ReorderableRowContextValue | null>(null);
66
+
67
+ export const ReorderableRowProvider = ReorderableRowContext.Provider;
68
+
69
+ /** Reorder state for the enclosing `ReorderableList` row, or `null` outside a list. */
70
+ export function useReorderableRow(): ReorderableRowContextValue | null {
71
+ return useContext(ReorderableRowContext);
72
+ }
@@ -0,0 +1,80 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ScrollViewProps } from 'react-native';
3
+
4
+ export interface ReorderableListRenderItemInfo<T> {
5
+ /** The item from `data` that this row renders. */
6
+ item: T;
7
+ /** Zero-based position of `item` in `data`. */
8
+ index: number;
9
+ /**
10
+ * `true` while this row is being dragged. Restyle the row with color or opacity only: on iOS,
11
+ * changing `zIndex` on the row (or anything wrapping its handle) mid-drag cancels the drag.
12
+ */
13
+ isDragging: boolean;
14
+ }
15
+
16
+ /**
17
+ * Overridable copy for reorder announcements and accessibility actions.
18
+ *
19
+ * Placeholders: `{item}` is the row name (from `getItemLabel`, or the `item` template),
20
+ * `{position}` is a 1-based position, and `{count}` is the number of rows.
21
+ */
22
+ export interface ReorderableListLabels {
23
+ /**
24
+ * Row name used when `getItemLabel` is not provided. `{position}` is the row's position
25
+ * before the move.
26
+ * @default 'item {position}'
27
+ */
28
+ item: string;
29
+ /** @default 'Picked up {item}. Position {position} of {count}.' */
30
+ pickedUp: string;
31
+ /** @default 'Moved {item} to position {position} of {count}.' */
32
+ movedTo: string;
33
+ /** @default 'Dropped {item} at position {position} of {count}.' */
34
+ dropped: string;
35
+ /** @default 'Reorder cancelled. {item} returned to position {position} of {count}.' */
36
+ cancelled: string;
37
+ /**
38
+ * Read by screen readers when a drag handle receives focus on web.
39
+ * @default 'To pick up an item, press Space or Enter. Use the arrow keys to move it, then press Space or Enter to drop it, or Escape to cancel.'
40
+ */
41
+ screenReaderInstructions: string;
42
+ /** @default 'Move up' */
43
+ moveUp: string;
44
+ /** @default 'Move down' */
45
+ moveDown: string;
46
+ /** @default 'Move to top' */
47
+ moveToTop: string;
48
+ /** @default 'Move to bottom' */
49
+ moveToBottom: string;
50
+ }
51
+
52
+ export interface ReorderableListProps<T> extends Omit<
53
+ ScrollViewProps,
54
+ 'children' | 'horizontal' | 'role'
55
+ > {
56
+ /** Items in their current order. The parent owns this array and updates it from `onReorder`. */
57
+ data: T[];
58
+ /**
59
+ * Returns a stable, unique key for an item. Keys identify rows while they move, so they must
60
+ * not be derived from `index`. Duplicate keys log a warning in development.
61
+ */
62
+ keyExtractor: (item: T, index: number) => string;
63
+ /** Renders one row. Compose with `ListItem` and `ListItem.DragHandle`. */
64
+ renderItem: (info: ReorderableListRenderItemInfo<T>) => ReactNode;
65
+ /** Called with the reordered array after a drop or an accessibility move. */
66
+ onReorder: (data: T[]) => void;
67
+ /** Called after `onReorder` with the moved row's previous and new zero-based positions. */
68
+ onReorderIndices?: (from: number, to: number) => void;
69
+ /**
70
+ * Returns the name screen readers use for a row in reorder announcements. Without it,
71
+ * announcements use the `item` label (`item 2`). Keys are never announced.
72
+ */
73
+ getItemLabel?: (item: T, index: number) => string;
74
+ /**
75
+ * Overrides for announcement and accessibility-action copy. Unset keys keep their defaults.
76
+ * @default {}
77
+ */
78
+ labels?: Partial<ReorderableListLabels>;
79
+ className?: string;
80
+ }
@@ -0,0 +1,159 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import type { AccessibilityActionEvent, AccessibilityActionInfo } from 'react-native';
3
+ import { formatReorderMessage, resolveReorderableListLabels } from './labels';
4
+ import { reorderItems } from './reorderItems';
5
+ import type { ReorderableRowContextValue, ReorderableRowHandle } from './rowContext';
6
+ import type { ReorderableListLabels, ReorderableListProps } from './types';
7
+
8
+ type ReorderActionName = 'moveUp' | 'moveDown' | 'moveToTop' | 'moveToBottom';
9
+
10
+ export function buildReorderAccessibilityActions(
11
+ index: number,
12
+ count: number,
13
+ labels: ReorderableListLabels,
14
+ ): AccessibilityActionInfo[] {
15
+ const actions: AccessibilityActionInfo[] = [];
16
+ if (index > 0) {
17
+ actions.push({ name: 'moveUp', label: labels.moveUp });
18
+ if (index > 1) actions.push({ name: 'moveToTop', label: labels.moveToTop });
19
+ }
20
+ if (index < count - 1) {
21
+ actions.push({ name: 'moveDown', label: labels.moveDown });
22
+ if (index < count - 2) actions.push({ name: 'moveToBottom', label: labels.moveToBottom });
23
+ }
24
+ return actions;
25
+ }
26
+
27
+ /** Target index for a reorder accessibility action, or `undefined` for other actions. */
28
+ export function getReorderActionTarget(
29
+ actionName: string,
30
+ index: number,
31
+ count: number,
32
+ ): number | undefined {
33
+ switch (actionName as ReorderActionName) {
34
+ case 'moveUp':
35
+ return index - 1;
36
+ case 'moveDown':
37
+ return index + 1;
38
+ case 'moveToTop':
39
+ return 0;
40
+ case 'moveToBottom':
41
+ return count - 1;
42
+ default:
43
+ return undefined;
44
+ }
45
+ }
46
+
47
+ /** Duplicate keys in `data`, for the development warning. */
48
+ export function findDuplicateKeys<T>(
49
+ data: readonly T[],
50
+ keyExtractor: (item: T, index: number) => string,
51
+ ): string[] {
52
+ const seen = new Set<string>();
53
+ const duplicates = new Set<string>();
54
+ data.forEach((item, index) => {
55
+ const key = keyExtractor(item, index);
56
+ if (seen.has(key)) duplicates.add(key);
57
+ seen.add(key);
58
+ });
59
+ return [...duplicates];
60
+ }
61
+
62
+ type ReorderControllerOptions<T> = Pick<
63
+ ReorderableListProps<T>,
64
+ 'data' | 'onReorder' | 'onReorderIndices' | 'getItemLabel' | 'labels'
65
+ > & {
66
+ announce: (message: string) => void;
67
+ };
68
+
69
+ export interface ReorderController<T> {
70
+ labels: ReorderableListLabels;
71
+ /** Commits a move from one position to another and announces it. */
72
+ moveItem: (fromIndex: number, toIndex: number) => void;
73
+ /** Formats an announcement for the item currently at `fromIndex`. */
74
+ describe: (
75
+ key: 'pickedUp' | 'movedTo' | 'dropped' | 'cancelled',
76
+ fromIndex: number,
77
+ position: number,
78
+ ) => string | undefined;
79
+ data: T[];
80
+ }
81
+
82
+ export function useReorderController<T>({
83
+ data,
84
+ onReorder,
85
+ onReorderIndices,
86
+ getItemLabel,
87
+ labels: labelsProp,
88
+ announce,
89
+ }: ReorderControllerOptions<T>): ReorderController<T> {
90
+ const labels = useMemo(() => resolveReorderableListLabels(labelsProp), [labelsProp]);
91
+
92
+ const describe = useCallback<ReorderController<T>['describe']>(
93
+ (key, fromIndex, position) => {
94
+ const item = data[fromIndex];
95
+ if (item === undefined) return undefined;
96
+ return formatReorderMessage(key, {
97
+ labels,
98
+ getItemLabel,
99
+ item,
100
+ fromIndex,
101
+ position,
102
+ count: data.length,
103
+ });
104
+ },
105
+ [data, getItemLabel, labels],
106
+ );
107
+
108
+ const moveItem = useCallback(
109
+ (fromIndex: number, toIndex: number) => {
110
+ if (fromIndex < 0 || fromIndex >= data.length) return;
111
+ const target = Math.min(Math.max(toIndex, 0), data.length - 1);
112
+ if (target === fromIndex) return;
113
+ const message = describe('movedTo', fromIndex, target);
114
+ onReorder(reorderItems(data, fromIndex, target));
115
+ onReorderIndices?.(fromIndex, target);
116
+ if (message) announce(message);
117
+ },
118
+ [announce, data, describe, onReorder, onReorderIndices],
119
+ );
120
+
121
+ // Stable identity: web's accessibility memo keys off this object. A fresh return every render
122
+ // rebuilds createReorderAnnouncements and clears lastAnnouncedIndex, so pick-up is replaced by
123
+ // "moved to" the current position.
124
+ return useMemo(() => ({ labels, moveItem, describe, data }), [labels, moveItem, describe, data]);
125
+ }
126
+
127
+ interface RowValueOptions {
128
+ index: number;
129
+ count: number;
130
+ isDragging: boolean;
131
+ handle: ReorderableRowHandle;
132
+ labels: ReorderableListLabels;
133
+ moveItem: (fromIndex: number, toIndex: number) => void;
134
+ }
135
+
136
+ export function useReorderableRowValue({
137
+ index,
138
+ count,
139
+ isDragging,
140
+ handle,
141
+ labels,
142
+ moveItem,
143
+ }: RowValueOptions): ReorderableRowContextValue {
144
+ return useMemo(() => {
145
+ const move = (toIndex: number) => moveItem(index, toIndex);
146
+ return {
147
+ isDragging,
148
+ index,
149
+ count,
150
+ move,
151
+ accessibilityActions: buildReorderAccessibilityActions(index, count, labels),
152
+ onAccessibilityAction: (event: AccessibilityActionEvent) => {
153
+ const target = getReorderActionTarget(event.nativeEvent.actionName, index, count);
154
+ if (target !== undefined) move(target);
155
+ },
156
+ handle,
157
+ };
158
+ }, [count, handle, index, isDragging, labels, moveItem]);
159
+ }
@@ -0,0 +1,60 @@
1
+ import type { ReorderController } from './useReorder';
2
+
3
+ type Id = string | number;
4
+
5
+ interface DragArgs {
6
+ active: { id: Id };
7
+ over: { id: Id } | null;
8
+ }
9
+
10
+ /** Maps a dnd-kit drop to `data` positions, or `null` when nothing should move. */
11
+ export function resolveDragEnd(
12
+ ids: readonly string[],
13
+ activeId: Id,
14
+ overId: Id | null | undefined,
15
+ ): { from: number; to: number } | null {
16
+ if (overId == null || activeId === overId) return null;
17
+ const from = ids.indexOf(String(activeId));
18
+ const to = ids.indexOf(String(overId));
19
+ if (from === -1 || to === -1) return null;
20
+ return { from, to };
21
+ }
22
+
23
+ /**
24
+ * Position-based dnd-kit announcements. dnd-kit's defaults read the raw draggable ids, which are
25
+ * `keyExtractor` keys — these never mention them.
26
+ */
27
+ export function createReorderAnnouncements<T>(
28
+ ids: readonly string[],
29
+ describe: ReorderController<T>['describe'],
30
+ ) {
31
+ const indexOf = (id: Id | undefined) => (id === undefined ? -1 : ids.indexOf(String(id)));
32
+ // dnd-kit reports the row's own slot as "over" right after pick-up; skip repeats so the
33
+ // pick-up message is not replaced by "moved to" its current position.
34
+ let lastAnnouncedIndex = -1;
35
+
36
+ return {
37
+ onDragStart({ active }: Pick<DragArgs, 'active'>) {
38
+ const from = indexOf(active.id);
39
+ lastAnnouncedIndex = from;
40
+ return from === -1 ? undefined : describe('pickedUp', from, from);
41
+ },
42
+ onDragOver({ active, over }: DragArgs) {
43
+ const from = indexOf(active.id);
44
+ const to = indexOf(over?.id);
45
+ if (from === -1 || to === -1 || to === lastAnnouncedIndex) return undefined;
46
+ lastAnnouncedIndex = to;
47
+ return describe('movedTo', from, to);
48
+ },
49
+ onDragEnd({ active, over }: DragArgs) {
50
+ const from = indexOf(active.id);
51
+ if (from === -1) return undefined;
52
+ const to = indexOf(over?.id);
53
+ return describe('dropped', from, to === -1 ? from : to);
54
+ },
55
+ onDragCancel({ active }: DragArgs) {
56
+ const from = indexOf(active.id);
57
+ return from === -1 ? undefined : describe('cancelled', from, from);
58
+ },
59
+ };
60
+ }