@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
@@ -0,0 +1,175 @@
1
+ /**
2
+ * `00` L1 test 1, the adjacency check (MPO-23, gap ledger GL-41). A Button,
3
+ * an Input, a Select trigger and a ListItem in one `XStack` at the same size
4
+ * token, with only a `gap`, must agree on height and on radius. They differ
5
+ * only in interior padding (`00` L9f). `constraintAudit` checks that each
6
+ * value lands on a token step; nothing checked that siblings agree.
7
+ *
8
+ * The DOM here cannot lay out and does not resolve Tamagui's CSS variables,
9
+ * so each frame's box is read from the atoms it paints (`_h-44px`,
10
+ * `_btlr-t-radius-4`, `_mih-t-size-4`) and resolved through the live token
11
+ * table. That is the same box a browser computes for these frames: none of
12
+ * them grows past its height or min-height with one line of content.
13
+ *
14
+ * The law holds for height at every size stop and for the controls' radius at
15
+ * the default stop. ListItem joins the height check only: it is a flush row
16
+ * (SP-EDGE, radius 0) and never takes the controls' radius (Clay 2026-09-26,
17
+ * MPO-23 ruling 1A). Two disagreements are open and pinned below as expected
18
+ * failures, each naming its culprit. Fixing one makes its `it.fails` go red,
19
+ * which is the signal to turn it into a plain assertion.
20
+ */
21
+
22
+ import { Input, Select } from "@multiplatform.one/forms";
23
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
24
+ import { Preset, type Size } from "@multiplatform.one/theme";
25
+ import { cleanup } from "@testing-library/react";
26
+ import type { ReactElement } from "react";
27
+ import { getTokens } from "tamagui";
28
+ import { afterEach, describe, expect, it } from "vitest";
29
+ import { Button } from "./Button";
30
+ import { ListItem, XStack } from "./tamagui";
31
+
32
+ afterEach(cleanup);
33
+
34
+ type Box = { height: number; radius: number };
35
+ type Row = Record<"Button" | "Input" | "Select trigger" | "ListItem", Box>;
36
+
37
+ const stops = [
38
+ ["small", "$3"],
39
+ ["medium", "$4"],
40
+ ["large", "$5"],
41
+ ] as const satisfies readonly (readonly [Size, string])[];
42
+
43
+ function atom(el: HTMLElement, prop: string) {
44
+ for (const cls of String(el.className || "").split(/\s+/)) {
45
+ if (!cls.startsWith(`_${prop}-`)) continue;
46
+ const value = cls.slice(prop.length + 2);
47
+ if (/^-?\d+(?:\.\d+)?px$|^t-(?:size|radius)-\d+$/.test(value)) return value;
48
+ }
49
+ return undefined;
50
+ }
51
+
52
+ function px(value: string | undefined): number | undefined {
53
+ if (value === undefined) return undefined;
54
+ const literal = value.match(/^(-?\d+(?:\.\d+)?)px$/);
55
+ if (literal) return Number(literal[1]);
56
+ const [, scale, step] = value.match(/^t-(size|radius)-(\d+)$/) ?? [];
57
+ const tokens = getTokens() as unknown as Record<string, Record<string, { val: unknown }>>;
58
+ return Number.parseFloat(String(tokens[scale ?? ""]?.[`$${step}`]?.val));
59
+ }
60
+
61
+ function box(el: HTMLElement | null, name: string): Box {
62
+ if (!el) throw new Error(`${name} frame not rendered`);
63
+ const height = px(atom(el, "h") ?? atom(el, "mih"));
64
+ if (height === undefined) throw new Error(`${name} paints no height`);
65
+ return { height, radius: px(atom(el, "btlr")) ?? 0 };
66
+ }
67
+
68
+ function measure(ui: ReactElement): Row {
69
+ const { container } = renderWithProviders(ui);
70
+ const frame = (selector: string) => container.querySelector<HTMLElement>(selector);
71
+ return {
72
+ Button: box(frame("[data-mp-button-height]"), "Button"),
73
+ Input: box(frame("[data-mp-input-box]"), "Input"),
74
+ "Select trigger": box(frame('[data-testid="select-trigger"]'), "Select trigger"),
75
+ ListItem: box(frame('[data-testid="l1-list-item"]'), "ListItem"),
76
+ };
77
+ }
78
+
79
+ /** The mpo size channel: the size knob, which every house control reads. */
80
+ function atKnob(knob: Size, token: string) {
81
+ return measure(
82
+ <Preset overrides={{ size: knob }}>
83
+ <XStack gap="$2">
84
+ <Button>Save</Button>
85
+ <Input placeholder="Name" />
86
+ <Select options={[{ value: "a", label: "A" }]} placeholder="Pick" />
87
+ <ListItem size={token as "$4"} title="Row" data-testid="l1-list-item" />
88
+ </XStack>
89
+ </Preset>,
90
+ );
91
+ }
92
+
93
+ /** The same token handed to each component directly. */
94
+ function atSizeProp(token: "$3" | "$4" | "$5") {
95
+ return measure(
96
+ <XStack gap="$2">
97
+ <Button size={token}>Save</Button>
98
+ <Input size={token} placeholder="Name" />
99
+ <Select size={token} options={[{ value: "a", label: "A" }]} placeholder="Pick" />
100
+ <ListItem size={token} title="Row" data-testid="l1-list-item" />
101
+ </XStack>,
102
+ );
103
+ }
104
+
105
+ function heights(row: Row) {
106
+ return Object.fromEntries(Object.entries(row).map(([name, b]) => [name, b.height]));
107
+ }
108
+
109
+ function radii(row: Row, names: (keyof Row)[]) {
110
+ return Object.fromEntries(names.map((name) => [name, row[name].radius]));
111
+ }
112
+
113
+ function agreed(values: Record<string, number>) {
114
+ const first = Object.values(values)[0];
115
+ return Object.fromEntries(Object.keys(values).map((name) => [name, first]));
116
+ }
117
+
118
+ const controls: (keyof Row)[] = ["Button", "Input", "Select trigger"];
119
+
120
+ describe("00 L1 adjacency: Button, Input, Select trigger and ListItem at one size token", () => {
121
+ for (const [knob, token] of stops) {
122
+ it(`all four paint one height at size ${knob} (${token})`, () => {
123
+ const row = heights(atKnob(knob, token));
124
+ expect(row).toEqual(agreed(row));
125
+ });
126
+ }
127
+
128
+ it("catches a deliberate violation: a Button ejected to its own height", () => {
129
+ const row = heights(
130
+ measure(
131
+ <XStack gap="$2">
132
+ <Button height={30} sizeRecipeEscape="L1 adjacency fixture">
133
+ Save
134
+ </Button>
135
+ <Input placeholder="Name" />
136
+ <Select options={[{ value: "a", label: "A" }]} placeholder="Pick" />
137
+ <ListItem size="$4" title="Row" data-testid="l1-list-item" />
138
+ </XStack>,
139
+ ),
140
+ );
141
+ expect(row.Button).toBe(30);
142
+ expect(row).not.toEqual(agreed(row));
143
+ });
144
+
145
+ it("Button, Input and Select trigger paint one radius at the default size", () => {
146
+ const row = radii(atKnob("medium", "$4"), controls);
147
+ expect(row).toEqual(agreed(row));
148
+ expect(row.Button).toBeGreaterThan(0);
149
+ });
150
+
151
+ it("ListItem stays a flush row beside them: radius 0, never the controls' radius", () => {
152
+ const row = radii(atKnob("medium", "$4"), [...controls, "ListItem"]);
153
+ expect(row.ListItem).toBe(0);
154
+ expect(row.Button).toBeGreaterThan(0);
155
+ });
156
+
157
+ for (const [knob, token] of stops.filter(([k]) => k !== "medium")) {
158
+ it.fails(`OPEN: Button and Input paint one radius at size ${knob} (Button's radius follows size, Input's only the radius knob)`, () => {
159
+ const row = radii(atKnob(knob, token), controls);
160
+ expect(row).toEqual(agreed(row));
161
+ });
162
+ }
163
+
164
+ it("an explicit size prop agrees with itself at the default token", () => {
165
+ const row = heights(atSizeProp("$4"));
166
+ expect(row).toEqual(agreed(row));
167
+ });
168
+
169
+ for (const token of ["$3", "$5"] as const) {
170
+ it.fails(`OPEN: an explicit size=${token} moves Input and Select trigger with Button (Input paints the knob height, Select ignores size)`, () => {
171
+ const row = heights(atSizeProp(token));
172
+ expect(row).toEqual(agreed(row));
173
+ });
174
+ }
175
+ });
@@ -1,5 +1,5 @@
1
1
  import { renderWithProviders } from "@multiplatform.one/test-utils";
2
- import { describe, expect, it, vi } from "vitest";
2
+ import { describe, expect, it } from "vitest";
3
3
  import { Code, CodeInline } from "./index";
4
4
 
5
5
  describe("Code", () => {
@@ -27,7 +27,7 @@ describe("Code", () => {
27
27
  const codeElement = container.querySelector("code");
28
28
  expect(codeElement).toBeTruthy();
29
29
  expect(codeElement!.getAttribute("data-code-font")).toBe("mono");
30
- // React lowercases an unknown dataSet prop to dataset="[object Object]".
30
+ // An RN prop map on this host would land as dataset="[object Object]".
31
31
  expect(codeElement!.hasAttribute("dataset")).toBe(false);
32
32
  });
33
33
  });
@@ -51,21 +51,4 @@ describe("CodeInline", () => {
51
51
  const codeElement = container.querySelector("code");
52
52
  expect(codeElement).toBeDefined();
53
53
  });
54
-
55
- it("emits data-code-font on web without a dataSet host-prop leak", () => {
56
- const leak = /does not recognize the `dataSet` prop on a DOM element/;
57
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
58
- try {
59
- const { container } = renderWithProviders(<CodeInline>npm install</CodeInline>);
60
- const codeElement = container.querySelector("code");
61
- expect(codeElement).toBeDefined();
62
- expect(codeElement?.getAttribute("data-code-font")).toBe("mono");
63
- const leaks = errorSpy.mock.calls
64
- .map((args) => args.map(String).join(" "))
65
- .filter((message) => leak.test(message));
66
- expect(leaks).toEqual([]);
67
- } finally {
68
- errorSpy.mockRestore();
69
- }
70
- });
71
54
  });
@@ -81,12 +81,6 @@ export const CodeInline = CodeInlineFrame.styleable((props, ref) => {
81
81
  fontFamily="$mono"
82
82
  letterSpacing={0}
83
83
  data-code-font="mono"
84
- // react-native-web reads dataSet; React-DOM warns on the camel host
85
- // prop. Keep the kebab attribute on every target; spread dataSet
86
- // only when TAMAGUI_TARGET is native.
87
- {...(process.env.TAMAGUI_TARGET === "native"
88
- ? ({ dataSet: { codeFont: "mono" } } as object)
89
- : {})}
90
84
  />
91
85
  );
92
86
  });
@@ -10,10 +10,11 @@ import { LinearGradient } from "@tamagui/linear-gradient";
10
10
  import { ClipboardIcon } from "@phosphor-icons/react";
11
11
  import {
12
12
  MIN_PRESS_TARGET,
13
- isTouchSurface,
13
+ useTouchSurface,
14
14
  pressTargetHitSlop,
15
15
  sizeRecipeForToken,
16
16
  useResolvedKnobs,
17
+ transitionProps,
17
18
  } from "@multiplatform.one/theme";
18
19
  import { useEffect, useRef, useState } from "react";
19
20
  import { useTranslation } from "../../shared/i18n";
@@ -75,6 +76,7 @@ export function CodeBlock({
75
76
  const { region, copy } = useCopyFeedback();
76
77
  const { t } = useTranslation();
77
78
  const { knobProps } = useResolvedKnobs();
79
+ const touch = useTouchSurface();
78
80
  // Content type scale follows the size knob (T-VALUE via sizeToken, LC-18);
79
81
  // an explicit size prop ejects.
80
82
  const codeSize = (size ?? knobProps.sizeToken) as any;
@@ -126,7 +128,7 @@ export function CodeBlock({
126
128
  const activateCopy = () => {
127
129
  void copy(codeText);
128
130
  };
129
- const capSquare = sizeRecipeForToken(String(codeSize), { touch: isTouchSurface() }).height;
131
+ const capSquare = sizeRecipeForToken(String(codeSize), { touch }).height;
130
132
 
131
133
  return (
132
134
  <YStack position="relative" marginBottom="$4">
@@ -134,11 +136,11 @@ export function CodeBlock({
134
136
  <YStack
135
137
  ref={clampRef}
136
138
  position="relative"
139
+ {...transitionProps(transition)}
137
140
  {...(clamped && {
138
141
  maxHeight: expandTarget ?? clampHeight,
139
142
  overflow: "hidden" as const,
140
143
  ...knobProps.borderRadius,
141
- ...(transition ? { transition } : {}),
142
144
  })}
143
145
  >
144
146
  {isCutoff && expandTarget == null && (
@@ -125,8 +125,8 @@ describe("HighlightedCode", () => {
125
125
  // The actual LC-07 pin: bodyFont serif / fontWeight bold must not reach
126
126
  // code text, so the line keeps the mono family class.
127
127
  expect(line!.className).toContain("font_mono");
128
- // React does not recognise `dataSet`; leaving it on the line logged an
129
- // unknown-prop error and wrote dataset="[object Object]" on every node.
128
+ // React does not recognise the RN prop map; leaving it on the line logged
129
+ // an unknown-prop error and wrote dataset="[object Object]" on every node.
130
130
  expect(container.querySelectorAll("[dataset]")).toHaveLength(0);
131
131
  });
132
132
  });
@@ -0,0 +1,55 @@
1
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
2
+ import type { ReactElement } from "react";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { Code, CodeInline } from "./Code";
5
+ import type { HighlightLine } from "./Highlighter";
6
+ import { HighlightedCode } from "./HighlightedCode";
7
+
8
+ const tokens: HighlightLine[] = [[{ content: "const x = 1;" }]];
9
+
10
+ const cases: [string, ReactElement][] = [
11
+ ["Code", <Code>const x = 1;</Code>],
12
+ ["CodeInline", <CodeInline>npm install</CodeInline>],
13
+ ["HighlightedCode", <HighlightedCode tokens={tokens} />],
14
+ ];
15
+
16
+ function hostProps(el: Element): Record<string, unknown> {
17
+ const key = Object.keys(el).find((k) => k.startsWith("__reactProps$"));
18
+ return key ? ((el as unknown as Record<string, Record<string, unknown>>)[key] ?? {}) : {};
19
+ }
20
+
21
+ function formatConsoleCall(args: unknown[]): string {
22
+ const [format = "", ...substitutions] = args.map(String);
23
+ let next = 0;
24
+ return format.replace(/%s/g, () => substitutions[next++] ?? "");
25
+ }
26
+
27
+ afterEach(() => {
28
+ vi.unstubAllEnvs();
29
+ });
30
+
31
+ describe.each(["web", "native"])("code family host props, TAMAGUI_TARGET=%s", (target) => {
32
+ it.each(cases)("%s hands the code-font node no dataset prop map", (_name, node) => {
33
+ vi.stubEnv("TAMAGUI_TARGET", target);
34
+ const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
35
+ try {
36
+ const { container } = renderWithProviders(node);
37
+ const hosts = Array.from(container.querySelectorAll("[data-code-font]"));
38
+ expect(hosts.length).toBeGreaterThan(0);
39
+ for (const host of hosts) {
40
+ const props = hostProps(host);
41
+ expect(props["data-code-font"]).toBe("mono");
42
+ expect(Object.keys(props).filter((key) => key.toLowerCase() === "dataset")).toEqual([]);
43
+ expect(host.hasAttribute("dataset")).toBe(false);
44
+ }
45
+ // React warns once per prop name per process, so the console alone
46
+ // cannot prove a later case clean; the props check above can.
47
+ const unknownProps = errorSpy.mock.calls
48
+ .map(formatConsoleCall)
49
+ .filter((message) => /does not recognize the `\w+` prop on a DOM element/.test(message));
50
+ expect(unknownProps).toEqual([]);
51
+ } finally {
52
+ errorSpy.mockRestore();
53
+ }
54
+ });
55
+ });
@@ -0,0 +1,60 @@
1
+ // @vitest-environment jsdom
2
+ import { act } from "react";
3
+ import { renderToString } from "react-dom/server";
4
+ import { hydrateRoot } from "react-dom/client";
5
+ import { TestProviders } from "@multiplatform.one/test-utils";
6
+ import { sizeRecipeForToken } from "@multiplatform.one/theme";
7
+ import { describe, expect, it, vi } from "vitest";
8
+ import { themed } from "./themed";
9
+
10
+ const host = vi.hoisted(() => ({ touch: false }));
11
+ vi.mock("@multiplatform.one/platform", async (importOriginal) => ({
12
+ ...(await importOriginal<typeof import("@multiplatform.one/platform")>()),
13
+ isWeb: true,
14
+ isTouchable: false,
15
+ get isWebTouchable() {
16
+ return host.touch;
17
+ },
18
+ }));
19
+
20
+ const Icon = themed(({ size }: { size?: string | number }) => <svg width={size} height={size} />);
21
+ function Fixture() {
22
+ return (
23
+ <TestProviders>
24
+ <Icon size="$4" />
25
+ </TestProviders>
26
+ );
27
+ }
28
+
29
+ describe("token icon hydration", () => {
30
+ it("preserves the server SVG before adopting touch sizing", async () => {
31
+ host.touch = false;
32
+ const container = document.createElement("div");
33
+ container.innerHTML = renderToString(<Fixture />);
34
+ document.body.append(container);
35
+ const icon = container.querySelector("svg");
36
+ const errors: unknown[] = [];
37
+ const spy = vi.spyOn(console, "error").mockImplementation((...args) => errors.push(args));
38
+ host.touch = true;
39
+ let root: ReturnType<typeof hydrateRoot> | undefined;
40
+ try {
41
+ await act(async () => {
42
+ root = hydrateRoot(container, <Fixture />, {
43
+ onRecoverableError: (error) => errors.push(error),
44
+ });
45
+ });
46
+ expect(container.querySelector("svg")).toBe(icon);
47
+ expect(icon?.getAttribute("width")).toBe(
48
+ String(sizeRecipeForToken("$4", { touch: true }).iconSize),
49
+ );
50
+ expect(
51
+ errors.filter((error) => /hydration|hydrated|didn't match/i.test(String(error))),
52
+ ).toEqual([]);
53
+ } finally {
54
+ await act(async () => root?.unmount());
55
+ spy.mockRestore();
56
+ container.remove();
57
+ host.touch = false;
58
+ }
59
+ });
60
+ });
@@ -1,4 +1,4 @@
1
- import { isTouchSurface, sizeRecipeForToken } from "@multiplatform.one/theme";
1
+ import { useTouchSurface, sizeRecipeForToken } from "@multiplatform.one/theme";
2
2
  import type { FC } from "react";
3
3
  import { getVariable, useProps, useTheme } from "tamagui";
4
4
 
@@ -9,6 +9,7 @@ import { getVariable, useProps, useTheme } from "tamagui";
9
9
  */
10
10
  export function themed<A extends FC>(Component: A) {
11
11
  return ((props: any) => {
12
+ const touch = useTouchSurface();
12
13
  const { color, disableTheme, size } = useProps(props);
13
14
  const theme = useTheme();
14
15
  const fromTheme =
@@ -19,11 +20,7 @@ export function themed<A extends FC>(Component: A) {
19
20
  <Component
20
21
  {...props}
21
22
  color={colorValue}
22
- size={
23
- typeof size === "string"
24
- ? sizeRecipeForToken(size, { touch: isTouchSurface() }).iconSize
25
- : size
26
- }
23
+ size={typeof size === "string" ? sizeRecipeForToken(size, { touch }).iconSize : size}
27
24
  />
28
25
  );
29
26
  }) as A;
@@ -35,9 +35,9 @@ export const knobImmuneExports: Readonly<Record<string, string>> = {
35
35
  ScrollView: "scroll container lego; no opinionated chrome, fragments ride the content",
36
36
  // Separator left the registry 2026-08-28 (MPO-47): bare `Separator` is now
37
37
  // a house implementation (./Separator — painted 1px line), so it no longer
38
- // qualifies for a row (house implementations ARE the knob story). The
39
- // weight-knob question stays an ADJUDICATION-CANDIDATE on the component
40
- // header (MPO-21 G10 / G0-clay R9).
38
+ // qualifies for a row (house implementations ARE the knob story). Its
39
+ // weight has no knob by ruling (MPO-21 G10-A, 2026-09-26), and Circle and
40
+ // Square stay knob-immune lego with no per-role radius (MPO-21 R-B).
41
41
  // MPO-21 G1 — Fold's substrate. Logic shell only; Accordion (and Fold)
42
42
  // own the painted chrome. Restored with the three below after MPO-21 R9 G8
43
43
  // (the Adapt drop) rewrote this file from a pre-MPO-21 copy and took them
@@ -1,6 +1,6 @@
1
- import { cleanup, screen } from "@testing-library/react";
1
+ import { cleanup, fireEvent, screen } from "@testing-library/react";
2
2
  import { renderWithProviders } from "@multiplatform.one/test-utils";
3
- import { afterEach, describe, expect, it } from "vitest";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
4
  import { Paragraph, YStack } from "tamagui";
5
5
  import {
6
6
  FeedLayout,
@@ -8,6 +8,7 @@ import {
8
8
  PaneScaffold,
9
9
  ReadingWidth,
10
10
  SupportingPaneLayout,
11
+ type PaneScaffoldProps,
11
12
  } from "./PaneScaffold";
12
13
  import * as Layouts from "./index";
13
14
 
@@ -277,3 +278,148 @@ describe("ReadingWidth", () => {
277
278
  expect(el).toHaveStyle({ maxWidth: "70ch" });
278
279
  });
279
280
  });
281
+
282
+ // MPO-253: the seam runs on one axis. A vertical scaffold docks secondary
283
+ // under primary with the sash, clamps and fraction contract the side-by-side
284
+ // split already has, turned on its side.
285
+ describe("PaneScaffold orientation (MPO-253)", () => {
286
+ function renderSplit(props: Partial<PaneScaffoldProps> = {}) {
287
+ renderWithProviders(
288
+ <PaneScaffold
289
+ sizeClass="expanded"
290
+ primary={<Paragraph>Body</Paragraph>}
291
+ secondary={<Paragraph>Editor</Paragraph>}
292
+ {...props}
293
+ />,
294
+ );
295
+ return screen.getByTestId("pane-sash");
296
+ }
297
+
298
+ function boundRow(width: number, height: number) {
299
+ const row = screen.getByTestId("pane-primary").parentElement as HTMLElement;
300
+ row.getBoundingClientRect = () =>
301
+ ({
302
+ x: 0,
303
+ y: 0,
304
+ top: 0,
305
+ left: 0,
306
+ right: width,
307
+ bottom: height,
308
+ width,
309
+ height,
310
+ toJSON: () => ({}),
311
+ }) as DOMRect;
312
+ }
313
+
314
+ it("docks secondary under primary behind a horizontal sash", () => {
315
+ const sash = renderSplit({ orientation: "vertical" });
316
+ const root = screen.getByTestId("pane-scaffold");
317
+ expect(root).toHaveAttribute("data-orientation", "vertical");
318
+ expect(root).toHaveAttribute("data-pane-layout", "split");
319
+ expect(root).toHaveAttribute("data-sash", "true");
320
+ expect(sash).toHaveAttribute("aria-orientation", "horizontal");
321
+ const primary = screen.getByTestId("pane-primary");
322
+ const secondary = screen.getByTestId("pane-secondary");
323
+ expect(
324
+ primary.compareDocumentPosition(secondary) & Node.DOCUMENT_POSITION_FOLLOWING,
325
+ ).toBeTruthy();
326
+ });
327
+
328
+ it("keeps the side-by-side sash vertical by default", () => {
329
+ const sash = renderSplit();
330
+ expect(screen.getByTestId("pane-scaffold")).toHaveAttribute("data-orientation", "horizontal");
331
+ expect(sash).toHaveAttribute("aria-orientation", "vertical");
332
+ });
333
+
334
+ it.each([
335
+ ["horizontal", "ArrowRight", "ArrowLeft"],
336
+ ["vertical", "ArrowDown", "ArrowUp"],
337
+ ] as const)("%s: arrows step the fraction along its own axis", (orientation, forward, back) => {
338
+ const sash = renderSplit({ orientation });
339
+ expect(sash).toHaveAttribute("aria-valuenow", "50");
340
+ fireEvent.keyDown(sash, { key: forward });
341
+ expect(sash).toHaveAttribute("aria-valuenow", "52");
342
+ fireEvent.keyDown(sash, { key: forward, shiftKey: true });
343
+ expect(sash).toHaveAttribute("aria-valuenow", "62");
344
+ fireEvent.keyDown(sash, { key: back });
345
+ expect(sash).toHaveAttribute("aria-valuenow", "60");
346
+ fireEvent.keyDown(sash, { key: "Enter" });
347
+ expect(sash).toHaveAttribute("aria-valuenow", "50");
348
+ });
349
+
350
+ it("vertical ignores the horizontal arrows", () => {
351
+ const sash = renderSplit({ orientation: "vertical" });
352
+ fireEvent.keyDown(sash, { key: "ArrowRight" });
353
+ fireEvent.keyDown(sash, { key: "ArrowLeft" });
354
+ expect(sash).toHaveAttribute("aria-valuenow", "50");
355
+ });
356
+
357
+ it("vertical honours the clamps: the default is clamped, Home / End land on min / max", () => {
358
+ const sash = renderSplit({
359
+ orientation: "vertical",
360
+ paneFlex: [9, 1],
361
+ minPaneFraction: 0.3,
362
+ maxPaneFraction: 0.7,
363
+ });
364
+ expect(sash).toHaveAttribute("aria-valuemin", "30");
365
+ expect(sash).toHaveAttribute("aria-valuemax", "70");
366
+ expect(sash).toHaveAttribute("aria-valuenow", "70");
367
+ fireEvent.keyDown(sash, { key: "Home" });
368
+ expect(sash).toHaveAttribute("aria-valuenow", "30");
369
+ fireEvent.keyDown(sash, { key: "End" });
370
+ expect(sash).toHaveAttribute("aria-valuenow", "70");
371
+ });
372
+
373
+ it("vertical is controlled through primaryFraction / onPrimaryFractionChange", () => {
374
+ const onChange = vi.fn();
375
+ const sash = renderSplit({
376
+ orientation: "vertical",
377
+ primaryFraction: 0.4,
378
+ onPrimaryFractionChange: onChange,
379
+ });
380
+ expect(sash).toHaveAttribute("aria-valuenow", "40");
381
+ fireEvent.keyDown(sash, { key: "ArrowDown" });
382
+ expect(onChange).toHaveBeenCalledTimes(1);
383
+ expect(onChange.mock.calls[0][0]).toBeCloseTo(0.42);
384
+ expect(sash).toHaveAttribute("aria-valuenow", "40");
385
+ });
386
+
387
+ it.each([
388
+ ["horizontal", { clientX: 200, clientY: 0 }, { clientX: 280, clientY: 0 }],
389
+ ["vertical", { clientX: 0, clientY: 100 }, { clientX: 0, clientY: 140 }],
390
+ ] as const)(
391
+ "%s: dragging the seam moves the fraction by the distance over the row",
392
+ (orientation, down, move) => {
393
+ const sash = renderSplit({ orientation });
394
+ boundRow(800, 400);
395
+ fireEvent.pointerDown(sash, { button: 0, ...down });
396
+ fireEvent.pointerMove(window, move);
397
+ expect(sash).toHaveAttribute("aria-valuenow", "60");
398
+ fireEvent.pointerUp(window, move);
399
+ fireEvent.pointerMove(window, { clientX: 0, clientY: 0 });
400
+ expect(sash).toHaveAttribute("aria-valuenow", "60");
401
+ },
402
+ );
403
+
404
+ it("vertical stacks like horizontal below the two-pane budget", () => {
405
+ renderWithProviders(
406
+ <PaneScaffold
407
+ sizeClass="compact"
408
+ orientation="vertical"
409
+ primary={<Paragraph>Body</Paragraph>}
410
+ secondary={<Paragraph>Editor</Paragraph>}
411
+ />,
412
+ );
413
+ const root = screen.getByTestId("pane-scaffold");
414
+ expect(root).toHaveAttribute("data-pane-layout", "stack");
415
+ expect(root).toHaveAttribute("data-orientation", "vertical");
416
+ expect(screen.queryByTestId("pane-sash")).not.toBeInTheDocument();
417
+ expect(screen.getByText("Editor")).toBeInTheDocument();
418
+ });
419
+
420
+ it("vertical keeps the seam on a narrow window when maxPanes ejects", () => {
421
+ const sash = renderSplit({ orientation: "vertical", sizeClass: "compact", maxPanes: 2 });
422
+ expect(screen.getByTestId("pane-scaffold")).toHaveAttribute("data-pane-layout", "split");
423
+ expect(sash).toHaveAttribute("aria-orientation", "horizontal");
424
+ });
425
+ });
@@ -110,7 +110,7 @@ const meta: Meta<typeof PaneScaffold> = {
110
110
  docs: {
111
111
  description: {
112
112
  component:
113
- 'Adaptive TWO-pane shell (DG-LAY-01/02) — `primary` and `secondary`, no more. Size classes: compact <640, medium ≥640, expanded ≥860 (FloatingPanel), large ≥1024, xl ≥1280. Two panes from expanded up unless `maxPanes` ejects. Side-by-side uses a hairline sash (Linear / VS Code / Polaris Frame); pass `paneGap` to eject to spaced columns. Below expanded the panes stack and the sash goes away, so nothing is hidden on a phone; `narrowLayout="swap"` (what `activePane` opts into) is the list-detail exception. Three regions come from nesting a scaffold inside a pane — see the Nested three-region story; the size class rides down, so the inner scaffold stacks with the outer.',
113
+ 'Adaptive TWO-pane shell (DG-LAY-01/02) — `primary` and `secondary`, no more. Size classes: compact <640, medium ≥640, expanded ≥860 (FloatingPanel), large ≥1024, xl ≥1280. Two panes from expanded up unless `maxPanes` ejects. Side-by-side uses a hairline sash (Linear / VS Code / Polaris Frame); pass `paneGap` to eject to spaced columns. Below expanded the panes stack and the sash goes away, so nothing is hidden on a phone; `narrowLayout="swap"` (what `activePane` opts into) is the list-detail exception. Three regions come from nesting a scaffold inside a pane — see the Nested three-region story; the size class rides down, so the inner scaffold stacks with the outer. `orientation="vertical"` docks `secondary` under `primary` with the same sash turned on its side (↑/↓, Home/End, double-press resets) — see the Docked editor story.',
114
114
  },
115
115
  },
116
116
  },
@@ -119,6 +119,7 @@ const meta: Meta<typeof PaneScaffold> = {
119
119
  control: "select",
120
120
  options: ["compact", "medium", "expanded", "large", "xl"],
121
121
  },
122
+ orientation: { control: "radio", options: ["horizontal", "vertical"] },
122
123
  activePane: { control: "radio", options: ["primary", "secondary"] },
123
124
  narrowLayout: { control: "radio", options: ["stack", "swap"] },
124
125
  maxPanes: { control: "radio", options: [1, 2, undefined] },
@@ -202,6 +203,37 @@ export const NestedThreeRegion: Story = {
202
203
  ),
203
204
  };
204
205
 
206
+ export const DockedEditor: Story = {
207
+ name: "Docked editor (vertical)",
208
+ args: { sizeClass: "xl", orientation: "vertical" },
209
+ parameters: {
210
+ docs: {
211
+ description: {
212
+ story:
213
+ '`orientation="vertical"` runs the seam across the page: the body above, an editor docked below, resized by dragging the seam (↑/↓ from the keyboard, Home/End for the clamps, double-press resets). The body here is itself a horizontal scaffold, which is how a strip editor sits under a multi-pane page. A vertical scaffold splits the height its parent gives it, so the frame is bounded.',
214
+ },
215
+ },
216
+ },
217
+ render: (args) => (
218
+ <YStack height={520} padding="$4">
219
+ <SplitFrame height={480}>
220
+ <PaneScaffold
221
+ {...args}
222
+ paneFlex={[3, 1]}
223
+ primary={
224
+ <PaneScaffold
225
+ paneFlex={[2, 1]}
226
+ primary={<PaneFill label="Page body" hint="Drag the seam below · ↑/↓ resize" />}
227
+ secondary={<PaneFill label="Inspector" muted />}
228
+ />
229
+ }
230
+ secondary={<PaneFill label="Editor" hint="Docked under the page" muted />}
231
+ />
232
+ </SplitFrame>
233
+ </YStack>
234
+ ),
235
+ };
236
+
205
237
  export const CompactActiveSecondary: Story = {
206
238
  name: "Compact (swap)",
207
239
  args: { sizeClass: "compact", activePane: "secondary" },