@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
@@ -48,11 +48,11 @@ const ALLOWLIST_VALUE_NAMES = [
48
48
  // barrel and had been red ever since. MPO-78: CI runs `turbo run build`
49
49
  // only, so nothing caught it.)
50
50
  "Circle",
51
- // MPO-21 G1 / G3 / G4. Restored by MPO-148; MPO-169 locks them here so a
51
+ // MPO-21 G3 / G4. Restored by MPO-148; MPO-169 locks them here so a
52
52
  // later Adapt-style rewrite cannot drop them without this spec going red.
53
53
  // LinearGradient (G2) is not on the main tamagui barrel — see the dedicated
54
- // assertion below rather than this identity check.
55
- "Collapsible",
54
+ // assertion below rather than this identity check. Collapsible (G1) is a
55
+ // house shadow since MPO-265: its Trigger defaults to type="button".
56
56
  "Group",
57
57
  "ListItem",
58
58
  "ScrollView",
@@ -138,6 +138,7 @@ const HOUSE_SHADOWS = [
138
138
  "Avatar",
139
139
  "Button",
140
140
  "Card",
141
+ "Collapsible",
141
142
  "Fieldset",
142
143
  "H1",
143
144
  "H2",
@@ -18,9 +18,9 @@
18
18
  */
19
19
  import { cleanup, screen } from "@testing-library/react";
20
20
  import { renderWithProviders } from "@multiplatform.one/test-utils";
21
- import { Preset } from "@multiplatform.one/theme";
21
+ import { Preset, useResolvedKnobs } from "@multiplatform.one/theme";
22
22
  import type { ReactElement } from "react";
23
- import { Theme } from "tamagui";
23
+ import { SizableText, Theme } from "tamagui";
24
24
  import { afterEach, describe, expect, it } from "vitest";
25
25
  import { Accordion } from "./Accordion";
26
26
  import { componentColors } from "./componentColors";
@@ -177,6 +177,104 @@ describe("Meter — textAccent reaches the ok-zone value readout (MPO-154)", ()
177
177
  });
178
178
  });
179
179
 
180
+ /**
181
+ * low == medium alone also passes a surface pinned to a static step, so the
182
+ * dim stops are checked against a probe painting the resolver's own colour.
183
+ */
184
+ function restInk(el: Element): string | undefined {
185
+ for (const cls of Array.from(el.classList)) {
186
+ if (!cls.startsWith("_col-")) continue;
187
+ const value = cls.slice("_col-".length);
188
+ if (/^\d*(hover|active|focus|press|disabled)/.test(value)) continue;
189
+ return value;
190
+ }
191
+ return undefined;
192
+ }
193
+
194
+ function ResolverInk() {
195
+ const { knobProps } = useResolvedKnobs();
196
+ return (
197
+ <SizableText data-resolver-ink={knobProps.textAccentColor} color={knobProps.textAccentColor}>
198
+ resolver ink
199
+ </SizableText>
200
+ );
201
+ }
202
+
203
+ function againstResolver(
204
+ scheme: Scheme,
205
+ accent: Accent,
206
+ node: ReactElement,
207
+ pick: (container: HTMLElement) => Element | null,
208
+ ) {
209
+ const { container } = renderWithProviders(
210
+ inScheme(
211
+ scheme,
212
+ <Preset overrides={{ textAccent: accent }}>
213
+ {node}
214
+ <ResolverInk />
215
+ </Preset>,
216
+ ),
217
+ );
218
+ const surface = pick(container);
219
+ const probe = container.querySelector("[data-resolver-ink]");
220
+ const result = {
221
+ surface: surface ? restInk(surface) : undefined,
222
+ resolver: probe ? restInk(probe) : undefined,
223
+ token: probe?.getAttribute("data-resolver-ink") ?? null,
224
+ };
225
+ cleanup();
226
+ return result;
227
+ }
228
+
229
+ describe("the dim stops paint the resolver's AA floor, not a pinned step (MPO-154)", () => {
230
+ const surfaces: Array<[string, ReactElement, (container: HTMLElement) => Element | null]> = [
231
+ [
232
+ "Tabs inactive label",
233
+ <Tabs
234
+ key="tabs"
235
+ items={[
236
+ { value: "address", label: "Address & Contact", content: <>address panel</> },
237
+ { value: "settings", label: "Settings", content: <>settings panel</> },
238
+ ]}
239
+ defaultValue="address"
240
+ />,
241
+ () => screen.getByText("Settings"),
242
+ ],
243
+ [
244
+ "Accordion description",
245
+ <Accordion key="accordion" defaultValue="shipping">
246
+ <Accordion.Item value="shipping">
247
+ <Accordion.Trigger description="Where should we deliver the order?">
248
+ Shipping details
249
+ </Accordion.Trigger>
250
+ <Accordion.Content>ship</Accordion.Content>
251
+ </Accordion.Item>
252
+ </Accordion>,
253
+ () => screen.getByText("Where should we deliver the order?"),
254
+ ],
255
+ [
256
+ "Quote attribution",
257
+ <Quote key="quote" cite="Alan Kay">
258
+ The best way to predict the future.
259
+ </Quote>,
260
+ (container) => container.querySelector("cite"),
261
+ ],
262
+ ];
263
+
264
+ for (const scheme of schemes) {
265
+ for (const [name, node, pick] of surfaces) {
266
+ for (const accent of ["low", "medium"] as const) {
267
+ it(`${scheme}: ${name} at ${accent} equals the resolver's textAccentColor`, () => {
268
+ const { surface, resolver, token } = againstResolver(scheme, accent, node, pick);
269
+ expect(token).toBe("$color11");
270
+ expect(resolver).toBeDefined();
271
+ expect(surface).toBe(resolver);
272
+ });
273
+ }
274
+ }
275
+ }
276
+ });
277
+
180
278
  describe("componentColors keeps no second answer for secondary ink (MPO-154)", () => {
181
279
  it("the text table exposes no static secondary or muted step", () => {
182
280
  const text = componentColors.text as Record<string, string | undefined>;
@@ -13,6 +13,7 @@ import {
13
13
  useReadableTextOn,
14
14
  useResolvedKnobs,
15
15
  wasKeyboardFocus,
16
+ transitionProps,
16
17
  } from "@multiplatform.one/theme";
17
18
  import { useHotkey } from "@tanstack/react-hotkeys";
18
19
  import type { RegisterableHotkey } from "@tanstack/react-hotkeys";
@@ -513,7 +514,7 @@ export function List<T>({
513
514
  ...corners,
514
515
  }}
515
516
  cursor="pointer"
516
- {...(knobProps.transition ? { transition: knobProps.transition } : {})}
517
+ {...transitionProps(knobProps.transition)}
517
518
  onPress={() => {
518
519
  commitSelection(index);
519
520
  onItemClick?.(item);
@@ -0,0 +1,339 @@
1
+ import { cleanup, fireEvent, screen } from "@testing-library/react";
2
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
3
+ import { formatMonthYear } from "@multiplatform.one/theme";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { MatrixView, type MatrixRow } from "./Matrix";
6
+ import { generateBuckets, type MatrixCell } from "./matrixMath";
7
+
8
+ afterEach(cleanup);
9
+
10
+ const rows: MatrixRow[] = [
11
+ { id: "a", label: "Checking" },
12
+ { id: "b", label: "Savings" },
13
+ { id: "c", label: "HSA" },
14
+ ];
15
+
16
+ // Checking spans Jan–Mar with two points in January (folded to 10, the grid
17
+ // max); Savings carries a real zero in January and an error in March; HSA
18
+ // has no point at all.
19
+ const cells: MatrixCell[] = [
20
+ { rowId: "a", date: new Date(2026, 0, 12), value: 4 },
21
+ { rowId: "a", date: new Date(2026, 0, 20), value: 6 },
22
+ { rowId: "a", date: new Date(2026, 1, 3), value: 2 },
23
+ { rowId: "a", date: new Date(2026, 2, 9), value: 10 },
24
+ { rowId: "b", date: new Date(2026, 0, 5), value: 0 },
25
+ { rowId: "b", date: new Date(2026, 2, 2), value: 3, intent: "error" },
26
+ ];
27
+
28
+ const cell = (container: HTMLElement, id: string) =>
29
+ container.querySelector(`[data-matrix-cell="${id}"]`) as HTMLElement;
30
+
31
+ describe("MatrixView", () => {
32
+ it("generates the column axis from the rule over the data's range, one row per subject", () => {
33
+ const { container } = renderWithProviders(
34
+ <MatrixView rows={rows} cells={cells} rule="month" />,
35
+ );
36
+
37
+ const columns = Array.from(container.querySelectorAll("[data-matrix-column]"));
38
+ expect(columns.map((el) => el.getAttribute("data-matrix-column"))).toEqual([
39
+ "2026-01-01",
40
+ "2026-02-01",
41
+ "2026-03-01",
42
+ ]);
43
+ expect(columns[0].textContent).toBe(formatMonthYear(new Date(2026, 0, 1)));
44
+ expect(columns[0].getAttribute("aria-label")).toBe(formatMonthYear(new Date(2026, 0, 1)));
45
+ expect(container.querySelector('[data-matrix-view="month"]')).toBeTruthy();
46
+
47
+ const labels = Array.from(container.querySelectorAll("[data-matrix-row-label]"));
48
+ expect(labels.map((el) => el.textContent)).toEqual(["Checking", "Savings", "HSA"]);
49
+ expect(container.querySelectorAll("[data-matrix-row]")).toHaveLength(3);
50
+ expect(container.querySelectorAll("[data-matrix-cell]")).toHaveLength(9);
51
+ expect(screen.getByText("Subject")).toBeInTheDocument();
52
+ });
53
+
54
+ it("regenerates the axis when the rule is switched, nothing re-declared", () => {
55
+ const onRuleChange = vi.fn();
56
+ const { container } = renderWithProviders(
57
+ <MatrixView rows={rows} cells={cells} rule="month" onRuleChange={onRuleChange} />,
58
+ );
59
+
60
+ fireEvent.click(screen.getByText("Week"));
61
+
62
+ expect(onRuleChange).toHaveBeenCalledWith("week");
63
+ expect(container.querySelector('[data-matrix-view="week"]')).toBeTruthy();
64
+ const expected = generateBuckets(
65
+ { start: new Date(2026, 0, 5), end: new Date(2026, 2, 10) },
66
+ "week",
67
+ );
68
+ const columns = Array.from(container.querySelectorAll("[data-matrix-column]"));
69
+ expect(columns.map((el) => el.getAttribute("data-matrix-column"))).toEqual(
70
+ expected.map((b) => b.key),
71
+ );
72
+ expect(columns.length).toBeGreaterThan(3);
73
+ });
74
+
75
+ it("snaps an explicit window to bucket boundaries", () => {
76
+ const { container } = renderWithProviders(
77
+ <MatrixView
78
+ rows={rows}
79
+ cells={cells}
80
+ rule="month"
81
+ range={{ start: new Date(2025, 10, 20), end: new Date(2026, 4, 2) }}
82
+ />,
83
+ );
84
+ const columns = Array.from(container.querySelectorAll("[data-matrix-column]"));
85
+ expect(columns.map((el) => el.getAttribute("data-matrix-column"))).toEqual([
86
+ "2025-11-01",
87
+ "2025-12-01",
88
+ "2026-01-01",
89
+ "2026-02-01",
90
+ "2026-03-01",
91
+ "2026-04-01",
92
+ "2026-05-01",
93
+ ]);
94
+ });
95
+
96
+ it("a cell carries its folded value and a paint strength; an intent rides the theme ramp", () => {
97
+ const { container } = renderWithProviders(
98
+ <MatrixView rows={rows} cells={cells} rule="month" />,
99
+ );
100
+
101
+ const aJan = cell(container, "a:2026-01-01");
102
+ expect(aJan.textContent).toBe("10");
103
+ expect(aJan.getAttribute("data-matrix-cell-state")).toBe("value");
104
+ expect(aJan.querySelector("[data-matrix-fill]")!.getAttribute("data-matrix-fill")).toBe("1");
105
+ expect(aJan.getAttribute("aria-label")).toBe(
106
+ `Checking, ${formatMonthYear(new Date(2026, 0, 1))}: 10`,
107
+ );
108
+
109
+ const aFeb = cell(container, "a:2026-02-01");
110
+ expect(aFeb.textContent).toBe("2");
111
+ expect(aFeb.querySelector("[data-matrix-fill]")!.getAttribute("data-matrix-fill")).toBe("0.2");
112
+
113
+ const bMar = cell(container, "b:2026-03-01");
114
+ expect(bMar.getAttribute("data-matrix-cell-intent")).toBe("error");
115
+ expect(cell(container, "a:2026-03-01").getAttribute("data-matrix-cell-intent")).toBeNull();
116
+ });
117
+
118
+ it("an empty bucket is a different state from a zero", () => {
119
+ const { container } = renderWithProviders(
120
+ <MatrixView rows={rows} cells={cells} rule="month" />,
121
+ );
122
+
123
+ const zero = cell(container, "b:2026-01-01");
124
+ expect(zero.getAttribute("data-matrix-cell-state")).toBe("zero");
125
+ expect(zero.textContent).toBe("0");
126
+ expect(zero.querySelector("[data-matrix-fill]")!.getAttribute("data-matrix-fill")).toBe("0");
127
+
128
+ const gap = cell(container, "c:2026-01-01");
129
+ expect(gap.getAttribute("data-matrix-cell-state")).toBe("empty");
130
+ expect(gap.querySelector("[data-matrix-fill]")).toBeNull();
131
+ expect(gap.textContent).toBe("·");
132
+ expect(gap.getAttribute("aria-label")).toBe(
133
+ `HSA, ${formatMonthYear(new Date(2026, 0, 1))}: No data`,
134
+ );
135
+ expect(cell(container, "b:2026-02-01").getAttribute("data-matrix-cell-state")).toBe("empty");
136
+ });
137
+
138
+ it("carries a legend naming the empty state and the ramp, plus the caller's entries", () => {
139
+ const { container } = renderWithProviders(
140
+ <MatrixView
141
+ rows={rows}
142
+ cells={cells}
143
+ rule="month"
144
+ legendItems={[{ label: "Failed import", intent: "error" }]}
145
+ />,
146
+ );
147
+ const legend = container.querySelector("[data-matrix-legend]")!;
148
+ expect(legend).toBeTruthy();
149
+ expect(legend.textContent).toContain("No data");
150
+ expect(legend.textContent).toContain("Less");
151
+ expect(legend.textContent).toContain("More");
152
+ expect(legend.textContent).toContain("Failed import");
153
+ const swatches = Array.from(legend.querySelectorAll("[data-matrix-legend-swatch]")).map((el) =>
154
+ el.getAttribute("data-matrix-legend-swatch"),
155
+ );
156
+ expect(swatches).toEqual(["empty", "0", "0.25", "0.5", "0.75", "1", "1"]);
157
+
158
+ cleanup();
159
+ const { container: bare } = renderWithProviders(
160
+ <MatrixView rows={rows} cells={cells} rule="month" showLegend={false} />,
161
+ );
162
+ expect(bare.querySelector("[data-matrix-legend]")).toBeNull();
163
+ });
164
+
165
+ it("anchors every inset fill to its own swatch or cell, so no fill resolves against the viewport", () => {
166
+ const { container } = renderWithProviders(
167
+ <MatrixView
168
+ rows={rows}
169
+ cells={cells}
170
+ rule="month"
171
+ legendItems={[{ label: "Failed import", intent: "error" }]}
172
+ />,
173
+ );
174
+ const swatches = Array.from(
175
+ container.querySelectorAll(
176
+ '[data-matrix-legend-swatch]:not([data-matrix-legend-swatch="empty"])',
177
+ ),
178
+ ) as HTMLElement[];
179
+ const cellFills = Array.from(container.querySelectorAll("[data-matrix-fill]")) as HTMLElement[];
180
+ expect(swatches).toHaveLength(6);
181
+ expect(cellFills.length).toBeGreaterThan(0);
182
+
183
+ const fills = [
184
+ ...swatches.map((swatch) => swatch.firstElementChild as HTMLElement),
185
+ ...cellFills,
186
+ ];
187
+ for (const fill of fills) {
188
+ expect(getComputedStyle(fill).position).toBe("absolute");
189
+ expect(getComputedStyle(fill.parentElement!).position).toBe("relative");
190
+ }
191
+ });
192
+
193
+ it("a cell press reports its row, bucket and summary; an empty bucket presses too", () => {
194
+ const onCellPress = vi.fn();
195
+ const { container } = renderWithProviders(
196
+ <MatrixView rows={rows} cells={cells} rule="month" onCellPress={onCellPress} />,
197
+ );
198
+
199
+ const aJan = cell(container, "a:2026-01-01");
200
+ expect(aJan.getAttribute("role")).toBe("button");
201
+ fireEvent.click(aJan);
202
+ expect(onCellPress).toHaveBeenCalledTimes(1);
203
+ const ref = onCellPress.mock.calls[0][0];
204
+ expect(ref.row.id).toBe("a");
205
+ expect(ref.bucket.key).toBe("2026-01-01");
206
+ expect(ref.bucket.start).toEqual(new Date(2026, 0, 1));
207
+ expect(ref.summary.value).toBe(10);
208
+ expect(ref.summary.count).toBe(2);
209
+
210
+ fireEvent.click(cell(container, "c:2026-02-01"));
211
+ expect(onCellPress).toHaveBeenCalledTimes(2);
212
+ expect(onCellPress.mock.calls[1][0].summary).toBeUndefined();
213
+ expect(onCellPress.mock.calls[1][0].row.id).toBe("c");
214
+
215
+ fireEvent.keyDown(aJan, { key: "Enter" });
216
+ expect(onCellPress).toHaveBeenCalledTimes(3);
217
+ fireEvent.keyDown(aJan, { key: " " });
218
+ expect(onCellPress).toHaveBeenCalledTimes(4);
219
+ fireEvent.keyDown(aJan, { key: "a" });
220
+ expect(onCellPress).toHaveBeenCalledTimes(4);
221
+ });
222
+
223
+ it("without onCellPress a cell is not a control", () => {
224
+ const { container } = renderWithProviders(
225
+ <MatrixView rows={rows} cells={cells} rule="month" />,
226
+ );
227
+ expect(cell(container, "a:2026-01-01").getAttribute("role")).toBeNull();
228
+ expect(container.querySelectorAll('[data-matrix-cell][role="button"]')).toHaveLength(0);
229
+ });
230
+
231
+ it("keeps the header row and subject column outside the grid scrollers, and the header follows the grid scroll", () => {
232
+ const scrolls: { target: HTMLElement; left: number }[] = [];
233
+ const original = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "scroll");
234
+ Object.defineProperty(HTMLElement.prototype, "scroll", {
235
+ configurable: true,
236
+ writable: true,
237
+ value(this: HTMLElement, options: { left?: number }) {
238
+ if (typeof options?.left === "number") scrolls.push({ target: this, left: options.left });
239
+ },
240
+ });
241
+ try {
242
+ const { container } = renderWithProviders(
243
+ <MatrixView rows={rows} cells={cells} rule="month" />,
244
+ );
245
+
246
+ const axisRow = container.querySelector("[data-matrix-axis-row]") as HTMLElement;
247
+ const axis = container.querySelector("[data-matrix-axis]") as HTMLElement;
248
+ const body = container.querySelector("[data-matrix-scroll-body]") as HTMLElement;
249
+ const labels = container.querySelector("[data-matrix-labels]") as HTMLElement;
250
+ const canvas = container.querySelector("[data-matrix-canvas]") as HTMLElement;
251
+ expect(axisRow).toBeTruthy();
252
+ expect(body.contains(axisRow)).toBe(false);
253
+ expect(axisRow.contains(axis)).toBe(true);
254
+ expect(body.contains(labels)).toBe(true);
255
+ expect(body.contains(canvas)).toBe(true);
256
+
257
+ let scroller: HTMLElement | null = canvas.parentElement;
258
+ while (scroller && scroller !== body && !scroller.contains(labels)) {
259
+ scroller = scroller.parentElement;
260
+ }
261
+ expect(scroller).toBe(body);
262
+
263
+ let node: HTMLElement | null = canvas.parentElement;
264
+ let followed = false;
265
+ while (node && node !== body && !followed) {
266
+ Object.defineProperty(node, "scrollLeft", { configurable: true, value: 300 });
267
+ fireEvent.scroll(node);
268
+ followed = scrolls.some((call) => call.left === 300 && call.target.contains(axis));
269
+ node = node.parentElement;
270
+ }
271
+ expect(followed).toBe(true);
272
+ expect(scrolls.some((call) => call.target.contains(canvas))).toBe(false);
273
+ } finally {
274
+ if (original) Object.defineProperty(HTMLElement.prototype, "scroll", original);
275
+ else delete (HTMLElement.prototype as any).scroll;
276
+ }
277
+ });
278
+
279
+ it("shows the matrix skeleton while loading with no rows (W9 / DG-ST-02)", () => {
280
+ const { container } = renderWithProviders(<MatrixView rows={[]} cells={[]} isLoading />);
281
+ expect(container.querySelector('[data-async-skeleton="matrix"]')).toBeTruthy();
282
+ expect(container.querySelector("[data-matrix-column]")).toBeNull();
283
+ });
284
+
285
+ it("forbids empty chrome when error is set, and shows the empty state otherwise (W9 / DG-ST-01)", () => {
286
+ renderWithProviders(
287
+ <MatrixView rows={[]} cells={[]} emptyTitle="No accounts yet" error="Coverage failed" />,
288
+ );
289
+ expect(screen.queryByText("No accounts yet")).toBeNull();
290
+ expect(screen.getByText("Coverage failed")).toBeInTheDocument();
291
+ cleanup();
292
+
293
+ renderWithProviders(<MatrixView rows={[]} cells={[]} emptyTitle="No accounts yet" />);
294
+ expect(screen.getByText("No accounts yet")).toBeInTheDocument();
295
+ });
296
+
297
+ it("formatCell overrides the visible text and a point label wins over the number", () => {
298
+ const { container } = renderWithProviders(
299
+ <MatrixView
300
+ rows={rows}
301
+ cells={[...cells, { rowId: "c", date: new Date(2026, 1, 10), value: 7, label: "7 new" }]}
302
+ rule="month"
303
+ formatCell={(summary) => summary.label ?? `${summary.count}p`}
304
+ />,
305
+ );
306
+ expect(cell(container, "a:2026-01-01").textContent).toBe("2p");
307
+ expect(cell(container, "c:2026-02-01").textContent).toBe("7 new");
308
+ cleanup();
309
+
310
+ const { container: plain } = renderWithProviders(
311
+ <MatrixView
312
+ rows={rows}
313
+ cells={[...cells, { rowId: "c", date: new Date(2026, 1, 10), value: 7, label: "7 new" }]}
314
+ rule="month"
315
+ />,
316
+ );
317
+ expect(cell(plain, "a:2026-01-01").textContent).toBe("10");
318
+ expect(cell(plain, "c:2026-02-01").textContent).toBe("7 new");
319
+ });
320
+
321
+ it("scales intensity against the largest value on screen, not one outside the window or the rows", () => {
322
+ const { container } = renderWithProviders(
323
+ <MatrixView
324
+ rows={rows}
325
+ cells={[
326
+ ...cells,
327
+ { rowId: "a", date: new Date(2025, 5, 1), value: 1000 },
328
+ { rowId: "zz", date: new Date(2026, 0, 3), value: 500 },
329
+ ]}
330
+ rule="month"
331
+ range={{ start: new Date(2026, 0, 1), end: new Date(2026, 3, 1) }}
332
+ />,
333
+ );
334
+ const fill = (id: string) =>
335
+ cell(container, id).querySelector("[data-matrix-fill]")!.getAttribute("data-matrix-fill");
336
+ expect(fill("a:2026-01-01")).toBe("1");
337
+ expect(fill("a:2026-02-01")).toBe("0.2");
338
+ });
339
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The Matrix stories are the only in-repo render of the view (MPO-254 AC5:
3
+ * a subject-by-month grid with gaps). happy-dom runs no layout, so the
4
+ * pinned scrollers are not exercised here; what is asserted is that every
5
+ * story mounts and that the Main story's data really does put a zero and a
6
+ * gap on screen as two different cell states.
7
+ */
8
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
9
+ import { cleanup } from "@testing-library/react";
10
+ import { afterEach, describe, expect, it, vi } from "vitest";
11
+ import meta, { Default, Intents, WeekRule } from "./Matrix.stories";
12
+
13
+ vi.mock("@multiplatform.one/storybook", () => ({ action: () => () => undefined }));
14
+
15
+ afterEach(cleanup);
16
+
17
+ describe("Matrix stories", () => {
18
+ it("declares the Components/Matrix title", () => {
19
+ expect(meta.title).toBe("Components/Matrix");
20
+ });
21
+
22
+ it("every exported story mounts", () => {
23
+ for (const story of [Default, Intents, WeekRule]) {
24
+ const { container } = renderWithProviders(<>{story.render()}</>);
25
+ expect(container.querySelector("[data-matrix-view]")).toBeTruthy();
26
+ cleanup();
27
+ }
28
+ });
29
+
30
+ it("the Main story renders a subject-by-month grid where a gap and a zero are different states", () => {
31
+ const { container } = renderWithProviders(<>{Default.render()}</>);
32
+ expect(container.querySelector('[data-matrix-view="month"]')).toBeTruthy();
33
+ expect(container.querySelectorAll("[data-matrix-column]")).toHaveLength(12);
34
+ expect(container.querySelectorAll("[data-matrix-row-label]")).toHaveLength(6);
35
+
36
+ const zero = container.querySelector('[data-matrix-cell="savings:2026-04-01"]')!;
37
+ expect(zero.getAttribute("data-matrix-cell-state")).toBe("zero");
38
+ expect(zero.querySelector("[data-matrix-fill]")).toBeTruthy();
39
+ expect(zero.textContent).toBe("0");
40
+
41
+ const gap = container.querySelector('[data-matrix-cell="credit:2026-05-01"]')!;
42
+ expect(gap.getAttribute("data-matrix-cell-state")).toBe("empty");
43
+ expect(gap.querySelector("[data-matrix-fill]")).toBeNull();
44
+ expect(gap.textContent).not.toBe("0");
45
+
46
+ const hsaRow = container.querySelector('[data-matrix-row="hsa"]')!;
47
+ expect(hsaRow.querySelectorAll('[data-matrix-cell-state="empty"]')).toHaveLength(12);
48
+
49
+ const filled = container.querySelector('[data-matrix-cell="checking:2026-12-01"]')!;
50
+ expect(filled.getAttribute("data-matrix-cell-state")).toBe("value");
51
+ expect(filled.querySelector("[data-matrix-fill]")?.getAttribute("data-matrix-fill")).toBe("1");
52
+ });
53
+
54
+ it("the Intents story rides the theme ramps and names them in the legend", () => {
55
+ const { container } = renderWithProviders(<>{Intents.render()}</>);
56
+ const down = container.querySelector('[data-matrix-cell="auth:2026-03-05"]')!;
57
+ expect(down.getAttribute("data-matrix-cell-intent")).toBe("error");
58
+ expect(
59
+ container
60
+ .querySelector('[data-matrix-cell="search:2026-03-01"]')!
61
+ .getAttribute("data-matrix-cell-state"),
62
+ ).toBe("empty");
63
+ const legend = container.querySelector("[data-matrix-legend]")!;
64
+ expect(legend.textContent).toContain("Healthy");
65
+ expect(legend.textContent).toContain("Degraded");
66
+ expect(legend.textContent).toContain("Down");
67
+ });
68
+ });