@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,738 @@
1
+ import { componentColors, sectionHeading } from "../componentColors";
2
+ import { AsyncBoundary } from "../layouts/AsyncBoundary";
3
+ import { Skeleton } from "../Skeleton";
4
+ import { ViewSwitcher } from "../layouts/ViewSwitcher";
5
+ import { withInterp } from "../shared/t";
6
+ import {
7
+ Intent,
8
+ MIN_PRESS_TARGET,
9
+ ensureKeyboardModalityTracking,
10
+ formatNumber,
11
+ hairline,
12
+ hairlineWidth,
13
+ keyboardFocusRingProps,
14
+ useReadableTextOn,
15
+ useResolvedKnobs,
16
+ wasKeyboardFocus,
17
+ } from "@multiplatform.one/theme";
18
+ import React, { useCallback, useMemo, useRef, useState, type ReactNode } from "react";
19
+ import { useTranslation } from "../shared/i18n";
20
+ import { ScrollView as RNScrollView } from "react-native";
21
+ import { ScrollView, Text, XStack, YStack, isWeb, type YStackProps } from "tamagui";
22
+ import { formatRangeLabel } from "./ganttMath";
23
+ import {
24
+ alignMatrixRange,
25
+ matrixCellIntensity,
26
+ matrixCellKey,
27
+ computeMatrixRange,
28
+ generateBuckets,
29
+ matrixMaxValue,
30
+ summarizeMatrixCells,
31
+ type MatrixBucket,
32
+ type MatrixBucketRule,
33
+ type MatrixCell,
34
+ type MatrixCellSummary,
35
+ type MatrixIntent,
36
+ type MatrixRange,
37
+ } from "./matrixMath";
38
+
39
+ // Same split as the Gantt: the grid canvas needs real scroll events to drive
40
+ // the pinned axis follower, and Tamagui's styled ScrollView does not deliver
41
+ // onScroll on native.
42
+ const CanvasScroller = (isWeb ? ScrollView : RNScrollView) as typeof ScrollView;
43
+
44
+ /**
45
+ * Inline-END hairline rule for the label column edge (SB-M-1215 pattern
46
+ * shared with Calendar/Gantt: tamagui's RN `borderEndWidth` does not compile
47
+ * to working CSS on web).
48
+ */
49
+ const hairlineEnd = isWeb
50
+ ? {
51
+ borderInlineEndWidth: hairlineWidth,
52
+ borderInlineEndStyle: "solid" as any,
53
+ className: "mp-hairline-ie",
54
+ }
55
+ : { borderEndWidth: hairlineWidth };
56
+
57
+ /** Lowest paint strength a present cell takes, so a zero stays visible. */
58
+ const FILL_FLOOR = 0.14;
59
+ /** Intensity from which the cell text switches to on-fill ink. */
60
+ const ON_FILL_INK_FROM = 0.55;
61
+ const LEGEND_STEPS = [0, 0.25, 0.5, 0.75, 1] as const;
62
+
63
+ /** One subject row of the matrix. */
64
+ export interface MatrixRow {
65
+ /** Unique row identifier; `MatrixCell.rowId` points at it. */
66
+ id: string;
67
+ /** Row header text and the subject half of every cell's accessible name. */
68
+ label: string;
69
+ /** Any additional data */
70
+ [key: string]: unknown;
71
+ }
72
+
73
+ /** Which intersection a press was about. `summary` is undefined for an empty bucket. */
74
+ export interface MatrixCellRef {
75
+ row: MatrixRow;
76
+ bucket: MatrixBucket;
77
+ summary: MatrixCellSummary | undefined;
78
+ }
79
+
80
+ /** A legend entry beyond the built-in ramp: a solid swatch with a label. */
81
+ export interface MatrixLegendItem {
82
+ label: string;
83
+ intent?: MatrixIntent;
84
+ color?: string;
85
+ }
86
+
87
+ export interface MatrixViewProps extends Omit<YStackProps, "children"> {
88
+ /** Subjects, one row each, in display order. */
89
+ rows: MatrixRow[];
90
+ /** Data points; the view folds them into (row, bucket) intersections. */
91
+ cells: MatrixCell[];
92
+ /** Initial bucket rule the column axis is generated from (default: 'month'). */
93
+ rule?: MatrixBucketRule;
94
+ /** Called when the rule changes via the switcher. */
95
+ onRuleChange?: (rule: MatrixBucketRule) => void;
96
+ /**
97
+ * Explicit window, snapped to bucket boundaries. Omitted, the window is
98
+ * exactly the buckets the data spans (no padding).
99
+ */
100
+ range?: MatrixRange;
101
+ /** First day of week for week alignment: 0 = Sunday, 1 = Monday (default: 0) */
102
+ firstDayOfWeek?: 0 | 1;
103
+ /** Called when a cell is pressed (pointer, or Enter/Space on a focused cell). Empty buckets press too. */
104
+ onCellPress?: (ref: MatrixCellRef) => void;
105
+ /** Visible cell text (default: the summed value, else the point count). */
106
+ formatCell?: (summary: MatrixCellSummary) => string;
107
+ /** Whether data is loading (skeleton shows only while there are no rows) */
108
+ isLoading?: boolean;
109
+ /** Failed load (DG-ST-01 / W9). Wins over the empty state. */
110
+ error?: boolean | string | Error | ReactNode | null;
111
+ /** Retry handler for the default error UI. */
112
+ onRetry?: () => void;
113
+ /** Title for the default empty state */
114
+ emptyTitle?: string;
115
+ /** Description for the default empty state */
116
+ emptyDescription?: string;
117
+ /** Height of the grid in pixels (default steps with the size knob) */
118
+ height?: number;
119
+ /** Width of the subject column in pixels (default: 180) */
120
+ labelWidth?: number;
121
+ /** Width of one bucket column in pixels (default steps with the size knob; never below the press target) */
122
+ columnWidth?: number;
123
+ /** Header text of the subject column (default: "Subject"). */
124
+ subjectLabel?: string;
125
+ /** Show the day/week/month rule switcher (default: true) */
126
+ showRuleSwitcher?: boolean;
127
+ /** Show the legend strip under the grid (default: true) */
128
+ showLegend?: boolean;
129
+ /** Extra legend entries after the built-in empty + ramp swatches. */
130
+ legendItems?: MatrixLegendItem[];
131
+ }
132
+
133
+ // ── Fill resolution ───────────────────────────────────────────
134
+
135
+ /**
136
+ * Semantic intents ride their hue sub-theme (`<Intent name>` → `$color9`,
137
+ * LC-05); the default identity stays on `$accentBackground`. Explicit
138
+ * colors pass through untouched (Axiom 11 / LC-27).
139
+ */
140
+ function CellIntentWrap({ intent, children }: { intent?: MatrixIntent; children: ReactNode }) {
141
+ if (!intent || intent === "accent") return <>{children}</>;
142
+ return <Intent name={intent}>{children}</Intent>;
143
+ }
144
+
145
+ function resolveFill(color: string | undefined, intent: MatrixIntent | undefined): string {
146
+ return color ?? (intent && intent !== "accent" ? "$color9" : "$accentBackground");
147
+ }
148
+
149
+ function fillOpacity(intensity: number): number {
150
+ return FILL_FLOOR + (1 - FILL_FLOOR) * intensity;
151
+ }
152
+
153
+ // ── Cell ──────────────────────────────────────────────────────
154
+
155
+ interface MatrixCellBoxProps {
156
+ row: MatrixRow;
157
+ bucket: MatrixBucket;
158
+ summary: MatrixCellSummary | undefined;
159
+ intensity: number;
160
+ fill: string;
161
+ text: string;
162
+ name: string;
163
+ width: number;
164
+ height: number;
165
+ lastColumn: boolean;
166
+ onPress?: () => void;
167
+ }
168
+
169
+ function MatrixCellBox({
170
+ row,
171
+ bucket,
172
+ summary,
173
+ intensity,
174
+ fill,
175
+ text,
176
+ name,
177
+ width,
178
+ height,
179
+ lastColumn,
180
+ onPress,
181
+ }: MatrixCellBoxProps) {
182
+ const { knobProps } = useResolvedKnobs();
183
+ const readable = useReadableTextOn(fill);
184
+ const [keyboardRing, setKeyboardRing] = useState(false);
185
+ const empty = summary === undefined;
186
+ const state = summary === undefined ? "empty" : summary.value === 0 ? "zero" : "value";
187
+ const ink = !empty && intensity >= ON_FILL_INK_FROM && readable ? readable : "$color";
188
+ const pressable = onPress !== undefined;
189
+ return (
190
+ <YStack
191
+ width={width}
192
+ height={height}
193
+ alignItems="center"
194
+ justifyContent="center"
195
+ position="relative"
196
+ overflow="hidden"
197
+ {...(lastColumn ? null : hairlineEnd)}
198
+ borderColor={componentColors.divider}
199
+ cursor={pressable ? "pointer" : undefined}
200
+ hoverStyle={pressable ? { backgroundColor: componentColors.interactive.hover } : undefined}
201
+ pressStyle={pressable ? { opacity: 0.8 } : undefined}
202
+ role={pressable ? "button" : undefined}
203
+ tabIndex={pressable ? 0 : undefined}
204
+ aria-label={name}
205
+ // LC-71: one ring on the cell (the perceived control), keyboard-only.
206
+ {...(keyboardRing ? keyboardFocusRingProps : { outlineWidth: 0 })}
207
+ onFocus={pressable ? () => setKeyboardRing(wasKeyboardFocus()) : undefined}
208
+ onBlur={pressable ? () => setKeyboardRing(false) : undefined}
209
+ onPress={onPress}
210
+ onKeyDown={
211
+ pressable
212
+ ? (((ke: any) => {
213
+ if (ke.key !== "Enter" && ke.key !== " ") return;
214
+ ke.preventDefault?.();
215
+ onPress();
216
+ }) as any)
217
+ : undefined
218
+ }
219
+ {...{
220
+ "data-matrix-cell": `${row.id}:${bucket.key}`,
221
+ "data-matrix-cell-state": state,
222
+ ...(summary?.intent ? { "data-matrix-cell-intent": summary.intent } : null),
223
+ }}
224
+ >
225
+ {!empty && (
226
+ <YStack
227
+ position="absolute"
228
+ top={0}
229
+ left={0}
230
+ right={0}
231
+ bottom={0}
232
+ backgroundColor={fill as any}
233
+ opacity={fillOpacity(intensity)}
234
+ pointerEvents="none"
235
+ {...{ "data-matrix-fill": String(intensity) }}
236
+ />
237
+ )}
238
+ <Text
239
+ {...knobProps.label}
240
+ color={empty ? componentColors.text.subtle : ink}
241
+ numberOfLines={1}
242
+ pointerEvents="none"
243
+ zIndex={1}
244
+ aria-hidden
245
+ >
246
+ {empty ? "·" : text}
247
+ </Text>
248
+ </YStack>
249
+ );
250
+ }
251
+
252
+ // ── Legend ────────────────────────────────────────────────────
253
+
254
+ function LegendSwatch({
255
+ intensity,
256
+ fill,
257
+ empty,
258
+ }: {
259
+ intensity?: number;
260
+ fill?: string;
261
+ empty?: boolean;
262
+ }) {
263
+ const { knobProps } = useResolvedKnobs();
264
+ return (
265
+ <YStack
266
+ width={14}
267
+ height={14}
268
+ {...knobProps.borderRadius}
269
+ position="relative"
270
+ overflow="hidden"
271
+ alignItems="center"
272
+ justifyContent="center"
273
+ borderWidth={hairlineWidth}
274
+ borderColor={componentColors.surface.border}
275
+ {...{ "data-matrix-legend-swatch": empty ? "empty" : String(intensity ?? 1) }}
276
+ >
277
+ {empty ? (
278
+ <Text fontSize={10} lineHeight={10} color={componentColors.text.subtle} aria-hidden>
279
+ ·
280
+ </Text>
281
+ ) : (
282
+ <YStack
283
+ position="absolute"
284
+ top={0}
285
+ left={0}
286
+ right={0}
287
+ bottom={0}
288
+ backgroundColor={(fill ?? "$accentBackground") as any}
289
+ opacity={fillOpacity(intensity ?? 1)}
290
+ />
291
+ )}
292
+ </YStack>
293
+ );
294
+ }
295
+
296
+ // ── Loading skeleton (LC-20: mirrors the real anatomy) ────────
297
+
298
+ const SKELETON_ROWS = [96, 72, 112, 64, 88] as const;
299
+
300
+ function MatrixSkeleton({
301
+ labelWidth,
302
+ columnWidth,
303
+ rowHeight,
304
+ }: {
305
+ labelWidth: number;
306
+ columnWidth: number;
307
+ rowHeight: number;
308
+ }) {
309
+ const { t } = useTranslation();
310
+ const { knobProps } = useResolvedKnobs();
311
+ const edgePad = knobProps.panelPadding.padding;
312
+ const columns = [0, 1, 2, 3, 4, 5];
313
+ return (
314
+ <YStack
315
+ width="100%"
316
+ aria-busy
317
+ aria-label={t("Loading")}
318
+ {...{ "data-matrix-skeleton": "true", "data-async-skeleton": "matrix" }}
319
+ >
320
+ <XStack
321
+ height={rowHeight}
322
+ alignItems="center"
323
+ {...hairline.bottom}
324
+ borderColor="$borderColor"
325
+ >
326
+ <XStack width={labelWidth} paddingHorizontal={edgePad}>
327
+ <Skeleton width={64} height={12} />
328
+ </XStack>
329
+ {columns.map((column) => (
330
+ <XStack key={column} width={columnWidth} justifyContent="center">
331
+ <Skeleton width={Math.max(24, columnWidth - 24)} height={12} />
332
+ </XStack>
333
+ ))}
334
+ </XStack>
335
+ {SKELETON_ROWS.map((labelSkeletonWidth, index) => (
336
+ <XStack
337
+ key={index}
338
+ height={rowHeight}
339
+ alignItems="center"
340
+ {...(index < SKELETON_ROWS.length - 1 ? hairline.bottom : null)}
341
+ borderColor={componentColors.divider}
342
+ >
343
+ <XStack width={labelWidth} paddingHorizontal={edgePad}>
344
+ <Skeleton width={labelSkeletonWidth} height={12} />
345
+ </XStack>
346
+ {columns.map((column) => (
347
+ <XStack key={column} width={columnWidth} justifyContent="center">
348
+ {(column + index) % 3 === 0 ? null : (
349
+ <Skeleton
350
+ variant="rounded"
351
+ width={Math.max(16, columnWidth - 16)}
352
+ height={Math.max(10, rowHeight - 20)}
353
+ />
354
+ )}
355
+ </XStack>
356
+ ))}
357
+ </XStack>
358
+ ))}
359
+ </YStack>
360
+ );
361
+ }
362
+
363
+ // ── MatrixView ────────────────────────────────────────────────
364
+
365
+ /**
366
+ * Generic matrix view: one row per subject, one column per time bucket, one
367
+ * cell per intersection carrying a value and a paint strength rather than a
368
+ * record. Account by month, person by week, service by day.
369
+ *
370
+ * The column axis is GENERATED from a bucket rule (day / week / month) over
371
+ * the data's range, using the Gantt's own tick placement and day math
372
+ * (`ganttMath`), so it is a real axis and not a hand-declared column set.
373
+ * A cell paints its fill at an intensity: explicit, or the summed value
374
+ * scaled against the grid's max, or full strength for bare presence. A real
375
+ * zero paints at the floor of the ramp; a bucket nothing landed in paints
376
+ * nothing and shows a dot, so an absence never reads as a zero. Every cell
377
+ * has a full accessible name and, with `onCellPress`, is a button.
378
+ *
379
+ * The header row stays pinned while rows scroll vertically (labels and rows
380
+ * share one vertical scroller so alignment is structural); the subject
381
+ * column stays pinned while the grid scrolls horizontally, the header strip
382
+ * being a scroll-follower of the grid scroller, which works on web and
383
+ * native alike (the Gantt mechanism). Loading / empty / error are honest
384
+ * first-class states (Axiom 6) via AsyncBoundary.
385
+ *
386
+ * @example
387
+ * ```tsx
388
+ * <MatrixView
389
+ * rows={accounts.map((a) => ({ id: a.id, label: a.name }))}
390
+ * cells={statements.map((s) => ({ rowId: s.account, date: s.date, value: s.count }))}
391
+ * rule="month"
392
+ * onCellPress={({ row, bucket }) => openStatements(row.id, bucket.start)}
393
+ * />
394
+ * ```
395
+ */
396
+ export function MatrixView({
397
+ rows,
398
+ cells,
399
+ rule: initialRule = "month",
400
+ onRuleChange,
401
+ range: rangeProp,
402
+ firstDayOfWeek = 0,
403
+ onCellPress,
404
+ formatCell,
405
+ isLoading = false,
406
+ error = null,
407
+ onRetry,
408
+ emptyTitle,
409
+ emptyDescription,
410
+ height: heightProp,
411
+ labelWidth = 180,
412
+ columnWidth: columnWidthProp,
413
+ subjectLabel,
414
+ showRuleSwitcher = true,
415
+ showLegend = true,
416
+ legendItems,
417
+ ...stackProps
418
+ }: MatrixViewProps) {
419
+ const { t } = useTranslation();
420
+ const { knobProps } = useResolvedKnobs();
421
+ const [rule, setRule] = useState<MatrixBucketRule>(initialRule);
422
+ if (typeof document !== "undefined") ensureKeyboardModalityTracking();
423
+
424
+ // ── Knob-derived geometry (LC-18: size/space step the density) ──────────
425
+ const sz = knobProps.sizeToken as string;
426
+ const height = heightProp ?? (sz === "$3" ? 400 : sz === "$5" ? 560 : 480);
427
+ // Cell paint is the press target (LC-54): never below 44px on either axis.
428
+ const rowHeight = Math.max(MIN_PRESS_TARGET, sz === "$3" ? 44 : sz === "$5" ? 56 : 48);
429
+ const columnWidth = Math.max(
430
+ MIN_PRESS_TARGET,
431
+ columnWidthProp ?? (sz === "$3" ? 56 : sz === "$5" ? 72 : 64),
432
+ );
433
+ const axisHeight = sz === "$3" ? 40 : sz === "$5" ? 52 : 46;
434
+
435
+ // ── Axis + cells ─────────────────────────────────────────────────────────
436
+ const range = useMemo(
437
+ () =>
438
+ rangeProp
439
+ ? alignMatrixRange(rangeProp, rule, firstDayOfWeek)
440
+ : computeMatrixRange(
441
+ cells.map((cell) => cell.date),
442
+ rule,
443
+ firstDayOfWeek,
444
+ ),
445
+ [rangeProp, cells, rule, firstDayOfWeek],
446
+ );
447
+ const buckets = useMemo(
448
+ () => generateBuckets(range, rule, firstDayOfWeek),
449
+ [range, rule, firstDayOfWeek],
450
+ );
451
+ const summaries = useMemo(
452
+ () => summarizeMatrixCells(cells, rule, firstDayOfWeek),
453
+ [cells, rule, firstDayOfWeek],
454
+ );
455
+ const maxValue = useMemo(() => {
456
+ const rowIds = new Set(rows.map((row) => row.id));
457
+ const bucketKeys = new Set(buckets.map((bucket) => bucket.key));
458
+ return matrixMaxValue(
459
+ Array.from(summaries.values()).filter(
460
+ (summary) => rowIds.has(summary.rowId) && bucketKeys.has(summary.bucketKey),
461
+ ),
462
+ );
463
+ }, [summaries, rows, buckets]);
464
+ const gridWidth = buckets.length * columnWidth;
465
+ const rangeLabel = useMemo(() => formatRangeLabel(range), [range]);
466
+
467
+ const changeRule = useCallback(
468
+ (next: MatrixBucketRule) => {
469
+ setRule(next);
470
+ onRuleChange?.(next);
471
+ },
472
+ [onRuleChange],
473
+ );
474
+
475
+ const cellText = useCallback(
476
+ (summary: MatrixCellSummary): string => {
477
+ if (formatCell) return formatCell(summary);
478
+ if (summary.label !== undefined) return summary.label;
479
+ return formatNumber(summary.value ?? summary.count);
480
+ },
481
+ [formatCell],
482
+ );
483
+
484
+ // ── Pinned-axis sync (the Gantt follower) ────────────────────────────────
485
+ const axisScrollRef = useRef<any>(null);
486
+ const handleCanvasScroll = useCallback((event: any) => {
487
+ const x = event?.nativeEvent?.contentOffset?.x ?? 0;
488
+ axisScrollRef.current?.scrollTo?.({ x, animated: false });
489
+ }, []);
490
+
491
+ // Manual mirroring is not implemented here; force the scrollers LTR so the
492
+ // follower offsets stay 0-at-content-start on web.
493
+ const scrollerDirection = { direction: "ltr" as const };
494
+ const showSkeleton = !error && isLoading && rows.length === 0;
495
+
496
+ return (
497
+ <AsyncBoundary
498
+ loading={
499
+ showSkeleton ? (
500
+ <MatrixSkeleton labelWidth={labelWidth} columnWidth={columnWidth} rowHeight={rowHeight} />
501
+ ) : (
502
+ false
503
+ )
504
+ }
505
+ empty={!error && !isLoading && rows.length === 0}
506
+ error={error}
507
+ onRetry={onRetry}
508
+ emptyTitle={emptyTitle ?? t("No subjects")}
509
+ emptyDescription={emptyDescription}
510
+ {...stackProps}
511
+ >
512
+ <YStack
513
+ height={height}
514
+ overflow="hidden"
515
+ backgroundColor="$background"
516
+ elevation={knobProps.elevation}
517
+ {...knobProps.containerRadius}
518
+ {...{
519
+ "data-matrix-view": rule,
520
+ "data-size": knobProps.size,
521
+ "data-density": knobProps.density,
522
+ }}
523
+ >
524
+ {/* Header */}
525
+ <XStack
526
+ {...knobProps.panelPadding}
527
+ alignItems="center"
528
+ justifyContent="space-between"
529
+ {...knobProps.gap}
530
+ {...hairline.bottom}
531
+ borderColor="$borderColor"
532
+ >
533
+ <Text {...knobProps.heading} {...sectionHeading} numberOfLines={1}>
534
+ {rangeLabel}
535
+ </Text>
536
+ {showRuleSwitcher && (
537
+ <ViewSwitcher
538
+ views={[
539
+ { type: "day", label: t("Day") },
540
+ { type: "week", label: t("Week") },
541
+ { type: "month", label: t("Month") },
542
+ ]}
543
+ currentView={rule}
544
+ onViewChange={(val) => changeRule(val as MatrixBucketRule)}
545
+ />
546
+ )}
547
+ </XStack>
548
+
549
+ {/* Pinned axis row: the subject header cell + the bucket header strip
550
+ stay put while rows scroll vertically. The strip follows the grid
551
+ scroller, so headers and cells can never drift apart. */}
552
+ <XStack
553
+ {...hairline.bottom}
554
+ borderColor="$borderColor"
555
+ {...{ "data-matrix-axis-row": "true" }}
556
+ >
557
+ <XStack
558
+ width={labelWidth}
559
+ flexShrink={0}
560
+ height={axisHeight}
561
+ alignItems="center"
562
+ paddingHorizontal={knobProps.panelPadding.padding}
563
+ {...hairlineEnd}
564
+ borderColor="$borderColor"
565
+ {...{ backgroundColor: componentColors.surface.background }}
566
+ >
567
+ <Text {...knobProps.label} color={knobProps.textAccentColor} numberOfLines={1}>
568
+ {subjectLabel ?? t("Subject")}
569
+ </Text>
570
+ </XStack>
571
+ <ScrollView
572
+ ref={axisScrollRef}
573
+ horizontal
574
+ flex={1}
575
+ scrollEnabled={false}
576
+ showsHorizontalScrollIndicator={false}
577
+ style={scrollerDirection as any}
578
+ >
579
+ <XStack
580
+ width={gridWidth}
581
+ height={axisHeight}
582
+ {...{
583
+ backgroundColor: componentColors.surface.background,
584
+ "data-matrix-axis": rule,
585
+ }}
586
+ >
587
+ {buckets.map((bucket, index) => (
588
+ <XStack
589
+ key={bucket.key}
590
+ width={columnWidth}
591
+ height={axisHeight}
592
+ alignItems="center"
593
+ justifyContent="center"
594
+ {...(index === buckets.length - 1 ? null : hairlineEnd)}
595
+ borderColor={componentColors.divider}
596
+ aria-label={bucket.name}
597
+ {...{ "data-matrix-column": bucket.key }}
598
+ >
599
+ <Text {...knobProps.label} color={knobProps.textAccentColor} numberOfLines={1}>
600
+ {bucket.label}
601
+ </Text>
602
+ </XStack>
603
+ ))}
604
+ </XStack>
605
+ </ScrollView>
606
+ </XStack>
607
+
608
+ {/* Body: subject labels + grid share one vertical scroller so rows stay
609
+ aligned; the grid owns its horizontal scroller (LC-10). */}
610
+ <ScrollView flex={1}>
611
+ <XStack {...{ "data-matrix-scroll-body": "true" }}>
612
+ <YStack
613
+ width={labelWidth}
614
+ flexShrink={0}
615
+ {...hairlineEnd}
616
+ borderColor="$borderColor"
617
+ {...{ "data-matrix-labels": "true" }}
618
+ >
619
+ {rows.map((row, index) => (
620
+ <XStack
621
+ key={row.id}
622
+ height={rowHeight}
623
+ alignItems="center"
624
+ paddingHorizontal={knobProps.panelPadding.padding}
625
+ {...(index === rows.length - 1 ? null : hairline.bottom)}
626
+ borderColor={componentColors.divider}
627
+ {...{ "data-matrix-row-label": row.id }}
628
+ >
629
+ <Text {...knobProps.body} numberOfLines={1} color="$color">
630
+ {row.label}
631
+ </Text>
632
+ </XStack>
633
+ ))}
634
+ </YStack>
635
+
636
+ <CanvasScroller
637
+ horizontal
638
+ {...(isWeb ? { flex: 1 } : null)}
639
+ showsHorizontalScrollIndicator
640
+ onScroll={handleCanvasScroll}
641
+ scrollEventThrottle={16}
642
+ style={(isWeb ? scrollerDirection : [{ flex: 1 }, scrollerDirection]) as any}
643
+ >
644
+ <YStack width={gridWidth} {...{ "data-matrix-canvas": "true" }}>
645
+ {rows.map((row, rowIndex) => (
646
+ <XStack
647
+ key={row.id}
648
+ height={rowHeight}
649
+ {...(rowIndex === rows.length - 1 ? null : hairline.bottom)}
650
+ borderColor={componentColors.divider}
651
+ {...{ "data-matrix-row": row.id }}
652
+ >
653
+ {buckets.map((bucket, columnIndex) => {
654
+ const summary = summaries.get(matrixCellKey(row.id, bucket.key));
655
+ const intensity = summary ? matrixCellIntensity(summary, maxValue) : 0;
656
+ const fill = resolveFill(summary?.color, summary?.intent);
657
+ const text = summary ? cellText(summary) : t("No data");
658
+ const name = withInterp(t("{{subject}}, {{bucket}}: {{value}}"), {
659
+ subject: row.label,
660
+ bucket: bucket.name,
661
+ value: text,
662
+ });
663
+ return (
664
+ <CellIntentWrap
665
+ key={bucket.key}
666
+ intent={summary?.color ? undefined : summary?.intent}
667
+ >
668
+ <MatrixCellBox
669
+ row={row}
670
+ bucket={bucket}
671
+ summary={summary}
672
+ intensity={intensity}
673
+ fill={fill}
674
+ text={text}
675
+ name={name}
676
+ width={columnWidth}
677
+ height={rowHeight}
678
+ lastColumn={columnIndex === buckets.length - 1}
679
+ onPress={
680
+ onCellPress ? () => onCellPress({ row, bucket, summary }) : undefined
681
+ }
682
+ />
683
+ </CellIntentWrap>
684
+ );
685
+ })}
686
+ </XStack>
687
+ ))}
688
+ </YStack>
689
+ </CanvasScroller>
690
+ </XStack>
691
+ </ScrollView>
692
+
693
+ {showLegend && (
694
+ <XStack
695
+ {...knobProps.panelPadding}
696
+ alignItems="center"
697
+ flexWrap="wrap"
698
+ {...knobProps.gap}
699
+ {...hairline.top}
700
+ borderColor="$borderColor"
701
+ {...{ "data-matrix-legend": "true" }}
702
+ >
703
+ <XStack alignItems="center" gap="$1.5">
704
+ <LegendSwatch empty />
705
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
706
+ {t("No data")}
707
+ </Text>
708
+ </XStack>
709
+ <XStack alignItems="center" gap="$1.5">
710
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
711
+ {t("Less")}
712
+ </Text>
713
+ {LEGEND_STEPS.map((step) => (
714
+ <LegendSwatch key={step} intensity={step} />
715
+ ))}
716
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
717
+ {t("More")}
718
+ </Text>
719
+ </XStack>
720
+ {legendItems?.map((item, index) => (
721
+ <CellIntentWrap
722
+ key={`${item.label}-${index}`}
723
+ intent={item.color ? undefined : item.intent}
724
+ >
725
+ <XStack alignItems="center" gap="$1.5">
726
+ <LegendSwatch fill={resolveFill(item.color, item.intent)} />
727
+ <Text {...knobProps.label} color={knobProps.textAccentColor}>
728
+ {item.label}
729
+ </Text>
730
+ </XStack>
731
+ </CellIntentWrap>
732
+ ))}
733
+ </XStack>
734
+ )}
735
+ </YStack>
736
+ </AsyncBoundary>
737
+ );
738
+ }