@multiplatform.one/components 7.10.0 → 7.15.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 +100 -2
  2. package/dist/cjs/Accordion/index.cjs +2 -6
  3. package/dist/cjs/Accordion/index.native.js +2 -6
  4. package/dist/cjs/Accordion/index.native.js.map +1 -1
  5. package/dist/cjs/AnimateHeight/index.cjs +1 -3
  6. package/dist/cjs/AnimateHeight/index.native.js +1 -3
  7. package/dist/cjs/AnimateHeight/index.native.js.map +1 -1
  8. package/dist/cjs/Chip/index.cjs +8 -1
  9. package/dist/cjs/Chip/index.native.js +8 -1
  10. package/dist/cjs/Chip/index.native.js.map +1 -1
  11. package/dist/cjs/SheetModal/index.cjs +22 -3
  12. package/dist/cjs/SheetModal/index.native.js +115 -27
  13. package/dist/cjs/SheetModal/index.native.js.map +1 -1
  14. package/dist/cjs/Tabs/index.cjs +35 -18
  15. package/dist/cjs/Tabs/index.native.js +39 -18
  16. package/dist/cjs/Tabs/index.native.js.map +1 -1
  17. package/dist/cjs/Text.cjs +2 -0
  18. package/dist/cjs/Text.native.js +2 -0
  19. package/dist/cjs/Text.native.js.map +1 -1
  20. package/dist/cjs/TreeSelect/index.cjs +55 -52
  21. package/dist/cjs/TreeSelect/index.native.js +55 -52
  22. package/dist/cjs/TreeSelect/index.native.js.map +1 -1
  23. package/dist/cjs/Video/PlayerChrome.cjs +3 -9
  24. package/dist/cjs/Video/PlayerChrome.native.js +3 -9
  25. package/dist/cjs/Video/PlayerChrome.native.js.map +1 -1
  26. package/dist/cjs/code/Code/index.cjs +6 -0
  27. package/dist/cjs/code/Code/index.native.js +7 -4
  28. package/dist/cjs/code/Code/index.native.js.map +1 -1
  29. package/dist/cjs/code/CodeBlock/index.cjs +4 -5
  30. package/dist/cjs/code/CodeBlock/index.native.js +4 -5
  31. package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
  32. package/dist/cjs/icons/themed.cjs +2 -1
  33. package/dist/cjs/icons/themed.native.js +2 -1
  34. package/dist/cjs/icons/themed.native.js.map +1 -1
  35. package/dist/cjs/knobImmuneExports.cjs +3 -3
  36. package/dist/cjs/knobImmuneExports.native.js +3 -3
  37. package/dist/cjs/layouts/PaneScaffold.cjs +70 -42
  38. package/dist/cjs/layouts/PaneScaffold.native.js +75 -48
  39. package/dist/cjs/layouts/PaneScaffold.native.js.map +1 -1
  40. package/dist/cjs/layouts/page.cjs +2 -1
  41. package/dist/cjs/layouts/page.native.js +2 -1
  42. package/dist/cjs/layouts/page.native.js.map +1 -1
  43. package/dist/cjs/layouts/sidebar.cjs +1 -3
  44. package/dist/cjs/layouts/sidebar.native.js +1 -3
  45. package/dist/cjs/layouts/sidebar.native.js.map +1 -1
  46. package/dist/cjs/layouts/tags.native.js.map +1 -1
  47. package/dist/cjs/surfaces.cjs +0 -32
  48. package/dist/cjs/surfaces.native.js +0 -32
  49. package/dist/cjs/surfaces.native.js.map +1 -1
  50. package/dist/cjs/views/List.cjs +1 -3
  51. package/dist/cjs/views/List.native.js +1 -3
  52. package/dist/cjs/views/List.native.js.map +1 -1
  53. package/dist/cjs/views/Matrix.cjs +568 -0
  54. package/dist/cjs/views/Matrix.native.js +612 -0
  55. package/dist/cjs/views/Matrix.native.js.map +1 -0
  56. package/dist/cjs/views/TreeView.cjs +175 -43
  57. package/dist/cjs/views/TreeView.native.js +181 -46
  58. package/dist/cjs/views/TreeView.native.js.map +1 -1
  59. package/dist/cjs/views/ganttMath.cjs +2 -0
  60. package/dist/cjs/views/ganttMath.native.js +2 -0
  61. package/dist/cjs/views/ganttMath.native.js.map +1 -1
  62. package/dist/cjs/views/index.cjs +16 -0
  63. package/dist/cjs/views/index.native.js +16 -0
  64. package/dist/cjs/views/index.native.js.map +1 -1
  65. package/dist/cjs/views/matrixMath.cjs +157 -0
  66. package/dist/cjs/views/matrixMath.native.js +229 -0
  67. package/dist/cjs/views/matrixMath.native.js.map +1 -0
  68. package/dist/esm/Accordion/index.mjs +3 -7
  69. package/dist/esm/Accordion/index.mjs.map +1 -1
  70. package/dist/esm/Accordion/index.native.js +3 -7
  71. package/dist/esm/Accordion/index.native.js.map +1 -1
  72. package/dist/esm/AnimateHeight/index.mjs +2 -4
  73. package/dist/esm/AnimateHeight/index.mjs.map +1 -1
  74. package/dist/esm/AnimateHeight/index.native.js +2 -4
  75. package/dist/esm/AnimateHeight/index.native.js.map +1 -1
  76. package/dist/esm/Avatar/getInitials.native.js.map +1 -1
  77. package/dist/esm/Avatar/index.native.js.map +1 -1
  78. package/dist/esm/Badge/index.native.js.map +1 -1
  79. package/dist/esm/Button.native.js.map +1 -1
  80. package/dist/esm/Carousel/index.native.js.map +1 -1
  81. package/dist/esm/Chip/index.mjs +9 -2
  82. package/dist/esm/Chip/index.mjs.map +1 -1
  83. package/dist/esm/Chip/index.native.js +9 -2
  84. package/dist/esm/Chip/index.native.js.map +1 -1
  85. package/dist/esm/Collapsible.native.js.map +1 -1
  86. package/dist/esm/ContextMenu/index.native.js.map +1 -1
  87. package/dist/esm/CopyField/index.native.js.map +1 -1
  88. package/dist/esm/DotIndicator/index.native.js.map +1 -1
  89. package/dist/esm/DropdownMenu/index.native.js.map +1 -1
  90. package/dist/esm/ErrorBoundary/index.native.js.map +1 -1
  91. package/dist/esm/Heading.native.js.map +1 -1
  92. package/dist/esm/Meter/index.native.js.map +1 -1
  93. package/dist/esm/Meter/meter.native.js.map +1 -1
  94. package/dist/esm/Pagination.native.js.map +1 -1
  95. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  96. package/dist/esm/Quote/index.native.js.map +1 -1
  97. package/dist/esm/Separator/index.native.js.map +1 -1
  98. package/dist/esm/SheetModal/detents.native.js.map +1 -1
  99. package/dist/esm/SheetModal/index.mjs +22 -3
  100. package/dist/esm/SheetModal/index.mjs.map +1 -1
  101. package/dist/esm/SheetModal/index.native.js +117 -29
  102. package/dist/esm/SheetModal/index.native.js.map +1 -1
  103. package/dist/esm/Skeleton/index.native.js.map +1 -1
  104. package/dist/esm/SkipLink/index.native.js.map +1 -1
  105. package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
  106. package/dist/esm/SortableList/index.native.js.map +1 -1
  107. package/dist/esm/SortableList/useSortableRows.native.js.map +1 -1
  108. package/dist/esm/Tabs/index.mjs +36 -19
  109. package/dist/esm/Tabs/index.mjs.map +1 -1
  110. package/dist/esm/Tabs/index.native.js +40 -19
  111. package/dist/esm/Tabs/index.native.js.map +1 -1
  112. package/dist/esm/Text.mjs +3 -1
  113. package/dist/esm/Text.mjs.map +1 -1
  114. package/dist/esm/Text.native.js +3 -1
  115. package/dist/esm/Text.native.js.map +1 -1
  116. package/dist/esm/Toast/index.native.js.map +1 -1
  117. package/dist/esm/Toast/store.native.js.map +1 -1
  118. package/dist/esm/Tooltip/nativePress.native.js.map +1 -1
  119. package/dist/esm/Tooltip/tooltipShared.native.js.map +1 -1
  120. package/dist/esm/TreeSelect/index.mjs +56 -53
  121. package/dist/esm/TreeSelect/index.mjs.map +1 -1
  122. package/dist/esm/TreeSelect/index.native.js +56 -53
  123. package/dist/esm/TreeSelect/index.native.js.map +1 -1
  124. package/dist/esm/TreeSelect/treeSelectModel.native.js.map +1 -1
  125. package/dist/esm/Video/PlayerChrome.mjs +4 -10
  126. package/dist/esm/Video/PlayerChrome.mjs.map +1 -1
  127. package/dist/esm/Video/PlayerChrome.native.js +4 -10
  128. package/dist/esm/Video/PlayerChrome.native.js.map +1 -1
  129. package/dist/esm/Video/VideoFrame.native.js.map +1 -1
  130. package/dist/esm/Video/types.native.js.map +1 -1
  131. package/dist/esm/Wheel/index.native.js.map +1 -1
  132. package/dist/esm/Wizard/index.native.js.map +1 -1
  133. package/dist/esm/actions/ActionBar.native.js.map +1 -1
  134. package/dist/esm/actions/DestructiveAction.native.js.map +1 -1
  135. package/dist/esm/actions/devWarn.native.js.map +1 -1
  136. package/dist/esm/actions/index.native.js.map +1 -1
  137. package/dist/esm/actions/severity.native.js.map +1 -1
  138. package/dist/esm/charts/Axis.native.js.map +1 -1
  139. package/dist/esm/charts/BarChart.native.js.map +1 -1
  140. package/dist/esm/charts/ChartDataView.native.js.map +1 -1
  141. package/dist/esm/charts/ChartSurface.native.js.map +1 -1
  142. package/dist/esm/charts/LineChart.native.js.map +1 -1
  143. package/dist/esm/charts/PieChart.native.js.map +1 -1
  144. package/dist/esm/charts/Sparkline.native.js.map +1 -1
  145. package/dist/esm/charts/accessibility.native.js.map +1 -1
  146. package/dist/esm/charts/composeChart.native.js.map +1 -1
  147. package/dist/esm/charts/index.native.js.map +1 -1
  148. package/dist/esm/charts/math.native.js.map +1 -1
  149. package/dist/esm/charts/series.native.js.map +1 -1
  150. package/dist/esm/charts/svg.native.js.map +1 -1
  151. package/dist/esm/charts/types.native.js.map +1 -1
  152. package/dist/esm/charts/useChartTheme.native.js.map +1 -1
  153. package/dist/esm/code/Code/index.mjs +6 -0
  154. package/dist/esm/code/Code/index.mjs.map +1 -1
  155. package/dist/esm/code/Code/index.native.js +10 -7
  156. package/dist/esm/code/Code/index.native.js.map +1 -1
  157. package/dist/esm/code/CodeBlock/index.mjs +5 -6
  158. package/dist/esm/code/CodeBlock/index.mjs.map +1 -1
  159. package/dist/esm/code/CodeBlock/index.native.js +5 -6
  160. package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
  161. package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
  162. package/dist/esm/code/Highlighter/index.native.js.map +1 -1
  163. package/dist/esm/code/Pre/index.native.js.map +1 -1
  164. package/dist/esm/code/index.native.js.map +1 -1
  165. package/dist/esm/componentColors.native.js.map +1 -1
  166. package/dist/esm/feedback/ConfirmDialog.native.js.map +1 -1
  167. package/dist/esm/feedback/NotifyHost.native.js.map +1 -1
  168. package/dist/esm/feedback/copy.native.js.map +1 -1
  169. package/dist/esm/feedback/index.native.js.map +1 -1
  170. package/dist/esm/feedback/notify.native.js.map +1 -1
  171. package/dist/esm/feedback/route.native.js.map +1 -1
  172. package/dist/esm/feedback/store.native.js.map +1 -1
  173. package/dist/esm/hooks/index.native.js.map +1 -1
  174. package/dist/esm/hooks/useClipboard.native.js.map +1 -1
  175. package/dist/esm/hooks/useDocumentTitle.native.js.map +1 -1
  176. package/dist/esm/icons/CheckRegular.native.js.map +1 -1
  177. package/dist/esm/icons/MinusRegular.native.js.map +1 -1
  178. package/dist/esm/icons/index.native.js.map +1 -1
  179. package/dist/esm/icons/lucide.native.js.map +1 -1
  180. package/dist/esm/icons/themed.mjs +3 -2
  181. package/dist/esm/icons/themed.mjs.map +1 -1
  182. package/dist/esm/icons/themed.native.js +3 -2
  183. package/dist/esm/icons/themed.native.js.map +1 -1
  184. package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
  185. package/dist/esm/images/Image/shared.native.js.map +1 -1
  186. package/dist/esm/images/Image/types.native.js.map +1 -1
  187. package/dist/esm/images/index.native.js.map +1 -1
  188. package/dist/esm/index.native.js.map +1 -1
  189. package/dist/esm/knobImmuneExports.mjs +3 -3
  190. package/dist/esm/knobImmuneExports.native.js +3 -3
  191. package/dist/esm/knobImmuneExports.native.js.map +1 -1
  192. package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
  193. package/dist/esm/layouts/Debug/index.native.js.map +1 -1
  194. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  195. package/dist/esm/layouts/KeyboardAvoidingWrapper/behavior.native.js.map +1 -1
  196. package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
  197. package/dist/esm/layouts/PaneScaffold.mjs +70 -42
  198. package/dist/esm/layouts/PaneScaffold.mjs.map +1 -1
  199. package/dist/esm/layouts/PaneScaffold.native.js +75 -48
  200. package/dist/esm/layouts/PaneScaffold.native.js.map +1 -1
  201. package/dist/esm/layouts/SafeAreaWrapper/insets.native.js.map +1 -1
  202. package/dist/esm/layouts/ViewSwitcher.native.js.map +1 -1
  203. package/dist/esm/layouts/attachments.native.js.map +1 -1
  204. package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
  205. package/dist/esm/layouts/index.native.js.map +1 -1
  206. package/dist/esm/layouts/page.mjs +3 -2
  207. package/dist/esm/layouts/page.mjs.map +1 -1
  208. package/dist/esm/layouts/page.native.js +3 -2
  209. package/dist/esm/layouts/page.native.js.map +1 -1
  210. package/dist/esm/layouts/pageHeadingScope.native.js.map +1 -1
  211. package/dist/esm/layouts/sidebar.mjs +2 -4
  212. package/dist/esm/layouts/sidebar.mjs.map +1 -1
  213. package/dist/esm/layouts/sidebar.native.js +2 -4
  214. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  215. package/dist/esm/layouts/tags.mjs.map +1 -1
  216. package/dist/esm/layouts/tags.native.js.map +1 -1
  217. package/dist/esm/layouts/timeline.native.js.map +1 -1
  218. package/dist/esm/layouts/toolbar.native.js.map +1 -1
  219. package/dist/esm/shared/actionScan.native.js.map +1 -1
  220. package/dist/esm/shared/i18n.native.js.map +1 -1
  221. package/dist/esm/shared/t.native.js.map +1 -1
  222. package/dist/esm/shared/textRidesText.native.js.map +1 -1
  223. package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
  224. package/dist/esm/surfaces.mjs +1 -33
  225. package/dist/esm/surfaces.mjs.map +1 -1
  226. package/dist/esm/surfaces.native.js +1 -33
  227. package/dist/esm/surfaces.native.js.map +1 -1
  228. package/dist/esm/tamagui.native.js.map +1 -1
  229. package/dist/esm/testSetup.native.js.map +1 -1
  230. package/dist/esm/utils/setTinted.native.js.map +1 -1
  231. package/dist/esm/utils/unwrapText.native.js.map +1 -1
  232. package/dist/esm/views/Calendar.native.js.map +1 -1
  233. package/dist/esm/views/Dashboard.native.js.map +1 -1
  234. package/dist/esm/views/Gantt.native.js.map +1 -1
  235. package/dist/esm/views/ImageGrid.native.js.map +1 -1
  236. package/dist/esm/views/List.mjs +2 -4
  237. package/dist/esm/views/List.mjs.map +1 -1
  238. package/dist/esm/views/List.native.js +2 -4
  239. package/dist/esm/views/List.native.js.map +1 -1
  240. package/dist/esm/views/Matrix.mjs +543 -0
  241. package/dist/esm/views/Matrix.mjs.map +1 -0
  242. package/dist/esm/views/Matrix.native.js +573 -0
  243. package/dist/esm/views/Matrix.native.js.map +1 -0
  244. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  245. package/dist/esm/views/TreeView.mjs +175 -43
  246. package/dist/esm/views/TreeView.mjs.map +1 -1
  247. package/dist/esm/views/TreeView.native.js +181 -46
  248. package/dist/esm/views/TreeView.native.js.map +1 -1
  249. package/dist/esm/views/calendarMath.native.js.map +1 -1
  250. package/dist/esm/views/ganttMath.mjs +1 -1
  251. package/dist/esm/views/ganttMath.mjs.map +1 -1
  252. package/dist/esm/views/ganttMath.native.js +1 -1
  253. package/dist/esm/views/ganttMath.native.js.map +1 -1
  254. package/dist/esm/views/index.mjs +4 -2
  255. package/dist/esm/views/index.mjs.map +1 -1
  256. package/dist/esm/views/index.native.js +4 -2
  257. package/dist/esm/views/index.native.js.map +1 -1
  258. package/dist/esm/views/kanbanDnd.native.js.map +1 -1
  259. package/dist/esm/views/matrixMath.mjs +122 -0
  260. package/dist/esm/views/matrixMath.mjs.map +1 -0
  261. package/dist/esm/views/matrixMath.native.js +191 -0
  262. package/dist/esm/views/matrixMath.native.js.map +1 -0
  263. package/dist/esm/views/treeViewDnd.native.js.map +1 -1
  264. package/dist/esm/views/treeViewLayout.native.js.map +1 -1
  265. package/dist/esm/views/treeViewMotion.native.js.map +1 -1
  266. package/dist/esm/views/treeViewReveal.native.js.map +1 -1
  267. package/dist/jsx/Accordion/index.mjs +3 -7
  268. package/dist/jsx/Accordion/index.mjs.map +1 -1
  269. package/dist/jsx/Accordion/index.native.js +2 -6
  270. package/dist/jsx/Accordion/index.native.js.map +1 -1
  271. package/dist/jsx/AnimateHeight/index.mjs +2 -4
  272. package/dist/jsx/AnimateHeight/index.mjs.map +1 -1
  273. package/dist/jsx/AnimateHeight/index.native.js +1 -3
  274. package/dist/jsx/AnimateHeight/index.native.js.map +1 -1
  275. package/dist/jsx/Chip/index.mjs +9 -2
  276. package/dist/jsx/Chip/index.mjs.map +1 -1
  277. package/dist/jsx/Chip/index.native.js +8 -1
  278. package/dist/jsx/Chip/index.native.js.map +1 -1
  279. package/dist/jsx/SheetModal/index.mjs +22 -3
  280. package/dist/jsx/SheetModal/index.mjs.map +1 -1
  281. package/dist/jsx/SheetModal/index.native.js +115 -27
  282. package/dist/jsx/SheetModal/index.native.js.map +1 -1
  283. package/dist/jsx/Tabs/index.mjs +36 -19
  284. package/dist/jsx/Tabs/index.mjs.map +1 -1
  285. package/dist/jsx/Tabs/index.native.js +39 -18
  286. package/dist/jsx/Tabs/index.native.js.map +1 -1
  287. package/dist/jsx/Text.mjs +3 -1
  288. package/dist/jsx/Text.mjs.map +1 -1
  289. package/dist/jsx/Text.native.js +2 -0
  290. package/dist/jsx/Text.native.js.map +1 -1
  291. package/dist/jsx/TreeSelect/index.mjs +56 -53
  292. package/dist/jsx/TreeSelect/index.mjs.map +1 -1
  293. package/dist/jsx/TreeSelect/index.native.js +55 -52
  294. package/dist/jsx/TreeSelect/index.native.js.map +1 -1
  295. package/dist/jsx/Video/PlayerChrome.mjs +4 -10
  296. package/dist/jsx/Video/PlayerChrome.mjs.map +1 -1
  297. package/dist/jsx/Video/PlayerChrome.native.js +3 -9
  298. package/dist/jsx/Video/PlayerChrome.native.js.map +1 -1
  299. package/dist/jsx/code/Code/index.mjs +6 -0
  300. package/dist/jsx/code/Code/index.mjs.map +1 -1
  301. package/dist/jsx/code/Code/index.native.js +7 -4
  302. package/dist/jsx/code/Code/index.native.js.map +1 -1
  303. package/dist/jsx/code/CodeBlock/index.mjs +5 -6
  304. package/dist/jsx/code/CodeBlock/index.mjs.map +1 -1
  305. package/dist/jsx/code/CodeBlock/index.native.js +4 -5
  306. package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
  307. package/dist/jsx/icons/themed.mjs +3 -2
  308. package/dist/jsx/icons/themed.mjs.map +1 -1
  309. package/dist/jsx/icons/themed.native.js +2 -1
  310. package/dist/jsx/icons/themed.native.js.map +1 -1
  311. package/dist/jsx/knobImmuneExports.mjs +3 -3
  312. package/dist/jsx/knobImmuneExports.native.js +3 -3
  313. package/dist/jsx/layouts/PaneScaffold.mjs +70 -42
  314. package/dist/jsx/layouts/PaneScaffold.mjs.map +1 -1
  315. package/dist/jsx/layouts/PaneScaffold.native.js +75 -48
  316. package/dist/jsx/layouts/PaneScaffold.native.js.map +1 -1
  317. package/dist/jsx/layouts/page.mjs +3 -2
  318. package/dist/jsx/layouts/page.mjs.map +1 -1
  319. package/dist/jsx/layouts/page.native.js +2 -1
  320. package/dist/jsx/layouts/page.native.js.map +1 -1
  321. package/dist/jsx/layouts/sidebar.mjs +2 -4
  322. package/dist/jsx/layouts/sidebar.mjs.map +1 -1
  323. package/dist/jsx/layouts/sidebar.native.js +1 -3
  324. package/dist/jsx/layouts/sidebar.native.js.map +1 -1
  325. package/dist/jsx/layouts/tags.mjs.map +1 -1
  326. package/dist/jsx/layouts/tags.native.js.map +1 -1
  327. package/dist/jsx/surfaces.mjs +1 -33
  328. package/dist/jsx/surfaces.mjs.map +1 -1
  329. package/dist/jsx/surfaces.native.js +0 -32
  330. package/dist/jsx/surfaces.native.js.map +1 -1
  331. package/dist/jsx/views/List.mjs +2 -4
  332. package/dist/jsx/views/List.mjs.map +1 -1
  333. package/dist/jsx/views/List.native.js +1 -3
  334. package/dist/jsx/views/List.native.js.map +1 -1
  335. package/dist/jsx/views/Matrix.mjs +543 -0
  336. package/dist/jsx/views/Matrix.mjs.map +1 -0
  337. package/dist/jsx/views/Matrix.native.js +612 -0
  338. package/dist/jsx/views/Matrix.native.js.map +1 -0
  339. package/dist/jsx/views/TreeView.mjs +175 -43
  340. package/dist/jsx/views/TreeView.mjs.map +1 -1
  341. package/dist/jsx/views/TreeView.native.js +181 -46
  342. package/dist/jsx/views/TreeView.native.js.map +1 -1
  343. package/dist/jsx/views/ganttMath.mjs +1 -1
  344. package/dist/jsx/views/ganttMath.mjs.map +1 -1
  345. package/dist/jsx/views/ganttMath.native.js +2 -0
  346. package/dist/jsx/views/ganttMath.native.js.map +1 -1
  347. package/dist/jsx/views/index.mjs +4 -2
  348. package/dist/jsx/views/index.mjs.map +1 -1
  349. package/dist/jsx/views/index.native.js +16 -0
  350. package/dist/jsx/views/index.native.js.map +1 -1
  351. package/dist/jsx/views/matrixMath.mjs +122 -0
  352. package/dist/jsx/views/matrixMath.mjs.map +1 -0
  353. package/dist/jsx/views/matrixMath.native.js +229 -0
  354. package/dist/jsx/views/matrixMath.native.js.map +1 -0
  355. package/package.json +12 -12
  356. package/src/Accordion/Accordion.spec.tsx +20 -0
  357. package/src/Accordion/index.tsx +8 -4
  358. package/src/AnimateHeight/index.tsx +143 -142
  359. package/src/Chip/Chip.borderLadder.spec.tsx +54 -0
  360. package/src/Chip/Chip.pressSlop.spec.tsx +57 -0
  361. package/src/Chip/index.tsx +15 -1
  362. package/src/Heading.native.spec.tsx +1 -1
  363. package/src/Separator/index.tsx +3 -3
  364. package/src/SheetModal/SheetModal.detentLayout.spec.tsx +173 -0
  365. package/src/SheetModal/SheetModal.fill.spec.tsx +147 -0
  366. package/src/SheetModal/SheetModal.ownedBody.spec.tsx +200 -0
  367. package/src/SheetModal/SheetModal.stories.spec.tsx +23 -2
  368. package/src/SheetModal/SheetModal.stories.tsx +275 -2
  369. package/src/SheetModal/SheetModal.touch.spec.tsx +63 -0
  370. package/src/SheetModal/index.native.tsx +143 -21
  371. package/src/SheetModal/index.tsx +40 -3
  372. package/src/Tabs/Tabs.hydration.spec.tsx +123 -0
  373. package/src/Tabs/Tabs.lifecycle.spec.tsx +123 -0
  374. package/src/Tabs/Tabs.measurement.spec.tsx +186 -0
  375. package/src/Tabs/Tabs.stories.tsx +43 -0
  376. package/src/Tabs/index.tsx +45 -15
  377. package/src/Text.spec.tsx +32 -2
  378. package/src/Text.tsx +7 -6
  379. package/src/Toast/Toast.spec.tsx +50 -0
  380. package/src/Tooltip/Tooltip.spec.tsx +55 -2
  381. package/src/TreeSelect/TreeSelect.spec.tsx +15 -15
  382. package/src/TreeSelect/index.tsx +53 -50
  383. package/src/Video/PlayerChrome.tsx +4 -3
  384. package/src/Wheel/Wheel.spec.tsx +4 -4
  385. package/src/adjacency.l1.spec.tsx +175 -0
  386. package/src/code/Code/code.spec.tsx +2 -19
  387. package/src/code/Code/index.tsx +0 -6
  388. package/src/code/CodeBlock/index.tsx +5 -3
  389. package/src/code/highlightedCode.spec.tsx +2 -2
  390. package/src/code/hostProps.spec.tsx +55 -0
  391. package/src/icons/themed.hydration.spec.tsx +60 -0
  392. package/src/icons/themed.tsx +3 -6
  393. package/src/knobImmuneExports.ts +3 -3
  394. package/src/layouts/PaneScaffold.spec.tsx +148 -2
  395. package/src/layouts/PaneScaffold.stories.tsx +33 -1
  396. package/src/layouts/PaneScaffold.tsx +101 -48
  397. package/src/layouts/page.designLaw.spec.tsx +20 -0
  398. package/src/layouts/page.tsx +2 -0
  399. package/src/layouts/sidebar.tsx +2 -3
  400. package/src/layouts/tags.tsx +2 -1
  401. package/src/overlayElevation.spec.tsx +39 -51
  402. package/src/surfaces.spec.tsx +13 -10
  403. package/src/surfaces.tsx +4 -33
  404. package/src/tamagui.spec.ts +4 -3
  405. package/src/textAccentSecondary.spec.tsx +100 -2
  406. package/src/views/List.tsx +2 -1
  407. package/src/views/Matrix.spec.tsx +339 -0
  408. package/src/views/Matrix.stories.spec.tsx +68 -0
  409. package/src/views/Matrix.stories.tsx +153 -0
  410. package/src/views/Matrix.tsx +738 -0
  411. package/src/views/TreeView.reveal.spec.tsx +7 -0
  412. package/src/views/TreeView.reveal.stories.tsx +134 -0
  413. package/src/views/TreeView.scroll-index.spec.tsx +246 -0
  414. package/src/views/TreeView.scroll.spec.tsx +711 -50
  415. package/src/views/TreeView.tsx +230 -49
  416. package/src/views/ganttMath.ts +4 -2
  417. package/src/views/index.ts +30 -0
  418. package/src/views/matrixMath.spec.ts +237 -0
  419. package/src/views/matrixMath.ts +251 -0
  420. package/src/views/views.spec.tsx +3 -1
  421. package/types/Accordion/index.d.ts.map +1 -1
  422. package/types/AnimateHeight/index.d.ts.map +1 -1
  423. package/types/Chip/index.d.ts.map +1 -1
  424. package/types/Separator/index.d.ts +3 -3
  425. package/types/SheetModal/index.d.ts +8 -1
  426. package/types/SheetModal/index.d.ts.map +1 -1
  427. package/types/SheetModal/index.native.d.ts +1 -1
  428. package/types/SheetModal/index.native.d.ts.map +1 -1
  429. package/types/Tabs/index.d.ts.map +1 -1
  430. package/types/Text.d.ts.map +1 -1
  431. package/types/TreeSelect/index.d.ts.map +1 -1
  432. package/types/Video/PlayerChrome.d.ts.map +1 -1
  433. package/types/code/Code/index.d.ts.map +1 -1
  434. package/types/code/CodeBlock/index.d.ts.map +1 -1
  435. package/types/icons/themed.d.ts.map +1 -1
  436. package/types/layouts/PaneScaffold.d.ts +14 -1
  437. package/types/layouts/PaneScaffold.d.ts.map +1 -1
  438. package/types/layouts/page.d.ts.map +1 -1
  439. package/types/layouts/sidebar.d.ts.map +1 -1
  440. package/types/layouts/tags.d.ts.map +1 -1
  441. package/types/surfaces.d.ts.map +1 -1
  442. package/types/views/List.d.ts.map +1 -1
  443. package/types/views/Matrix.d.ts +102 -0
  444. package/types/views/Matrix.d.ts.map +1 -0
  445. package/types/views/TreeView.d.ts +10 -1
  446. package/types/views/TreeView.d.ts.map +1 -1
  447. package/types/views/ganttMath.d.ts +4 -0
  448. package/types/views/ganttMath.d.ts.map +1 -1
  449. package/types/views/index.d.ts +3 -1
  450. package/types/views/index.d.ts.map +1 -1
  451. package/types/views/matrixMath.d.ts +114 -0
  452. package/types/views/matrixMath.d.ts.map +1 -0
@@ -25,6 +25,13 @@ export interface SheetModalProps {
25
25
  snapPoint?: number;
26
26
  /** Wrap children in the sheet's scroll container. */
27
27
  scrollable?: boolean;
28
+ /**
29
+ * Opt the body into the sheet's remaining height instead of sizing it to
30
+ * its content. A consumer-owned viewport (a virtualized list, a measured
31
+ * workspace) cannot bootstrap from an intrinsic host: it reports zero and
32
+ * then mounts nothing. Off by default, so existing sheets are unchanged.
33
+ */
34
+ fill?: boolean;
28
35
  }
29
36
 
30
37
  const FOCUSABLE =
@@ -32,6 +39,14 @@ const FOCUSABLE =
32
39
 
33
40
  const IN_DIALOG_REGION = { inDialogRegion: true } as const;
34
41
 
42
+ /*
43
+ * MPO-299 fill: the web frame already has a definite height from the Sheet's
44
+ * percent snap point, so fill only opens the body chain inside it. A flex
45
+ * basis on the frame itself would replace that height, not extend it.
46
+ */
47
+ const fillSlot = { flex: 1, minHeight: 0 } as const;
48
+ const nonShrinking = { flexShrink: 0 } as const;
49
+
35
50
  /** LC-71: the sheet frame is not a control. Auto-focus must not paint a ring. */
36
51
  const frameRingReset = {
37
52
  outlineWidth: 0,
@@ -140,6 +155,7 @@ export function SheetModal({
140
155
  header,
141
156
  snapPoint = 60,
142
157
  scrollable,
158
+ fill = false,
143
159
  }: SheetModalProps) {
144
160
  const { knobProps } = useResolvedKnobs();
145
161
  const transition = knobProps.transition;
@@ -190,9 +206,30 @@ export function SheetModal({
190
206
  {...frameRingReset}
191
207
  >
192
208
  <DialogRegionContext.Provider value={IN_DIALOG_REGION}>
193
- {header ? <View id={titleId}>{header}</View> : null}
194
- {scrollable ? <Sheet.ScrollView>{children}</Sheet.ScrollView> : children}
195
- <View style={{ height: "env(safe-area-inset-bottom)" }} />
209
+ {header ? (
210
+ <View id={titleId} {...(fill ? nonShrinking : null)}>
211
+ {header}
212
+ </View>
213
+ ) : null}
214
+ {fill ? (
215
+ <View testID="sheet-modal-body" {...fillSlot}>
216
+ {scrollable ? (
217
+ <Sheet.ScrollView testID="sheet-modal-scroll" {...fillSlot}>
218
+ {children}
219
+ </Sheet.ScrollView>
220
+ ) : (
221
+ children
222
+ )}
223
+ </View>
224
+ ) : scrollable ? (
225
+ <Sheet.ScrollView testID="sheet-modal-scroll">{children}</Sheet.ScrollView>
226
+ ) : (
227
+ children
228
+ )}
229
+ <View
230
+ style={{ height: "env(safe-area-inset-bottom)" }}
231
+ {...(fill ? nonShrinking : null)}
232
+ />
196
233
  </DialogRegionContext.Provider>
197
234
  </SheetFrame>
198
235
  </Sheet>
@@ -0,0 +1,123 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * MPO-279: on touch mobile web the server cannot know the device, so it renders
4
+ * pointer-sized tabs. Hydration has to reproduce that markup, then adopt the
5
+ * touch floor; a first client render at touch size is an attribute mismatch
6
+ * React never patches.
7
+ */
8
+ import { TestProviders } from "@multiplatform.one/test-utils";
9
+ import { act } from "react";
10
+ import { hydrateRoot, type Root } from "react-dom/client";
11
+ import { renderToString } from "react-dom/server";
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
13
+ import { Tabs } from "./index";
14
+
15
+ const host = vi.hoisted(() => {
16
+ vi.stubGlobal("matchMedia", () => ({
17
+ matches: false,
18
+ addEventListener() {},
19
+ removeEventListener() {},
20
+ addListener() {},
21
+ removeListener() {},
22
+ }));
23
+ return { web: true, touch: false };
24
+ });
25
+ vi.mock("@multiplatform.one/platform", async (importOriginal) => ({
26
+ ...(await importOriginal<typeof import("@multiplatform.one/platform")>()),
27
+ get isWeb() {
28
+ return host.web;
29
+ },
30
+ get isTouchable() {
31
+ return !host.web && host.touch;
32
+ },
33
+ get isWebTouchable() {
34
+ return host.web && host.touch;
35
+ },
36
+ }));
37
+
38
+ const items = [
39
+ { value: "address", label: "Address & Contact", content: <>address panel</> },
40
+ { value: "settings", label: "Settings", content: <>settings panel</> },
41
+ ];
42
+
43
+ function Fixture() {
44
+ return (
45
+ <TestProviders>
46
+ <Tabs items={items} defaultValue="address" />
47
+ </TestProviders>
48
+ );
49
+ }
50
+
51
+ /** Every `_h-` height atom painted inside each tab, in document order. */
52
+ function tabHeights(root: ParentNode): string[][] {
53
+ return [...root.querySelectorAll('[role="tab"]')].map((tab) =>
54
+ [tab, ...tab.querySelectorAll("*")].flatMap((node) =>
55
+ [...node.classList].filter((name) => /^_h-\d/.test(name)),
56
+ ),
57
+ );
58
+ }
59
+
60
+ let container: HTMLDivElement;
61
+ let root: Root | undefined;
62
+ let errors: unknown[];
63
+
64
+ beforeEach(() => {
65
+ host.web = true;
66
+ host.touch = false;
67
+ errors = [];
68
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
69
+ vi.spyOn(console, "error").mockImplementation((...args) => errors.push(args));
70
+ container = document.createElement("div");
71
+ document.body.append(container);
72
+ });
73
+
74
+ afterEach(async () => {
75
+ if (root) await act(async () => root?.unmount());
76
+ root = undefined;
77
+ container.remove();
78
+ vi.restoreAllMocks();
79
+ });
80
+
81
+ async function hydrate() {
82
+ await act(async () => {
83
+ root = hydrateRoot(container, <Fixture />, {
84
+ onRecoverableError: (error) => errors.push(error),
85
+ });
86
+ });
87
+ }
88
+
89
+ const hydrationErrors = () =>
90
+ errors.filter((error) => /hydration|hydrated|didn't match/i.test(String(error)));
91
+
92
+ describe("Tabs sizing through hydration (MPO-279)", () => {
93
+ it("keeps the server's pointer-sized tabs through hydration, then adopts the touch floor", async () => {
94
+ container.innerHTML = renderToString(<Fixture />);
95
+ const serverTabs = [...container.querySelectorAll('[role="tab"]')];
96
+ const serverHeights = tabHeights(container);
97
+ expect(serverHeights.flat()).not.toHaveLength(0);
98
+
99
+ host.touch = true;
100
+ await hydrate();
101
+
102
+ expect(hydrationErrors()).toEqual([]);
103
+ expect([...container.querySelectorAll('[role="tab"]')]).toEqual(serverTabs);
104
+ const clientHeights = tabHeights(container);
105
+ expect(clientHeights).not.toEqual(serverHeights);
106
+ for (const heights of clientHeights) expect(heights).toContain("_h-48px");
107
+ });
108
+
109
+ it("leaves pointer sizing alone on a pointer device", async () => {
110
+ container.innerHTML = renderToString(<Fixture />);
111
+ const serverHeights = tabHeights(container);
112
+ await hydrate();
113
+ expect(hydrationErrors()).toEqual([]);
114
+ expect(tabHeights(container)).toEqual(serverHeights);
115
+ });
116
+
117
+ it("sizes native tabs to the touch floor on the first render", () => {
118
+ host.web = false;
119
+ host.touch = true;
120
+ container.innerHTML = renderToString(<Fixture />);
121
+ for (const heights of tabHeights(container)) expect(heights).toContain("_h-48px");
122
+ });
123
+ });
@@ -0,0 +1,123 @@
1
+ import { act, cleanup, screen } from "@testing-library/react";
2
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
3
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
4
+ import { Tabs } from "./index";
5
+
6
+ const observed = vi.hoisted(() => ({ scroll: null as any }));
7
+ vi.mock("tamagui", async (original) => {
8
+ const actual = await original<any>();
9
+ const React = await import("react");
10
+ return {
11
+ ...actual,
12
+ ScrollView: React.forwardRef((props: any, ref) => {
13
+ observed.scroll = props;
14
+ return React.createElement(actual.ScrollView, { ...props, ref });
15
+ }),
16
+ };
17
+ });
18
+ const observers: ControlledResizeObserver[] = [];
19
+ class ControlledResizeObserver {
20
+ elements = new Set<Element>();
21
+ constructor(readonly callback: ResizeObserverCallback) {
22
+ observers.push(this);
23
+ }
24
+ observe(element: Element) {
25
+ this.elements.add(element);
26
+ }
27
+ unobserve(element: Element) {
28
+ this.elements.delete(element);
29
+ }
30
+ disconnect() {
31
+ this.elements.clear();
32
+ }
33
+ }
34
+ let precedingGrowth = 0;
35
+ const items = Array.from({ length: 6 }, (_, index) => ({
36
+ value: `tab-${index}`,
37
+ label: `Tab ${index + 1}`,
38
+ }));
39
+ function indexFor(element: HTMLElement) {
40
+ if (element.getAttribute("role") !== "tab") return -1;
41
+ return Number(element.textContent?.match(/Tab (\d)/)?.[1] ?? 0) - 1;
42
+ }
43
+ beforeEach(() => {
44
+ precedingGrowth = 0;
45
+ observers.length = 0;
46
+ vi.stubGlobal("ResizeObserver", ControlledResizeObserver);
47
+ vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockImplementation(
48
+ function (this: HTMLElement) {
49
+ const index = indexFor(this);
50
+ return index < 0 ? 0 : 100 + (index === 0 ? precedingGrowth : 0);
51
+ },
52
+ );
53
+ vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockImplementation(
54
+ function (this: HTMLElement) {
55
+ return indexFor(this) < 0 ? 0 : 40;
56
+ },
57
+ );
58
+ vi.spyOn(HTMLElement.prototype, "offsetLeft", "get").mockImplementation(
59
+ function (this: HTMLElement) {
60
+ const index = indexFor(this);
61
+ return index < 0 ? 0 : index * 100 + (index > 0 ? precedingGrowth : 0);
62
+ },
63
+ );
64
+ });
65
+ afterEach(() => {
66
+ cleanup();
67
+ vi.restoreAllMocks();
68
+ vi.unstubAllGlobals();
69
+ });
70
+ function measured() {
71
+ const tree = renderWithProviders(<Tabs items={items} value="tab-5" />);
72
+ const host = tree.container.querySelector('[data-tabs-scroll="true"]') as any;
73
+ const scroll = vi.spyOn(host, "scrollTo").mockImplementation(() => {});
74
+ act(() => observed.scroll.onLayout({ nativeEvent: { layout: { width: 390 } } }));
75
+ act(() => observed.scroll.onContentSizeChange(600, 40));
76
+ expect(scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
77
+ act(() => observed.scroll.onScroll({ nativeEvent: { contentOffset: { x: 210 } } }));
78
+ scroll.mockClear();
79
+ return { ...tree, scroll };
80
+ }
81
+ it("uses the primitive's real initial measurement and selection effect", () => {
82
+ measured();
83
+ });
84
+ it("refreshes selected position after a preceding label grows without resizing the selected trigger", () => {
85
+ const tree = measured();
86
+ precedingGrowth = 100;
87
+ tree.rerender(
88
+ <Tabs
89
+ value="tab-5"
90
+ items={items.map((item, index) =>
91
+ index === 0 ? { ...item, label: "Tab 1 with loaded details" } : item,
92
+ )}
93
+ />,
94
+ );
95
+ const first = screen.getByRole("tab", { name: "Tab 1 with loaded details" });
96
+ act(() => {
97
+ for (const observer of observers) {
98
+ if (observer.elements.has(first))
99
+ observer.callback(
100
+ [
101
+ {
102
+ target: first,
103
+ contentRect: first.getBoundingClientRect(),
104
+ borderBoxSize: [],
105
+ contentBoxSize: [],
106
+ devicePixelContentBoxSize: [],
107
+ },
108
+ ],
109
+ observer as unknown as ResizeObserver,
110
+ );
111
+ }
112
+ observed.scroll.onContentSizeChange(700, 40);
113
+ });
114
+ const selected = screen.getByRole("tab", { name: "Tab 6" });
115
+ expect(selected.offsetLeft).toBe(600);
116
+ const lastRequest = tree.scroll.mock.calls.at(-1)?.[0] as { x: number } | undefined;
117
+ expect(lastRequest?.x).toBeGreaterThanOrEqual(selected.offsetLeft + selected.offsetWidth - 390);
118
+ expect(lastRequest!.x).toBeLessThanOrEqual(selected.offsetLeft);
119
+ tree.scroll.mockClear();
120
+ act(() => observed.scroll.onScroll({ nativeEvent: { contentOffset: { x: 0 } } }));
121
+ act(() => observed.scroll.onContentSizeChange(700, 40));
122
+ expect(tree.scroll).not.toHaveBeenCalled();
123
+ });
@@ -0,0 +1,186 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { act, cleanup, screen } from "@testing-library/react";
3
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
4
+ import { Tabs, tabsScrollIntoViewX } from "./index";
5
+
6
+ const observed = vi.hoisted(() => ({ scroll: null as any, interactions: new Map<string, any>() }));
7
+
8
+ // The actual component and primitives render. Only public measurement events
9
+ // and the imperative scroll boundary are controlled; this is not browser proof.
10
+ vi.mock("tamagui", async (importOriginal) => {
11
+ const actual = await importOriginal<any>();
12
+ const React = await import("react");
13
+ return {
14
+ ...actual,
15
+ ScrollView: React.forwardRef((props: any, ref) => {
16
+ observed.scroll = props;
17
+ return React.createElement(actual.ScrollView, { ...props, ref });
18
+ }),
19
+ styled: (...args: any[]) => {
20
+ const Component = actual.styled(...args);
21
+ if (args[0] !== actual.Tabs.Tab) return Component;
22
+ return React.forwardRef((props: any, ref) => {
23
+ observed.interactions.set(props.value, props.onInteraction);
24
+ return React.createElement(Component, { ...props, ref });
25
+ });
26
+ },
27
+ };
28
+ });
29
+
30
+ const items = Array.from({ length: 6 }, (_, index) => ({
31
+ value: `tab-${index}`,
32
+ label: `Tab ${index + 1}`,
33
+ }));
34
+ const last = { x: 500, y: 0, width: 100, height: 40 };
35
+
36
+ afterEach(() => {
37
+ cleanup();
38
+ vi.restoreAllMocks();
39
+ observed.scroll = null;
40
+ observed.interactions.clear();
41
+ document.documentElement.removeAttribute("dir");
42
+ });
43
+
44
+ function fixture(mode: "value" | "defaultValue" = "value") {
45
+ const rendered = renderWithProviders(<Tabs items={items} {...{ [mode]: "tab-5" }} />);
46
+ expect(screen.getByRole("tab", { name: "Tab 6" })).toHaveAttribute("aria-selected", "true");
47
+ const host = rendered.container.querySelector('[data-tabs-scroll="true"]') as any;
48
+ expect(typeof host.scrollTo).toBe("function");
49
+ const scroll = vi.spyOn(host, "scrollTo").mockImplementation(() => {});
50
+ const viewport = (width: number) =>
51
+ act(() => observed.scroll.onLayout({ nativeEvent: { layout: { width } } }));
52
+ const content = (width: number) => act(() => observed.scroll.onContentSizeChange(width, 40));
53
+ const active = (layout = last) => act(() => observed.interactions.get("tab-5")("select", layout));
54
+ const manualScroll = (x: number) =>
55
+ act(() => observed.scroll.onScroll({ nativeEvent: { contentOffset: { x } } }));
56
+ return { ...rendered, scroll, viewport, content, active, manualScroll };
57
+ }
58
+
59
+ describe.each(["value", "defaultValue"] as const)(
60
+ "Tabs public measurement ordering (%s)",
61
+ (mode) => {
62
+ it("reveals initial last tab when metrics arrive before its layout (control)", () => {
63
+ const tree = fixture(mode);
64
+ tree.viewport(390);
65
+ tree.content(600);
66
+ tree.active();
67
+ expect(tree.scroll).toHaveBeenCalledWith({ x: 210, animated: true });
68
+ });
69
+
70
+ it("reveals initial last tab when content arrives after its layout", () => {
71
+ const tree = fixture(mode);
72
+ tree.viewport(390);
73
+ tree.active();
74
+ expect(tree.scroll).not.toHaveBeenCalled();
75
+ tree.content(600);
76
+ expect(tree.scroll).toHaveBeenCalled();
77
+ });
78
+
79
+ it("corrects after a viewport shrink with unchanged active-tab bounds", () => {
80
+ const tree = fixture(mode);
81
+ tree.viewport(800);
82
+ tree.content(600);
83
+ tree.active();
84
+ expect(tree.scroll).not.toHaveBeenCalled();
85
+ tree.viewport(390);
86
+ expect(tree.scroll).toHaveBeenCalled();
87
+ });
88
+
89
+ it("does not force a manual scroll back after the active tab was revealed", () => {
90
+ const tree = fixture(mode);
91
+ tree.viewport(390);
92
+ tree.content(600);
93
+ tree.active();
94
+ expect(tree.scroll).toHaveBeenCalledWith({ x: 210, animated: true });
95
+ tree.scroll.mockClear();
96
+ tree.manualScroll(0);
97
+ expect(tree.scroll).not.toHaveBeenCalled();
98
+ });
99
+ },
100
+ );
101
+
102
+ it.each(["value", "defaultValue"] as const)(
103
+ "reveals after late viewport measurement (%s)",
104
+ (mode) => {
105
+ const tree = fixture(mode);
106
+ tree.content(600);
107
+ tree.active();
108
+ expect(tree.scroll).not.toHaveBeenCalled();
109
+ tree.viewport(390);
110
+ expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
111
+ },
112
+ );
113
+
114
+ it("retries after a hidden viewport becomes usable and ignores identical measurements", () => {
115
+ const tree = fixture();
116
+ tree.viewport(0);
117
+ tree.content(600);
118
+ tree.active();
119
+ expect(tree.scroll).not.toHaveBeenCalled();
120
+ tree.viewport(390);
121
+ expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
122
+ tree.manualScroll(210);
123
+ tree.scroll.mockClear();
124
+ tree.viewport(390);
125
+ tree.content(600);
126
+ tree.active();
127
+ expect(tree.scroll).not.toHaveBeenCalled();
128
+ tree.viewport(800);
129
+ expect(tree.scroll).not.toHaveBeenCalled();
130
+ });
131
+
132
+ it("rechecks changed content width without fighting subsequent manual scroll", () => {
133
+ const tree = fixture();
134
+ tree.viewport(390);
135
+ tree.content(390);
136
+ tree.active();
137
+ expect(tree.scroll).not.toHaveBeenCalled();
138
+ tree.content(600);
139
+ expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 210, animated: true });
140
+ tree.manualScroll(210);
141
+ tree.scroll.mockClear();
142
+ tree.content(620);
143
+ expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 230, animated: true });
144
+ tree.manualScroll(230);
145
+ tree.scroll.mockClear();
146
+ tree.manualScroll(0);
147
+ tree.active();
148
+ tree.viewport(390);
149
+ tree.content(620);
150
+ expect(tree.scroll).not.toHaveBeenCalled();
151
+ tree.content(650);
152
+ expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: 242, animated: true });
153
+ });
154
+
155
+ it("waits for the latest selected tab's layout when dimensions were pending", () => {
156
+ const tree = fixture();
157
+ tree.viewport(390);
158
+ tree.active();
159
+ tree.rerender(<Tabs items={items} value="tab-0" />);
160
+ tree.content(600);
161
+ expect(tree.scroll).not.toHaveBeenCalled();
162
+ act(() => observed.interactions.get("tab-0")("select", { ...last, x: 0 }));
163
+ expect(tree.scroll).not.toHaveBeenCalled();
164
+ expect(screen.getByRole("tab", { name: "Tab 1" })).toHaveAttribute("aria-selected", "true");
165
+ });
166
+
167
+ it("uses the negative RTL offset range after delayed dimensions", () => {
168
+ document.documentElement.setAttribute("dir", "rtl");
169
+ const tree = fixture();
170
+ tree.viewport(390);
171
+ tree.active({ ...last, x: 0 });
172
+ tree.content(600);
173
+ expect(tree.scroll).toHaveBeenCalledExactlyOnceWith({ x: -210, animated: true });
174
+ tree.manualScroll(-210);
175
+ tree.scroll.mockClear();
176
+ tree.manualScroll(0);
177
+ expect(tree.scroll).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it.each([0, -1, Number.NaN, Number.POSITIVE_INFINITY])(
181
+ "does not scroll with unusable width %s",
182
+ (width) => {
183
+ expect(tabsScrollIntoViewX(last, 0, width, 600, "ltr")).toBeUndefined();
184
+ expect(tabsScrollIntoViewX({ ...last, width }, 0, 390, 600, "ltr")).toBeUndefined();
185
+ },
186
+ );
@@ -362,3 +362,46 @@ export const EqualSmall: Story = {
362
362
  </YStack>
363
363
  ),
364
364
  };
365
+
366
+ export const InitialSixthActive: Story = {
367
+ args: { defaultValue: "section-6", ariaLabel: "Example sections" },
368
+ render: (args) => {
369
+ const [wide, setWide] = React.useState(false);
370
+ const [fullLabels, setFullLabels] = React.useState(true);
371
+ const [hidden, setHidden] = React.useState(false);
372
+ const [mount, setMount] = React.useState(0);
373
+ const items: TabsItem[] = Array.from({ length: 6 }, (_, index) => ({
374
+ value: `section-${index + 1}`,
375
+ label: fullLabels ? `Section ${index + 1} overview` : `${index + 1}`,
376
+ content: (
377
+ <Panel
378
+ title={`Section ${index + 1}`}
379
+ body="The selected section stays visible when the strip is measured or resized."
380
+ />
381
+ ),
382
+ }));
383
+ return (
384
+ <YStack gap="$3">
385
+ <XStack gap="$2" flexWrap="wrap">
386
+ <Button onPress={() => setWide((value) => !value)}>
387
+ {wide ? "Set width to 390px" : "Set width to 800px"}
388
+ </Button>
389
+ <Button onPress={() => setFullLabels((value) => !value)}>
390
+ {fullLabels ? "Use short labels" : "Load full labels"}
391
+ </Button>
392
+ <Button onPress={() => setHidden((value) => !value)}>
393
+ {hidden ? "Show strip" : "Hide strip"}
394
+ </Button>
395
+ <Button onPress={() => setMount((value) => value + 1)}>Reset to sixth tab</Button>
396
+ </XStack>
397
+ <Text color="$color11">
398
+ Use short labels, then load full labels to change content width. Hide and show the strip
399
+ to defer usable measurements. Scroll manually after reveal to check that it stays put.
400
+ </Text>
401
+ <YStack width={wide ? 800 : 390} maxWidth="100%" display={hidden ? "none" : "flex"}>
402
+ <Tabs key={mount} {...args} items={items} />
403
+ </YStack>
404
+ </YStack>
405
+ );
406
+ },
407
+ };
@@ -37,6 +37,7 @@ import {
37
37
  useResolvedKnobs,
38
38
  wasKeyboardFocus,
39
39
  type GroupPosition,
40
+ transitionProps,
40
41
  } from "@multiplatform.one/theme";
41
42
  import type { ReactNode } from "react";
42
43
  import React from "react";
@@ -174,6 +175,14 @@ export function tabsScrollIntoViewX(
174
175
  direction: TabsDirection,
175
176
  pad = 32,
176
177
  ): number | undefined {
178
+ if (
179
+ ![layout.x, layout.width, layout.height, offset, viewport, content].every(Number.isFinite) ||
180
+ layout.width <= 0 ||
181
+ layout.height <= 0 ||
182
+ viewport <= 0 ||
183
+ content <= 0
184
+ )
185
+ return undefined;
177
186
  const range = Math.max(content - viewport, 0);
178
187
  if (range <= 0) return undefined;
179
188
  const minScrollLeft = direction === "rtl" ? -range : 0;
@@ -366,23 +375,28 @@ function TabsRoot({
366
375
  };
367
376
 
368
377
  // Active tab layout (relative to the list) for the sliding indicator
369
- const [activeLayout, setActiveLayout] = React.useState<TabLayout | null>(null);
370
- const handleInteraction = (type: string, layout: TabLayout | null) => {
371
- if (type !== "select" || !layout) return;
378
+ const [activeLayout, setActiveLayout] = React.useState<(TabLayout & { value: string }) | null>(
379
+ null,
380
+ );
381
+ const handleInteraction = (tabValue: string, type: string, layout: TabLayout | null) => {
382
+ if (type !== "select" || !layout || tabValue !== currentValue) return;
372
383
  setActiveLayout((prev) =>
373
384
  prev &&
385
+ prev.value === tabValue &&
374
386
  prev.x === layout.x &&
375
387
  prev.y === layout.y &&
376
388
  prev.width === layout.width &&
377
389
  prev.height === layout.height
378
390
  ? prev
379
- : layout,
391
+ : { ...layout, value: tabValue },
380
392
  );
381
393
  };
382
394
 
383
395
  // Overflow tracking for edge fades + keep-active-visible scrolling
384
396
  const scrollRef = React.useRef<ScrollView>(null);
397
+ const tabRefs = React.useRef(new Map<string, HTMLElement>());
385
398
  const scrollMetrics = React.useRef({ offset: 0, viewport: 0, content: 0 });
399
+ const [sizeRevision, setSizeRevision] = React.useState(0);
386
400
  const [fades, setFades] = React.useState({ left: false, right: false });
387
401
  const updateFades = () => {
388
402
  const { offset, viewport, content } = scrollMetrics.current;
@@ -390,20 +404,38 @@ function TabsRoot({
390
404
  setFades((prev) => (prev.left === next.left && prev.right === next.right ? prev : next));
391
405
  };
392
406
 
407
+ const updateScrollSize = (dimension: "viewport" | "content", size: number) => {
408
+ if (!Object.is(scrollMetrics.current[dimension], size)) {
409
+ scrollMetrics.current[dimension] = size;
410
+ if (isWeb) {
411
+ const selected = tabRefs.current.get(currentValue);
412
+ if (selected && selected.offsetWidth > 0 && selected.offsetHeight > 0) {
413
+ handleInteraction(currentValue, "select", {
414
+ x: selected.offsetLeft,
415
+ y: selected.offsetTop,
416
+ width: selected.offsetWidth,
417
+ height: selected.offsetHeight,
418
+ });
419
+ }
420
+ }
421
+ setSizeRevision((current) => current + 1);
422
+ }
423
+ updateFades();
424
+ };
425
+
393
426
  // Scroll the newly active tab into view when it is clipped
394
427
  React.useEffect(() => {
395
- if (!activeLayout) return;
428
+ if (!activeLayout || activeLayout.value !== currentValue) return;
396
429
  const { offset, viewport, content } = scrollMetrics.current;
397
430
  const target = tabsScrollIntoViewX(activeLayout, offset, viewport, content, direction);
398
- if (target !== undefined) {
431
+ if (target !== undefined && Math.abs(target - offset) >= 1) {
399
432
  scrollRef.current?.scrollTo({ x: target, animated: true });
400
433
  }
401
- }, [activeLayout, direction]);
434
+ }, [activeLayout, currentValue, direction, sizeRevision]);
402
435
 
403
436
  const isUnderline = variant === "underline";
404
437
  const isBand = variant === "band";
405
438
  const equalDistribution = isBand || distribution === "equal";
406
- const tabRefs = React.useRef(new Map<string, HTMLElement>());
407
439
  const onTabKeyDown = (event: React.KeyboardEvent, value: string) => {
408
440
  if (!["Home", "End", "ArrowLeft", "ArrowRight"].includes(event.key)) return;
409
441
  const enabled = items.filter((item) => !disabled && !item.disabled);
@@ -461,7 +493,7 @@ function TabsRoot({
461
493
  x={activeLayout.x}
462
494
  width={activeLayout.width}
463
495
  data-tabs-indicator="underline"
464
- {...(transition ? { transition } : undefined)}
496
+ {...transitionProps(transition)}
465
497
  />
466
498
  </Theme>
467
499
  ) : (
@@ -477,7 +509,7 @@ function TabsRoot({
477
509
  {...(knobProps.elevation !== undefined
478
510
  ? { elevation: knobProps.elevation }
479
511
  : undefined)}
480
- {...(transition ? { transition } : undefined)}
512
+ {...transitionProps(transition)}
481
513
  />
482
514
  </Theme>
483
515
  )
@@ -576,7 +608,7 @@ function TabsRoot({
576
608
  accessibilityState: { selected: isActive, disabled: Boolean(isDisabled) },
577
609
  })}
578
610
  hitSlop={tabHitSlop}
579
- onInteraction={handleInteraction}
611
+ onInteraction={(type, layout) => handleInteraction(item.value, type, layout)}
580
612
  cursor={isDisabled ? "not-allowed" : "pointer"}
581
613
  backgroundColor={
582
614
  isBand
@@ -716,12 +748,10 @@ function TabsRoot({
716
748
  updateFades();
717
749
  }}
718
750
  onLayout={(e) => {
719
- scrollMetrics.current.viewport = e.nativeEvent.layout.width;
720
- updateFades();
751
+ updateScrollSize("viewport", e.nativeEvent.layout.width);
721
752
  }}
722
753
  onContentSizeChange={(w) => {
723
- scrollMetrics.current.content = w;
724
- updateFades();
754
+ updateScrollSize("content", w);
725
755
  }}
726
756
  >
727
757
  {tabList}