@multiplatform.one/components 7.11.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 (413) hide show
  1. package/README.md +2 -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.native.js +70 -22
  12. package/dist/cjs/SheetModal/index.native.js.map +1 -1
  13. package/dist/cjs/Tabs/index.cjs +2 -6
  14. package/dist/cjs/Tabs/index.native.js +2 -6
  15. package/dist/cjs/Tabs/index.native.js.map +1 -1
  16. package/dist/cjs/Text.cjs +2 -0
  17. package/dist/cjs/Text.native.js +2 -0
  18. package/dist/cjs/Text.native.js.map +1 -1
  19. package/dist/cjs/TreeSelect/index.cjs +55 -52
  20. package/dist/cjs/TreeSelect/index.native.js +55 -52
  21. package/dist/cjs/TreeSelect/index.native.js.map +1 -1
  22. package/dist/cjs/Video/PlayerChrome.cjs +3 -9
  23. package/dist/cjs/Video/PlayerChrome.native.js +3 -9
  24. package/dist/cjs/Video/PlayerChrome.native.js.map +1 -1
  25. package/dist/cjs/code/Code/index.cjs +6 -0
  26. package/dist/cjs/code/Code/index.native.js +7 -4
  27. package/dist/cjs/code/Code/index.native.js.map +1 -1
  28. package/dist/cjs/code/CodeBlock/index.cjs +4 -5
  29. package/dist/cjs/code/CodeBlock/index.native.js +4 -5
  30. package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
  31. package/dist/cjs/icons/themed.cjs +2 -1
  32. package/dist/cjs/icons/themed.native.js +2 -1
  33. package/dist/cjs/icons/themed.native.js.map +1 -1
  34. package/dist/cjs/knobImmuneExports.cjs +3 -3
  35. package/dist/cjs/knobImmuneExports.native.js +3 -3
  36. package/dist/cjs/layouts/PaneScaffold.cjs +70 -42
  37. package/dist/cjs/layouts/PaneScaffold.native.js +75 -48
  38. package/dist/cjs/layouts/PaneScaffold.native.js.map +1 -1
  39. package/dist/cjs/layouts/page.cjs +2 -1
  40. package/dist/cjs/layouts/page.native.js +2 -1
  41. package/dist/cjs/layouts/page.native.js.map +1 -1
  42. package/dist/cjs/layouts/sidebar.cjs +1 -3
  43. package/dist/cjs/layouts/sidebar.native.js +1 -3
  44. package/dist/cjs/layouts/sidebar.native.js.map +1 -1
  45. package/dist/cjs/surfaces.cjs +0 -32
  46. package/dist/cjs/surfaces.native.js +0 -32
  47. package/dist/cjs/surfaces.native.js.map +1 -1
  48. package/dist/cjs/views/List.cjs +1 -3
  49. package/dist/cjs/views/List.native.js +1 -3
  50. package/dist/cjs/views/List.native.js.map +1 -1
  51. package/dist/cjs/views/Matrix.cjs +568 -0
  52. package/dist/cjs/views/Matrix.native.js +612 -0
  53. package/dist/cjs/views/Matrix.native.js.map +1 -0
  54. package/dist/cjs/views/ganttMath.cjs +2 -0
  55. package/dist/cjs/views/ganttMath.native.js +2 -0
  56. package/dist/cjs/views/ganttMath.native.js.map +1 -1
  57. package/dist/cjs/views/index.cjs +16 -0
  58. package/dist/cjs/views/index.native.js +16 -0
  59. package/dist/cjs/views/index.native.js.map +1 -1
  60. package/dist/cjs/views/matrixMath.cjs +157 -0
  61. package/dist/cjs/views/matrixMath.native.js +229 -0
  62. package/dist/cjs/views/matrixMath.native.js.map +1 -0
  63. package/dist/esm/Accordion/index.mjs +3 -7
  64. package/dist/esm/Accordion/index.mjs.map +1 -1
  65. package/dist/esm/Accordion/index.native.js +3 -7
  66. package/dist/esm/Accordion/index.native.js.map +1 -1
  67. package/dist/esm/AnimateHeight/index.mjs +2 -4
  68. package/dist/esm/AnimateHeight/index.mjs.map +1 -1
  69. package/dist/esm/AnimateHeight/index.native.js +2 -4
  70. package/dist/esm/AnimateHeight/index.native.js.map +1 -1
  71. package/dist/esm/Avatar/getInitials.native.js.map +1 -1
  72. package/dist/esm/Avatar/index.native.js.map +1 -1
  73. package/dist/esm/Badge/index.native.js.map +1 -1
  74. package/dist/esm/Button.native.js.map +1 -1
  75. package/dist/esm/Carousel/index.native.js.map +1 -1
  76. package/dist/esm/Chip/index.mjs +9 -2
  77. package/dist/esm/Chip/index.mjs.map +1 -1
  78. package/dist/esm/Chip/index.native.js +9 -2
  79. package/dist/esm/Chip/index.native.js.map +1 -1
  80. package/dist/esm/Collapsible.native.js.map +1 -1
  81. package/dist/esm/ContextMenu/index.native.js.map +1 -1
  82. package/dist/esm/CopyField/index.native.js.map +1 -1
  83. package/dist/esm/DotIndicator/index.native.js.map +1 -1
  84. package/dist/esm/DropdownMenu/index.native.js.map +1 -1
  85. package/dist/esm/ErrorBoundary/index.native.js.map +1 -1
  86. package/dist/esm/Heading.native.js.map +1 -1
  87. package/dist/esm/Meter/index.native.js.map +1 -1
  88. package/dist/esm/Meter/meter.native.js.map +1 -1
  89. package/dist/esm/Pagination.native.js.map +1 -1
  90. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  91. package/dist/esm/Quote/index.native.js.map +1 -1
  92. package/dist/esm/Separator/index.native.js.map +1 -1
  93. package/dist/esm/SheetModal/detents.native.js.map +1 -1
  94. package/dist/esm/SheetModal/index.native.js +71 -23
  95. package/dist/esm/SheetModal/index.native.js.map +1 -1
  96. package/dist/esm/Skeleton/index.native.js.map +1 -1
  97. package/dist/esm/SkipLink/index.native.js.map +1 -1
  98. package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
  99. package/dist/esm/SortableList/index.native.js.map +1 -1
  100. package/dist/esm/SortableList/useSortableRows.native.js.map +1 -1
  101. package/dist/esm/Tabs/index.mjs +3 -7
  102. package/dist/esm/Tabs/index.mjs.map +1 -1
  103. package/dist/esm/Tabs/index.native.js +3 -7
  104. package/dist/esm/Tabs/index.native.js.map +1 -1
  105. package/dist/esm/Text.mjs +3 -1
  106. package/dist/esm/Text.mjs.map +1 -1
  107. package/dist/esm/Text.native.js +3 -1
  108. package/dist/esm/Text.native.js.map +1 -1
  109. package/dist/esm/Toast/index.native.js.map +1 -1
  110. package/dist/esm/Toast/store.native.js.map +1 -1
  111. package/dist/esm/Tooltip/nativePress.native.js.map +1 -1
  112. package/dist/esm/Tooltip/tooltipShared.native.js.map +1 -1
  113. package/dist/esm/TreeSelect/index.mjs +56 -53
  114. package/dist/esm/TreeSelect/index.mjs.map +1 -1
  115. package/dist/esm/TreeSelect/index.native.js +56 -53
  116. package/dist/esm/TreeSelect/index.native.js.map +1 -1
  117. package/dist/esm/TreeSelect/treeSelectModel.native.js.map +1 -1
  118. package/dist/esm/Video/PlayerChrome.mjs +4 -10
  119. package/dist/esm/Video/PlayerChrome.mjs.map +1 -1
  120. package/dist/esm/Video/PlayerChrome.native.js +4 -10
  121. package/dist/esm/Video/PlayerChrome.native.js.map +1 -1
  122. package/dist/esm/Video/VideoFrame.native.js.map +1 -1
  123. package/dist/esm/Video/types.native.js.map +1 -1
  124. package/dist/esm/Wheel/index.native.js.map +1 -1
  125. package/dist/esm/Wizard/index.native.js.map +1 -1
  126. package/dist/esm/actions/ActionBar.native.js.map +1 -1
  127. package/dist/esm/actions/DestructiveAction.native.js.map +1 -1
  128. package/dist/esm/actions/devWarn.native.js.map +1 -1
  129. package/dist/esm/actions/index.native.js.map +1 -1
  130. package/dist/esm/actions/severity.native.js.map +1 -1
  131. package/dist/esm/charts/Axis.native.js.map +1 -1
  132. package/dist/esm/charts/BarChart.native.js.map +1 -1
  133. package/dist/esm/charts/ChartDataView.native.js.map +1 -1
  134. package/dist/esm/charts/ChartSurface.native.js.map +1 -1
  135. package/dist/esm/charts/LineChart.native.js.map +1 -1
  136. package/dist/esm/charts/PieChart.native.js.map +1 -1
  137. package/dist/esm/charts/Sparkline.native.js.map +1 -1
  138. package/dist/esm/charts/accessibility.native.js.map +1 -1
  139. package/dist/esm/charts/composeChart.native.js.map +1 -1
  140. package/dist/esm/charts/index.native.js.map +1 -1
  141. package/dist/esm/charts/math.native.js.map +1 -1
  142. package/dist/esm/charts/series.native.js.map +1 -1
  143. package/dist/esm/charts/svg.native.js.map +1 -1
  144. package/dist/esm/charts/types.native.js.map +1 -1
  145. package/dist/esm/charts/useChartTheme.native.js.map +1 -1
  146. package/dist/esm/code/Code/index.mjs +6 -0
  147. package/dist/esm/code/Code/index.mjs.map +1 -1
  148. package/dist/esm/code/Code/index.native.js +10 -7
  149. package/dist/esm/code/Code/index.native.js.map +1 -1
  150. package/dist/esm/code/CodeBlock/index.mjs +5 -6
  151. package/dist/esm/code/CodeBlock/index.mjs.map +1 -1
  152. package/dist/esm/code/CodeBlock/index.native.js +5 -6
  153. package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
  154. package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
  155. package/dist/esm/code/Highlighter/index.native.js.map +1 -1
  156. package/dist/esm/code/Pre/index.native.js.map +1 -1
  157. package/dist/esm/code/index.native.js.map +1 -1
  158. package/dist/esm/componentColors.native.js.map +1 -1
  159. package/dist/esm/feedback/ConfirmDialog.native.js.map +1 -1
  160. package/dist/esm/feedback/NotifyHost.native.js.map +1 -1
  161. package/dist/esm/feedback/copy.native.js.map +1 -1
  162. package/dist/esm/feedback/index.native.js.map +1 -1
  163. package/dist/esm/feedback/notify.native.js.map +1 -1
  164. package/dist/esm/feedback/route.native.js.map +1 -1
  165. package/dist/esm/feedback/store.native.js.map +1 -1
  166. package/dist/esm/hooks/index.native.js.map +1 -1
  167. package/dist/esm/hooks/useClipboard.native.js.map +1 -1
  168. package/dist/esm/hooks/useDocumentTitle.native.js.map +1 -1
  169. package/dist/esm/icons/CheckRegular.native.js.map +1 -1
  170. package/dist/esm/icons/MinusRegular.native.js.map +1 -1
  171. package/dist/esm/icons/index.native.js.map +1 -1
  172. package/dist/esm/icons/lucide.native.js.map +1 -1
  173. package/dist/esm/icons/themed.mjs +3 -2
  174. package/dist/esm/icons/themed.mjs.map +1 -1
  175. package/dist/esm/icons/themed.native.js +3 -2
  176. package/dist/esm/icons/themed.native.js.map +1 -1
  177. package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
  178. package/dist/esm/images/Image/shared.native.js.map +1 -1
  179. package/dist/esm/images/Image/types.native.js.map +1 -1
  180. package/dist/esm/images/index.native.js.map +1 -1
  181. package/dist/esm/index.native.js.map +1 -1
  182. package/dist/esm/knobImmuneExports.mjs +3 -3
  183. package/dist/esm/knobImmuneExports.native.js +3 -3
  184. package/dist/esm/knobImmuneExports.native.js.map +1 -1
  185. package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
  186. package/dist/esm/layouts/Debug/index.native.js.map +1 -1
  187. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  188. package/dist/esm/layouts/KeyboardAvoidingWrapper/behavior.native.js.map +1 -1
  189. package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
  190. package/dist/esm/layouts/PaneScaffold.mjs +70 -42
  191. package/dist/esm/layouts/PaneScaffold.mjs.map +1 -1
  192. package/dist/esm/layouts/PaneScaffold.native.js +75 -48
  193. package/dist/esm/layouts/PaneScaffold.native.js.map +1 -1
  194. package/dist/esm/layouts/SafeAreaWrapper/insets.native.js.map +1 -1
  195. package/dist/esm/layouts/ViewSwitcher.native.js.map +1 -1
  196. package/dist/esm/layouts/attachments.native.js.map +1 -1
  197. package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
  198. package/dist/esm/layouts/index.native.js.map +1 -1
  199. package/dist/esm/layouts/page.mjs +3 -2
  200. package/dist/esm/layouts/page.mjs.map +1 -1
  201. package/dist/esm/layouts/page.native.js +3 -2
  202. package/dist/esm/layouts/page.native.js.map +1 -1
  203. package/dist/esm/layouts/pageHeadingScope.native.js.map +1 -1
  204. package/dist/esm/layouts/sidebar.mjs +2 -4
  205. package/dist/esm/layouts/sidebar.mjs.map +1 -1
  206. package/dist/esm/layouts/sidebar.native.js +2 -4
  207. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  208. package/dist/esm/layouts/tags.native.js.map +1 -1
  209. package/dist/esm/layouts/timeline.native.js.map +1 -1
  210. package/dist/esm/layouts/toolbar.native.js.map +1 -1
  211. package/dist/esm/shared/actionScan.native.js.map +1 -1
  212. package/dist/esm/shared/i18n.native.js.map +1 -1
  213. package/dist/esm/shared/t.native.js.map +1 -1
  214. package/dist/esm/shared/textRidesText.native.js.map +1 -1
  215. package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
  216. package/dist/esm/surfaces.mjs +1 -33
  217. package/dist/esm/surfaces.mjs.map +1 -1
  218. package/dist/esm/surfaces.native.js +1 -33
  219. package/dist/esm/surfaces.native.js.map +1 -1
  220. package/dist/esm/tamagui.native.js.map +1 -1
  221. package/dist/esm/testSetup.native.js.map +1 -1
  222. package/dist/esm/utils/setTinted.native.js.map +1 -1
  223. package/dist/esm/utils/unwrapText.native.js.map +1 -1
  224. package/dist/esm/views/Calendar.native.js.map +1 -1
  225. package/dist/esm/views/Dashboard.native.js.map +1 -1
  226. package/dist/esm/views/Gantt.native.js.map +1 -1
  227. package/dist/esm/views/ImageGrid.native.js.map +1 -1
  228. package/dist/esm/views/List.mjs +2 -4
  229. package/dist/esm/views/List.mjs.map +1 -1
  230. package/dist/esm/views/List.native.js +2 -4
  231. package/dist/esm/views/List.native.js.map +1 -1
  232. package/dist/esm/views/Matrix.mjs +543 -0
  233. package/dist/esm/views/Matrix.mjs.map +1 -0
  234. package/dist/esm/views/Matrix.native.js +573 -0
  235. package/dist/esm/views/Matrix.native.js.map +1 -0
  236. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  237. package/dist/esm/views/TreeView.native.js.map +1 -1
  238. package/dist/esm/views/calendarMath.native.js.map +1 -1
  239. package/dist/esm/views/ganttMath.mjs +1 -1
  240. package/dist/esm/views/ganttMath.mjs.map +1 -1
  241. package/dist/esm/views/ganttMath.native.js +1 -1
  242. package/dist/esm/views/ganttMath.native.js.map +1 -1
  243. package/dist/esm/views/index.mjs +4 -2
  244. package/dist/esm/views/index.mjs.map +1 -1
  245. package/dist/esm/views/index.native.js +4 -2
  246. package/dist/esm/views/index.native.js.map +1 -1
  247. package/dist/esm/views/kanbanDnd.native.js.map +1 -1
  248. package/dist/esm/views/matrixMath.mjs +122 -0
  249. package/dist/esm/views/matrixMath.mjs.map +1 -0
  250. package/dist/esm/views/matrixMath.native.js +191 -0
  251. package/dist/esm/views/matrixMath.native.js.map +1 -0
  252. package/dist/esm/views/treeViewDnd.native.js.map +1 -1
  253. package/dist/esm/views/treeViewLayout.native.js.map +1 -1
  254. package/dist/esm/views/treeViewMotion.native.js.map +1 -1
  255. package/dist/esm/views/treeViewReveal.native.js.map +1 -1
  256. package/dist/jsx/Accordion/index.mjs +3 -7
  257. package/dist/jsx/Accordion/index.mjs.map +1 -1
  258. package/dist/jsx/Accordion/index.native.js +2 -6
  259. package/dist/jsx/Accordion/index.native.js.map +1 -1
  260. package/dist/jsx/AnimateHeight/index.mjs +2 -4
  261. package/dist/jsx/AnimateHeight/index.mjs.map +1 -1
  262. package/dist/jsx/AnimateHeight/index.native.js +1 -3
  263. package/dist/jsx/AnimateHeight/index.native.js.map +1 -1
  264. package/dist/jsx/Chip/index.mjs +9 -2
  265. package/dist/jsx/Chip/index.mjs.map +1 -1
  266. package/dist/jsx/Chip/index.native.js +8 -1
  267. package/dist/jsx/Chip/index.native.js.map +1 -1
  268. package/dist/jsx/SheetModal/index.native.js +70 -22
  269. package/dist/jsx/SheetModal/index.native.js.map +1 -1
  270. package/dist/jsx/Tabs/index.mjs +3 -7
  271. package/dist/jsx/Tabs/index.mjs.map +1 -1
  272. package/dist/jsx/Tabs/index.native.js +2 -6
  273. package/dist/jsx/Tabs/index.native.js.map +1 -1
  274. package/dist/jsx/Text.mjs +3 -1
  275. package/dist/jsx/Text.mjs.map +1 -1
  276. package/dist/jsx/Text.native.js +2 -0
  277. package/dist/jsx/Text.native.js.map +1 -1
  278. package/dist/jsx/TreeSelect/index.mjs +56 -53
  279. package/dist/jsx/TreeSelect/index.mjs.map +1 -1
  280. package/dist/jsx/TreeSelect/index.native.js +55 -52
  281. package/dist/jsx/TreeSelect/index.native.js.map +1 -1
  282. package/dist/jsx/Video/PlayerChrome.mjs +4 -10
  283. package/dist/jsx/Video/PlayerChrome.mjs.map +1 -1
  284. package/dist/jsx/Video/PlayerChrome.native.js +3 -9
  285. package/dist/jsx/Video/PlayerChrome.native.js.map +1 -1
  286. package/dist/jsx/code/Code/index.mjs +6 -0
  287. package/dist/jsx/code/Code/index.mjs.map +1 -1
  288. package/dist/jsx/code/Code/index.native.js +7 -4
  289. package/dist/jsx/code/Code/index.native.js.map +1 -1
  290. package/dist/jsx/code/CodeBlock/index.mjs +5 -6
  291. package/dist/jsx/code/CodeBlock/index.mjs.map +1 -1
  292. package/dist/jsx/code/CodeBlock/index.native.js +4 -5
  293. package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
  294. package/dist/jsx/icons/themed.mjs +3 -2
  295. package/dist/jsx/icons/themed.mjs.map +1 -1
  296. package/dist/jsx/icons/themed.native.js +2 -1
  297. package/dist/jsx/icons/themed.native.js.map +1 -1
  298. package/dist/jsx/knobImmuneExports.mjs +3 -3
  299. package/dist/jsx/knobImmuneExports.native.js +3 -3
  300. package/dist/jsx/layouts/PaneScaffold.mjs +70 -42
  301. package/dist/jsx/layouts/PaneScaffold.mjs.map +1 -1
  302. package/dist/jsx/layouts/PaneScaffold.native.js +75 -48
  303. package/dist/jsx/layouts/PaneScaffold.native.js.map +1 -1
  304. package/dist/jsx/layouts/page.mjs +3 -2
  305. package/dist/jsx/layouts/page.mjs.map +1 -1
  306. package/dist/jsx/layouts/page.native.js +2 -1
  307. package/dist/jsx/layouts/page.native.js.map +1 -1
  308. package/dist/jsx/layouts/sidebar.mjs +2 -4
  309. package/dist/jsx/layouts/sidebar.mjs.map +1 -1
  310. package/dist/jsx/layouts/sidebar.native.js +1 -3
  311. package/dist/jsx/layouts/sidebar.native.js.map +1 -1
  312. package/dist/jsx/surfaces.mjs +1 -33
  313. package/dist/jsx/surfaces.mjs.map +1 -1
  314. package/dist/jsx/surfaces.native.js +0 -32
  315. package/dist/jsx/surfaces.native.js.map +1 -1
  316. package/dist/jsx/views/List.mjs +2 -4
  317. package/dist/jsx/views/List.mjs.map +1 -1
  318. package/dist/jsx/views/List.native.js +1 -3
  319. package/dist/jsx/views/List.native.js.map +1 -1
  320. package/dist/jsx/views/Matrix.mjs +543 -0
  321. package/dist/jsx/views/Matrix.mjs.map +1 -0
  322. package/dist/jsx/views/Matrix.native.js +612 -0
  323. package/dist/jsx/views/Matrix.native.js.map +1 -0
  324. package/dist/jsx/views/ganttMath.mjs +1 -1
  325. package/dist/jsx/views/ganttMath.mjs.map +1 -1
  326. package/dist/jsx/views/ganttMath.native.js +2 -0
  327. package/dist/jsx/views/ganttMath.native.js.map +1 -1
  328. package/dist/jsx/views/index.mjs +4 -2
  329. package/dist/jsx/views/index.mjs.map +1 -1
  330. package/dist/jsx/views/index.native.js +16 -0
  331. package/dist/jsx/views/index.native.js.map +1 -1
  332. package/dist/jsx/views/matrixMath.mjs +122 -0
  333. package/dist/jsx/views/matrixMath.mjs.map +1 -0
  334. package/dist/jsx/views/matrixMath.native.js +229 -0
  335. package/dist/jsx/views/matrixMath.native.js.map +1 -0
  336. package/package.json +10 -10
  337. package/src/Accordion/Accordion.spec.tsx +20 -0
  338. package/src/Accordion/index.tsx +7 -3
  339. package/src/AnimateHeight/index.tsx +143 -142
  340. package/src/Chip/Chip.borderLadder.spec.tsx +54 -0
  341. package/src/Chip/Chip.pressSlop.spec.tsx +57 -0
  342. package/src/Chip/index.tsx +15 -1
  343. package/src/Heading.native.spec.tsx +1 -1
  344. package/src/Separator/index.tsx +3 -3
  345. package/src/SheetModal/SheetModal.detentLayout.spec.tsx +173 -0
  346. package/src/SheetModal/SheetModal.touch.spec.tsx +63 -0
  347. package/src/SheetModal/index.native.tsx +95 -17
  348. package/src/Tabs/Tabs.hydration.spec.tsx +123 -0
  349. package/src/Tabs/index.tsx +3 -2
  350. package/src/Text.spec.tsx +32 -2
  351. package/src/Text.tsx +7 -6
  352. package/src/Toast/Toast.spec.tsx +50 -0
  353. package/src/Tooltip/Tooltip.spec.tsx +55 -2
  354. package/src/TreeSelect/TreeSelect.spec.tsx +15 -15
  355. package/src/TreeSelect/index.tsx +53 -50
  356. package/src/Video/PlayerChrome.tsx +4 -3
  357. package/src/Wheel/Wheel.spec.tsx +4 -4
  358. package/src/adjacency.l1.spec.tsx +175 -0
  359. package/src/code/Code/code.spec.tsx +2 -19
  360. package/src/code/Code/index.tsx +0 -6
  361. package/src/code/CodeBlock/index.tsx +5 -3
  362. package/src/code/highlightedCode.spec.tsx +2 -2
  363. package/src/code/hostProps.spec.tsx +55 -0
  364. package/src/icons/themed.hydration.spec.tsx +60 -0
  365. package/src/icons/themed.tsx +3 -6
  366. package/src/knobImmuneExports.ts +3 -3
  367. package/src/layouts/PaneScaffold.spec.tsx +148 -2
  368. package/src/layouts/PaneScaffold.stories.tsx +33 -1
  369. package/src/layouts/PaneScaffold.tsx +101 -48
  370. package/src/layouts/page.designLaw.spec.tsx +20 -0
  371. package/src/layouts/page.tsx +2 -0
  372. package/src/layouts/sidebar.tsx +2 -3
  373. package/src/overlayElevation.spec.tsx +39 -51
  374. package/src/surfaces.spec.tsx +13 -10
  375. package/src/surfaces.tsx +4 -33
  376. package/src/tamagui.spec.ts +4 -3
  377. package/src/textAccentSecondary.spec.tsx +100 -2
  378. package/src/views/List.tsx +2 -1
  379. package/src/views/Matrix.spec.tsx +339 -0
  380. package/src/views/Matrix.stories.spec.tsx +68 -0
  381. package/src/views/Matrix.stories.tsx +153 -0
  382. package/src/views/Matrix.tsx +738 -0
  383. package/src/views/ganttMath.ts +4 -2
  384. package/src/views/index.ts +30 -0
  385. package/src/views/matrixMath.spec.ts +237 -0
  386. package/src/views/matrixMath.ts +251 -0
  387. package/src/views/views.spec.tsx +3 -1
  388. package/types/Accordion/index.d.ts.map +1 -1
  389. package/types/AnimateHeight/index.d.ts.map +1 -1
  390. package/types/Chip/index.d.ts.map +1 -1
  391. package/types/Separator/index.d.ts +3 -3
  392. package/types/SheetModal/index.native.d.ts.map +1 -1
  393. package/types/Tabs/index.d.ts.map +1 -1
  394. package/types/Text.d.ts.map +1 -1
  395. package/types/TreeSelect/index.d.ts.map +1 -1
  396. package/types/Video/PlayerChrome.d.ts.map +1 -1
  397. package/types/code/Code/index.d.ts.map +1 -1
  398. package/types/code/CodeBlock/index.d.ts.map +1 -1
  399. package/types/icons/themed.d.ts.map +1 -1
  400. package/types/layouts/PaneScaffold.d.ts +14 -1
  401. package/types/layouts/PaneScaffold.d.ts.map +1 -1
  402. package/types/layouts/page.d.ts.map +1 -1
  403. package/types/layouts/sidebar.d.ts.map +1 -1
  404. package/types/surfaces.d.ts.map +1 -1
  405. package/types/views/List.d.ts.map +1 -1
  406. package/types/views/Matrix.d.ts +102 -0
  407. package/types/views/Matrix.d.ts.map +1 -0
  408. package/types/views/ganttMath.d.ts +4 -0
  409. package/types/views/ganttMath.d.ts.map +1 -1
  410. package/types/views/index.d.ts +3 -1
  411. package/types/views/index.d.ts.map +1 -1
  412. package/types/views/matrixMath.d.ts +114 -0
  413. package/types/views/matrixMath.d.ts.map +1 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * MPO-68 / MPO-299: what the native sheet lays out at a settled detent.
3
+ *
4
+ * The iOS simulator run on 965825866 dragged a sheet to a lower detent: it
5
+ * kept its full height and slid it below the screen, or under the keyboard,
6
+ * so the tail of a list inside it was unreachable.
7
+ */
8
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
9
+ import { act, cleanup } from "@testing-library/react";
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
+ import { Animated, PanResponder, type PanResponderCallbacks } from "react-native";
12
+ import { Text } from "tamagui";
13
+ import { resolveSheetDetents } from "./detents";
14
+ import type { SheetModalProps } from "./index";
15
+ import { SheetModal } from "./index.native";
16
+
17
+ const keyboard = vi.hoisted(() => ({ inset: 0 }));
18
+
19
+ vi.mock("react-native-web", async (importOriginal) => ({
20
+ ...(await importOriginal<typeof import("react-native")>()),
21
+ ActionSheetIOS: undefined,
22
+ Modal: ({ children }: { children: import("react").ReactNode }) => children,
23
+ useWindowDimensions: () => ({ width: 390, height: 844, scale: 3, fontScale: 1 }),
24
+ }));
25
+ vi.mock("../shared/useKeyboardInset", () => ({ useKeyboardInset: () => keyboard.inset }));
26
+
27
+ let pan: PanResponderCallbacks;
28
+ let springs: Array<{ toValue: number; done?: (result: { finished: boolean }) => void }>;
29
+
30
+ beforeEach(() => {
31
+ keyboard.inset = 0;
32
+ springs = [];
33
+ vi.spyOn(PanResponder, "create").mockImplementation((config) => {
34
+ pan = config;
35
+ return { panHandlers: {} } as never;
36
+ });
37
+ vi.spyOn(Animated, "spring").mockImplementation((_value, config) => ({
38
+ start: (done) => {
39
+ springs.push({ toValue: config.toValue as number, done });
40
+ },
41
+ stop: vi.fn(),
42
+ reset: vi.fn(),
43
+ }));
44
+ vi.spyOn(Animated, "timing").mockImplementation(() => ({
45
+ start: (done) => done?.({ finished: true }),
46
+ stop: vi.fn(),
47
+ reset: vi.fn(),
48
+ }));
49
+ });
50
+ afterEach(() => {
51
+ cleanup();
52
+ vi.restoreAllMocks();
53
+ });
54
+
55
+ function px(value: string) {
56
+ return Number.parseFloat(value);
57
+ }
58
+
59
+ function open(props: Partial<SheetModalProps> = {}) {
60
+ const view = renderWithProviders(
61
+ <SheetModal open onOpenChange={() => {}} scrollable {...props}>
62
+ <Text>row</Text>
63
+ </SheetModal>,
64
+ );
65
+ const frame = view.getByTestId("sheet-modal-frame");
66
+ const full = px(frame.style.maxHeight);
67
+ return { view, frame, full };
68
+ }
69
+
70
+ /** react-native-web keeps the onLayout handler on the node; call it the way a device layout would. */
71
+ function layout(element: HTMLElement, height: number) {
72
+ const handler = (element as unknown as Record<string, (event: unknown) => void>)
73
+ .__reactLayoutHandler;
74
+ act(() => handler({ nativeEvent: { layout: { x: 0, y: 0, width: 390, height } } }));
75
+ }
76
+
77
+ function drag(dy: number) {
78
+ act(() => {
79
+ pan.onPanResponderGrant?.({} as never, {} as never);
80
+ pan.onPanResponderMove?.({} as never, { dy } as never);
81
+ pan.onPanResponderRelease?.({} as never, { dy, vy: 0 } as never);
82
+ });
83
+ }
84
+
85
+ function settle() {
86
+ const last = springs[springs.length - 1];
87
+ act(() => last.done?.({ finished: true }));
88
+ return last;
89
+ }
90
+
91
+ describe("a native sheet at rest on a lower detent", () => {
92
+ it("is laid out at the detent height, not slid down at full height", () => {
93
+ const { frame, full } = open();
94
+ const [, lower] = resolveSheetDetents({ travel: full, scrollable: true });
95
+ expect(lower).toBeLessThan(full);
96
+ drag(full - lower);
97
+ expect(settle().toValue).toBe(full - lower);
98
+ expect(px(frame.style.maxHeight)).toBe(lower);
99
+ expect(frame.style.transform).toContain("translateY(0px)");
100
+ });
101
+
102
+ it("gives a filled body the detent as its definite height", () => {
103
+ const { frame, full } = open({ fill: true });
104
+ const [, lower] = resolveSheetDetents({ travel: full, scrollable: true });
105
+ drag(full - lower);
106
+ settle();
107
+ expect(px(frame.style.height)).toBe(lower);
108
+ expect(px(frame.style.maxHeight)).toBe(lower);
109
+ });
110
+
111
+ it("ends above the keyboard when the keyboard is up", () => {
112
+ keyboard.inset = 300;
113
+ const { frame, full } = open();
114
+ expect(frame.parentElement?.style.paddingBottom).toBe("300px");
115
+ const [, lower] = resolveSheetDetents({ travel: full, scrollable: true });
116
+ drag(full - lower);
117
+ settle();
118
+ expect(px(frame.style.maxHeight)).toBe(lower);
119
+ expect(frame.style.transform).toContain("translateY(0px)");
120
+ });
121
+
122
+ it("grows back to full height under a still top edge when a drag picks it up", () => {
123
+ const { frame, full } = open();
124
+ const [, lower] = resolveSheetDetents({ travel: full, scrollable: true });
125
+ drag(full - lower);
126
+ settle();
127
+ act(() => pan.onPanResponderGrant?.({} as never, {} as never));
128
+ expect(px(frame.style.maxHeight)).toBe(full);
129
+ expect(frame.style.transform).toContain(`translateY(${full - lower}px)`);
130
+ act(() => pan.onPanResponderRelease?.({} as never, { dy: -(full - lower), vy: 0 } as never));
131
+ expect(settle().toValue).toBe(0);
132
+ expect(px(frame.style.maxHeight)).toBe(full);
133
+ });
134
+
135
+ it("keeps its top edge when the keyboard moved the max height under it", () => {
136
+ const { view, frame, full } = open();
137
+ layout(frame, full);
138
+ const [, lower] = resolveSheetDetents({ travel: full, scrollable: true });
139
+ drag(full - lower);
140
+ settle();
141
+ keyboard.inset = 300;
142
+ view.rerender(
143
+ <SheetModal open onOpenChange={() => {}} scrollable>
144
+ <Text>row</Text>
145
+ </SheetModal>,
146
+ );
147
+ const lifted = 844 - 300 - 48;
148
+ expect(px(frame.style.maxHeight)).toBe(Math.min(lifted, lower));
149
+ act(() => pan.onPanResponderGrant?.({} as never, {} as never));
150
+ expect(px(frame.style.maxHeight)).toBe(lifted);
151
+ expect(frame.style.transform).toContain(`translateY(${lifted - lower}px)`);
152
+ });
153
+
154
+ it("keeps the drag when a scroll inside the sheet asks for the touch", () => {
155
+ open();
156
+ expect(pan.onPanResponderTerminationRequest?.({} as never, {} as never)).toBe(false);
157
+ });
158
+
159
+ it("forgets the rest height when the sheet closes and opens again", () => {
160
+ const { view, frame, full } = open();
161
+ const [, lower] = resolveSheetDetents({ travel: full, scrollable: true });
162
+ drag(full - lower);
163
+ settle();
164
+ const sheet = (next: boolean) => (
165
+ <SheetModal open={next} onOpenChange={() => {}} scrollable>
166
+ <Text>row</Text>
167
+ </SheetModal>
168
+ );
169
+ view.rerender(sheet(false));
170
+ view.rerender(sheet(true));
171
+ expect(px(frame.style.maxHeight)).toBe(full);
172
+ });
173
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * MPO-299: an owned ScrollView inside the native sheet never scrolled on
3
+ * the iOS simulator, while the same swipe scrolled the Combobox sheet.
4
+ *
5
+ * On native, tamagui turns a press or focus-visible pseudo into a JS
6
+ * responder claim (`onStartShouldSetResponder` returning true). A surface
7
+ * holding that claim takes every touch that starts on plain content inside
8
+ * it, and a responder ancestor stops the ScrollView's own pan.
9
+ */
10
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
11
+ import { cleanup } from "@testing-library/react";
12
+ import { afterEach, expect, it, vi } from "vitest";
13
+ import { Text } from "tamagui";
14
+ import { SheetModal } from "./index.native";
15
+
16
+ const stacks = vi.hoisted(() => new Map<string, Record<string, unknown>>());
17
+
18
+ vi.mock("react-native-web", async (importOriginal) => ({
19
+ ...(await importOriginal<typeof import("react-native")>()),
20
+ ActionSheetIOS: undefined,
21
+ Modal: ({ children }: { children: import("react").ReactNode }) => children,
22
+ }));
23
+ vi.mock("tamagui", async (importOriginal) => {
24
+ const actual = await importOriginal<typeof import("tamagui")>();
25
+ const React = await import("react");
26
+ const YStack = React.forwardRef((props: Record<string, unknown>, ref) => {
27
+ if (typeof props.testID === "string") stacks.set(props.testID, props);
28
+ return React.createElement(actual.YStack as never, { ...props, ref });
29
+ });
30
+ return { ...actual, YStack };
31
+ });
32
+
33
+ afterEach(() => {
34
+ cleanup();
35
+ stacks.clear();
36
+ });
37
+
38
+ const claimsResponder = [
39
+ "pressStyle",
40
+ "focusStyle",
41
+ "focusVisibleStyle",
42
+ "onPress",
43
+ "onPressIn",
44
+ "onPressOut",
45
+ "onLongPress",
46
+ ];
47
+
48
+ it("keeps every native sheet stack out of the touch responder", () => {
49
+ for (const fill of [false, true]) {
50
+ renderWithProviders(
51
+ <SheetModal open onOpenChange={() => {}} fill={fill} scrollable>
52
+ <Text>row</Text>
53
+ </SheetModal>,
54
+ );
55
+ for (const id of ["sheet-modal-surface", "sheet-modal-content"]) {
56
+ const stack = stacks.get(id);
57
+ expect(stack, id).toBeDefined();
58
+ for (const key of claimsResponder) expect(stack, `${id} ${key}`).not.toHaveProperty(key);
59
+ }
60
+ cleanup();
61
+ stacks.clear();
62
+ }
63
+ });
@@ -10,7 +10,15 @@
10
10
  * height with a large-detent cap. LC-71: the frame is not a control.
11
11
  */
12
12
 
13
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
+ import {
14
+ useCallback,
15
+ useContext,
16
+ useEffect,
17
+ useLayoutEffect,
18
+ useMemo,
19
+ useRef,
20
+ useState,
21
+ } from "react";
14
22
  import {
15
23
  Animated,
16
24
  Easing,
@@ -23,6 +31,7 @@ import {
23
31
  View as RNView,
24
32
  useWindowDimensions,
25
33
  } from "react-native";
34
+ import { SafeAreaInsetsContext, initialWindowMetrics } from "react-native-safe-area-context";
26
35
  import { useResolvedKnobs } from "@multiplatform.one/theme";
27
36
  import { minSheetHeight, resolveSheetDetents } from "./detents";
28
37
  import { useTranslation } from "../shared/i18n";
@@ -62,6 +71,7 @@ export function SheetModal({
62
71
  const { knobProps } = useResolvedKnobs();
63
72
  const { height: windowHeight } = useWindowDimensions();
64
73
  const keyboardInset = useKeyboardInset();
74
+ const topInset = useContext(SafeAreaInsetsContext)?.top ?? initialWindowMetrics?.insets.top ?? 0;
65
75
  const [backdropArmed, setBackdropArmed] = useState(false);
66
76
  const armTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
67
77
 
@@ -71,11 +81,12 @@ export function SheetModal({
71
81
  if (backdropArmed) close();
72
82
  }, [backdropArmed, close]);
73
83
 
74
- // MPO-64: the sheet ends above the keyboard rather than running under it.
84
+ // MPO-64: the sheet ends above the keyboard rather than running under it,
85
+ // and below the status bar with a strip of backdrop still showing.
75
86
  const available = Math.max(0, windowHeight - keyboardInset);
76
87
  const maxHeight = Math.max(
77
88
  minSheetHeight,
78
- Math.round(Math.min(windowHeight * sheetMaxHeightRatio, available - backdropStrip)),
89
+ Math.round(Math.min(windowHeight * sheetMaxHeightRatio, available - topInset - backdropStrip)),
79
90
  );
80
91
 
81
92
  /*
@@ -127,8 +138,59 @@ export function SheetModal({
127
138
  const presentationRef = useRef(0);
128
139
  const activeRef = useRef(false);
129
140
 
141
+ /*
142
+ * MPO-68 / MPO-299: a sheet at rest below full height is LAID OUT at that
143
+ * height. Translating a full-height frame down pushed its bottom off the
144
+ * screen, or under the keyboard, and the scroll owner inside still measured
145
+ * the full height, so the tail could never be scrolled into view.
146
+ *
147
+ * `frameCap` rides the same Animated.View as `translateY`, so the swap
148
+ * between "full height, translated" and "detent height, at 0" lands in one
149
+ * commit and the top edge does not move. `restHeightRef` is null at full
150
+ * height; a gesture always runs uncapped, in `travel` coordinates.
151
+ */
152
+ const frameCap = useRef(new Animated.Value(maxHeight)).current;
153
+ const maxHeightRef = useRef(maxHeight);
154
+ const restHeightRef = useRef<number | null>(null);
155
+ // The last uncapped layout, and whether it filled the max height. A capped
156
+ // frame measures only its cap, so when the keyboard moves the max height
157
+ // under a resting sheet, its full height is re-derived from these.
158
+ const naturalRef = useRef(0);
159
+ const filledRef = useRef(false);
160
+
161
+ useLayoutEffect(() => {
162
+ maxHeightRef.current = maxHeight;
163
+ const rest = restHeightRef.current;
164
+ frameCap.setValue(rest === null ? maxHeight : Math.min(maxHeight, rest));
165
+ if (rest !== null && naturalRef.current > 0) {
166
+ setMeasuredHeight(filledRef.current ? maxHeight : Math.min(naturalRef.current, maxHeight));
167
+ }
168
+ }, [frameCap, maxHeight]);
169
+
170
+ const restAt = useCallback(
171
+ (height: number) => {
172
+ restHeightRef.current = height;
173
+ frameCap.setValue(Math.min(maxHeightRef.current, height));
174
+ restRef.current = 0;
175
+ translateY.setValue(0);
176
+ },
177
+ [frameCap, translateY],
178
+ );
179
+
180
+ const releaseRest = useCallback(() => {
181
+ const rest = restHeightRef.current;
182
+ if (rest === null) return;
183
+ restHeightRef.current = null;
184
+ const offset = Math.max(0, travelRef.current - Math.min(maxHeightRef.current, rest));
185
+ frameCap.setValue(maxHeightRef.current);
186
+ restRef.current = offset;
187
+ translateY.setValue(offset);
188
+ }, [frameCap, translateY]);
189
+
130
190
  const settleTo = useCallback(
131
- (toValue: number, velocity = 0) => {
191
+ (height: number, velocity = 0) => {
192
+ const presentation = presentationRef.current;
193
+ const toValue = Math.max(0, travelRef.current - height);
132
194
  restRef.current = toValue;
133
195
  Animated.spring(translateY, {
134
196
  toValue,
@@ -141,9 +203,12 @@ export function SheetModal({
141
203
  // Modal does not advance on the New Architecture, and the drag path
142
204
  // already writes this value from JS.
143
205
  useNativeDriver: false,
144
- }).start();
206
+ }).start(({ finished }) => {
207
+ if (!finished || toValue === 0) return;
208
+ if (activeRef.current && presentation === presentationRef.current) restAt(height);
209
+ });
145
210
  },
146
- [translateY],
211
+ [restAt, translateY],
147
212
  );
148
213
 
149
214
  const dismiss = useCallback(
@@ -171,10 +236,15 @@ export function SheetModal({
171
236
  PanResponder.create({
172
237
  onStartShouldSetPanResponder: () => true,
173
238
  onMoveShouldSetPanResponder: (_evt, g) => Math.abs(g.dy) > 2,
239
+ // Lifting a rest cap can scroll the list inside, and a scroll lets
240
+ // any ancestor ScrollView the finger started under claim the touch,
241
+ // ending the drag and blurring a focused input on release.
242
+ onPanResponderTerminationRequest: () => false,
174
243
  onPanResponderGrant: () => {
175
244
  translateY.stopAnimation((value) => {
176
245
  if (typeof value === "number" && Number.isFinite(value)) restRef.current = value;
177
246
  });
247
+ releaseRest();
178
248
  },
179
249
  onPanResponderMove: (_evt, g) => {
180
250
  const next = restRef.current + g.dy;
@@ -199,11 +269,11 @@ export function SheetModal({
199
269
  best = height;
200
270
  }
201
271
  }
202
- settleTo(tvl - best, g.vy);
272
+ settleTo(best, g.vy);
203
273
  },
204
- onPanResponderTerminate: () => settleTo(restRef.current),
274
+ onPanResponderTerminate: () => settleTo(travelRef.current - restRef.current),
205
275
  }),
206
- [dismiss, settleTo, translateY],
276
+ [dismiss, releaseRest, settleTo, translateY],
207
277
  );
208
278
 
209
279
  /*
@@ -246,6 +316,8 @@ export function SheetModal({
246
316
  useEffect(() => {
247
317
  presentationRef.current += 1;
248
318
  activeRef.current = open;
319
+ restHeightRef.current = null;
320
+ frameCap.setValue(maxHeightRef.current);
249
321
  if (open) {
250
322
  enter();
251
323
  } else {
@@ -265,7 +337,7 @@ export function SheetModal({
265
337
  }
266
338
  translateY.stopAnimation();
267
339
  };
268
- }, [open, enter, translateY]);
340
+ }, [open, enter, frameCap, translateY]);
269
341
 
270
342
  return (
271
343
  <Modal
@@ -287,8 +359,12 @@ export function SheetModal({
287
359
  </Pressable>
288
360
  <Animated.View
289
361
  onLayout={(e) => {
362
+ if (restHeightRef.current !== null) return;
290
363
  const h = Math.round(e.nativeEvent.layout.height);
291
- if (h > 0 && h !== measuredHeight) setMeasuredHeight(h);
364
+ if (h <= 0) return;
365
+ naturalRef.current = h;
366
+ filledRef.current = h >= maxHeightRef.current - 1;
367
+ if (h !== measuredHeight) setMeasuredHeight(h);
292
368
  }}
293
369
  testID="sheet-modal-frame"
294
370
  style={{
@@ -296,10 +372,10 @@ export function SheetModal({
296
372
  // the sheet cannot give way to the keyboard.
297
373
  flexShrink: 1,
298
374
  minHeight: 0,
299
- maxHeight,
375
+ maxHeight: frameCap,
300
376
  // The cap is already keyboard- and window-aware; fill only asks
301
377
  // for it as a definite height rather than an upper bound.
302
- ...(fill ? { height: maxHeight } : null),
378
+ ...(fill ? { height: frameCap } : null),
303
379
  transform: [{ translateY }],
304
380
  }}
305
381
  >
@@ -311,13 +387,15 @@ export function SheetModal({
311
387
  borderBottomRightRadius={0}
312
388
  elevation={knobProps.elevation}
313
389
  maxHeight={maxHeight}
314
- {...(fill ? { ...fillSlot, flexShrink: 1 } : null)}
390
+ flexShrink={1}
391
+ minHeight={0}
392
+ {...(fill ? fillSlot : null)}
315
393
  paddingTop="$2"
316
394
  paddingBottom={keyboardInset > 0 ? 8 : bottomInset}
317
395
  overflow="hidden"
318
- outlineWidth={0}
319
- focusStyle={{ outlineWidth: 0 }}
320
- focusVisibleStyle={{ outlineWidth: 0 }}
396
+ // No focus or press pseudo on this surface: on native tamagui
397
+ // turns one into a touch-responder claim, and a responder here
398
+ // takes every drag from the ScrollView inside (MPO-299).
321
399
  accessibilityViewIsModal
322
400
  onAccessibilityEscape={close}
323
401
  >
@@ -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
+ });
@@ -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";
@@ -492,7 +493,7 @@ function TabsRoot({
492
493
  x={activeLayout.x}
493
494
  width={activeLayout.width}
494
495
  data-tabs-indicator="underline"
495
- {...(transition ? { transition } : undefined)}
496
+ {...transitionProps(transition)}
496
497
  />
497
498
  </Theme>
498
499
  ) : (
@@ -508,7 +509,7 @@ function TabsRoot({
508
509
  {...(knobProps.elevation !== undefined
509
510
  ? { elevation: knobProps.elevation }
510
511
  : undefined)}
511
- {...(transition ? { transition } : undefined)}
512
+ {...transitionProps(transition)}
512
513
  />
513
514
  </Theme>
514
515
  )
package/src/Text.spec.tsx CHANGED
@@ -21,10 +21,10 @@
21
21
  */
22
22
  import { cleanup, screen } from "@testing-library/react";
23
23
  import { renderWithProviders } from "@multiplatform.one/test-utils";
24
- import { Preset } from "@multiplatform.one/theme";
24
+ import { Preset, useAccentOnSurface } from "@multiplatform.one/theme";
25
25
  import { XStack } from "tamagui";
26
26
  import { afterEach, describe, expect, it } from "vitest";
27
- import { Text } from "./Text";
27
+ import { Anchor, Text } from "./Text";
28
28
 
29
29
  afterEach(cleanup);
30
30
 
@@ -181,6 +181,36 @@ describe("house Text — fontWeight and bodyFont reach the text node", () => {
181
181
  });
182
182
  });
183
183
 
184
+ describe("house Anchor — link chrome is house Text's, in the accent (MPO-21 G11-A)", () => {
185
+ function AccentProbe({ onInk }: { onInk: (ink: string) => void }) {
186
+ onInk(useAccentOnSurface());
187
+ return null;
188
+ }
189
+
190
+ it("paints the AA-safe accent ink, not tamagui's link colour", () => {
191
+ let ink = "";
192
+ const anchor = renderTextClasses(
193
+ <>
194
+ <AccentProbe onInk={(value) => (ink = value)} />
195
+ <Anchor href="#">{LABEL}</Anchor>
196
+ </>,
197
+ ["_col-"],
198
+ );
199
+ expect(ink).not.toBe("");
200
+ const accented = renderTextClasses(<Anchor color={ink as never}>{LABEL}</Anchor>, ["_col-"]);
201
+ const body = renderTextClasses(<Text>{LABEL}</Text>, ["_col-"]);
202
+ expect(anchor).not.toHaveLength(0);
203
+ expect(anchor).toEqual(accented);
204
+ expect(anchor).not.toEqual(body);
205
+ });
206
+
207
+ it("a consumer colour still ejects (STD-EJECT-LAST)", () => {
208
+ const ejected = renderTextClasses(<Anchor color="$color9">{LABEL}</Anchor>, ["_col-"]);
209
+ const explicit = renderTextClasses(<Text color="$color9">{LABEL}</Text>, ["_col-"]);
210
+ expect(ejected).toEqual(explicit);
211
+ });
212
+ });
213
+
184
214
  // The NATIVE arm lives in ./Text.native.spec.tsx. It used to sit here as a
185
215
  // `vi.resetModules()` + dynamic-import block, but that returned a SPLIT module
186
216
  // graph — a fresh `renderWithProviders` over a stale `useResolvedKnobs` — and
package/src/Text.tsx CHANGED
@@ -24,10 +24,10 @@
24
24
  * 500/600 are not label weights; see tamagui-rendered-reference.md §9)
25
25
  * - textAccent → knobProps.textAccentColor (high $color / medium+low
26
26
  * $color11, the AA legibility floor) on Text/SizableText/Paragraph.
27
- * Anchor keeps tamagui's link ink: link chrome has no owning LC rule yet
28
- * (the former registry row's ADJUDICATION-CANDIDATE stands, tracked in
29
- * the MR), and painting body accent over it would silently restyle every
30
- * link before that adjudication.
27
+ * - link ink → Anchor paints the accent, as the most chromatic step of the
28
+ * theme's accent ramp that still clears AA on both page surfaces
29
+ * (`useAccentOnSurface`). House Text owns link chrome (Clay 2026-09-26,
30
+ * MPO-21 G11-A); a consumer `color` still ejects.
31
31
  *
32
32
  * Default body/label ramp (tamagui-rendered-reference.md §9, the $4 / $true
33
33
  * stop): 14px / 25px / 400. createInterFont's size+10 table yields 14/24;
@@ -38,7 +38,7 @@
38
38
  * Raw primitives stay reachable as TamaguiText / TamaguiSizableText /
39
39
  * TamaguiParagraph / TamaguiAnchor (./tamagui.ts escape hatches, LC-45).
40
40
  */
41
- import { useResolvedKnobs } from "@multiplatform.one/theme";
41
+ import { useAccentOnSurface, useResolvedKnobs } from "@multiplatform.one/theme";
42
42
  import {
43
43
  Anchor as RawAnchor,
44
44
  type AnchorProps,
@@ -81,5 +81,6 @@ export function Paragraph(props: ParagraphProps) {
81
81
 
82
82
  export function Anchor(props: AnchorProps) {
83
83
  const { knobProps } = useResolvedKnobs();
84
- return <RawAnchor {...knobProps.body} {...props} />;
84
+ const linkInk = useAccentOnSurface();
85
+ return <RawAnchor {...knobProps.body} color={linkInk} {...props} />;
85
86
  }