@multiplatform.one/components 7.8.0 → 7.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (452) hide show
  1. package/README.md +98 -0
  2. package/dist/cjs/Accordion/index.cjs +3 -3
  3. package/dist/cjs/Accordion/index.native.js +4 -4
  4. package/dist/cjs/Accordion/index.native.js.map +1 -1
  5. package/dist/cjs/Chip/index.cjs +1 -1
  6. package/dist/cjs/Chip/index.native.js +1 -1
  7. package/dist/cjs/Chip/index.native.js.map +1 -1
  8. package/dist/cjs/ContextMenu/index.cjs +3 -3
  9. package/dist/cjs/ContextMenu/index.native.js +3 -3
  10. package/dist/cjs/ContextMenu/index.native.js.map +1 -1
  11. package/dist/cjs/DropdownMenu/index.cjs +3 -3
  12. package/dist/cjs/DropdownMenu/index.native.js +3 -3
  13. package/dist/cjs/DropdownMenu/index.native.js.map +1 -1
  14. package/dist/cjs/Meter/index.cjs +3 -2
  15. package/dist/cjs/Meter/index.native.js +3 -2
  16. package/dist/cjs/Meter/index.native.js.map +1 -1
  17. package/dist/cjs/Meter/meter.cjs +1 -2
  18. package/dist/cjs/Meter/meter.native.js +1 -2
  19. package/dist/cjs/Meter/meter.native.js.map +1 -1
  20. package/dist/cjs/Pagination.cjs +2 -2
  21. package/dist/cjs/Pagination.native.js +2 -2
  22. package/dist/cjs/Pagination.native.js.map +1 -1
  23. package/dist/cjs/ProgressSteps/index.cjs +3 -3
  24. package/dist/cjs/ProgressSteps/index.native.js +3 -3
  25. package/dist/cjs/ProgressSteps/index.native.js.map +1 -1
  26. package/dist/cjs/Quote/index.cjs +1 -2
  27. package/dist/cjs/Quote/index.native.js +1 -2
  28. package/dist/cjs/Quote/index.native.js.map +1 -1
  29. package/dist/cjs/SheetModal/index.cjs +22 -3
  30. package/dist/cjs/SheetModal/index.native.js +50 -10
  31. package/dist/cjs/SheetModal/index.native.js.map +1 -1
  32. package/dist/cjs/Tabs/index.cjs +34 -13
  33. package/dist/cjs/Tabs/index.native.js +38 -13
  34. package/dist/cjs/Tabs/index.native.js.map +1 -1
  35. package/dist/cjs/Toast/index.cjs +4 -5
  36. package/dist/cjs/Toast/index.native.js +4 -5
  37. package/dist/cjs/Toast/index.native.js.map +1 -1
  38. package/dist/cjs/TreeSelect/index.cjs +46 -7
  39. package/dist/cjs/TreeSelect/index.native.js +58 -7
  40. package/dist/cjs/TreeSelect/index.native.js.map +1 -1
  41. package/dist/cjs/charts/PieChart.cjs +1 -2
  42. package/dist/cjs/charts/PieChart.native.js +1 -2
  43. package/dist/cjs/charts/PieChart.native.js.map +1 -1
  44. package/dist/cjs/code/HighlightedCode/index.cjs +1 -1
  45. package/dist/cjs/code/HighlightedCode/index.native.js +1 -1
  46. package/dist/cjs/code/HighlightedCode/index.native.js.map +1 -1
  47. package/dist/cjs/componentColors.cjs +6 -4
  48. package/dist/cjs/componentColors.native.js +6 -4
  49. package/dist/cjs/componentColors.native.js.map +1 -1
  50. package/dist/cjs/images/Image/ImageFrame.cjs +4 -2
  51. package/dist/cjs/images/Image/ImageFrame.native.js +4 -2
  52. package/dist/cjs/images/Image/ImageFrame.native.js.map +1 -1
  53. package/dist/cjs/layouts/AsyncBoundary.cjs +1 -2
  54. package/dist/cjs/layouts/AsyncBoundary.native.js +1 -2
  55. package/dist/cjs/layouts/AsyncBoundary.native.js.map +1 -1
  56. package/dist/cjs/layouts/EmptyState.cjs +6 -2
  57. package/dist/cjs/layouts/EmptyState.native.js +10 -6
  58. package/dist/cjs/layouts/EmptyState.native.js.map +1 -1
  59. package/dist/cjs/layouts/attachments.cjs +4 -4
  60. package/dist/cjs/layouts/attachments.native.js +4 -4
  61. package/dist/cjs/layouts/attachments.native.js.map +1 -1
  62. package/dist/cjs/layouts/breadcrumbs.cjs +1 -2
  63. package/dist/cjs/layouts/breadcrumbs.native.js +1 -2
  64. package/dist/cjs/layouts/breadcrumbs.native.js.map +1 -1
  65. package/dist/cjs/layouts/page.cjs +2 -2
  66. package/dist/cjs/layouts/page.native.js +2 -2
  67. package/dist/cjs/layouts/page.native.js.map +1 -1
  68. package/dist/cjs/layouts/sidebar.cjs +2 -2
  69. package/dist/cjs/layouts/sidebar.native.js +2 -2
  70. package/dist/cjs/layouts/sidebar.native.js.map +1 -1
  71. package/dist/cjs/layouts/tags.cjs +6 -7
  72. package/dist/cjs/layouts/tags.native.js +6 -9
  73. package/dist/cjs/layouts/tags.native.js.map +1 -1
  74. package/dist/cjs/layouts/timeline.cjs +5 -5
  75. package/dist/cjs/layouts/timeline.native.js +5 -5
  76. package/dist/cjs/layouts/timeline.native.js.map +1 -1
  77. package/dist/cjs/surfaces.cjs +1 -0
  78. package/dist/cjs/surfaces.native.js +1 -0
  79. package/dist/cjs/surfaces.native.js.map +1 -1
  80. package/dist/cjs/views/Calendar.cjs +11 -8
  81. package/dist/cjs/views/Calendar.native.js +11 -8
  82. package/dist/cjs/views/Calendar.native.js.map +1 -1
  83. package/dist/cjs/views/Dashboard.cjs +6 -6
  84. package/dist/cjs/views/Dashboard.native.js +7 -7
  85. package/dist/cjs/views/Dashboard.native.js.map +1 -1
  86. package/dist/cjs/views/Gantt.cjs +3 -3
  87. package/dist/cjs/views/Gantt.native.js +3 -3
  88. package/dist/cjs/views/Gantt.native.js.map +1 -1
  89. package/dist/cjs/views/Kanban.cjs +2 -2
  90. package/dist/cjs/views/Kanban.native.js +2 -2
  91. package/dist/cjs/views/Kanban.native.js.map +1 -1
  92. package/dist/cjs/views/ReportBuilder.cjs +4 -4
  93. package/dist/cjs/views/ReportBuilder.native.js +4 -4
  94. package/dist/cjs/views/ReportBuilder.native.js.map +1 -1
  95. package/dist/cjs/views/TreeView.cjs +248 -19
  96. package/dist/cjs/views/TreeView.native.js +264 -20
  97. package/dist/cjs/views/TreeView.native.js.map +1 -1
  98. package/dist/cjs/views/treeViewReveal.cjs +55 -0
  99. package/dist/cjs/views/treeViewReveal.native.js +80 -0
  100. package/dist/cjs/views/treeViewReveal.native.js.map +1 -0
  101. package/dist/esm/Accordion/index.mjs +3 -3
  102. package/dist/esm/Accordion/index.mjs.map +1 -1
  103. package/dist/esm/Accordion/index.native.js +4 -4
  104. package/dist/esm/Accordion/index.native.js.map +1 -1
  105. package/dist/esm/Chip/index.mjs +1 -1
  106. package/dist/esm/Chip/index.mjs.map +1 -1
  107. package/dist/esm/Chip/index.native.js +1 -1
  108. package/dist/esm/Chip/index.native.js.map +1 -1
  109. package/dist/esm/ContextMenu/index.mjs +3 -3
  110. package/dist/esm/ContextMenu/index.mjs.map +1 -1
  111. package/dist/esm/ContextMenu/index.native.js +3 -3
  112. package/dist/esm/ContextMenu/index.native.js.map +1 -1
  113. package/dist/esm/DropdownMenu/index.mjs +3 -3
  114. package/dist/esm/DropdownMenu/index.mjs.map +1 -1
  115. package/dist/esm/DropdownMenu/index.native.js +3 -3
  116. package/dist/esm/DropdownMenu/index.native.js.map +1 -1
  117. package/dist/esm/Meter/index.mjs +3 -2
  118. package/dist/esm/Meter/index.mjs.map +1 -1
  119. package/dist/esm/Meter/index.native.js +3 -2
  120. package/dist/esm/Meter/index.native.js.map +1 -1
  121. package/dist/esm/Meter/meter.mjs +1 -2
  122. package/dist/esm/Meter/meter.mjs.map +1 -1
  123. package/dist/esm/Meter/meter.native.js +1 -2
  124. package/dist/esm/Meter/meter.native.js.map +1 -1
  125. package/dist/esm/Pagination.mjs +2 -2
  126. package/dist/esm/Pagination.mjs.map +1 -1
  127. package/dist/esm/Pagination.native.js +2 -2
  128. package/dist/esm/Pagination.native.js.map +1 -1
  129. package/dist/esm/ProgressSteps/index.mjs +3 -3
  130. package/dist/esm/ProgressSteps/index.mjs.map +1 -1
  131. package/dist/esm/ProgressSteps/index.native.js +3 -3
  132. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  133. package/dist/esm/Quote/index.mjs +1 -2
  134. package/dist/esm/Quote/index.mjs.map +1 -1
  135. package/dist/esm/Quote/index.native.js +1 -2
  136. package/dist/esm/Quote/index.native.js.map +1 -1
  137. package/dist/esm/SheetModal/index.mjs +22 -3
  138. package/dist/esm/SheetModal/index.mjs.map +1 -1
  139. package/dist/esm/SheetModal/index.native.js +51 -11
  140. package/dist/esm/SheetModal/index.native.js.map +1 -1
  141. package/dist/esm/Tabs/index.mjs +34 -13
  142. package/dist/esm/Tabs/index.mjs.map +1 -1
  143. package/dist/esm/Tabs/index.native.js +38 -13
  144. package/dist/esm/Tabs/index.native.js.map +1 -1
  145. package/dist/esm/Toast/index.mjs +4 -5
  146. package/dist/esm/Toast/index.mjs.map +1 -1
  147. package/dist/esm/Toast/index.native.js +4 -5
  148. package/dist/esm/Toast/index.native.js.map +1 -1
  149. package/dist/esm/TreeSelect/index.mjs +46 -7
  150. package/dist/esm/TreeSelect/index.mjs.map +1 -1
  151. package/dist/esm/TreeSelect/index.native.js +58 -7
  152. package/dist/esm/TreeSelect/index.native.js.map +1 -1
  153. package/dist/esm/charts/PieChart.mjs +1 -2
  154. package/dist/esm/charts/PieChart.mjs.map +1 -1
  155. package/dist/esm/charts/PieChart.native.js +1 -2
  156. package/dist/esm/charts/PieChart.native.js.map +1 -1
  157. package/dist/esm/code/HighlightedCode/index.mjs +1 -1
  158. package/dist/esm/code/HighlightedCode/index.mjs.map +1 -1
  159. package/dist/esm/code/HighlightedCode/index.native.js +1 -1
  160. package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
  161. package/dist/esm/componentColors.mjs +6 -4
  162. package/dist/esm/componentColors.mjs.map +1 -1
  163. package/dist/esm/componentColors.native.js +6 -4
  164. package/dist/esm/componentColors.native.js.map +1 -1
  165. package/dist/esm/images/Image/ImageFrame.mjs +4 -2
  166. package/dist/esm/images/Image/ImageFrame.mjs.map +1 -1
  167. package/dist/esm/images/Image/ImageFrame.native.js +4 -2
  168. package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
  169. package/dist/esm/layouts/AsyncBoundary.mjs +1 -2
  170. package/dist/esm/layouts/AsyncBoundary.mjs.map +1 -1
  171. package/dist/esm/layouts/AsyncBoundary.native.js +1 -2
  172. package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
  173. package/dist/esm/layouts/EmptyState.mjs +7 -3
  174. package/dist/esm/layouts/EmptyState.mjs.map +1 -1
  175. package/dist/esm/layouts/EmptyState.native.js +11 -7
  176. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  177. package/dist/esm/layouts/attachments.mjs +4 -4
  178. package/dist/esm/layouts/attachments.mjs.map +1 -1
  179. package/dist/esm/layouts/attachments.native.js +4 -4
  180. package/dist/esm/layouts/attachments.native.js.map +1 -1
  181. package/dist/esm/layouts/breadcrumbs.mjs +1 -2
  182. package/dist/esm/layouts/breadcrumbs.mjs.map +1 -1
  183. package/dist/esm/layouts/breadcrumbs.native.js +1 -2
  184. package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
  185. package/dist/esm/layouts/page.mjs +2 -2
  186. package/dist/esm/layouts/page.mjs.map +1 -1
  187. package/dist/esm/layouts/page.native.js +2 -2
  188. package/dist/esm/layouts/page.native.js.map +1 -1
  189. package/dist/esm/layouts/sidebar.mjs +2 -2
  190. package/dist/esm/layouts/sidebar.mjs.map +1 -1
  191. package/dist/esm/layouts/sidebar.native.js +2 -2
  192. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  193. package/dist/esm/layouts/tags.mjs +6 -7
  194. package/dist/esm/layouts/tags.mjs.map +1 -1
  195. package/dist/esm/layouts/tags.native.js +6 -9
  196. package/dist/esm/layouts/tags.native.js.map +1 -1
  197. package/dist/esm/layouts/timeline.mjs +5 -5
  198. package/dist/esm/layouts/timeline.mjs.map +1 -1
  199. package/dist/esm/layouts/timeline.native.js +5 -5
  200. package/dist/esm/layouts/timeline.native.js.map +1 -1
  201. package/dist/esm/surfaces.mjs +1 -0
  202. package/dist/esm/surfaces.mjs.map +1 -1
  203. package/dist/esm/surfaces.native.js +1 -0
  204. package/dist/esm/surfaces.native.js.map +1 -1
  205. package/dist/esm/views/Calendar.mjs +11 -8
  206. package/dist/esm/views/Calendar.mjs.map +1 -1
  207. package/dist/esm/views/Calendar.native.js +11 -8
  208. package/dist/esm/views/Calendar.native.js.map +1 -1
  209. package/dist/esm/views/Dashboard.mjs +6 -6
  210. package/dist/esm/views/Dashboard.mjs.map +1 -1
  211. package/dist/esm/views/Dashboard.native.js +7 -7
  212. package/dist/esm/views/Dashboard.native.js.map +1 -1
  213. package/dist/esm/views/Gantt.mjs +3 -3
  214. package/dist/esm/views/Gantt.mjs.map +1 -1
  215. package/dist/esm/views/Gantt.native.js +3 -3
  216. package/dist/esm/views/Gantt.native.js.map +1 -1
  217. package/dist/esm/views/Kanban.mjs +2 -2
  218. package/dist/esm/views/Kanban.mjs.map +1 -1
  219. package/dist/esm/views/Kanban.native.js +2 -2
  220. package/dist/esm/views/Kanban.native.js.map +1 -1
  221. package/dist/esm/views/ReportBuilder.mjs +4 -4
  222. package/dist/esm/views/ReportBuilder.mjs.map +1 -1
  223. package/dist/esm/views/ReportBuilder.native.js +4 -4
  224. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  225. package/dist/esm/views/TreeView.mjs +249 -20
  226. package/dist/esm/views/TreeView.mjs.map +1 -1
  227. package/dist/esm/views/TreeView.native.js +265 -21
  228. package/dist/esm/views/TreeView.native.js.map +1 -1
  229. package/dist/esm/views/treeViewReveal.mjs +29 -0
  230. package/dist/esm/views/treeViewReveal.mjs.map +1 -0
  231. package/dist/esm/views/treeViewReveal.native.js +51 -0
  232. package/dist/esm/views/treeViewReveal.native.js.map +1 -0
  233. package/dist/jsx/Accordion/index.mjs +3 -3
  234. package/dist/jsx/Accordion/index.mjs.map +1 -1
  235. package/dist/jsx/Accordion/index.native.js +4 -4
  236. package/dist/jsx/Accordion/index.native.js.map +1 -1
  237. package/dist/jsx/Chip/index.mjs +1 -1
  238. package/dist/jsx/Chip/index.mjs.map +1 -1
  239. package/dist/jsx/Chip/index.native.js +1 -1
  240. package/dist/jsx/Chip/index.native.js.map +1 -1
  241. package/dist/jsx/ContextMenu/index.mjs +3 -3
  242. package/dist/jsx/ContextMenu/index.mjs.map +1 -1
  243. package/dist/jsx/ContextMenu/index.native.js +3 -3
  244. package/dist/jsx/ContextMenu/index.native.js.map +1 -1
  245. package/dist/jsx/DropdownMenu/index.mjs +3 -3
  246. package/dist/jsx/DropdownMenu/index.mjs.map +1 -1
  247. package/dist/jsx/DropdownMenu/index.native.js +3 -3
  248. package/dist/jsx/DropdownMenu/index.native.js.map +1 -1
  249. package/dist/jsx/Meter/index.mjs +3 -2
  250. package/dist/jsx/Meter/index.mjs.map +1 -1
  251. package/dist/jsx/Meter/index.native.js +3 -2
  252. package/dist/jsx/Meter/index.native.js.map +1 -1
  253. package/dist/jsx/Meter/meter.mjs +1 -2
  254. package/dist/jsx/Meter/meter.mjs.map +1 -1
  255. package/dist/jsx/Meter/meter.native.js +1 -2
  256. package/dist/jsx/Meter/meter.native.js.map +1 -1
  257. package/dist/jsx/Pagination.mjs +2 -2
  258. package/dist/jsx/Pagination.mjs.map +1 -1
  259. package/dist/jsx/Pagination.native.js +2 -2
  260. package/dist/jsx/Pagination.native.js.map +1 -1
  261. package/dist/jsx/ProgressSteps/index.mjs +3 -3
  262. package/dist/jsx/ProgressSteps/index.mjs.map +1 -1
  263. package/dist/jsx/ProgressSteps/index.native.js +3 -3
  264. package/dist/jsx/ProgressSteps/index.native.js.map +1 -1
  265. package/dist/jsx/Quote/index.mjs +1 -2
  266. package/dist/jsx/Quote/index.mjs.map +1 -1
  267. package/dist/jsx/Quote/index.native.js +1 -2
  268. package/dist/jsx/Quote/index.native.js.map +1 -1
  269. package/dist/jsx/SheetModal/index.mjs +22 -3
  270. package/dist/jsx/SheetModal/index.mjs.map +1 -1
  271. package/dist/jsx/SheetModal/index.native.js +50 -10
  272. package/dist/jsx/SheetModal/index.native.js.map +1 -1
  273. package/dist/jsx/Tabs/index.mjs +34 -13
  274. package/dist/jsx/Tabs/index.mjs.map +1 -1
  275. package/dist/jsx/Tabs/index.native.js +38 -13
  276. package/dist/jsx/Tabs/index.native.js.map +1 -1
  277. package/dist/jsx/Toast/index.mjs +4 -5
  278. package/dist/jsx/Toast/index.mjs.map +1 -1
  279. package/dist/jsx/Toast/index.native.js +4 -5
  280. package/dist/jsx/Toast/index.native.js.map +1 -1
  281. package/dist/jsx/TreeSelect/index.mjs +46 -7
  282. package/dist/jsx/TreeSelect/index.mjs.map +1 -1
  283. package/dist/jsx/TreeSelect/index.native.js +58 -7
  284. package/dist/jsx/TreeSelect/index.native.js.map +1 -1
  285. package/dist/jsx/charts/PieChart.mjs +1 -2
  286. package/dist/jsx/charts/PieChart.mjs.map +1 -1
  287. package/dist/jsx/charts/PieChart.native.js +1 -2
  288. package/dist/jsx/charts/PieChart.native.js.map +1 -1
  289. package/dist/jsx/code/HighlightedCode/index.mjs +1 -1
  290. package/dist/jsx/code/HighlightedCode/index.mjs.map +1 -1
  291. package/dist/jsx/code/HighlightedCode/index.native.js +1 -1
  292. package/dist/jsx/code/HighlightedCode/index.native.js.map +1 -1
  293. package/dist/jsx/componentColors.mjs +6 -4
  294. package/dist/jsx/componentColors.mjs.map +1 -1
  295. package/dist/jsx/componentColors.native.js +6 -4
  296. package/dist/jsx/componentColors.native.js.map +1 -1
  297. package/dist/jsx/images/Image/ImageFrame.mjs +4 -2
  298. package/dist/jsx/images/Image/ImageFrame.mjs.map +1 -1
  299. package/dist/jsx/images/Image/ImageFrame.native.js +4 -2
  300. package/dist/jsx/images/Image/ImageFrame.native.js.map +1 -1
  301. package/dist/jsx/layouts/AsyncBoundary.mjs +1 -2
  302. package/dist/jsx/layouts/AsyncBoundary.mjs.map +1 -1
  303. package/dist/jsx/layouts/AsyncBoundary.native.js +1 -2
  304. package/dist/jsx/layouts/AsyncBoundary.native.js.map +1 -1
  305. package/dist/jsx/layouts/EmptyState.mjs +7 -3
  306. package/dist/jsx/layouts/EmptyState.mjs.map +1 -1
  307. package/dist/jsx/layouts/EmptyState.native.js +10 -6
  308. package/dist/jsx/layouts/EmptyState.native.js.map +1 -1
  309. package/dist/jsx/layouts/attachments.mjs +4 -4
  310. package/dist/jsx/layouts/attachments.mjs.map +1 -1
  311. package/dist/jsx/layouts/attachments.native.js +4 -4
  312. package/dist/jsx/layouts/attachments.native.js.map +1 -1
  313. package/dist/jsx/layouts/breadcrumbs.mjs +1 -2
  314. package/dist/jsx/layouts/breadcrumbs.mjs.map +1 -1
  315. package/dist/jsx/layouts/breadcrumbs.native.js +1 -2
  316. package/dist/jsx/layouts/breadcrumbs.native.js.map +1 -1
  317. package/dist/jsx/layouts/page.mjs +2 -2
  318. package/dist/jsx/layouts/page.mjs.map +1 -1
  319. package/dist/jsx/layouts/page.native.js +2 -2
  320. package/dist/jsx/layouts/page.native.js.map +1 -1
  321. package/dist/jsx/layouts/sidebar.mjs +2 -2
  322. package/dist/jsx/layouts/sidebar.mjs.map +1 -1
  323. package/dist/jsx/layouts/sidebar.native.js +2 -2
  324. package/dist/jsx/layouts/sidebar.native.js.map +1 -1
  325. package/dist/jsx/layouts/tags.mjs +6 -7
  326. package/dist/jsx/layouts/tags.mjs.map +1 -1
  327. package/dist/jsx/layouts/tags.native.js +6 -9
  328. package/dist/jsx/layouts/tags.native.js.map +1 -1
  329. package/dist/jsx/layouts/timeline.mjs +5 -5
  330. package/dist/jsx/layouts/timeline.mjs.map +1 -1
  331. package/dist/jsx/layouts/timeline.native.js +5 -5
  332. package/dist/jsx/layouts/timeline.native.js.map +1 -1
  333. package/dist/jsx/surfaces.mjs +1 -0
  334. package/dist/jsx/surfaces.mjs.map +1 -1
  335. package/dist/jsx/surfaces.native.js +1 -0
  336. package/dist/jsx/surfaces.native.js.map +1 -1
  337. package/dist/jsx/views/Calendar.mjs +11 -8
  338. package/dist/jsx/views/Calendar.mjs.map +1 -1
  339. package/dist/jsx/views/Calendar.native.js +11 -8
  340. package/dist/jsx/views/Calendar.native.js.map +1 -1
  341. package/dist/jsx/views/Dashboard.mjs +6 -6
  342. package/dist/jsx/views/Dashboard.mjs.map +1 -1
  343. package/dist/jsx/views/Dashboard.native.js +7 -7
  344. package/dist/jsx/views/Dashboard.native.js.map +1 -1
  345. package/dist/jsx/views/Gantt.mjs +3 -3
  346. package/dist/jsx/views/Gantt.mjs.map +1 -1
  347. package/dist/jsx/views/Gantt.native.js +3 -3
  348. package/dist/jsx/views/Gantt.native.js.map +1 -1
  349. package/dist/jsx/views/Kanban.mjs +2 -2
  350. package/dist/jsx/views/Kanban.mjs.map +1 -1
  351. package/dist/jsx/views/Kanban.native.js +2 -2
  352. package/dist/jsx/views/Kanban.native.js.map +1 -1
  353. package/dist/jsx/views/ReportBuilder.mjs +4 -4
  354. package/dist/jsx/views/ReportBuilder.mjs.map +1 -1
  355. package/dist/jsx/views/ReportBuilder.native.js +4 -4
  356. package/dist/jsx/views/ReportBuilder.native.js.map +1 -1
  357. package/dist/jsx/views/TreeView.mjs +249 -20
  358. package/dist/jsx/views/TreeView.mjs.map +1 -1
  359. package/dist/jsx/views/TreeView.native.js +264 -20
  360. package/dist/jsx/views/TreeView.native.js.map +1 -1
  361. package/dist/jsx/views/treeViewReveal.mjs +29 -0
  362. package/dist/jsx/views/treeViewReveal.mjs.map +1 -0
  363. package/dist/jsx/views/treeViewReveal.native.js +80 -0
  364. package/dist/jsx/views/treeViewReveal.native.js.map +1 -0
  365. package/package.json +11 -11
  366. package/src/Accordion/index.tsx +4 -6
  367. package/src/Chip/index.tsx +1 -1
  368. package/src/ContextMenu/index.tsx +3 -3
  369. package/src/DropdownMenu/index.tsx +3 -3
  370. package/src/Meter/Meter.spec.tsx +12 -4
  371. package/src/Meter/index.tsx +3 -2
  372. package/src/Meter/meter.ts +6 -1
  373. package/src/Pagination.tsx +2 -2
  374. package/src/ProgressSteps/index.tsx +3 -3
  375. package/src/Quote/index.tsx +1 -3
  376. package/src/SheetModal/SheetModal.fill.spec.tsx +147 -0
  377. package/src/SheetModal/SheetModal.ownedBody.spec.tsx +200 -0
  378. package/src/SheetModal/SheetModal.stories.spec.tsx +23 -2
  379. package/src/SheetModal/SheetModal.stories.tsx +275 -2
  380. package/src/SheetModal/index.native.tsx +50 -6
  381. package/src/SheetModal/index.tsx +40 -3
  382. package/src/Tabs/Tabs.lifecycle.spec.tsx +123 -0
  383. package/src/Tabs/Tabs.measurement.spec.tsx +186 -0
  384. package/src/Tabs/Tabs.stories.tsx +43 -0
  385. package/src/Tabs/index.tsx +44 -15
  386. package/src/Toast/Toast.spec.tsx +120 -48
  387. package/src/Toast/index.tsx +5 -3
  388. package/src/TreeSelect/index.tsx +61 -1
  389. package/src/charts/PieChart.tsx +1 -2
  390. package/src/code/HighlightedCode/index.tsx +1 -1
  391. package/src/componentColors.ts +6 -4
  392. package/src/images/Image/ImageFrame.tsx +2 -2
  393. package/src/layouts/AsyncBoundary.tsx +1 -2
  394. package/src/layouts/EmptyState.spec.tsx +58 -1
  395. package/src/layouts/EmptyState.tsx +11 -7
  396. package/src/layouts/KeyboardAvoidingWrapper/KeyboardAvoidingWrapper.stories.tsx +3 -3
  397. package/src/layouts/PaneScaffold.stories.tsx +3 -8
  398. package/src/layouts/SafeAreaWrapper/SafeAreaWrapper.stories.tsx +3 -3
  399. package/src/layouts/attachments.tsx +4 -4
  400. package/src/layouts/breadcrumbs.tsx +1 -2
  401. package/src/layouts/page.tsx +2 -2
  402. package/src/layouts/sidebar.tsx +2 -2
  403. package/src/layouts/tags-trigger.spec.tsx +32 -0
  404. package/src/layouts/tags.tsx +7 -9
  405. package/src/layouts/timeline.tsx +5 -5
  406. package/src/surfaces.spec.tsx +97 -3
  407. package/src/surfaces.tsx +4 -2
  408. package/src/textAccentSecondary.spec.tsx +187 -0
  409. package/src/views/Calendar.tsx +9 -8
  410. package/src/views/Dashboard.tsx +13 -8
  411. package/src/views/Gantt.tsx +3 -3
  412. package/src/views/Kanban.native.tsx +2 -2
  413. package/src/views/Kanban.tsx +2 -2
  414. package/src/views/ReportBuilder.tsx +4 -4
  415. package/src/views/TreeView.reveal.spec.tsx +174 -0
  416. package/src/views/TreeView.reveal.stories.tsx +249 -0
  417. package/src/views/TreeView.scroll-index.spec.tsx +246 -0
  418. package/src/views/TreeView.scroll.spec.tsx +857 -0
  419. package/src/views/TreeView.spec.tsx +1 -1
  420. package/src/views/TreeView.tsx +353 -29
  421. package/src/views/treeViewReveal.spec.ts +83 -0
  422. package/src/views/treeViewReveal.ts +45 -0
  423. package/src/views/views.spec.tsx +13 -12
  424. package/types/Accordion/index.d.ts.map +1 -1
  425. package/types/Meter/index.d.ts.map +1 -1
  426. package/types/Meter/meter.d.ts +5 -1
  427. package/types/Meter/meter.d.ts.map +1 -1
  428. package/types/Quote/index.d.ts.map +1 -1
  429. package/types/SheetModal/index.d.ts +8 -1
  430. package/types/SheetModal/index.d.ts.map +1 -1
  431. package/types/SheetModal/index.native.d.ts +1 -1
  432. package/types/SheetModal/index.native.d.ts.map +1 -1
  433. package/types/Tabs/index.d.ts.map +1 -1
  434. package/types/Toast/index.d.ts.map +1 -1
  435. package/types/TreeSelect/index.d.ts +9 -1
  436. package/types/TreeSelect/index.d.ts.map +1 -1
  437. package/types/charts/PieChart.d.ts.map +1 -1
  438. package/types/componentColors.d.ts +1 -4
  439. package/types/componentColors.d.ts.map +1 -1
  440. package/types/images/Image/ImageFrame.d.ts.map +1 -1
  441. package/types/layouts/AsyncBoundary.d.ts.map +1 -1
  442. package/types/layouts/EmptyState.d.ts.map +1 -1
  443. package/types/layouts/breadcrumbs.d.ts.map +1 -1
  444. package/types/layouts/tags.d.ts.map +1 -1
  445. package/types/surfaces.d.ts.map +1 -1
  446. package/types/views/Calendar.d.ts.map +1 -1
  447. package/types/views/Dashboard.d.ts +7 -3
  448. package/types/views/Dashboard.d.ts.map +1 -1
  449. package/types/views/TreeView.d.ts +19 -1
  450. package/types/views/TreeView.d.ts.map +1 -1
  451. package/types/views/treeViewReveal.d.ts +11 -0
  452. package/types/views/treeViewReveal.d.ts.map +1 -0
@@ -0,0 +1,174 @@
1
+ import type { TreeViewProps as PublicTreeViewProps } from "@multiplatform.one/components";
2
+ import { act, cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
3
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
4
+ import { afterEach, describe, expect, expectTypeOf, it, vi } from "vitest";
5
+ import { TreeView, type TreeNode, type TreeViewProps } from "./TreeView";
6
+
7
+ const nodes: TreeNode[] = [
8
+ {
9
+ id: "folders",
10
+ label: "Folders",
11
+ children: [
12
+ {
13
+ id: "archive",
14
+ label: "Archive",
15
+ children: [{ id: "item", label: "Item" }],
16
+ },
17
+ ],
18
+ },
19
+ { id: "other", label: "Other", children: [{ id: "other-item", label: "Other item" }] },
20
+ ];
21
+
22
+ afterEach(cleanup);
23
+
24
+ describe("TreeView public expansion contract", () => {
25
+ it("exposes the agreed reveal request through TreeViewProps", () => {
26
+ expectTypeOf<PublicTreeViewProps>().toEqualTypeOf<TreeViewProps>();
27
+ expectTypeOf<TreeViewProps["revealRequest"]>().toEqualTypeOf<
28
+ { id: string; requestId: number } | undefined
29
+ >();
30
+ expectTypeOf<TreeViewProps["onRevealComplete"]>().toEqualTypeOf<
31
+ ((request: { id: string; requestId: number }) => void) | undefined
32
+ >();
33
+ expectTypeOf<TreeViewProps["initialScrollOffset"]>().toEqualTypeOf<number | undefined>();
34
+ expectTypeOf<TreeViewProps["onScrollOffsetChange"]>().toEqualTypeOf<
35
+ ((offset: number) => void) | undefined
36
+ >();
37
+ expectTypeOf<TreeViewProps["expandedIds"]>().toEqualTypeOf<string[] | undefined>();
38
+ expectTypeOf<TreeViewProps["onExpandedIdsChange"]>().toEqualTypeOf<
39
+ ((ids: string[]) => void) | undefined
40
+ >();
41
+ });
42
+
43
+ it("waits for controlled expansion to be accepted, overriding initial defaults", () => {
44
+ const change = vi.fn();
45
+ const toggle = vi.fn();
46
+ const props = {
47
+ nodes,
48
+ expandedIds: [],
49
+ defaultExpandedIds: ["folders"],
50
+ expandByDefault: true,
51
+ onExpandedIdsChange: change,
52
+ onNodeToggle: toggle,
53
+ };
54
+ const { rerender } = renderWithProviders(<TreeView {...props} />);
55
+ expect(screen.queryByText("Archive")).toBeNull();
56
+ fireEvent.click(screen.getByText("Folders"));
57
+ expect(change).toHaveBeenCalledExactlyOnceWith(["folders"]);
58
+ expect(toggle).toHaveBeenCalledExactlyOnceWith(nodes[0]);
59
+ expect(screen.queryByText("Archive")).toBeNull();
60
+ rerender(<TreeView {...props} expandedIds={["folders"]} />);
61
+ expect(screen.getByText("Archive")).toBeInTheDocument();
62
+ expect(change).toHaveBeenCalledTimes(1);
63
+ rerender(<TreeView {...props} expandedIds={[]} />);
64
+ expect(screen.queryByText("Archive")).toBeNull();
65
+ expect(change).toHaveBeenCalledTimes(1);
66
+ });
67
+
68
+ it("keeps uncontrolled toggles and their new notification working", () => {
69
+ const change = vi.fn();
70
+ renderWithProviders(<TreeView nodes={nodes} onExpandedIdsChange={change} />);
71
+ fireEvent.click(screen.getByText("Folders"));
72
+ expect(screen.getByText("Archive")).toBeInTheDocument();
73
+ expect(change).toHaveBeenNthCalledWith(1, ["folders"]);
74
+ fireEvent.click(screen.getByText("Folders"));
75
+ expect(screen.queryByText("Archive")).toBeNull();
76
+ expect(change).toHaveBeenNthCalledWith(2, []);
77
+ });
78
+
79
+ it("does not expand or select when only selectedId changes", async () => {
80
+ const change = vi.fn();
81
+ const select = vi.fn();
82
+ const { rerender } = renderWithProviders(
83
+ <TreeView nodes={nodes} onExpandedIdsChange={change} onNodeSelect={select} />,
84
+ );
85
+ rerender(
86
+ <TreeView
87
+ nodes={nodes}
88
+ selectedId="item"
89
+ onExpandedIdsChange={change}
90
+ onNodeSelect={select}
91
+ />,
92
+ );
93
+ await act(async () => {});
94
+ expect(screen.queryByText("Item")).toBeNull();
95
+ expect(change).not.toHaveBeenCalled();
96
+ expect(select).not.toHaveBeenCalled();
97
+ });
98
+ });
99
+
100
+ describe("TreeView reveal expansion", () => {
101
+ it("merges only ancestors, waits for acceptance, and leaves selection and focus alone", async () => {
102
+ const change = vi.fn();
103
+ const select = vi.fn();
104
+ const toggle = vi.fn();
105
+ const props = {
106
+ nodes,
107
+ expandedIds: ["other"],
108
+ selectedId: "item",
109
+ revealRequest: { id: "item", requestId: 1 },
110
+ onExpandedIdsChange: change,
111
+ onNodeSelect: select,
112
+ onNodeToggle: toggle,
113
+ };
114
+ const { rerender } = renderWithProviders(<TreeView {...props} />);
115
+ await waitFor(() =>
116
+ expect(change).toHaveBeenCalledExactlyOnceWith(["other", "folders", "archive"]),
117
+ );
118
+ expect(screen.queryByText("Item")).toBeNull();
119
+ rerender(<TreeView {...props} expandedIds={["other"]} />);
120
+ await act(async () => {});
121
+ expect(change).toHaveBeenCalledTimes(1);
122
+ rerender(<TreeView {...props} expandedIds={["other", "folders", "archive"]} />);
123
+ expect(screen.getByText("Item").closest('[role="treeitem"]')).toHaveAttribute(
124
+ "aria-selected",
125
+ "true",
126
+ );
127
+ expect(screen.getByText("Other item")).toBeInTheDocument();
128
+ expect(select).not.toHaveBeenCalled();
129
+ expect(toggle).not.toHaveBeenCalled();
130
+ expect(screen.getByRole("tree")).not.toHaveFocus();
131
+ });
132
+
133
+ it("retains a request through missing data and expands after data arrives", async () => {
134
+ const change = vi.fn();
135
+ const props = {
136
+ revealRequest: { id: "item", requestId: 2 },
137
+ onExpandedIdsChange: change,
138
+ };
139
+ const { rerender } = renderWithProviders(<TreeView {...props} nodes={[]} />);
140
+ await act(async () => {});
141
+ expect(change).not.toHaveBeenCalled();
142
+ rerender(<TreeView {...props} nodes={nodes} />);
143
+ await waitFor(() => expect(screen.getByText("Item")).toBeInTheDocument());
144
+ expect(change).toHaveBeenCalledExactlyOnceWith(["folders", "archive"]);
145
+ });
146
+
147
+ it("replaces a missing request before its old data arrives", async () => {
148
+ const change = vi.fn();
149
+ const { rerender } = renderWithProviders(
150
+ <TreeView
151
+ nodes={[]}
152
+ revealRequest={{ id: "item", requestId: 1 }}
153
+ onExpandedIdsChange={change}
154
+ />,
155
+ );
156
+ rerender(
157
+ <TreeView
158
+ nodes={[]}
159
+ revealRequest={{ id: "other-item", requestId: 2 }}
160
+ onExpandedIdsChange={change}
161
+ />,
162
+ );
163
+ rerender(
164
+ <TreeView
165
+ nodes={nodes}
166
+ revealRequest={{ id: "other-item", requestId: 2 }}
167
+ onExpandedIdsChange={change}
168
+ />,
169
+ );
170
+ await waitFor(() => expect(screen.getByText("Other item")).toBeInTheDocument());
171
+ expect(screen.queryByText("Archive")).toBeNull();
172
+ expect(change).toHaveBeenCalledExactlyOnceWith(["other"]);
173
+ });
174
+ });
@@ -0,0 +1,249 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
2
+ import { Button } from "@multiplatform.one/forms";
3
+ import { useRef, useState } from "react";
4
+ import { Text, XStack, YStack } from "tamagui";
5
+ import { TreeView, type TreeNode, type TreeViewProps } from "./TreeView";
6
+
7
+ const folders: TreeNode[] = [
8
+ {
9
+ id: "folders",
10
+ label: "Folders",
11
+ children: [
12
+ {
13
+ id: "archive",
14
+ label: "Archive",
15
+ children: Array.from({ length: 500 }, (_, index) => ({
16
+ id: `item-${index}`,
17
+ label: `Item ${String(index).padStart(3, "0")}`,
18
+ })),
19
+ },
20
+ ],
21
+ },
22
+ { id: "other", label: "Other", children: [{ id: "other-item", label: "Other item" }] },
23
+ ];
24
+
25
+ const meta: Meta<typeof TreeView> = {
26
+ title: "Components/TreeView",
27
+ component: TreeView,
28
+ args: { height: 240, ariaLabel: "Reveal fixture" },
29
+ parameters: { status: { type: "stable" } },
30
+ };
31
+ export default meta;
32
+ type Story = StoryObj<typeof TreeView>;
33
+
34
+ function RevealFixture(args: TreeViewProps) {
35
+ const [nodes, setNodes] = useState(folders);
36
+ const [expandedIds, setExpandedIds] = useState<string[]>(["other"]);
37
+ const [selectedId, setSelectedId] = useState<string>();
38
+ const [revealRequest, setRevealRequest] = useState<TreeViewProps["revealRequest"]>();
39
+ const [completion, setCompletion] = useState<{
40
+ request: NonNullable<TreeViewProps["revealRequest"]>;
41
+ count: number;
42
+ }>();
43
+ const [deferExpansion, setDeferExpansion] = useState(false);
44
+ const [pendingExpansion, setPendingExpansion] = useState<string[]>();
45
+ const [hidden, setHidden] = useState(false);
46
+ const sequence = useRef(0);
47
+ const reveal = (id: string) => {
48
+ setSelectedId(id);
49
+ setRevealRequest({ id, requestId: ++sequence.current });
50
+ };
51
+ return (
52
+ <YStack gap="$3" width="100%" maxWidth={700}>
53
+ <XStack gap="$2" flexWrap="wrap">
54
+ <Button onPress={() => reveal("item-499")}>Reveal last item</Button>
55
+ <Button onPress={() => reveal("item-0")}>Reveal first item</Button>
56
+ <Button onPress={() => reveal("other-item")}>Reveal other item</Button>
57
+ <Button onPress={() => setSelectedId("item-499")}>Select only</Button>
58
+ <Button onPress={() => setExpandedIds(["other"])}>Collapse archive</Button>
59
+ </XStack>
60
+ <XStack gap="$2" flexWrap="wrap">
61
+ <Button onPress={() => setNodes([])}>Remove data</Button>
62
+ <Button onPress={() => setNodes(folders)}>Deliver data</Button>
63
+ <Button onPress={() => setHidden((value) => !value)}>
64
+ {hidden ? "Show tree" : "Hide tree"}
65
+ </Button>
66
+ <Button onPress={() => setDeferExpansion((value) => !value)}>
67
+ {deferExpansion ? "Accept expansion immediately" : "Defer expansion"}
68
+ </Button>
69
+ {pendingExpansion && (
70
+ <Button
71
+ onPress={() => {
72
+ setExpandedIds(pendingExpansion);
73
+ setPendingExpansion(undefined);
74
+ }}
75
+ >
76
+ Accept pending expansion
77
+ </Button>
78
+ )}
79
+ </XStack>
80
+ <Text>500 variable-height items. Request: {revealRequest?.requestId ?? "none"}</Text>
81
+ <Text>
82
+ Target: {revealRequest?.id ?? "none"}. Expanded: {expandedIds.join(", ")}
83
+ </Text>
84
+ <Text>
85
+ Measured completion:{" "}
86
+ {completion ? `${completion.request.id} (${completion.request.requestId})` : "none"}.
87
+ Callback count: {completion?.count ?? 0}
88
+ </Text>
89
+ <YStack display={hidden ? "none" : "flex"}>
90
+ <TreeView
91
+ {...args}
92
+ nodes={nodes}
93
+ expandedIds={expandedIds}
94
+ onExpandedIdsChange={(ids) => {
95
+ if (deferExpansion) setPendingExpansion(ids);
96
+ else setExpandedIds(ids);
97
+ }}
98
+ selectedId={selectedId}
99
+ revealRequest={revealRequest}
100
+ onRevealComplete={(request) => {
101
+ setCompletion((previous) => ({ request, count: (previous?.count ?? 0) + 1 }));
102
+ args.onRevealComplete?.(request);
103
+ }}
104
+ onNodeSelect={(node) => setSelectedId(node.id)}
105
+ renderNode={(node) => {
106
+ const index = Number(node.id.replace("item-", ""));
107
+ return (
108
+ <YStack
109
+ minHeight={Number.isFinite(index) && index % 2 ? 80 : 32}
110
+ justifyContent="center"
111
+ >
112
+ <Text>{node.label}</Text>
113
+ {Number.isFinite(index) && index % 2 === 1 && (
114
+ <Text fontSize="$2">Additional detail</Text>
115
+ )}
116
+ </YStack>
117
+ );
118
+ }}
119
+ />
120
+ </YStack>
121
+ </YStack>
122
+ );
123
+ }
124
+
125
+ export const ControlledReveal: Story = {
126
+ name: "Controlled reveal with 500 variable-height items",
127
+ render: (args) => <RevealFixture {...args} />,
128
+ };
129
+
130
+ function RetainedScrollFixture(args: TreeViewProps) {
131
+ const [mounted, setMounted] = useState(true);
132
+ const [generation, setGeneration] = useState(0);
133
+ const [nodes, setNodes] = useState(folders);
134
+ const [hidden, setHidden] = useState(false);
135
+ const [taller, setTaller] = useState(false);
136
+ const [savedOffset, setSavedOffset] = useState<number>();
137
+ const [observations, setObservations] = useState(0);
138
+ const [expandedIds, setExpandedIds] = useState(["folders", "archive", "other"]);
139
+ const [revealRequest, setRevealRequest] = useState<TreeViewProps["revealRequest"]>();
140
+ const [completion, setCompletion] = useState<string>();
141
+ const sequence = useRef(0);
142
+ const revealLast = () => setRevealRequest({ id: "item-499", requestId: ++sequence.current });
143
+ return (
144
+ <YStack gap="$3" width="100%" maxWidth={700}>
145
+ <Text>
146
+ Scroll manually, unmount, then mount. The observed offset and expansion survive outside the
147
+ tree. Remount with delayed data or a hidden host to check deferred restoration.
148
+ </Text>
149
+ <XStack gap="$2" flexWrap="wrap">
150
+ <Button onPress={() => setMounted((value) => !value)}>
151
+ {mounted ? "Unmount tree" : "Mount tree"}
152
+ </Button>
153
+ <Button
154
+ onPress={() => {
155
+ setNodes([]);
156
+ setMounted(true);
157
+ setGeneration((value) => value + 1);
158
+ }}
159
+ >
160
+ Remount without data
161
+ </Button>
162
+ <Button onPress={() => setNodes(folders)}>Deliver data</Button>
163
+ <Button
164
+ onPress={() => {
165
+ setHidden(true);
166
+ setMounted(true);
167
+ setGeneration((value) => value + 1);
168
+ }}
169
+ >
170
+ Remount hidden
171
+ </Button>
172
+ <Button onPress={() => setHidden((value) => !value)}>
173
+ {hidden ? "Show tree" : "Hide tree"}
174
+ </Button>
175
+ </XStack>
176
+ <XStack gap="$2" flexWrap="wrap">
177
+ <Button onPress={revealLast}>Reveal last item</Button>
178
+ <Button
179
+ onPress={() => {
180
+ revealLast();
181
+ setMounted(true);
182
+ setGeneration((value) => value + 1);
183
+ }}
184
+ >
185
+ Remount with reveal
186
+ </Button>
187
+ <Button onPress={() => setTaller((value) => !value)}>
188
+ {taller ? "Use configured height" : "Double viewport height"}
189
+ </Button>
190
+ </XStack>
191
+ <Text>
192
+ Retained observed offset: {savedOffset ?? "none"}. Observations: {observations}.
193
+ </Text>
194
+ <Text>
195
+ Initial fallback: {args.initialScrollOffset ?? "none"}. Mounted: {String(mounted)}.
196
+ </Text>
197
+ <Text>
198
+ Pending reveal: {revealRequest?.requestId ?? "none"}. Measured completion:{" "}
199
+ {completion ?? "none"}.
200
+ </Text>
201
+ <YStack display={hidden ? "none" : "flex"}>
202
+ {mounted && (
203
+ <TreeView
204
+ {...args}
205
+ key={generation}
206
+ nodes={nodes}
207
+ height={taller ? (args.height ?? 240) * 2 : args.height}
208
+ expandedIds={expandedIds}
209
+ onExpandedIdsChange={setExpandedIds}
210
+ initialScrollOffset={savedOffset ?? args.initialScrollOffset}
211
+ onScrollOffsetChange={(offset) => {
212
+ setSavedOffset(offset);
213
+ setObservations((value) => value + 1);
214
+ args.onScrollOffsetChange?.(offset);
215
+ }}
216
+ revealRequest={revealRequest}
217
+ onRevealComplete={(request) => {
218
+ setCompletion(`${request.id} (${request.requestId})`);
219
+ setRevealRequest((pending) =>
220
+ pending?.requestId === request.requestId ? undefined : pending,
221
+ );
222
+ args.onRevealComplete?.(request);
223
+ }}
224
+ renderNode={(node) => {
225
+ const index = Number(node.id.replace("item-", ""));
226
+ return (
227
+ <YStack
228
+ minHeight={Number.isFinite(index) && index % 2 ? 80 : 32}
229
+ justifyContent="center"
230
+ >
231
+ <Text>{node.label}</Text>
232
+ {Number.isFinite(index) && index % 2 === 1 && (
233
+ <Text fontSize="$2">Additional detail</Text>
234
+ )}
235
+ </YStack>
236
+ );
237
+ }}
238
+ />
239
+ )}
240
+ </YStack>
241
+ </YStack>
242
+ );
243
+ }
244
+
245
+ export const RetainedScroll: Story = {
246
+ name: "Retained scroll across unmount and delayed layout",
247
+ args: { initialScrollOffset: 640.5, ariaLabel: "Retained scroll fixture" },
248
+ render: (args) => <RetainedScrollFixture {...args} />,
249
+ };
@@ -0,0 +1,246 @@
1
+ import type { FlashListProps, FlashListRef } from "@shopify/flash-list";
2
+ import { act, cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
3
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
4
+ import { afterEach, expect, it, vi } from "vitest";
5
+ import { TreeView } from "./TreeView";
6
+
7
+ const observed = vi.hoisted(() => ({ list: null as FlashListRef<unknown> | null }));
8
+ vi.mock("@shopify/flash-list", async (importOriginal) => {
9
+ const actual = await importOriginal<typeof import("@shopify/flash-list")>();
10
+ const React = await import("react");
11
+ return {
12
+ ...actual,
13
+ FlashList: React.forwardRef<FlashListRef<unknown>, FlashListProps<unknown>>((props, ref) => {
14
+ const captureRef = React.useCallback(
15
+ (list: FlashListRef<unknown> | null) => {
16
+ observed.list = list;
17
+ if (typeof ref === "function") ref(list);
18
+ else if (ref) ref.current = list;
19
+ },
20
+ [ref],
21
+ );
22
+ return React.createElement(actual.FlashList, { ...props, ref: captureRef });
23
+ }),
24
+ };
25
+ });
26
+ afterEach(() => {
27
+ cleanup();
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ it("reports no preparatory index offset while the actual scroll transport is deferred", async () => {
32
+ vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(320);
33
+ vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(240);
34
+ vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockReturnValue({
35
+ x: 0,
36
+ y: 0,
37
+ top: 0,
38
+ left: 0,
39
+ right: 320,
40
+ bottom: 240,
41
+ width: 320,
42
+ height: 240,
43
+ toJSON: () => ({}),
44
+ });
45
+ const change = vi.fn();
46
+ renderWithProviders(
47
+ <TreeView
48
+ nodes={Array.from({ length: 100 }, (_, i) => ({ id: `row-${i}`, label: `Row ${i}` }))}
49
+ height={240}
50
+ onScrollOffsetChange={change}
51
+ />,
52
+ );
53
+ await waitFor(() => expect(observed.list?.getChildContainerDimensions().height).toBe(24000));
54
+ await waitFor(() => expect(change).toHaveBeenCalledWith(0));
55
+ expect(observed.list?.getWindowSize().height).toBe(240);
56
+ expect(observed.list?.getLayout(99)?.y).toBe(23760);
57
+ const native = observed.list!.getNativeScrollRef()!;
58
+ const transport = vi.spyOn(native, "scrollTo").mockImplementation(() => {});
59
+ const scroller = native.getScrollableNode();
60
+ const index = vi.spyOn(observed.list!, "scrollToIndex");
61
+ change.mockClear();
62
+ fireEvent.keyDown(screen.getByRole("tree"), { key: "End" });
63
+ expect(index).toHaveBeenCalledExactlyOnceWith({ index: 99, animated: true });
64
+ await act(async () => {
65
+ await new Promise((resolve) => setTimeout(resolve, 40));
66
+ });
67
+ expect(transport).toHaveBeenCalled();
68
+ expect(observed.list!.getAbsoluteLastScrollOffset()).toBeGreaterThan(0);
69
+ expect(scroller.scrollTop).toBe(0);
70
+ expect(change).not.toHaveBeenCalled();
71
+ await act(async () => {
72
+ await index.mock.results[0].value;
73
+ });
74
+ expect(change).not.toHaveBeenCalled();
75
+ fireEvent.scroll(scroller, { target: { scrollTop: 96.5 } });
76
+ await waitFor(() => expect(change).toHaveBeenCalledExactlyOnceWith(96.5));
77
+ change.mockClear();
78
+ fireEvent.keyDown(screen.getByRole("tree"), { key: "Home" });
79
+ expect(index).toHaveBeenLastCalledWith({ index: 0, animated: true });
80
+ await act(async () => {
81
+ await index.mock.results[1].value;
82
+ });
83
+ expect(change).not.toHaveBeenCalled();
84
+ fireEvent.scroll(scroller, { target: { scrollTop: 0 } });
85
+ await waitFor(() => expect(change).toHaveBeenCalledExactlyOnceWith(0));
86
+ });
87
+
88
+ it("positive control: actual scroll delivery reports the transported offset", async () => {
89
+ vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(320);
90
+ vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(240);
91
+ vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockReturnValue({
92
+ x: 0,
93
+ y: 0,
94
+ top: 0,
95
+ left: 0,
96
+ right: 320,
97
+ bottom: 240,
98
+ width: 320,
99
+ height: 240,
100
+ toJSON: () => ({}),
101
+ });
102
+ const change = vi.fn();
103
+ renderWithProviders(
104
+ <TreeView
105
+ nodes={Array.from({ length: 100 }, (_, i) => ({ id: `row-${i}`, label: `Row ${i}` }))}
106
+ height={240}
107
+ onScrollOffsetChange={change}
108
+ />,
109
+ );
110
+ await waitFor(() => expect(observed.list?.getChildContainerDimensions().height).toBe(24000));
111
+ await waitFor(() => expect(change).toHaveBeenCalledWith(0));
112
+ change.mockClear();
113
+ const native = observed.list!.getNativeScrollRef()!;
114
+ const scroller = native.getScrollableNode();
115
+ fireEvent.scroll(scroller, { target: { scrollTop: 96.5 } });
116
+ await waitFor(() => expect(change).toHaveBeenCalledWith(96.5));
117
+ });
118
+
119
+ function dimensions() {
120
+ vi.spyOn(HTMLElement.prototype, "clientWidth", "get").mockReturnValue(320);
121
+ vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(240);
122
+ vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
123
+ function (this: HTMLElement) {
124
+ let y = 0;
125
+ for (let ancestor = this.parentElement; ancestor; ancestor = ancestor.parentElement) {
126
+ y -= ancestor.scrollTop;
127
+ }
128
+ return {
129
+ x: 0,
130
+ y,
131
+ top: y,
132
+ left: 0,
133
+ right: 320,
134
+ bottom: y + 240,
135
+ width: 320,
136
+ height: 240,
137
+ toJSON: () => ({}),
138
+ };
139
+ },
140
+ );
141
+ }
142
+
143
+ it.each([false, true])(
144
+ "acknowledges index-prepared reveal only after physical delivery (delivered: %s)",
145
+ async (delivered) => {
146
+ dimensions();
147
+ const nodes = Array.from({ length: 100 }, (_, i) => ({ id: `row-${i}`, label: `Row ${i}` }));
148
+ const change = vi.fn();
149
+ const complete = vi.fn();
150
+ const tree = renderWithProviders(
151
+ <TreeView
152
+ nodes={nodes}
153
+ height={240}
154
+ onScrollOffsetChange={change}
155
+ onRevealComplete={complete}
156
+ />,
157
+ );
158
+ await waitFor(() => expect(observed.list?.getChildContainerDimensions().height).toBe(24000));
159
+ await waitFor(() => expect(change).toHaveBeenCalledWith(0));
160
+ const native = observed.list!.getNativeScrollRef()!;
161
+ const transport = vi.spyOn(native, "scrollTo").mockImplementation(() => {});
162
+ const scroller = native.getScrollableNode();
163
+ const index = vi.spyOn(observed.list!, "scrollToIndex");
164
+ fireEvent.keyDown(screen.getByRole("tree"), { key: "End" });
165
+ await act(async () => {
166
+ await index.mock.results[0].value;
167
+ });
168
+ expect(transport).toHaveBeenCalled();
169
+ expect(scroller.scrollTop).toBe(0);
170
+ const offset = observed.list!.getAbsoluteLastScrollOffset();
171
+ const visible = observed.list!.computeVisibleIndices();
172
+ const id = `row-${visible.startIndex}`;
173
+ const layout = observed.list!.getLayout(visible.startIndex)!;
174
+ expect(offset).toBeGreaterThan(0);
175
+ expect(layout.isHeightMeasured).toBe(true);
176
+ expect(screen.queryByText(`Row ${visible.startIndex}`)).not.toBeNull();
177
+ if (delivered) {
178
+ fireEvent.scroll(scroller, { target: { scrollTop: offset } });
179
+ expect(scroller.scrollTop).toBe(offset);
180
+ await waitFor(() => expect(change).toHaveBeenCalledWith(offset));
181
+ }
182
+ transport.mockClear();
183
+ tree.rerender(
184
+ <TreeView
185
+ nodes={nodes}
186
+ height={240}
187
+ onScrollOffsetChange={change}
188
+ onRevealComplete={complete}
189
+ revealRequest={{ id, requestId: 71 }}
190
+ />,
191
+ );
192
+ await act(async () => {
193
+ await new Promise((resolve) => setTimeout(resolve, 60));
194
+ });
195
+ if (!delivered) {
196
+ expect(complete).not.toHaveBeenCalled();
197
+ expect(scroller.scrollTop).toBe(0);
198
+ expect(transport).toHaveBeenCalledWith({ x: 0, y: offset, animated: false });
199
+ } else {
200
+ expect(complete).toHaveBeenCalledExactlyOnceWith({ id, requestId: 71 });
201
+ }
202
+ fireEvent.scroll(scroller, { target: { scrollTop: offset } });
203
+ await waitFor(() => expect(complete).toHaveBeenCalledExactlyOnceWith({ id, requestId: 71 }));
204
+ },
205
+ );
206
+
207
+ it("reveals an observed keyboard destination without a retention callback", async () => {
208
+ dimensions();
209
+ const nodes = Array.from({ length: 100 }, (_, i) => ({ id: `row-${i}`, label: `Row ${i}` }));
210
+ const complete = vi.fn();
211
+ const tree = renderWithProviders(
212
+ <TreeView nodes={nodes} height={240} onRevealComplete={complete} />,
213
+ );
214
+ await waitFor(() => expect(observed.list?.getChildContainerDimensions().height).toBe(24000));
215
+ const list = observed.list!;
216
+ const native = list.getNativeScrollRef()!;
217
+ const transport = vi.spyOn(native, "scrollTo").mockImplementation(() => {});
218
+ const scroller = native.getScrollableNode();
219
+ const index = vi.spyOn(list, "scrollToIndex");
220
+ fireEvent.keyDown(screen.getByRole("tree"), { key: "End" });
221
+ await act(async () => {
222
+ await index.mock.results[0].value;
223
+ });
224
+ const target = list.computeVisibleIndices().endIndex;
225
+ const layout = list.getLayout(target)!;
226
+ const offset = layout.y + list.getFirstItemOffset();
227
+ expect(offset).toBeGreaterThan(list.getAbsoluteLastScrollOffset());
228
+ expect(layout.isHeightMeasured).toBe(true);
229
+ expect(screen.getByText(`Row ${target}`)).toBeTruthy();
230
+ fireEvent.scroll(scroller, { target: { scrollTop: offset } });
231
+ await waitFor(() => expect(list.getAbsoluteLastScrollOffset()).toBe(offset));
232
+ transport.mockClear();
233
+ const revealScroll = vi.spyOn(list, "scrollToOffset");
234
+ tree.rerender(
235
+ <TreeView
236
+ nodes={nodes}
237
+ height={240}
238
+ onRevealComplete={complete}
239
+ revealRequest={{ id: `row-${target}`, requestId: 72 }}
240
+ />,
241
+ );
242
+ await waitFor(() =>
243
+ expect(complete).toHaveBeenCalledExactlyOnceWith({ id: `row-${target}`, requestId: 72 }),
244
+ );
245
+ expect(revealScroll).not.toHaveBeenCalled();
246
+ });