@multiplatform.one/components 7.8.0 → 7.10.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 (426) hide show
  1. package/dist/cjs/Accordion/index.cjs +3 -3
  2. package/dist/cjs/Accordion/index.native.js +4 -4
  3. package/dist/cjs/Accordion/index.native.js.map +1 -1
  4. package/dist/cjs/Chip/index.cjs +1 -1
  5. package/dist/cjs/Chip/index.native.js +1 -1
  6. package/dist/cjs/Chip/index.native.js.map +1 -1
  7. package/dist/cjs/ContextMenu/index.cjs +3 -3
  8. package/dist/cjs/ContextMenu/index.native.js +3 -3
  9. package/dist/cjs/ContextMenu/index.native.js.map +1 -1
  10. package/dist/cjs/DropdownMenu/index.cjs +3 -3
  11. package/dist/cjs/DropdownMenu/index.native.js +3 -3
  12. package/dist/cjs/DropdownMenu/index.native.js.map +1 -1
  13. package/dist/cjs/Meter/index.cjs +3 -2
  14. package/dist/cjs/Meter/index.native.js +3 -2
  15. package/dist/cjs/Meter/index.native.js.map +1 -1
  16. package/dist/cjs/Meter/meter.cjs +1 -2
  17. package/dist/cjs/Meter/meter.native.js +1 -2
  18. package/dist/cjs/Meter/meter.native.js.map +1 -1
  19. package/dist/cjs/Pagination.cjs +2 -2
  20. package/dist/cjs/Pagination.native.js +2 -2
  21. package/dist/cjs/Pagination.native.js.map +1 -1
  22. package/dist/cjs/ProgressSteps/index.cjs +3 -3
  23. package/dist/cjs/ProgressSteps/index.native.js +3 -3
  24. package/dist/cjs/ProgressSteps/index.native.js.map +1 -1
  25. package/dist/cjs/Quote/index.cjs +1 -2
  26. package/dist/cjs/Quote/index.native.js +1 -2
  27. package/dist/cjs/Quote/index.native.js.map +1 -1
  28. package/dist/cjs/Tabs/index.cjs +1 -1
  29. package/dist/cjs/Tabs/index.native.js +1 -1
  30. package/dist/cjs/Tabs/index.native.js.map +1 -1
  31. package/dist/cjs/Toast/index.cjs +4 -5
  32. package/dist/cjs/Toast/index.native.js +4 -5
  33. package/dist/cjs/Toast/index.native.js.map +1 -1
  34. package/dist/cjs/TreeSelect/index.cjs +46 -7
  35. package/dist/cjs/TreeSelect/index.native.js +58 -7
  36. package/dist/cjs/TreeSelect/index.native.js.map +1 -1
  37. package/dist/cjs/charts/PieChart.cjs +1 -2
  38. package/dist/cjs/charts/PieChart.native.js +1 -2
  39. package/dist/cjs/charts/PieChart.native.js.map +1 -1
  40. package/dist/cjs/code/HighlightedCode/index.cjs +1 -1
  41. package/dist/cjs/code/HighlightedCode/index.native.js +1 -1
  42. package/dist/cjs/code/HighlightedCode/index.native.js.map +1 -1
  43. package/dist/cjs/componentColors.cjs +6 -4
  44. package/dist/cjs/componentColors.native.js +6 -4
  45. package/dist/cjs/componentColors.native.js.map +1 -1
  46. package/dist/cjs/images/Image/ImageFrame.cjs +4 -2
  47. package/dist/cjs/images/Image/ImageFrame.native.js +4 -2
  48. package/dist/cjs/images/Image/ImageFrame.native.js.map +1 -1
  49. package/dist/cjs/layouts/AsyncBoundary.cjs +1 -2
  50. package/dist/cjs/layouts/AsyncBoundary.native.js +1 -2
  51. package/dist/cjs/layouts/AsyncBoundary.native.js.map +1 -1
  52. package/dist/cjs/layouts/EmptyState.cjs +6 -2
  53. package/dist/cjs/layouts/EmptyState.native.js +10 -6
  54. package/dist/cjs/layouts/EmptyState.native.js.map +1 -1
  55. package/dist/cjs/layouts/attachments.cjs +4 -4
  56. package/dist/cjs/layouts/attachments.native.js +4 -4
  57. package/dist/cjs/layouts/attachments.native.js.map +1 -1
  58. package/dist/cjs/layouts/breadcrumbs.cjs +1 -2
  59. package/dist/cjs/layouts/breadcrumbs.native.js +1 -2
  60. package/dist/cjs/layouts/breadcrumbs.native.js.map +1 -1
  61. package/dist/cjs/layouts/page.cjs +2 -2
  62. package/dist/cjs/layouts/page.native.js +2 -2
  63. package/dist/cjs/layouts/page.native.js.map +1 -1
  64. package/dist/cjs/layouts/sidebar.cjs +2 -2
  65. package/dist/cjs/layouts/sidebar.native.js +2 -2
  66. package/dist/cjs/layouts/sidebar.native.js.map +1 -1
  67. package/dist/cjs/layouts/tags.cjs +6 -7
  68. package/dist/cjs/layouts/tags.native.js +6 -9
  69. package/dist/cjs/layouts/tags.native.js.map +1 -1
  70. package/dist/cjs/layouts/timeline.cjs +5 -5
  71. package/dist/cjs/layouts/timeline.native.js +5 -5
  72. package/dist/cjs/layouts/timeline.native.js.map +1 -1
  73. package/dist/cjs/surfaces.cjs +1 -0
  74. package/dist/cjs/surfaces.native.js +1 -0
  75. package/dist/cjs/surfaces.native.js.map +1 -1
  76. package/dist/cjs/views/Calendar.cjs +11 -8
  77. package/dist/cjs/views/Calendar.native.js +11 -8
  78. package/dist/cjs/views/Calendar.native.js.map +1 -1
  79. package/dist/cjs/views/Dashboard.cjs +6 -6
  80. package/dist/cjs/views/Dashboard.native.js +7 -7
  81. package/dist/cjs/views/Dashboard.native.js.map +1 -1
  82. package/dist/cjs/views/Gantt.cjs +3 -3
  83. package/dist/cjs/views/Gantt.native.js +3 -3
  84. package/dist/cjs/views/Gantt.native.js.map +1 -1
  85. package/dist/cjs/views/Kanban.cjs +2 -2
  86. package/dist/cjs/views/Kanban.native.js +2 -2
  87. package/dist/cjs/views/Kanban.native.js.map +1 -1
  88. package/dist/cjs/views/ReportBuilder.cjs +4 -4
  89. package/dist/cjs/views/ReportBuilder.native.js +4 -4
  90. package/dist/cjs/views/ReportBuilder.native.js.map +1 -1
  91. package/dist/cjs/views/TreeView.cjs +103 -6
  92. package/dist/cjs/views/TreeView.native.js +115 -6
  93. package/dist/cjs/views/TreeView.native.js.map +1 -1
  94. package/dist/cjs/views/treeViewReveal.cjs +55 -0
  95. package/dist/cjs/views/treeViewReveal.native.js +80 -0
  96. package/dist/cjs/views/treeViewReveal.native.js.map +1 -0
  97. package/dist/esm/Accordion/index.mjs +3 -3
  98. package/dist/esm/Accordion/index.mjs.map +1 -1
  99. package/dist/esm/Accordion/index.native.js +4 -4
  100. package/dist/esm/Accordion/index.native.js.map +1 -1
  101. package/dist/esm/Chip/index.mjs +1 -1
  102. package/dist/esm/Chip/index.mjs.map +1 -1
  103. package/dist/esm/Chip/index.native.js +1 -1
  104. package/dist/esm/Chip/index.native.js.map +1 -1
  105. package/dist/esm/ContextMenu/index.mjs +3 -3
  106. package/dist/esm/ContextMenu/index.mjs.map +1 -1
  107. package/dist/esm/ContextMenu/index.native.js +3 -3
  108. package/dist/esm/ContextMenu/index.native.js.map +1 -1
  109. package/dist/esm/DropdownMenu/index.mjs +3 -3
  110. package/dist/esm/DropdownMenu/index.mjs.map +1 -1
  111. package/dist/esm/DropdownMenu/index.native.js +3 -3
  112. package/dist/esm/DropdownMenu/index.native.js.map +1 -1
  113. package/dist/esm/Meter/index.mjs +3 -2
  114. package/dist/esm/Meter/index.mjs.map +1 -1
  115. package/dist/esm/Meter/index.native.js +3 -2
  116. package/dist/esm/Meter/index.native.js.map +1 -1
  117. package/dist/esm/Meter/meter.mjs +1 -2
  118. package/dist/esm/Meter/meter.mjs.map +1 -1
  119. package/dist/esm/Meter/meter.native.js +1 -2
  120. package/dist/esm/Meter/meter.native.js.map +1 -1
  121. package/dist/esm/Pagination.mjs +2 -2
  122. package/dist/esm/Pagination.mjs.map +1 -1
  123. package/dist/esm/Pagination.native.js +2 -2
  124. package/dist/esm/Pagination.native.js.map +1 -1
  125. package/dist/esm/ProgressSteps/index.mjs +3 -3
  126. package/dist/esm/ProgressSteps/index.mjs.map +1 -1
  127. package/dist/esm/ProgressSteps/index.native.js +3 -3
  128. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  129. package/dist/esm/Quote/index.mjs +1 -2
  130. package/dist/esm/Quote/index.mjs.map +1 -1
  131. package/dist/esm/Quote/index.native.js +1 -2
  132. package/dist/esm/Quote/index.native.js.map +1 -1
  133. package/dist/esm/Tabs/index.mjs +1 -1
  134. package/dist/esm/Tabs/index.mjs.map +1 -1
  135. package/dist/esm/Tabs/index.native.js +1 -1
  136. package/dist/esm/Tabs/index.native.js.map +1 -1
  137. package/dist/esm/Toast/index.mjs +4 -5
  138. package/dist/esm/Toast/index.mjs.map +1 -1
  139. package/dist/esm/Toast/index.native.js +4 -5
  140. package/dist/esm/Toast/index.native.js.map +1 -1
  141. package/dist/esm/TreeSelect/index.mjs +46 -7
  142. package/dist/esm/TreeSelect/index.mjs.map +1 -1
  143. package/dist/esm/TreeSelect/index.native.js +58 -7
  144. package/dist/esm/TreeSelect/index.native.js.map +1 -1
  145. package/dist/esm/charts/PieChart.mjs +1 -2
  146. package/dist/esm/charts/PieChart.mjs.map +1 -1
  147. package/dist/esm/charts/PieChart.native.js +1 -2
  148. package/dist/esm/charts/PieChart.native.js.map +1 -1
  149. package/dist/esm/code/HighlightedCode/index.mjs +1 -1
  150. package/dist/esm/code/HighlightedCode/index.mjs.map +1 -1
  151. package/dist/esm/code/HighlightedCode/index.native.js +1 -1
  152. package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
  153. package/dist/esm/componentColors.mjs +6 -4
  154. package/dist/esm/componentColors.mjs.map +1 -1
  155. package/dist/esm/componentColors.native.js +6 -4
  156. package/dist/esm/componentColors.native.js.map +1 -1
  157. package/dist/esm/images/Image/ImageFrame.mjs +4 -2
  158. package/dist/esm/images/Image/ImageFrame.mjs.map +1 -1
  159. package/dist/esm/images/Image/ImageFrame.native.js +4 -2
  160. package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
  161. package/dist/esm/layouts/AsyncBoundary.mjs +1 -2
  162. package/dist/esm/layouts/AsyncBoundary.mjs.map +1 -1
  163. package/dist/esm/layouts/AsyncBoundary.native.js +1 -2
  164. package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
  165. package/dist/esm/layouts/EmptyState.mjs +7 -3
  166. package/dist/esm/layouts/EmptyState.mjs.map +1 -1
  167. package/dist/esm/layouts/EmptyState.native.js +11 -7
  168. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  169. package/dist/esm/layouts/attachments.mjs +4 -4
  170. package/dist/esm/layouts/attachments.mjs.map +1 -1
  171. package/dist/esm/layouts/attachments.native.js +4 -4
  172. package/dist/esm/layouts/attachments.native.js.map +1 -1
  173. package/dist/esm/layouts/breadcrumbs.mjs +1 -2
  174. package/dist/esm/layouts/breadcrumbs.mjs.map +1 -1
  175. package/dist/esm/layouts/breadcrumbs.native.js +1 -2
  176. package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
  177. package/dist/esm/layouts/page.mjs +2 -2
  178. package/dist/esm/layouts/page.mjs.map +1 -1
  179. package/dist/esm/layouts/page.native.js +2 -2
  180. package/dist/esm/layouts/page.native.js.map +1 -1
  181. package/dist/esm/layouts/sidebar.mjs +2 -2
  182. package/dist/esm/layouts/sidebar.mjs.map +1 -1
  183. package/dist/esm/layouts/sidebar.native.js +2 -2
  184. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  185. package/dist/esm/layouts/tags.mjs +6 -7
  186. package/dist/esm/layouts/tags.mjs.map +1 -1
  187. package/dist/esm/layouts/tags.native.js +6 -9
  188. package/dist/esm/layouts/tags.native.js.map +1 -1
  189. package/dist/esm/layouts/timeline.mjs +5 -5
  190. package/dist/esm/layouts/timeline.mjs.map +1 -1
  191. package/dist/esm/layouts/timeline.native.js +5 -5
  192. package/dist/esm/layouts/timeline.native.js.map +1 -1
  193. package/dist/esm/surfaces.mjs +1 -0
  194. package/dist/esm/surfaces.mjs.map +1 -1
  195. package/dist/esm/surfaces.native.js +1 -0
  196. package/dist/esm/surfaces.native.js.map +1 -1
  197. package/dist/esm/views/Calendar.mjs +11 -8
  198. package/dist/esm/views/Calendar.mjs.map +1 -1
  199. package/dist/esm/views/Calendar.native.js +11 -8
  200. package/dist/esm/views/Calendar.native.js.map +1 -1
  201. package/dist/esm/views/Dashboard.mjs +6 -6
  202. package/dist/esm/views/Dashboard.mjs.map +1 -1
  203. package/dist/esm/views/Dashboard.native.js +7 -7
  204. package/dist/esm/views/Dashboard.native.js.map +1 -1
  205. package/dist/esm/views/Gantt.mjs +3 -3
  206. package/dist/esm/views/Gantt.mjs.map +1 -1
  207. package/dist/esm/views/Gantt.native.js +3 -3
  208. package/dist/esm/views/Gantt.native.js.map +1 -1
  209. package/dist/esm/views/Kanban.mjs +2 -2
  210. package/dist/esm/views/Kanban.mjs.map +1 -1
  211. package/dist/esm/views/Kanban.native.js +2 -2
  212. package/dist/esm/views/Kanban.native.js.map +1 -1
  213. package/dist/esm/views/ReportBuilder.mjs +4 -4
  214. package/dist/esm/views/ReportBuilder.mjs.map +1 -1
  215. package/dist/esm/views/ReportBuilder.native.js +4 -4
  216. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  217. package/dist/esm/views/TreeView.mjs +104 -7
  218. package/dist/esm/views/TreeView.mjs.map +1 -1
  219. package/dist/esm/views/TreeView.native.js +116 -7
  220. package/dist/esm/views/TreeView.native.js.map +1 -1
  221. package/dist/esm/views/treeViewReveal.mjs +29 -0
  222. package/dist/esm/views/treeViewReveal.mjs.map +1 -0
  223. package/dist/esm/views/treeViewReveal.native.js +51 -0
  224. package/dist/esm/views/treeViewReveal.native.js.map +1 -0
  225. package/dist/jsx/Accordion/index.mjs +3 -3
  226. package/dist/jsx/Accordion/index.mjs.map +1 -1
  227. package/dist/jsx/Accordion/index.native.js +4 -4
  228. package/dist/jsx/Accordion/index.native.js.map +1 -1
  229. package/dist/jsx/Chip/index.mjs +1 -1
  230. package/dist/jsx/Chip/index.mjs.map +1 -1
  231. package/dist/jsx/Chip/index.native.js +1 -1
  232. package/dist/jsx/Chip/index.native.js.map +1 -1
  233. package/dist/jsx/ContextMenu/index.mjs +3 -3
  234. package/dist/jsx/ContextMenu/index.mjs.map +1 -1
  235. package/dist/jsx/ContextMenu/index.native.js +3 -3
  236. package/dist/jsx/ContextMenu/index.native.js.map +1 -1
  237. package/dist/jsx/DropdownMenu/index.mjs +3 -3
  238. package/dist/jsx/DropdownMenu/index.mjs.map +1 -1
  239. package/dist/jsx/DropdownMenu/index.native.js +3 -3
  240. package/dist/jsx/DropdownMenu/index.native.js.map +1 -1
  241. package/dist/jsx/Meter/index.mjs +3 -2
  242. package/dist/jsx/Meter/index.mjs.map +1 -1
  243. package/dist/jsx/Meter/index.native.js +3 -2
  244. package/dist/jsx/Meter/index.native.js.map +1 -1
  245. package/dist/jsx/Meter/meter.mjs +1 -2
  246. package/dist/jsx/Meter/meter.mjs.map +1 -1
  247. package/dist/jsx/Meter/meter.native.js +1 -2
  248. package/dist/jsx/Meter/meter.native.js.map +1 -1
  249. package/dist/jsx/Pagination.mjs +2 -2
  250. package/dist/jsx/Pagination.mjs.map +1 -1
  251. package/dist/jsx/Pagination.native.js +2 -2
  252. package/dist/jsx/Pagination.native.js.map +1 -1
  253. package/dist/jsx/ProgressSteps/index.mjs +3 -3
  254. package/dist/jsx/ProgressSteps/index.mjs.map +1 -1
  255. package/dist/jsx/ProgressSteps/index.native.js +3 -3
  256. package/dist/jsx/ProgressSteps/index.native.js.map +1 -1
  257. package/dist/jsx/Quote/index.mjs +1 -2
  258. package/dist/jsx/Quote/index.mjs.map +1 -1
  259. package/dist/jsx/Quote/index.native.js +1 -2
  260. package/dist/jsx/Quote/index.native.js.map +1 -1
  261. package/dist/jsx/Tabs/index.mjs +1 -1
  262. package/dist/jsx/Tabs/index.mjs.map +1 -1
  263. package/dist/jsx/Tabs/index.native.js +1 -1
  264. package/dist/jsx/Tabs/index.native.js.map +1 -1
  265. package/dist/jsx/Toast/index.mjs +4 -5
  266. package/dist/jsx/Toast/index.mjs.map +1 -1
  267. package/dist/jsx/Toast/index.native.js +4 -5
  268. package/dist/jsx/Toast/index.native.js.map +1 -1
  269. package/dist/jsx/TreeSelect/index.mjs +46 -7
  270. package/dist/jsx/TreeSelect/index.mjs.map +1 -1
  271. package/dist/jsx/TreeSelect/index.native.js +58 -7
  272. package/dist/jsx/TreeSelect/index.native.js.map +1 -1
  273. package/dist/jsx/charts/PieChart.mjs +1 -2
  274. package/dist/jsx/charts/PieChart.mjs.map +1 -1
  275. package/dist/jsx/charts/PieChart.native.js +1 -2
  276. package/dist/jsx/charts/PieChart.native.js.map +1 -1
  277. package/dist/jsx/code/HighlightedCode/index.mjs +1 -1
  278. package/dist/jsx/code/HighlightedCode/index.mjs.map +1 -1
  279. package/dist/jsx/code/HighlightedCode/index.native.js +1 -1
  280. package/dist/jsx/code/HighlightedCode/index.native.js.map +1 -1
  281. package/dist/jsx/componentColors.mjs +6 -4
  282. package/dist/jsx/componentColors.mjs.map +1 -1
  283. package/dist/jsx/componentColors.native.js +6 -4
  284. package/dist/jsx/componentColors.native.js.map +1 -1
  285. package/dist/jsx/images/Image/ImageFrame.mjs +4 -2
  286. package/dist/jsx/images/Image/ImageFrame.mjs.map +1 -1
  287. package/dist/jsx/images/Image/ImageFrame.native.js +4 -2
  288. package/dist/jsx/images/Image/ImageFrame.native.js.map +1 -1
  289. package/dist/jsx/layouts/AsyncBoundary.mjs +1 -2
  290. package/dist/jsx/layouts/AsyncBoundary.mjs.map +1 -1
  291. package/dist/jsx/layouts/AsyncBoundary.native.js +1 -2
  292. package/dist/jsx/layouts/AsyncBoundary.native.js.map +1 -1
  293. package/dist/jsx/layouts/EmptyState.mjs +7 -3
  294. package/dist/jsx/layouts/EmptyState.mjs.map +1 -1
  295. package/dist/jsx/layouts/EmptyState.native.js +10 -6
  296. package/dist/jsx/layouts/EmptyState.native.js.map +1 -1
  297. package/dist/jsx/layouts/attachments.mjs +4 -4
  298. package/dist/jsx/layouts/attachments.mjs.map +1 -1
  299. package/dist/jsx/layouts/attachments.native.js +4 -4
  300. package/dist/jsx/layouts/attachments.native.js.map +1 -1
  301. package/dist/jsx/layouts/breadcrumbs.mjs +1 -2
  302. package/dist/jsx/layouts/breadcrumbs.mjs.map +1 -1
  303. package/dist/jsx/layouts/breadcrumbs.native.js +1 -2
  304. package/dist/jsx/layouts/breadcrumbs.native.js.map +1 -1
  305. package/dist/jsx/layouts/page.mjs +2 -2
  306. package/dist/jsx/layouts/page.mjs.map +1 -1
  307. package/dist/jsx/layouts/page.native.js +2 -2
  308. package/dist/jsx/layouts/page.native.js.map +1 -1
  309. package/dist/jsx/layouts/sidebar.mjs +2 -2
  310. package/dist/jsx/layouts/sidebar.mjs.map +1 -1
  311. package/dist/jsx/layouts/sidebar.native.js +2 -2
  312. package/dist/jsx/layouts/sidebar.native.js.map +1 -1
  313. package/dist/jsx/layouts/tags.mjs +6 -7
  314. package/dist/jsx/layouts/tags.mjs.map +1 -1
  315. package/dist/jsx/layouts/tags.native.js +6 -9
  316. package/dist/jsx/layouts/tags.native.js.map +1 -1
  317. package/dist/jsx/layouts/timeline.mjs +5 -5
  318. package/dist/jsx/layouts/timeline.mjs.map +1 -1
  319. package/dist/jsx/layouts/timeline.native.js +5 -5
  320. package/dist/jsx/layouts/timeline.native.js.map +1 -1
  321. package/dist/jsx/surfaces.mjs +1 -0
  322. package/dist/jsx/surfaces.mjs.map +1 -1
  323. package/dist/jsx/surfaces.native.js +1 -0
  324. package/dist/jsx/surfaces.native.js.map +1 -1
  325. package/dist/jsx/views/Calendar.mjs +11 -8
  326. package/dist/jsx/views/Calendar.mjs.map +1 -1
  327. package/dist/jsx/views/Calendar.native.js +11 -8
  328. package/dist/jsx/views/Calendar.native.js.map +1 -1
  329. package/dist/jsx/views/Dashboard.mjs +6 -6
  330. package/dist/jsx/views/Dashboard.mjs.map +1 -1
  331. package/dist/jsx/views/Dashboard.native.js +7 -7
  332. package/dist/jsx/views/Dashboard.native.js.map +1 -1
  333. package/dist/jsx/views/Gantt.mjs +3 -3
  334. package/dist/jsx/views/Gantt.mjs.map +1 -1
  335. package/dist/jsx/views/Gantt.native.js +3 -3
  336. package/dist/jsx/views/Gantt.native.js.map +1 -1
  337. package/dist/jsx/views/Kanban.mjs +2 -2
  338. package/dist/jsx/views/Kanban.mjs.map +1 -1
  339. package/dist/jsx/views/Kanban.native.js +2 -2
  340. package/dist/jsx/views/Kanban.native.js.map +1 -1
  341. package/dist/jsx/views/ReportBuilder.mjs +4 -4
  342. package/dist/jsx/views/ReportBuilder.mjs.map +1 -1
  343. package/dist/jsx/views/ReportBuilder.native.js +4 -4
  344. package/dist/jsx/views/ReportBuilder.native.js.map +1 -1
  345. package/dist/jsx/views/TreeView.mjs +104 -7
  346. package/dist/jsx/views/TreeView.mjs.map +1 -1
  347. package/dist/jsx/views/TreeView.native.js +115 -6
  348. package/dist/jsx/views/TreeView.native.js.map +1 -1
  349. package/dist/jsx/views/treeViewReveal.mjs +29 -0
  350. package/dist/jsx/views/treeViewReveal.mjs.map +1 -0
  351. package/dist/jsx/views/treeViewReveal.native.js +80 -0
  352. package/dist/jsx/views/treeViewReveal.native.js.map +1 -0
  353. package/package.json +9 -9
  354. package/src/Accordion/index.tsx +4 -6
  355. package/src/Chip/index.tsx +1 -1
  356. package/src/ContextMenu/index.tsx +3 -3
  357. package/src/DropdownMenu/index.tsx +3 -3
  358. package/src/Meter/Meter.spec.tsx +12 -4
  359. package/src/Meter/index.tsx +3 -2
  360. package/src/Meter/meter.ts +6 -1
  361. package/src/Pagination.tsx +2 -2
  362. package/src/ProgressSteps/index.tsx +3 -3
  363. package/src/Quote/index.tsx +1 -3
  364. package/src/Tabs/index.tsx +2 -2
  365. package/src/Toast/Toast.spec.tsx +120 -48
  366. package/src/Toast/index.tsx +5 -3
  367. package/src/TreeSelect/index.tsx +61 -1
  368. package/src/charts/PieChart.tsx +1 -2
  369. package/src/code/HighlightedCode/index.tsx +1 -1
  370. package/src/componentColors.ts +6 -4
  371. package/src/images/Image/ImageFrame.tsx +2 -2
  372. package/src/layouts/AsyncBoundary.tsx +1 -2
  373. package/src/layouts/EmptyState.spec.tsx +58 -1
  374. package/src/layouts/EmptyState.tsx +11 -7
  375. package/src/layouts/KeyboardAvoidingWrapper/KeyboardAvoidingWrapper.stories.tsx +3 -3
  376. package/src/layouts/PaneScaffold.stories.tsx +3 -8
  377. package/src/layouts/SafeAreaWrapper/SafeAreaWrapper.stories.tsx +3 -3
  378. package/src/layouts/attachments.tsx +4 -4
  379. package/src/layouts/breadcrumbs.tsx +1 -2
  380. package/src/layouts/page.tsx +2 -2
  381. package/src/layouts/sidebar.tsx +2 -2
  382. package/src/layouts/tags-trigger.spec.tsx +32 -0
  383. package/src/layouts/tags.tsx +6 -9
  384. package/src/layouts/timeline.tsx +5 -5
  385. package/src/surfaces.spec.tsx +97 -3
  386. package/src/surfaces.tsx +4 -2
  387. package/src/textAccentSecondary.spec.tsx +187 -0
  388. package/src/views/Calendar.tsx +9 -8
  389. package/src/views/Dashboard.tsx +13 -8
  390. package/src/views/Gantt.tsx +3 -3
  391. package/src/views/Kanban.native.tsx +2 -2
  392. package/src/views/Kanban.tsx +2 -2
  393. package/src/views/ReportBuilder.tsx +4 -4
  394. package/src/views/TreeView.reveal.spec.tsx +167 -0
  395. package/src/views/TreeView.reveal.stories.tsx +115 -0
  396. package/src/views/TreeView.scroll.spec.tsx +196 -0
  397. package/src/views/TreeView.spec.tsx +1 -1
  398. package/src/views/TreeView.tsx +159 -16
  399. package/src/views/treeViewReveal.spec.ts +83 -0
  400. package/src/views/treeViewReveal.ts +45 -0
  401. package/src/views/views.spec.tsx +13 -12
  402. package/types/Accordion/index.d.ts.map +1 -1
  403. package/types/Meter/index.d.ts.map +1 -1
  404. package/types/Meter/meter.d.ts +5 -1
  405. package/types/Meter/meter.d.ts.map +1 -1
  406. package/types/Quote/index.d.ts.map +1 -1
  407. package/types/Tabs/index.d.ts.map +1 -1
  408. package/types/Toast/index.d.ts.map +1 -1
  409. package/types/TreeSelect/index.d.ts +9 -1
  410. package/types/TreeSelect/index.d.ts.map +1 -1
  411. package/types/charts/PieChart.d.ts.map +1 -1
  412. package/types/componentColors.d.ts +1 -4
  413. package/types/componentColors.d.ts.map +1 -1
  414. package/types/images/Image/ImageFrame.d.ts.map +1 -1
  415. package/types/layouts/AsyncBoundary.d.ts.map +1 -1
  416. package/types/layouts/EmptyState.d.ts.map +1 -1
  417. package/types/layouts/breadcrumbs.d.ts.map +1 -1
  418. package/types/layouts/tags.d.ts.map +1 -1
  419. package/types/surfaces.d.ts.map +1 -1
  420. package/types/views/Calendar.d.ts.map +1 -1
  421. package/types/views/Dashboard.d.ts +7 -3
  422. package/types/views/Dashboard.d.ts.map +1 -1
  423. package/types/views/TreeView.d.ts +10 -1
  424. package/types/views/TreeView.d.ts.map +1 -1
  425. package/types/views/treeViewReveal.d.ts +11 -0
  426. package/types/views/treeViewReveal.d.ts.map +1 -0
@@ -326,15 +326,20 @@ export interface KPICardProps extends Omit<YStackProps, "children"> {
326
326
  * The one sentinel resolver for trend sentiment color (LC-36
327
327
  * TREND-SENTIMENT): color encodes sentiment — `trend × goodDirection` —
328
328
  * never direction alone. Undeclared or `"none"` sentiment renders the
329
- * neutral muted tier; a metric whose polarity the system doesn't know
330
- * must not wear a verdict color.
329
+ * neutral tier; a metric whose polarity the system doesn't know must not
330
+ * wear a verdict color.
331
+ *
332
+ * MPO-154: the neutral ink is passed in, not looked up. It is ordinary
333
+ * secondary text, so the caller hands over `knobProps.textAccentColor` —
334
+ * a static tier here could not see the `textAccent` knob.
331
335
  */
332
336
  export function resolveTrendColor(
333
337
  trend: TrendDirection | undefined,
334
338
  goodDirection: KPICardConfig["goodDirection"],
339
+ neutral: string,
335
340
  ): string {
336
- if (!trend || trend === "neutral") return componentColors.text.muted;
337
- if (!goodDirection || goodDirection === "none") return componentColors.text.muted;
341
+ if (!trend || trend === "neutral") return neutral;
342
+ if (!goodDirection || goodDirection === "none") return neutral;
338
343
  return trend === goodDirection
339
344
  ? componentColors.semantic.success
340
345
  : componentColors.semantic.error;
@@ -373,8 +378,8 @@ export function KPICard({ config, ...stackProps }: KPICardProps) {
373
378
  );
374
379
 
375
380
  const trendColor = useMemo(
376
- () => resolveTrendColor(config.trend, config.goodDirection),
377
- [config.trend, config.goodDirection],
381
+ () => resolveTrendColor(config.trend, config.goodDirection, knobProps.textAccentColor),
382
+ [config.trend, config.goodDirection, knobProps.textAccentColor],
378
383
  );
379
384
 
380
385
  const trendSymbol = useMemo(() => {
@@ -660,7 +665,7 @@ export function ShortcutCard({ config, ...stackProps }: ShortcutCardProps) {
660
665
  {...knobProps.borderRadius}
661
666
  data-nested-px={px}
662
667
  >
663
- <Text {...knobProps.label} color={componentColors.text.secondary}>
668
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
664
669
  {config.count}
665
670
  </Text>
666
671
  </XStack>
@@ -708,7 +713,7 @@ export function ListCard({ config, headingLevel = 2, ...stackProps }: ListCardPr
708
713
  {item.label}
709
714
  </Text>
710
715
  {item.meta ? (
711
- <Text {...knobProps.label} color={componentColors.text.muted}>
716
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
712
717
  {item.meta}
713
718
  </Text>
714
719
  ) : null}
@@ -1576,7 +1576,7 @@ export function GanttView({
1576
1576
  borderColor="$borderColor"
1577
1577
  {...{ backgroundColor: componentColors.surface.background }}
1578
1578
  >
1579
- <Text {...knobProps.label} color={componentColors.text.muted} numberOfLines={1}>
1579
+ <Text {...knobProps.label} color={knobProps.textAccentColor} numberOfLines={1}>
1580
1580
  {t("Task")}
1581
1581
  </Text>
1582
1582
  </XStack>
@@ -1631,7 +1631,7 @@ export function GanttView({
1631
1631
  top={2}
1632
1632
  width={Math.max(0, band.width - 8)}
1633
1633
  {...knobProps.label}
1634
- color={componentColors.text.secondary}
1634
+ color={knobProps.textAccentColor}
1635
1635
  numberOfLines={1}
1636
1636
  >
1637
1637
  {band.label}
@@ -1645,7 +1645,7 @@ export function GanttView({
1645
1645
  {...(rtl ? { right: tick.x + 4 } : { left: tick.x + 4 })}
1646
1646
  top={axisHeight / 2}
1647
1647
  {...knobProps.label}
1648
- color={tick.major ? componentColors.text.secondary : componentColors.text.muted}
1648
+ color={knobProps.textAccentColor}
1649
1649
  numberOfLines={1}
1650
1650
  >
1651
1651
  {formatDayOfMonth(tick.date)}
@@ -861,7 +861,7 @@ function KanbanColumnView<T>({
861
861
  <XStack alignItems="center" {...knobProps.gap}>
862
862
  {itemCount !== undefined && (
863
863
  <Text
864
- color={componentColors.text.muted}
864
+ color={knobProps.textAccentColor}
865
865
  {...knobProps.label}
866
866
  {...knobProps.textWeight}
867
867
  data-async-count
@@ -1004,7 +1004,7 @@ function KanbanColumnView<T>({
1004
1004
  justifyContent="center"
1005
1005
  >
1006
1006
  {column.items.length === 0 && (
1007
- <Text color={componentColors.text.muted} {...knobProps.label}>
1007
+ <Text color={knobProps.textAccentColor} {...knobProps.label}>
1008
1008
  {t("Drop here")}
1009
1009
  </Text>
1010
1010
  )}
@@ -1361,7 +1361,7 @@ function KanbanColumnView<T>({
1361
1361
  <XStack alignItems="center" {...knobProps.gap}>
1362
1362
  {itemCount !== undefined && (
1363
1363
  <Text
1364
- color={componentColors.text.muted}
1364
+ color={knobProps.textAccentColor}
1365
1365
  {...knobProps.label}
1366
1366
  {...knobProps.textWeight}
1367
1367
  data-async-count
@@ -1551,7 +1551,7 @@ function KanbanColumnView<T>({
1551
1551
  justifyContent="center"
1552
1552
  >
1553
1553
  {isEmpty && (
1554
- <Text color={componentColors.text.muted} {...knobProps.label}>
1554
+ <Text color={knobProps.textAccentColor} {...knobProps.label}>
1555
1555
  {t("Drop here")}
1556
1556
  </Text>
1557
1557
  )}
@@ -513,7 +513,7 @@ export function ReportBuilder({
513
513
  <SortIcon
514
514
  size={iconPx}
515
515
  weight="bold"
516
- color={active ? componentColors.text.primary : componentColors.text.muted}
516
+ color={active ? componentColors.text.primary : knobProps.textAccentColor}
517
517
  opacity={active ? 1 : 0.35}
518
518
  />
519
519
  </XStack>
@@ -634,7 +634,7 @@ export function ReportBuilder({
634
634
  <Text {...knobProps.label} color="$accentColor" numberOfLines={1}>
635
635
  {groupByCol?.label ?? groupBy.field}: {groupKey}
636
636
  </Text>
637
- <Text {...knobProps.label} color={componentColors.text.muted}>
637
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
638
638
  ({group.rows.length} {group.rows.length === 1 ? t("record") : t("records")})
639
639
  </Text>
640
640
  <XStack flex={1} />
@@ -902,7 +902,7 @@ export function ReportBuilder({
902
902
  {...knobProps.gapLg}
903
903
  flexWrap="wrap"
904
904
  >
905
- <Text {...knobProps.label} color={componentColors.text.muted}>
905
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
906
906
  {t("Totals")}
907
907
  </Text>
908
908
  {totals.map(({ col, value }) => (
@@ -912,7 +912,7 @@ export function ReportBuilder({
912
912
  </Text>
913
913
  ))}
914
914
  <XStack flex={1} />
915
- <Text {...knobProps.label} color={componentColors.text.muted}>
915
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
916
916
  {t("{{count}} row(s)", { count: data.length })}
917
917
  </Text>
918
918
  </XStack>
@@ -0,0 +1,167 @@
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["expandedIds"]>().toEqualTypeOf<string[] | undefined>();
31
+ expectTypeOf<TreeViewProps["onExpandedIdsChange"]>().toEqualTypeOf<
32
+ ((ids: string[]) => void) | undefined
33
+ >();
34
+ });
35
+
36
+ it("waits for controlled expansion to be accepted, overriding initial defaults", () => {
37
+ const change = vi.fn();
38
+ const toggle = vi.fn();
39
+ const props = {
40
+ nodes,
41
+ expandedIds: [],
42
+ defaultExpandedIds: ["folders"],
43
+ expandByDefault: true,
44
+ onExpandedIdsChange: change,
45
+ onNodeToggle: toggle,
46
+ };
47
+ const { rerender } = renderWithProviders(<TreeView {...props} />);
48
+ expect(screen.queryByText("Archive")).toBeNull();
49
+ fireEvent.click(screen.getByText("Folders"));
50
+ expect(change).toHaveBeenCalledExactlyOnceWith(["folders"]);
51
+ expect(toggle).toHaveBeenCalledExactlyOnceWith(nodes[0]);
52
+ expect(screen.queryByText("Archive")).toBeNull();
53
+ rerender(<TreeView {...props} expandedIds={["folders"]} />);
54
+ expect(screen.getByText("Archive")).toBeInTheDocument();
55
+ expect(change).toHaveBeenCalledTimes(1);
56
+ rerender(<TreeView {...props} expandedIds={[]} />);
57
+ expect(screen.queryByText("Archive")).toBeNull();
58
+ expect(change).toHaveBeenCalledTimes(1);
59
+ });
60
+
61
+ it("keeps uncontrolled toggles and their new notification working", () => {
62
+ const change = vi.fn();
63
+ renderWithProviders(<TreeView nodes={nodes} onExpandedIdsChange={change} />);
64
+ fireEvent.click(screen.getByText("Folders"));
65
+ expect(screen.getByText("Archive")).toBeInTheDocument();
66
+ expect(change).toHaveBeenNthCalledWith(1, ["folders"]);
67
+ fireEvent.click(screen.getByText("Folders"));
68
+ expect(screen.queryByText("Archive")).toBeNull();
69
+ expect(change).toHaveBeenNthCalledWith(2, []);
70
+ });
71
+
72
+ it("does not expand or select when only selectedId changes", async () => {
73
+ const change = vi.fn();
74
+ const select = vi.fn();
75
+ const { rerender } = renderWithProviders(
76
+ <TreeView nodes={nodes} onExpandedIdsChange={change} onNodeSelect={select} />,
77
+ );
78
+ rerender(
79
+ <TreeView
80
+ nodes={nodes}
81
+ selectedId="item"
82
+ onExpandedIdsChange={change}
83
+ onNodeSelect={select}
84
+ />,
85
+ );
86
+ await act(async () => {});
87
+ expect(screen.queryByText("Item")).toBeNull();
88
+ expect(change).not.toHaveBeenCalled();
89
+ expect(select).not.toHaveBeenCalled();
90
+ });
91
+ });
92
+
93
+ describe("TreeView reveal expansion", () => {
94
+ it("merges only ancestors, waits for acceptance, and leaves selection and focus alone", async () => {
95
+ const change = vi.fn();
96
+ const select = vi.fn();
97
+ const toggle = vi.fn();
98
+ const props = {
99
+ nodes,
100
+ expandedIds: ["other"],
101
+ selectedId: "item",
102
+ revealRequest: { id: "item", requestId: 1 },
103
+ onExpandedIdsChange: change,
104
+ onNodeSelect: select,
105
+ onNodeToggle: toggle,
106
+ };
107
+ const { rerender } = renderWithProviders(<TreeView {...props} />);
108
+ await waitFor(() =>
109
+ expect(change).toHaveBeenCalledExactlyOnceWith(["other", "folders", "archive"]),
110
+ );
111
+ expect(screen.queryByText("Item")).toBeNull();
112
+ rerender(<TreeView {...props} expandedIds={["other"]} />);
113
+ await act(async () => {});
114
+ expect(change).toHaveBeenCalledTimes(1);
115
+ rerender(<TreeView {...props} expandedIds={["other", "folders", "archive"]} />);
116
+ expect(screen.getByText("Item").closest('[role="treeitem"]')).toHaveAttribute(
117
+ "aria-selected",
118
+ "true",
119
+ );
120
+ expect(screen.getByText("Other item")).toBeInTheDocument();
121
+ expect(select).not.toHaveBeenCalled();
122
+ expect(toggle).not.toHaveBeenCalled();
123
+ expect(screen.getByRole("tree")).not.toHaveFocus();
124
+ });
125
+
126
+ it("retains a request through missing data and expands after data arrives", async () => {
127
+ const change = vi.fn();
128
+ const props = {
129
+ revealRequest: { id: "item", requestId: 2 },
130
+ onExpandedIdsChange: change,
131
+ };
132
+ const { rerender } = renderWithProviders(<TreeView {...props} nodes={[]} />);
133
+ await act(async () => {});
134
+ expect(change).not.toHaveBeenCalled();
135
+ rerender(<TreeView {...props} nodes={nodes} />);
136
+ await waitFor(() => expect(screen.getByText("Item")).toBeInTheDocument());
137
+ expect(change).toHaveBeenCalledExactlyOnceWith(["folders", "archive"]);
138
+ });
139
+
140
+ it("replaces a missing request before its old data arrives", async () => {
141
+ const change = vi.fn();
142
+ const { rerender } = renderWithProviders(
143
+ <TreeView
144
+ nodes={[]}
145
+ revealRequest={{ id: "item", requestId: 1 }}
146
+ onExpandedIdsChange={change}
147
+ />,
148
+ );
149
+ rerender(
150
+ <TreeView
151
+ nodes={[]}
152
+ revealRequest={{ id: "other-item", requestId: 2 }}
153
+ onExpandedIdsChange={change}
154
+ />,
155
+ );
156
+ rerender(
157
+ <TreeView
158
+ nodes={nodes}
159
+ revealRequest={{ id: "other-item", requestId: 2 }}
160
+ onExpandedIdsChange={change}
161
+ />,
162
+ );
163
+ await waitFor(() => expect(screen.getByText("Other item")).toBeInTheDocument());
164
+ expect(screen.queryByText("Archive")).toBeNull();
165
+ expect(change).toHaveBeenCalledExactlyOnceWith(["other"]);
166
+ });
167
+ });
@@ -0,0 +1,115 @@
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 [deferExpansion, setDeferExpansion] = useState(false);
40
+ const [pendingExpansion, setPendingExpansion] = useState<string[]>();
41
+ const [hidden, setHidden] = useState(false);
42
+ const sequence = useRef(0);
43
+ const reveal = (id: string) => {
44
+ setSelectedId(id);
45
+ setRevealRequest({ id, requestId: ++sequence.current });
46
+ };
47
+ return (
48
+ <YStack gap="$3" width="100%" maxWidth={700}>
49
+ <XStack gap="$2" flexWrap="wrap">
50
+ <Button onPress={() => reveal("item-499")}>Reveal last item</Button>
51
+ <Button onPress={() => reveal("item-0")}>Reveal first item</Button>
52
+ <Button onPress={() => reveal("other-item")}>Reveal other item</Button>
53
+ <Button onPress={() => setSelectedId("item-499")}>Select only</Button>
54
+ <Button onPress={() => setExpandedIds(["other"])}>Collapse archive</Button>
55
+ </XStack>
56
+ <XStack gap="$2" flexWrap="wrap">
57
+ <Button onPress={() => setNodes([])}>Remove data</Button>
58
+ <Button onPress={() => setNodes(folders)}>Deliver data</Button>
59
+ <Button onPress={() => setHidden((value) => !value)}>
60
+ {hidden ? "Show tree" : "Hide tree"}
61
+ </Button>
62
+ <Button onPress={() => setDeferExpansion((value) => !value)}>
63
+ {deferExpansion ? "Accept expansion immediately" : "Defer expansion"}
64
+ </Button>
65
+ {pendingExpansion && (
66
+ <Button
67
+ onPress={() => {
68
+ setExpandedIds(pendingExpansion);
69
+ setPendingExpansion(undefined);
70
+ }}
71
+ >
72
+ Accept pending expansion
73
+ </Button>
74
+ )}
75
+ </XStack>
76
+ <Text>500 variable-height items. Request: {revealRequest?.requestId ?? "none"}</Text>
77
+ <Text>
78
+ Target: {revealRequest?.id ?? "none"}. Expanded: {expandedIds.join(", ")}
79
+ </Text>
80
+ <YStack display={hidden ? "none" : "flex"}>
81
+ <TreeView
82
+ {...args}
83
+ nodes={nodes}
84
+ expandedIds={expandedIds}
85
+ onExpandedIdsChange={(ids) => {
86
+ if (deferExpansion) setPendingExpansion(ids);
87
+ else setExpandedIds(ids);
88
+ }}
89
+ selectedId={selectedId}
90
+ revealRequest={revealRequest}
91
+ onNodeSelect={(node) => setSelectedId(node.id)}
92
+ renderNode={(node) => {
93
+ const index = Number(node.id.replace("item-", ""));
94
+ return (
95
+ <YStack
96
+ minHeight={Number.isFinite(index) && index % 2 ? 80 : 32}
97
+ justifyContent="center"
98
+ >
99
+ <Text>{node.label}</Text>
100
+ {Number.isFinite(index) && index % 2 === 1 && (
101
+ <Text fontSize="$2">Additional detail</Text>
102
+ )}
103
+ </YStack>
104
+ );
105
+ }}
106
+ />
107
+ </YStack>
108
+ </YStack>
109
+ );
110
+ }
111
+
112
+ export const ControlledReveal: Story = {
113
+ name: "Controlled reveal with 500 variable-height items",
114
+ render: (args) => <RevealFixture {...args} />,
115
+ };
@@ -0,0 +1,196 @@
1
+ import type { FlashListProps, FlashListRef } from "@shopify/flash-list";
2
+ import { act, cleanup, screen, waitFor } from "@testing-library/react";
3
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { TreeView, type TreeNode, type TreeViewProps } from "./TreeView";
6
+
7
+ const observed = vi.hoisted(() => ({
8
+ list: null as FlashListRef<unknown> | null,
9
+ props: null as FlashListProps<unknown> | null,
10
+ }));
11
+
12
+ // Keep the real list and rows. Happy DOM cannot measure them; only the public
13
+ // geometry/scroll boundary is modeled here. The Storybook fixture needs browser proof.
14
+ vi.mock("@shopify/flash-list", async (importOriginal) => {
15
+ const actual = await importOriginal<typeof import("@shopify/flash-list")>();
16
+ const React = await import("react");
17
+ return {
18
+ ...actual,
19
+ FlashList: React.forwardRef<FlashListRef<unknown>, FlashListProps<unknown>>((props, ref) => {
20
+ observed.props = props;
21
+ const captureRef = React.useCallback(
22
+ (list: FlashListRef<unknown> | null) => {
23
+ observed.list = list;
24
+ if (typeof ref === "function") ref(list);
25
+ else if (ref) ref.current = list;
26
+ },
27
+ [ref],
28
+ );
29
+ return React.createElement(actual.FlashList, { ...props, ref: captureRef });
30
+ }),
31
+ };
32
+ });
33
+
34
+ const nodes: TreeNode[] = [
35
+ {
36
+ id: "folders",
37
+ label: "Folders",
38
+ children: [{ id: "archive", label: "Archive", children: [{ id: "item", label: "Item" }] }],
39
+ },
40
+ { id: "other", label: "Other", children: [{ id: "other-item", label: "Other item" }] },
41
+ ];
42
+
43
+ beforeEach(() => {
44
+ observed.list = null;
45
+ observed.props = null;
46
+ });
47
+ afterEach(() => {
48
+ cleanup();
49
+ vi.restoreAllMocks();
50
+ });
51
+
52
+ async function settle() {
53
+ await act(async () => {
54
+ await new Promise((resolve) => setTimeout(resolve, 20));
55
+ });
56
+ }
57
+
58
+ function measuredTree(initial: Partial<TreeViewProps> = {}) {
59
+ const props = { nodes, expandByDefault: true, height: 240, ...initial };
60
+ const rendered = renderWithProviders(<TreeView {...props} />);
61
+ expect(observed.list).not.toBeNull();
62
+ const list = observed.list!;
63
+ const geometry = { offset: 0, height: 240, width: 320, measured: true, hostVisible: true };
64
+ vi.spyOn(screen.getByRole("tree"), "getBoundingClientRect").mockImplementation(() => ({
65
+ width: geometry.hostVisible ? geometry.width : 0,
66
+ height: geometry.hostVisible ? geometry.height : 0,
67
+ x: 0,
68
+ y: 0,
69
+ top: 0,
70
+ left: 0,
71
+ bottom: geometry.height,
72
+ right: geometry.width,
73
+ toJSON: () => ({}),
74
+ }));
75
+ vi.spyOn(list, "getWindowSize").mockImplementation(() => geometry);
76
+ vi.spyOn(list, "getFirstItemOffset").mockReturnValue(0);
77
+ vi.spyOn(list, "getAbsoluteLastScrollOffset").mockImplementation(() => geometry.offset);
78
+ vi.spyOn(list, "getLayout").mockImplementation((index) => ({
79
+ x: 0,
80
+ y: index * 400,
81
+ width: geometry.width,
82
+ height: index % 2 ? 32 : 80,
83
+ isHeightMeasured: geometry.measured,
84
+ isWidthMeasured: true,
85
+ }));
86
+ vi.spyOn(list, "computeVisibleIndices").mockImplementation(() => ({
87
+ startIndex: Math.floor(geometry.offset / 400),
88
+ endIndex: Math.floor((geometry.offset + geometry.height) / 400),
89
+ }));
90
+ const scroll = vi.spyOn(list, "scrollToOffset").mockImplementation(({ offset }) => {
91
+ geometry.offset = offset;
92
+ observed.props?.onScroll?.({ nativeEvent: { contentOffset: { y: offset } } } as never);
93
+ });
94
+ const oldScroll = vi.spyOn(list, "scrollToIndex");
95
+ const commitLayout = () => act(() => observed.props?.onCommitLayoutEffect?.());
96
+ const rerender = (next: Partial<TreeViewProps>) =>
97
+ rendered.rerender(<TreeView {...props} {...next} />);
98
+ return { ...rendered, rerender, geometry, scroll, oldScroll, commitLayout };
99
+ }
100
+
101
+ describe("TreeView measured reveal scheduling (modeled geometry)", () => {
102
+ it("scrolls the real mounted target and consumes the request only after measurement", async () => {
103
+ const tree = measuredTree();
104
+ tree.geometry.measured = false;
105
+ tree.rerender({ revealRequest: { id: "item", requestId: 1 }, selectedId: "item" });
106
+ await waitFor(() => expect(tree.scroll).toHaveBeenCalledWith({ offset: 640, animated: false }));
107
+ expect(screen.getByText("Item").closest('[role="treeitem"]')).toHaveAttribute(
108
+ "aria-selected",
109
+ "true",
110
+ );
111
+ tree.geometry.offset = 0;
112
+ tree.geometry.measured = true;
113
+ tree.commitLayout();
114
+ await waitFor(() => expect(tree.scroll).toHaveBeenCalledTimes(2));
115
+ await settle();
116
+ tree.geometry.offset = 0;
117
+ tree.commitLayout();
118
+ await settle();
119
+ expect(tree.scroll).toHaveBeenCalledTimes(2);
120
+ expect(tree.oldScroll).not.toHaveBeenCalled();
121
+ });
122
+
123
+ it("repeats the same target only when requestId changes, without stealing focus", async () => {
124
+ const tree = measuredTree();
125
+ tree.rerender({ revealRequest: { id: "item", requestId: 1 } });
126
+ await waitFor(() => expect(tree.scroll).toHaveBeenCalledTimes(1));
127
+ await settle();
128
+ tree.geometry.offset = 0;
129
+ tree.rerender({ revealRequest: { id: "item", requestId: 1 } });
130
+ tree.commitLayout();
131
+ await settle();
132
+ expect(tree.scroll).toHaveBeenCalledTimes(1);
133
+ tree.rerender({ revealRequest: { id: "item", requestId: 2 } });
134
+ await waitFor(() => expect(tree.scroll).toHaveBeenCalledTimes(2));
135
+ expect(screen.getByRole("tree")).not.toHaveFocus();
136
+ });
137
+
138
+ it("waits for both usable list dimensions and a visible host", async () => {
139
+ const tree = measuredTree();
140
+ tree.geometry.height = 0;
141
+ tree.rerender({ revealRequest: { id: "item", requestId: 1 } });
142
+ await settle();
143
+ expect(tree.scroll).not.toHaveBeenCalled();
144
+ tree.geometry.height = 240;
145
+ tree.geometry.hostVisible = false;
146
+ tree.commitLayout();
147
+ await settle();
148
+ expect(tree.scroll).not.toHaveBeenCalled();
149
+ tree.geometry.hostVisible = true;
150
+ tree.commitLayout();
151
+ await waitFor(() => expect(tree.scroll).toHaveBeenCalledTimes(1));
152
+ });
153
+
154
+ it("cancels queued A when B supersedes it and never starts an uncancellable index scroll", async () => {
155
+ const tree = measuredTree();
156
+ act(() => {
157
+ tree.rerender({ revealRequest: { id: "item", requestId: 10 } });
158
+ tree.commitLayout();
159
+ tree.rerender({ revealRequest: { id: "other-item", requestId: 11 } });
160
+ });
161
+ await waitFor(() =>
162
+ expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ offset: 1440, animated: false }),
163
+ );
164
+ await settle();
165
+ tree.commitLayout();
166
+ await settle();
167
+ expect(tree.scroll).toHaveBeenCalledTimes(1);
168
+ expect(tree.oldScroll).not.toHaveBeenCalled();
169
+ });
170
+
171
+ it("removing a request cancels its pending scroll", async () => {
172
+ const tree = measuredTree();
173
+ tree.geometry.height = 0;
174
+ tree.rerender({ revealRequest: { id: "item", requestId: 1 } });
175
+ await settle();
176
+ tree.rerender({ revealRequest: undefined });
177
+ tree.geometry.height = 240;
178
+ tree.commitLayout();
179
+ await settle();
180
+ expect(tree.scroll).not.toHaveBeenCalled();
181
+ tree.rerender({ revealRequest: { id: "item", requestId: 2 } });
182
+ await waitFor(() => expect(tree.scroll).toHaveBeenCalledTimes(1));
183
+ });
184
+
185
+ it("does not schedule more reveal work while its row refs detach on unmount", async () => {
186
+ const tree = measuredTree();
187
+ tree.geometry.height = 0;
188
+ tree.rerender({ revealRequest: { id: "item", requestId: 1 } });
189
+ await settle();
190
+ const frame = vi.spyOn(globalThis, "requestAnimationFrame");
191
+ tree.unmount();
192
+ expect(frame).not.toHaveBeenCalled();
193
+ await settle();
194
+ expect(tree.scroll).not.toHaveBeenCalled();
195
+ });
196
+ });
@@ -92,7 +92,7 @@ describe("TreeView knob wiring", () => {
92
92
  expect(tree.getAttribute("data-fill")).toBe("transparent");
93
93
  });
94
94
 
95
- it("row labels are T-VALUE primary ink, never componentColors.text.secondary", () => {
95
+ it("row labels are T-VALUE primary ink, never a secondary ramp step", () => {
96
96
  expect(treeViewSource).not.toMatch(/componentColors\.text\.(secondary|muted)/);
97
97
  expect(treeViewSource).toMatch(
98
98
  /color=\{isSelected && onAccentLabel \? onAccentLabel : "\$color"\}/,