@multiplatform.one/table 7.7.6 → 7.8.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 (357) hide show
  1. package/dist/cjs/components/ColumnHeaderFilter/index.cjs +74 -38
  2. package/dist/cjs/components/ColumnHeaderFilter/index.native.js +97 -51
  3. package/dist/cjs/components/ColumnHeaderFilter/index.native.js.map +1 -1
  4. package/dist/cjs/components/DataTable/DataTableCardControls.cjs +0 -1
  5. package/dist/cjs/components/DataTable/DataTableCardControls.native.js +0 -1
  6. package/dist/cjs/components/DataTable/DataTableCardControls.native.js.map +1 -1
  7. package/dist/cjs/components/DataTable/DataTableRoot.cjs +33 -6
  8. package/dist/cjs/components/DataTable/DataTableRoot.native.js +29 -4
  9. package/dist/cjs/components/DataTable/DataTableRoot.native.js.map +1 -1
  10. package/dist/cjs/components/DataTable/DataTableStateContext.native.js.map +1 -1
  11. package/dist/cjs/components/DataTableBulkActions/index.cjs +0 -4
  12. package/dist/cjs/components/DataTableBulkActions/index.native.js +0 -4
  13. package/dist/cjs/components/DataTableBulkActions/index.native.js.map +1 -1
  14. package/dist/cjs/components/DataTableCell/index.cjs +16 -1
  15. package/dist/cjs/components/DataTableCell/index.native.js +16 -1
  16. package/dist/cjs/components/DataTableCell/index.native.js.map +1 -1
  17. package/dist/cjs/components/DataTableDateFilter/index.cjs +0 -7
  18. package/dist/cjs/components/DataTableDateFilter/index.native.js +0 -7
  19. package/dist/cjs/components/DataTableDateFilter/index.native.js.map +1 -1
  20. package/dist/cjs/components/DataTableFilterButton/index.cjs +0 -1
  21. package/dist/cjs/components/DataTableFilterButton/index.native.js +0 -1
  22. package/dist/cjs/components/DataTableFilterChips/FilterChip.cjs +2 -1
  23. package/dist/cjs/components/DataTableFilterChips/FilterChip.native.js +4 -1
  24. package/dist/cjs/components/DataTableFilterChips/FilterChip.native.js.map +1 -1
  25. package/dist/cjs/components/DataTableFilters/index.cjs +0 -1
  26. package/dist/cjs/components/DataTableFilters/index.native.js +0 -1
  27. package/dist/cjs/components/DataTableGrouping/index.cjs +0 -8
  28. package/dist/cjs/components/DataTableGrouping/index.native.js +0 -8
  29. package/dist/cjs/components/DataTableGrouping/index.native.js.map +1 -1
  30. package/dist/cjs/components/DataTablePagination/index.cjs +29 -13
  31. package/dist/cjs/components/DataTablePagination/index.native.js +30 -14
  32. package/dist/cjs/components/DataTablePagination/index.native.js.map +1 -1
  33. package/dist/cjs/components/DataTableSearch/index.cjs +0 -3
  34. package/dist/cjs/components/DataTableSearch/index.native.js +0 -3
  35. package/dist/cjs/components/DataTableSearch/index.native.js.map +1 -1
  36. package/dist/cjs/components/DataTableToolbar/index.cjs +0 -1
  37. package/dist/cjs/components/DataTableToolbar/index.native.js +0 -1
  38. package/dist/cjs/components/DataTableToolbar/index.native.js.map +1 -1
  39. package/dist/cjs/components/DataTableViewOptions/index.cjs +0 -5
  40. package/dist/cjs/components/DataTableViewOptions/index.native.js +0 -5
  41. package/dist/cjs/components/DataTableViewOptions/index.native.js.map +1 -1
  42. package/dist/cjs/components/FilterManager.cjs +0 -1
  43. package/dist/cjs/components/FilterManager.native.js +0 -1
  44. package/dist/cjs/components/FilterManager.native.js.map +1 -1
  45. package/dist/cjs/components/FilterOperatorValueEditor/index.cjs +2 -2
  46. package/dist/cjs/components/FilterOperatorValueEditor/index.native.js +2 -2
  47. package/dist/cjs/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  48. package/dist/cjs/components/TableInput/index.cjs +2 -1
  49. package/dist/cjs/components/TableInput/index.native.js +4 -2
  50. package/dist/cjs/components/TableInput/index.native.js.map +1 -1
  51. package/dist/cjs/hooks/useDataTable.cjs +24 -8
  52. package/dist/cjs/hooks/useDataTable.native.js +28 -8
  53. package/dist/cjs/hooks/useDataTable.native.js.map +1 -1
  54. package/dist/cjs/index.cjs +12 -0
  55. package/dist/cjs/index.native.js +12 -0
  56. package/dist/cjs/index.native.js.map +1 -1
  57. package/dist/cjs/shared/devWarn.cjs +70 -0
  58. package/dist/cjs/shared/devWarn.native.js +84 -0
  59. package/dist/cjs/shared/devWarn.native.js.map +1 -0
  60. package/dist/cjs/utils/columnAlign.cjs +4 -1
  61. package/dist/cjs/utils/columnAlign.native.js +4 -1
  62. package/dist/cjs/utils/columnAlign.native.js.map +1 -1
  63. package/dist/cjs/utils/filterFields.cjs +5 -0
  64. package/dist/cjs/utils/filterFields.native.js +5 -0
  65. package/dist/cjs/utils/filterFields.native.js.map +1 -1
  66. package/dist/cjs/utils/filterUtils.cjs +30 -0
  67. package/dist/cjs/utils/filterUtils.native.js +50 -0
  68. package/dist/cjs/utils/filterUtils.native.js.map +1 -1
  69. package/dist/cjs/utils/money.cjs +137 -0
  70. package/dist/cjs/utils/money.native.js +165 -0
  71. package/dist/cjs/utils/money.native.js.map +1 -0
  72. package/dist/cjs/utils/rowAccumulator.cjs +48 -0
  73. package/dist/cjs/utils/rowAccumulator.native.js +51 -0
  74. package/dist/cjs/utils/rowAccumulator.native.js.map +1 -0
  75. package/dist/esm/components/ColumnHeaderFilter/index.mjs +78 -42
  76. package/dist/esm/components/ColumnHeaderFilter/index.mjs.map +1 -1
  77. package/dist/esm/components/ColumnHeaderFilter/index.native.js +101 -55
  78. package/dist/esm/components/ColumnHeaderFilter/index.native.js.map +1 -1
  79. package/dist/esm/components/DataTable/DataTableCardControls.mjs +0 -1
  80. package/dist/esm/components/DataTable/DataTableCardControls.mjs.map +1 -1
  81. package/dist/esm/components/DataTable/DataTableCardControls.native.js +0 -1
  82. package/dist/esm/components/DataTable/DataTableCardControls.native.js.map +1 -1
  83. package/dist/esm/components/DataTable/DataTableRoot.mjs +33 -6
  84. package/dist/esm/components/DataTable/DataTableRoot.mjs.map +1 -1
  85. package/dist/esm/components/DataTable/DataTableRoot.native.js +29 -4
  86. package/dist/esm/components/DataTable/DataTableRoot.native.js.map +1 -1
  87. package/dist/esm/components/DataTable/DataTableStateContext.mjs.map +1 -1
  88. package/dist/esm/components/DataTable/DataTableStateContext.native.js.map +1 -1
  89. package/dist/esm/components/DataTableBulkActions/index.mjs +0 -4
  90. package/dist/esm/components/DataTableBulkActions/index.mjs.map +1 -1
  91. package/dist/esm/components/DataTableBulkActions/index.native.js +0 -4
  92. package/dist/esm/components/DataTableBulkActions/index.native.js.map +1 -1
  93. package/dist/esm/components/DataTableCell/index.mjs +17 -2
  94. package/dist/esm/components/DataTableCell/index.mjs.map +1 -1
  95. package/dist/esm/components/DataTableCell/index.native.js +17 -2
  96. package/dist/esm/components/DataTableCell/index.native.js.map +1 -1
  97. package/dist/esm/components/DataTableDateFilter/index.mjs +0 -7
  98. package/dist/esm/components/DataTableDateFilter/index.mjs.map +1 -1
  99. package/dist/esm/components/DataTableDateFilter/index.native.js +0 -7
  100. package/dist/esm/components/DataTableDateFilter/index.native.js.map +1 -1
  101. package/dist/esm/components/DataTableFilterButton/index.mjs +0 -1
  102. package/dist/esm/components/DataTableFilterButton/index.mjs.map +1 -1
  103. package/dist/esm/components/DataTableFilterButton/index.native.js +0 -1
  104. package/dist/esm/components/DataTableFilterChips/FilterChip.mjs +3 -2
  105. package/dist/esm/components/DataTableFilterChips/FilterChip.mjs.map +1 -1
  106. package/dist/esm/components/DataTableFilterChips/FilterChip.native.js +5 -2
  107. package/dist/esm/components/DataTableFilterChips/FilterChip.native.js.map +1 -1
  108. package/dist/esm/components/DataTableFilters/index.mjs +0 -1
  109. package/dist/esm/components/DataTableFilters/index.mjs.map +1 -1
  110. package/dist/esm/components/DataTableFilters/index.native.js +0 -1
  111. package/dist/esm/components/DataTableGrouping/index.mjs +0 -8
  112. package/dist/esm/components/DataTableGrouping/index.mjs.map +1 -1
  113. package/dist/esm/components/DataTableGrouping/index.native.js +0 -8
  114. package/dist/esm/components/DataTableGrouping/index.native.js.map +1 -1
  115. package/dist/esm/components/DataTablePagination/index.mjs +30 -14
  116. package/dist/esm/components/DataTablePagination/index.mjs.map +1 -1
  117. package/dist/esm/components/DataTablePagination/index.native.js +31 -15
  118. package/dist/esm/components/DataTablePagination/index.native.js.map +1 -1
  119. package/dist/esm/components/DataTableSearch/index.mjs +0 -3
  120. package/dist/esm/components/DataTableSearch/index.mjs.map +1 -1
  121. package/dist/esm/components/DataTableSearch/index.native.js +0 -3
  122. package/dist/esm/components/DataTableSearch/index.native.js.map +1 -1
  123. package/dist/esm/components/DataTableToolbar/index.mjs +0 -1
  124. package/dist/esm/components/DataTableToolbar/index.mjs.map +1 -1
  125. package/dist/esm/components/DataTableToolbar/index.native.js +0 -1
  126. package/dist/esm/components/DataTableToolbar/index.native.js.map +1 -1
  127. package/dist/esm/components/DataTableViewOptions/index.mjs +0 -5
  128. package/dist/esm/components/DataTableViewOptions/index.mjs.map +1 -1
  129. package/dist/esm/components/DataTableViewOptions/index.native.js +0 -5
  130. package/dist/esm/components/DataTableViewOptions/index.native.js.map +1 -1
  131. package/dist/esm/components/FilterManager.mjs +0 -1
  132. package/dist/esm/components/FilterManager.mjs.map +1 -1
  133. package/dist/esm/components/FilterManager.native.js +0 -1
  134. package/dist/esm/components/FilterManager.native.js.map +1 -1
  135. package/dist/esm/components/FilterOperatorValueEditor/index.mjs +2 -2
  136. package/dist/esm/components/FilterOperatorValueEditor/index.mjs.map +1 -1
  137. package/dist/esm/components/FilterOperatorValueEditor/index.native.js +2 -2
  138. package/dist/esm/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  139. package/dist/esm/components/TableInput/index.mjs +3 -2
  140. package/dist/esm/components/TableInput/index.mjs.map +1 -1
  141. package/dist/esm/components/TableInput/index.native.js +5 -3
  142. package/dist/esm/components/TableInput/index.native.js.map +1 -1
  143. package/dist/esm/hooks/useDataTable.mjs +24 -8
  144. package/dist/esm/hooks/useDataTable.mjs.map +1 -1
  145. package/dist/esm/hooks/useDataTable.native.js +28 -8
  146. package/dist/esm/hooks/useDataTable.native.js.map +1 -1
  147. package/dist/esm/index.mjs +3 -1
  148. package/dist/esm/index.mjs.map +1 -1
  149. package/dist/esm/index.native.js +3 -1
  150. package/dist/esm/index.native.js.map +1 -1
  151. package/dist/esm/shared/devWarn.mjs +43 -0
  152. package/dist/esm/shared/devWarn.mjs.map +1 -0
  153. package/dist/esm/shared/devWarn.native.js +54 -0
  154. package/dist/esm/shared/devWarn.native.js.map +1 -0
  155. package/dist/esm/utils/columnAlign.mjs +4 -1
  156. package/dist/esm/utils/columnAlign.mjs.map +1 -1
  157. package/dist/esm/utils/columnAlign.native.js +4 -1
  158. package/dist/esm/utils/columnAlign.native.js.map +1 -1
  159. package/dist/esm/utils/filterFields.mjs +5 -0
  160. package/dist/esm/utils/filterFields.mjs.map +1 -1
  161. package/dist/esm/utils/filterFields.native.js +5 -0
  162. package/dist/esm/utils/filterFields.native.js.map +1 -1
  163. package/dist/esm/utils/filterUtils.mjs +30 -0
  164. package/dist/esm/utils/filterUtils.mjs.map +1 -1
  165. package/dist/esm/utils/filterUtils.native.js +50 -0
  166. package/dist/esm/utils/filterUtils.native.js.map +1 -1
  167. package/dist/esm/utils/money.mjs +104 -0
  168. package/dist/esm/utils/money.mjs.map +1 -0
  169. package/dist/esm/utils/money.native.js +129 -0
  170. package/dist/esm/utils/money.native.js.map +1 -0
  171. package/dist/esm/utils/rowAccumulator.mjs +23 -0
  172. package/dist/esm/utils/rowAccumulator.mjs.map +1 -0
  173. package/dist/esm/utils/rowAccumulator.native.js +23 -0
  174. package/dist/esm/utils/rowAccumulator.native.js.map +1 -0
  175. package/dist/jsx/components/ColumnHeaderFilter/index.mjs +78 -42
  176. package/dist/jsx/components/ColumnHeaderFilter/index.mjs.map +1 -1
  177. package/dist/jsx/components/ColumnHeaderFilter/index.native.js +97 -51
  178. package/dist/jsx/components/ColumnHeaderFilter/index.native.js.map +1 -1
  179. package/dist/jsx/components/DataTable/DataTableCardControls.mjs +0 -1
  180. package/dist/jsx/components/DataTable/DataTableCardControls.mjs.map +1 -1
  181. package/dist/jsx/components/DataTable/DataTableCardControls.native.js +0 -1
  182. package/dist/jsx/components/DataTable/DataTableCardControls.native.js.map +1 -1
  183. package/dist/jsx/components/DataTable/DataTableRoot.mjs +33 -6
  184. package/dist/jsx/components/DataTable/DataTableRoot.mjs.map +1 -1
  185. package/dist/jsx/components/DataTable/DataTableRoot.native.js +29 -4
  186. package/dist/jsx/components/DataTable/DataTableRoot.native.js.map +1 -1
  187. package/dist/jsx/components/DataTable/DataTableStateContext.mjs.map +1 -1
  188. package/dist/jsx/components/DataTable/DataTableStateContext.native.js.map +1 -1
  189. package/dist/jsx/components/DataTableBulkActions/index.mjs +0 -4
  190. package/dist/jsx/components/DataTableBulkActions/index.mjs.map +1 -1
  191. package/dist/jsx/components/DataTableBulkActions/index.native.js +0 -4
  192. package/dist/jsx/components/DataTableBulkActions/index.native.js.map +1 -1
  193. package/dist/jsx/components/DataTableCell/index.mjs +17 -2
  194. package/dist/jsx/components/DataTableCell/index.mjs.map +1 -1
  195. package/dist/jsx/components/DataTableCell/index.native.js +16 -1
  196. package/dist/jsx/components/DataTableCell/index.native.js.map +1 -1
  197. package/dist/jsx/components/DataTableDateFilter/index.mjs +0 -7
  198. package/dist/jsx/components/DataTableDateFilter/index.mjs.map +1 -1
  199. package/dist/jsx/components/DataTableDateFilter/index.native.js +0 -7
  200. package/dist/jsx/components/DataTableDateFilter/index.native.js.map +1 -1
  201. package/dist/jsx/components/DataTableFilterButton/index.mjs +0 -1
  202. package/dist/jsx/components/DataTableFilterButton/index.mjs.map +1 -1
  203. package/dist/jsx/components/DataTableFilterButton/index.native.js +0 -1
  204. package/dist/jsx/components/DataTableFilterChips/FilterChip.mjs +3 -2
  205. package/dist/jsx/components/DataTableFilterChips/FilterChip.mjs.map +1 -1
  206. package/dist/jsx/components/DataTableFilterChips/FilterChip.native.js +4 -1
  207. package/dist/jsx/components/DataTableFilterChips/FilterChip.native.js.map +1 -1
  208. package/dist/jsx/components/DataTableFilters/index.mjs +0 -1
  209. package/dist/jsx/components/DataTableFilters/index.mjs.map +1 -1
  210. package/dist/jsx/components/DataTableFilters/index.native.js +0 -1
  211. package/dist/jsx/components/DataTableGrouping/index.mjs +0 -8
  212. package/dist/jsx/components/DataTableGrouping/index.mjs.map +1 -1
  213. package/dist/jsx/components/DataTableGrouping/index.native.js +0 -8
  214. package/dist/jsx/components/DataTableGrouping/index.native.js.map +1 -1
  215. package/dist/jsx/components/DataTablePagination/index.mjs +30 -14
  216. package/dist/jsx/components/DataTablePagination/index.mjs.map +1 -1
  217. package/dist/jsx/components/DataTablePagination/index.native.js +30 -14
  218. package/dist/jsx/components/DataTablePagination/index.native.js.map +1 -1
  219. package/dist/jsx/components/DataTableSearch/index.mjs +0 -3
  220. package/dist/jsx/components/DataTableSearch/index.mjs.map +1 -1
  221. package/dist/jsx/components/DataTableSearch/index.native.js +0 -3
  222. package/dist/jsx/components/DataTableSearch/index.native.js.map +1 -1
  223. package/dist/jsx/components/DataTableToolbar/index.mjs +0 -1
  224. package/dist/jsx/components/DataTableToolbar/index.mjs.map +1 -1
  225. package/dist/jsx/components/DataTableToolbar/index.native.js +0 -1
  226. package/dist/jsx/components/DataTableToolbar/index.native.js.map +1 -1
  227. package/dist/jsx/components/DataTableViewOptions/index.mjs +0 -5
  228. package/dist/jsx/components/DataTableViewOptions/index.mjs.map +1 -1
  229. package/dist/jsx/components/DataTableViewOptions/index.native.js +0 -5
  230. package/dist/jsx/components/DataTableViewOptions/index.native.js.map +1 -1
  231. package/dist/jsx/components/FilterManager.mjs +0 -1
  232. package/dist/jsx/components/FilterManager.mjs.map +1 -1
  233. package/dist/jsx/components/FilterManager.native.js +0 -1
  234. package/dist/jsx/components/FilterManager.native.js.map +1 -1
  235. package/dist/jsx/components/FilterOperatorValueEditor/index.mjs +2 -2
  236. package/dist/jsx/components/FilterOperatorValueEditor/index.mjs.map +1 -1
  237. package/dist/jsx/components/FilterOperatorValueEditor/index.native.js +2 -2
  238. package/dist/jsx/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  239. package/dist/jsx/components/TableInput/index.mjs +3 -2
  240. package/dist/jsx/components/TableInput/index.mjs.map +1 -1
  241. package/dist/jsx/components/TableInput/index.native.js +4 -2
  242. package/dist/jsx/components/TableInput/index.native.js.map +1 -1
  243. package/dist/jsx/hooks/useDataTable.mjs +24 -8
  244. package/dist/jsx/hooks/useDataTable.mjs.map +1 -1
  245. package/dist/jsx/hooks/useDataTable.native.js +28 -8
  246. package/dist/jsx/hooks/useDataTable.native.js.map +1 -1
  247. package/dist/jsx/index.js +3 -1
  248. package/dist/jsx/index.js.map +1 -1
  249. package/dist/jsx/index.mjs +3 -1
  250. package/dist/jsx/index.mjs.map +1 -1
  251. package/dist/jsx/index.native.js +12 -0
  252. package/dist/jsx/index.native.js.map +1 -1
  253. package/dist/jsx/shared/devWarn.mjs +43 -0
  254. package/dist/jsx/shared/devWarn.mjs.map +1 -0
  255. package/dist/jsx/shared/devWarn.native.js +84 -0
  256. package/dist/jsx/shared/devWarn.native.js.map +1 -0
  257. package/dist/jsx/utils/columnAlign.mjs +4 -1
  258. package/dist/jsx/utils/columnAlign.mjs.map +1 -1
  259. package/dist/jsx/utils/columnAlign.native.js +4 -1
  260. package/dist/jsx/utils/columnAlign.native.js.map +1 -1
  261. package/dist/jsx/utils/filterFields.mjs +5 -0
  262. package/dist/jsx/utils/filterFields.mjs.map +1 -1
  263. package/dist/jsx/utils/filterFields.native.js +5 -0
  264. package/dist/jsx/utils/filterFields.native.js.map +1 -1
  265. package/dist/jsx/utils/filterUtils.mjs +30 -0
  266. package/dist/jsx/utils/filterUtils.mjs.map +1 -1
  267. package/dist/jsx/utils/filterUtils.native.js +50 -0
  268. package/dist/jsx/utils/filterUtils.native.js.map +1 -1
  269. package/dist/jsx/utils/money.mjs +104 -0
  270. package/dist/jsx/utils/money.mjs.map +1 -0
  271. package/dist/jsx/utils/money.native.js +165 -0
  272. package/dist/jsx/utils/money.native.js.map +1 -0
  273. package/dist/jsx/utils/rowAccumulator.mjs +23 -0
  274. package/dist/jsx/utils/rowAccumulator.mjs.map +1 -0
  275. package/dist/jsx/utils/rowAccumulator.native.js +51 -0
  276. package/dist/jsx/utils/rowAccumulator.native.js.map +1 -0
  277. package/package.json +12 -12
  278. package/src/components/ColumnHeaderFilter/ColumnHeaderFilter.spec.tsx +142 -7
  279. package/src/components/ColumnHeaderFilter/ColumnHeaderFilter.stories.tsx +46 -9
  280. package/src/components/ColumnHeaderFilter/index.tsx +104 -47
  281. package/src/components/DataTable/DataTable.stories.tsx +186 -14
  282. package/src/components/DataTable/DataTableCardControls.tsx +11 -13
  283. package/src/components/DataTable/DataTableRoot.tsx +40 -14
  284. package/src/components/DataTable/DataTableStateContext.ts +7 -0
  285. package/src/components/DataTable/columnWidthLayout.spec.tsx +195 -0
  286. package/src/components/DataTable/headerSorting.spec.tsx +25 -0
  287. package/src/components/DataTable/moneyColumn.spec.tsx +151 -0
  288. package/src/components/DataTable/rowAccumulator.spec.tsx +171 -0
  289. package/src/components/DataTableBulkActions/index.tsx +0 -4
  290. package/src/components/DataTableCell/index.tsx +28 -2
  291. package/src/components/DataTableColumnPinning/DataTableColumnPinning.stories.tsx +0 -4
  292. package/src/components/DataTableDateFilter/index.tsx +0 -7
  293. package/src/components/DataTableFilterButton/index.tsx +1 -1
  294. package/src/components/DataTableFilterChips/FilterChip.spec.tsx +6 -0
  295. package/src/components/DataTableFilterChips/FilterChip.tsx +3 -2
  296. package/src/components/DataTableFilters/index.tsx +1 -1
  297. package/src/components/DataTableGrouping/DataTableGrouping.stories.tsx +0 -4
  298. package/src/components/DataTableGrouping/index.tsx +1 -13
  299. package/src/components/DataTablePagination/DataTablePagination.stories.tsx +14 -1
  300. package/src/components/DataTablePagination/DataTablePagination.width.spec.tsx +51 -0
  301. package/src/components/DataTablePagination/index.tsx +60 -16
  302. package/src/components/DataTableSearch/index.tsx +2 -3
  303. package/src/components/DataTableToolbar/DataTableToolbar.stories.tsx +2 -4
  304. package/src/components/DataTableToolbar/index.tsx +1 -5
  305. package/src/components/DataTableViewOptions/index.tsx +2 -13
  306. package/src/components/FilterManager.tsx +1 -3
  307. package/src/components/FilterOperatorValueEditor/FilterOperatorValueEditor.spec.tsx +1 -1
  308. package/src/components/FilterOperatorValueEditor/FilterOperatorValueEditor.stories.tsx +1 -0
  309. package/src/components/FilterOperatorValueEditor/index.tsx +10 -2
  310. package/src/components/TableInput/index.native.tsx +3 -2
  311. package/src/components/TableInput/index.tsx +3 -2
  312. package/src/hooks/useDataTable.ts +43 -7
  313. package/src/index.ts +13 -0
  314. package/src/shared/devWarn.ts +97 -0
  315. package/src/types.ts +71 -2
  316. package/src/utils/columnAlign.ts +9 -1
  317. package/src/utils/filterFields.ts +23 -1
  318. package/src/utils/filterUtils.ts +50 -1
  319. package/src/utils/money.spec.ts +156 -0
  320. package/src/utils/money.ts +202 -0
  321. package/src/utils/rowAccumulator.ts +136 -0
  322. package/types/components/ColumnHeaderFilter/index.d.ts +9 -2
  323. package/types/components/ColumnHeaderFilter/index.d.ts.map +1 -1
  324. package/types/components/DataTable/DataTableCardControls.d.ts.map +1 -1
  325. package/types/components/DataTable/DataTableRoot.d.ts +1 -1
  326. package/types/components/DataTable/DataTableRoot.d.ts.map +1 -1
  327. package/types/components/DataTable/DataTableStateContext.d.ts +7 -0
  328. package/types/components/DataTable/DataTableStateContext.d.ts.map +1 -1
  329. package/types/components/DataTableBulkActions/index.d.ts.map +1 -1
  330. package/types/components/DataTableCell/index.d.ts.map +1 -1
  331. package/types/components/DataTableDateFilter/index.d.ts.map +1 -1
  332. package/types/components/DataTableFilterChips/FilterChip.d.ts.map +1 -1
  333. package/types/components/DataTableGrouping/index.d.ts.map +1 -1
  334. package/types/components/DataTablePagination/index.d.ts.map +1 -1
  335. package/types/components/DataTableSearch/index.d.ts.map +1 -1
  336. package/types/components/DataTableToolbar/index.d.ts.map +1 -1
  337. package/types/components/DataTableViewOptions/index.d.ts.map +1 -1
  338. package/types/components/FilterManager.d.ts.map +1 -1
  339. package/types/components/FilterOperatorValueEditor/index.d.ts.map +1 -1
  340. package/types/components/TableInput/index.d.ts.map +1 -1
  341. package/types/components/TableInput/index.native.d.ts.map +1 -1
  342. package/types/hooks/useDataTable.d.ts +1 -1
  343. package/types/hooks/useDataTable.d.ts.map +1 -1
  344. package/types/index.d.ts +3 -0
  345. package/types/index.d.ts.map +1 -1
  346. package/types/shared/devWarn.d.ts +50 -0
  347. package/types/shared/devWarn.d.ts.map +1 -0
  348. package/types/types.d.ts +71 -3
  349. package/types/types.d.ts.map +1 -1
  350. package/types/utils/columnAlign.d.ts.map +1 -1
  351. package/types/utils/filterFields.d.ts.map +1 -1
  352. package/types/utils/filterUtils.d.ts +1 -1
  353. package/types/utils/filterUtils.d.ts.map +1 -1
  354. package/types/utils/money.d.ts +94 -0
  355. package/types/utils/money.d.ts.map +1 -0
  356. package/types/utils/rowAccumulator.d.ts +107 -0
  357. package/types/utils/rowAccumulator.d.ts.map +1 -0
@@ -0,0 +1,195 @@
1
+ /**
2
+ * MPO-236 — per-column width under `columnLayout: "equal"`.
3
+ *
4
+ * `equal` is the default, and it flexes every cell to `flexGrow: 1,
5
+ * flexBasis: 0`. A column's `width` / `size` therefore never reaches the cell
6
+ * box: the render comes out identical to one that declared no widths at all,
7
+ * which reads as "the widths ARE applied and are not enough" and sends the
8
+ * reader looking in the wrong place.
9
+ *
10
+ * The default is not changed here — that would move every existing table's
11
+ * render. The combination warns instead, naming `columnLayout` as the reason.
12
+ */
13
+
14
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
15
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
16
+ import type { DataTableColumn } from "../../types";
17
+ import { __resetTableDevWarnSeen, warnColumnWidthIgnored } from "../../shared/devWarn";
18
+ import { DataTable } from "./index";
19
+
20
+ type Pokemon = { id: number; name: string; xp: number };
21
+
22
+ const sampleData: Pokemon[] = [
23
+ { id: 1, name: "Bulbasaur", xp: 64 },
24
+ { id: 2, name: "Charmander", xp: 62 },
25
+ ];
26
+
27
+ const plainColumns: DataTableColumn<Pokemon>[] = [
28
+ { id: "name", accessorKey: "name", header: "Name" },
29
+ { id: "xp", accessorKey: "xp", header: "XP" },
30
+ ];
31
+
32
+ const widthColumns: DataTableColumn<Pokemon>[] = [
33
+ { id: "name", accessorKey: "name", header: "Name", width: 320 },
34
+ { id: "xp", accessorKey: "xp", header: "XP", width: 80 },
35
+ ];
36
+
37
+ let warnings: string[] = [];
38
+ let warnSpy: ReturnType<typeof vi.spyOn>;
39
+
40
+ function startCapture() {
41
+ warnings = [];
42
+ __resetTableDevWarnSeen();
43
+ warnSpy = vi.spyOn(console, "warn").mockImplementation((...args) => {
44
+ warnings.push(args.map((arg) => String(arg)).join(" "));
45
+ });
46
+ }
47
+
48
+ function stopCapture() {
49
+ warnSpy.mockRestore();
50
+ __resetTableDevWarnSeen();
51
+ }
52
+
53
+ /** Only this rule's warnings — other table chrome may warn for its own reasons. */
54
+ function widthWarnings(): string[] {
55
+ return warnings.filter((line) => line.includes("column-width-ignored"));
56
+ }
57
+
58
+ /**
59
+ * React 19's client `useId` counter is module-global (`_r_<base32>_`), so two
60
+ * renders in one process never share generated ids. Normalize them out before
61
+ * comparing markup.
62
+ */
63
+ function stripGeneratedIds(html: string): string {
64
+ return html.replace(/_r_[0-9a-z]+_/g, "_r_id_");
65
+ }
66
+
67
+ /** `columnLayout` omitted on purpose in most calls: "equal" is the default. */
68
+ function renderTable(
69
+ columns: DataTableColumn<Pokemon>[],
70
+ columnLayout?: "equal" | "auto" | "scroll",
71
+ ) {
72
+ return renderWithProviders(
73
+ <DataTable
74
+ data={sampleData}
75
+ columns={columns as DataTableColumn<Record<string, any>>[]}
76
+ layout="table"
77
+ columnLayout={columnLayout}
78
+ enablePagination={false}
79
+ readOnly
80
+ />,
81
+ );
82
+ }
83
+
84
+ describe("warnColumnWidthIgnored", () => {
85
+ let prevEnv: string | undefined;
86
+
87
+ beforeEach(() => {
88
+ prevEnv = process.env.NODE_ENV;
89
+ process.env.NODE_ENV = "development";
90
+ startCapture();
91
+ });
92
+
93
+ afterEach(() => {
94
+ stopCapture();
95
+ process.env.NODE_ENV = prevEnv;
96
+ });
97
+
98
+ it("names columnLayout and the offending columns", () => {
99
+ warnColumnWidthIgnored({ columnLayout: "equal", columns: widthColumns });
100
+ expect(widthWarnings()).toHaveLength(1);
101
+ expect(widthWarnings()[0]).toContain("[table] column-width-ignored");
102
+ expect(widthWarnings()[0]).toContain("columnLayout=equal");
103
+ expect(widthWarnings()[0]).toContain("columns=name,xp");
104
+ });
105
+
106
+ it("catches the TanStack `size` spelling too", () => {
107
+ warnColumnWidthIgnored({ columnLayout: "equal", columns: [{ id: "avatar", size: 60 }] });
108
+ expect(widthWarnings()).toHaveLength(1);
109
+ expect(widthWarnings()[0]).toContain("columns=avatar");
110
+ });
111
+
112
+ it("falls back to accessorKey, then position, for the column name", () => {
113
+ warnColumnWidthIgnored({
114
+ columnLayout: "equal",
115
+ columns: [{ accessorKey: "xp", width: 80 }, { size: 60 }],
116
+ });
117
+ expect(widthWarnings()[0]).toContain("columns=xp,#1");
118
+ });
119
+
120
+ it("stays quiet when no column declares a width", () => {
121
+ warnColumnWidthIgnored({ columnLayout: "equal", columns: plainColumns });
122
+ expect(widthWarnings()).toHaveLength(0);
123
+ });
124
+
125
+ it.each(["auto", "scroll"] as const)("stays quiet under columnLayout=%s", (columnLayout) => {
126
+ warnColumnWidthIgnored({ columnLayout, columns: widthColumns });
127
+ expect(widthWarnings()).toHaveLength(0);
128
+ });
129
+
130
+ it("stays quiet with enableColumnResizing, where the width does survive", () => {
131
+ warnColumnWidthIgnored({
132
+ columnLayout: "equal",
133
+ enableColumnResizing: true,
134
+ columns: widthColumns,
135
+ });
136
+ expect(widthWarnings()).toHaveLength(0);
137
+ });
138
+
139
+ it("no-ops in production", () => {
140
+ process.env.NODE_ENV = "production";
141
+ warnColumnWidthIgnored({ columnLayout: "equal", columns: widthColumns });
142
+ expect(widthWarnings()).toHaveLength(0);
143
+ });
144
+
145
+ it("warns once per offending column set, not once per call", () => {
146
+ warnColumnWidthIgnored({ columnLayout: "equal", columns: widthColumns });
147
+ warnColumnWidthIgnored({ columnLayout: "equal", columns: widthColumns });
148
+ warnColumnWidthIgnored({ columnLayout: "equal", columns: widthColumns });
149
+ expect(widthWarnings()).toHaveLength(1);
150
+ });
151
+ });
152
+
153
+ describe("DataTable columnLayout=equal with per-column widths", () => {
154
+ beforeEach(startCapture);
155
+ afterEach(stopCapture);
156
+
157
+ it("discards the widths: the markup matches a table that declared none", () => {
158
+ const withWidths = renderTable(widthColumns);
159
+ const widthMarkup = stripGeneratedIds(withWidths.container.innerHTML);
160
+ withWidths.unmount();
161
+
162
+ const withoutWidths = renderTable(plainColumns);
163
+ expect(stripGeneratedIds(withoutWidths.container.innerHTML)).toBe(widthMarkup);
164
+ });
165
+
166
+ it("warns, so the discard is not silent", () => {
167
+ renderTable(widthColumns);
168
+ expect(widthWarnings()).toHaveLength(1);
169
+ expect(widthWarnings()[0]).toContain("component=DataTable");
170
+ });
171
+
172
+ it("warns once per table, not once per render", () => {
173
+ const { rerender } = renderTable(widthColumns);
174
+ rerender(
175
+ <DataTable
176
+ data={[...sampleData, { id: 3, name: "Squirtle", xp: 63 }]}
177
+ columns={widthColumns as DataTableColumn<Record<string, any>>[]}
178
+ layout="table"
179
+ enablePagination={false}
180
+ readOnly
181
+ />,
182
+ );
183
+ expect(widthWarnings()).toHaveLength(1);
184
+ });
185
+
186
+ it("does not warn under columnLayout=auto", () => {
187
+ renderTable(widthColumns, "auto");
188
+ expect(widthWarnings()).toHaveLength(0);
189
+ });
190
+
191
+ it("does not warn for a table with no per-column widths", () => {
192
+ renderTable(plainColumns);
193
+ expect(widthWarnings()).toHaveLength(0);
194
+ });
195
+ });
@@ -130,6 +130,31 @@ describe("DataTable header sorting (web grid)", () => {
130
130
  await waitFor(() => expect(header().getAttribute("aria-sort")).toBe("none"));
131
131
  });
132
132
 
133
+ it("gives Phosphor resolved CSS colors in every sort state", async () => {
134
+ const { header } = renderTable();
135
+ for (const direction of ["none", "ascending", "descending"]) {
136
+ await waitFor(() => expect(header().getAttribute("aria-sort")).toBe(direction));
137
+ const icon = header().querySelector("svg");
138
+ expect(icon).not.toBeNull();
139
+ const ink = icon?.getAttribute("fill") || icon?.getAttribute("color") || icon?.style.color;
140
+ expect(ink).toBeTruthy();
141
+ expect(ink).not.toMatch(/\$/);
142
+ fireEvent.click(header());
143
+ }
144
+ });
145
+
146
+ it("plain click replaces another column sort and Shift+click adds to it", async () => {
147
+ const { header, container, states } = renderTable();
148
+ const ageHeader = container.querySelector("#age-header") as HTMLElement;
149
+ fireEvent.click(header());
150
+ fireEvent.click(ageHeader);
151
+ await waitFor(() => expect(states.at(-1)?.sorting.map((sort) => sort.id)).toEqual(["age"]));
152
+ fireEvent.click(header(), { shiftKey: true });
153
+ await waitFor(() =>
154
+ expect(states.at(-1)?.sorting.map((sort) => sort.id)).toEqual(["age", "firstName"]),
155
+ );
156
+ });
157
+
133
158
  it("non-sortable headers get no press affordance, no tab stop, and no aria-sort", () => {
134
159
  const { container } = renderWithProviders(
135
160
  <DataTable
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The money column type end to end (MPO-252).
3
+ *
4
+ * A Frappe Currency field arrives as a decimal STRING, and before this the
5
+ * kit had no type for that, so it fell to "text". Three consequences, all
6
+ * asserted here: sorting was silently wrong ("9.00" above "10.00"), the
7
+ * filter offered `contains` instead of `isGreaterThan`, and formatting could
8
+ * only read a column-level currency constant even though a ledger mixes
9
+ * currencies row by row.
10
+ */
11
+
12
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
13
+ import type React from "react";
14
+ import { screen, waitFor } from "@testing-library/react";
15
+ import userEvent from "@testing-library/user-event";
16
+ import { describe, expect, it } from "vitest";
17
+ import type { DataTableColumn } from "../../types";
18
+ import { applyFilterToValue } from "../../utils/filterUtils";
19
+ import { mapFieldToFilterType, operatorsForFilterType } from "../../utils/filterFields";
20
+ import { getDefaultColumnAlign } from "../../utils/columnAlign";
21
+ import { DataTable } from "./index";
22
+
23
+ interface Posting {
24
+ id: string;
25
+ payee: string;
26
+ amount: string;
27
+ currency: string;
28
+ }
29
+
30
+ const postings: Posting[] = [
31
+ { id: "P-1", payee: "Nine", amount: "9.00", currency: "USD" },
32
+ { id: "P-2", payee: "Ten", amount: "10.00", currency: "USD" },
33
+ { id: "P-3", payee: "Rupees", amount: "250.75", currency: "INR" },
34
+ { id: "P-4", payee: "Refund", amount: "-455.87", currency: "USD" },
35
+ ];
36
+
37
+ const columns: DataTableColumn<Posting>[] = [
38
+ { id: "payee", accessorKey: "payee", header: "Payee" },
39
+ {
40
+ id: "amount",
41
+ accessorKey: "amount",
42
+ header: "Amount",
43
+ field: "money",
44
+ // Safe to turn on now: the money type carries an exact comparator.
45
+ sortable: true,
46
+ fieldProps: { currencyField: "currency", locale: "en-US" },
47
+ },
48
+ ];
49
+
50
+ describe("money column type", () => {
51
+ it("maps field money → filter type money, right-aligned by default", () => {
52
+ expect(mapFieldToFilterType("money")).toBe("money");
53
+ expect(
54
+ getDefaultColumnAlign({ id: "amount", field: "money" }),
55
+ "a money column right-aligns without a column-level currency constant",
56
+ ).toBe("right");
57
+ });
58
+
59
+ it("offers the numeric filter operators, range included", () => {
60
+ const operators = operatorsForFilterType("money");
61
+ expect(operators).toContain("isLessThan");
62
+ expect(operators).toContain("isGreaterThan");
63
+ expect(operators).toContain("isBetween");
64
+ expect(operators, "text operators are what a money column used to get").not.toContain(
65
+ "contains",
66
+ );
67
+ });
68
+
69
+ it("filters numerically on decimal strings", () => {
70
+ const gt = { operator: "isGreaterThan" as const, value: "9.5" };
71
+ expect(applyFilterToValue("10.00", gt, "money")).toBe(true);
72
+ expect(applyFilterToValue("9.00", gt, "money")).toBe(false);
73
+ // The same rows through the "text" fallback are the defect: "10.00" is
74
+ // greater than "9.5" as a number and LESS as a string, so the row that
75
+ // should match is the one that gets dropped.
76
+ expect(applyFilterToValue("10.00", gt, "text")).toBe(false);
77
+
78
+ const between = { operator: "isBetween" as const, value: null, values: ["0", "100"] };
79
+ expect(applyFilterToValue("10.00", between, "money")).toBe(true);
80
+ expect(applyFilterToValue("250.75", between, "money")).toBe(false);
81
+ expect(applyFilterToValue("-455.87", between, "money")).toBe(false);
82
+
83
+ const is = { operator: "is" as const, value: "10" };
84
+ expect(applyFilterToValue("10.00", is, "money"), "scale does not change equality").toBe(true);
85
+ });
86
+
87
+ it("sorts numerically in the table, not lexically", async () => {
88
+ const user = userEvent.setup();
89
+ const { container } = renderWithProviders(
90
+ <DataTable
91
+ data={postings}
92
+ columns={columns}
93
+ layout="table"
94
+ enablePagination={false}
95
+ readOnly
96
+ />,
97
+ );
98
+ await waitFor(() => expect(screen.getByText("$9.00")).toBeTruthy());
99
+
100
+ await user.click(screen.getByText("Amount"));
101
+ await waitFor(() => {
102
+ const payees = Array.from(container.querySelectorAll("tbody tr"))
103
+ .map((row) => row.querySelector("td")?.textContent)
104
+ .filter((text) => !!text);
105
+ // Ascending: -455.87, 9.00, 10.00, 250.75. Lexically it would have been
106
+ // -455.87, 10.00, 250.75, 9.00 — with 9.00 LAST.
107
+ expect(payees).toEqual(["Refund", "Nine", "Ten", "Rupees"]);
108
+ });
109
+ });
110
+
111
+ it("formats from the ROW's currency, not only a column-level constant", async () => {
112
+ renderWithProviders(
113
+ <DataTable
114
+ data={postings}
115
+ columns={columns}
116
+ layout="table"
117
+ enablePagination={false}
118
+ readOnly
119
+ />,
120
+ );
121
+ await waitFor(() => expect(screen.getByText("$9.00")).toBeTruthy());
122
+ expect(screen.getByText("$10.00")).toBeTruthy();
123
+ expect(screen.getByText("-$455.87")).toBeTruthy();
124
+ // One row carries INR and renders in INR, in the same column, at the same
125
+ // time — two currencies are two figures and neither converts.
126
+ expect(screen.getByText("₹250.75")).toBeTruthy();
127
+ });
128
+ });
129
+
130
+ describe("the MoneyColumn story", () => {
131
+ it("renders mixed currencies and a per-currency footer total", async () => {
132
+ // The story is the documented example. Rendering it here means it cannot
133
+ // rot into something that formats one currency for every row.
134
+ const { MoneyColumn } = await import("./DataTable.stories");
135
+ renderWithProviders(<>{(MoneyColumn.render as () => React.ReactNode)()}</>);
136
+
137
+ await waitFor(() => expect(screen.getByText("Coffee roaster")).toBeTruthy());
138
+ expect(screen.getByText("$9.00")).toBeTruthy();
139
+ expect(screen.getByText("₹250.75")).toBeTruthy();
140
+ // Two totals, not one: USD 9.00 + 10.00 - 455.87 + 9.99, INR 250.75 +
141
+ // 1200.00. Nothing converts between them.
142
+ expect(screen.getByText("-$426.88")).toBeTruthy();
143
+ expect(screen.getByText("₹1,450.75")).toBeTruthy();
144
+ for (const total of ["-$426.88", "₹1,450.75"]) {
145
+ expect(screen.getByText(total).closest("td")?.getAttribute("data-align")).toBe("right");
146
+ const content = screen.getByText(total).closest('[data-cell-align="right"]') as HTMLElement;
147
+ expect(content).toBeTruthy();
148
+ expect(content.style.textAlign).toBe("right");
149
+ }
150
+ });
151
+ });
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Row accumulator — a running balance across pages (MPO-250).
3
+ *
4
+ * The claim under test: a column can be a function of the row AND every row
5
+ * before it, the fold runs BEFORE pagination, and page two therefore
6
+ * continues rather than restarting from zero. That is what no table in the
7
+ * kit could do — `data` is the only channel and a `cell` renderer sees one
8
+ * row — and it is the default column in every register, statement and ledger.
9
+ */
10
+
11
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
12
+ import { screen, waitFor } from "@testing-library/react";
13
+ import userEvent from "@testing-library/user-event";
14
+ import { describe, expect, it } from "vitest";
15
+ import type { DataTableColumn, RowAccumulator } from "../../types";
16
+ import { accumulateRows } from "../../utils/rowAccumulator";
17
+ import { DataTable } from "./index";
18
+
19
+ interface Posting {
20
+ id: string;
21
+ date: string;
22
+ payee: string;
23
+ /** Cents — integers, so this spec asserts the FOLD, not decimal arithmetic. */
24
+ amount: number;
25
+ /** Derived by the accumulator. Declared here so a column can name it. */
26
+ running?: number;
27
+ }
28
+
29
+ /** Twelve postings of +100 each: the running balance is 100, 200, … 1200. */
30
+ const postings: Posting[] = Array.from({ length: 12 }, (_, i) => ({
31
+ id: `P-${String(i + 1).padStart(2, "0")}`,
32
+ date: `2026-01-${String(i + 1).padStart(2, "0")}`,
33
+ payee: `Payee ${String.fromCharCode(90 - i)}`, // Z, Y, X … — reverse of date order
34
+ amount: 100,
35
+ }));
36
+
37
+ const runningBalance: RowAccumulator<Posting, number> = {
38
+ initial: 0,
39
+ reduce: (carried, row) => carried + row.amount,
40
+ derive: (carried) => ({ running: carried }),
41
+ order: (a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0),
42
+ total: (carried) => carried,
43
+ };
44
+
45
+ const columns: DataTableColumn<Posting>[] = [
46
+ { id: "id", accessorKey: "id", header: "ID" },
47
+ { id: "payee", accessorKey: "payee", header: "Payee", sortable: true },
48
+ { id: "amount", accessorKey: "amount", header: "Amount" },
49
+ // Not sortable: sorting a fold column reorders it without refolding, so the
50
+ // figures stop reading down the column as a progression. Rendered with a
51
+ // marker so an assertion can tell a BALANCE of 100 from an AMOUNT of 100.
52
+ {
53
+ id: "running",
54
+ accessorKey: "running",
55
+ header: "Balance",
56
+ sortable: false,
57
+ cell: ({ row }) => `bal ${row.original.running}`,
58
+ },
59
+ ];
60
+
61
+ describe("accumulateRows", () => {
62
+ it("folds in the declared order and returns the rows in it", () => {
63
+ const shuffled = [...postings].reverse();
64
+ const { rows, total } = accumulateRows(shuffled, runningBalance);
65
+ expect(rows.map((r) => r.id)).toEqual(postings.map((p) => p.id));
66
+ expect(rows.map((r) => r.running)).toEqual([
67
+ 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200,
68
+ ]);
69
+ expect(total, "the fold read once at the end is the footer total").toBe(1200);
70
+ });
71
+
72
+ it("keeps the input array identity when there is no accumulator", () => {
73
+ const { rows, total } = accumulateRows(postings);
74
+ expect(rows).toBe(postings);
75
+ expect(total).toBeUndefined();
76
+ });
77
+
78
+ it("calls a factory seed once per fold, so a mutable seed cannot leak", () => {
79
+ const perCurrency: RowAccumulator<Posting, Record<string, number>> = {
80
+ initial: () => ({}),
81
+ reduce: (carried, row) => {
82
+ carried["USD"] = (carried["USD"] ?? 0) + row.amount;
83
+ return carried;
84
+ },
85
+ derive: (carried) => ({ running: carried["USD"] }),
86
+ };
87
+ const first = accumulateRows(postings, perCurrency);
88
+ const second = accumulateRows(postings, perCurrency);
89
+ expect(first.rows[11]!.running).toBe(1200);
90
+ expect(second.rows[11]!.running, "the second fold starts from a fresh seed").toBe(1200);
91
+ });
92
+ });
93
+
94
+ describe("DataTable rowAccumulator", () => {
95
+ it("continues the running balance onto page two instead of restarting", async () => {
96
+ const user = userEvent.setup();
97
+ renderWithProviders(
98
+ <DataTable
99
+ data={postings}
100
+ columns={columns}
101
+ rowAccumulator={runningBalance}
102
+ layout="table"
103
+ initialState={{ pagination: { pageIndex: 0, pageSize: 5 } }}
104
+ />,
105
+ );
106
+
107
+ // Page one: the fold's first five values.
108
+ await waitFor(() => expect(screen.getByText("P-01")).toBeTruthy());
109
+ expect(screen.getByText("bal 100")).toBeTruthy();
110
+ expect(screen.getByText("bal 500")).toBeTruthy();
111
+ expect(screen.queryByText("bal 600")).toBeNull();
112
+
113
+ // Page two CONTINUES. Before MPO-250 the only way to get a balance column
114
+ // was a per-row cell renderer, which would have restarted at 100 here.
115
+ await user.click(screen.getByLabelText("Go to next page"));
116
+ await waitFor(() => expect(screen.getByText("P-06")).toBeTruthy());
117
+ expect(screen.getByText("bal 600"), "page two starts where page one ended").toBeTruthy();
118
+ expect(screen.getByText("bal 1000")).toBeTruthy();
119
+ expect(screen.queryByText("bal 100"), "page one's first balance is gone").toBeNull();
120
+ });
121
+
122
+ it("does not refold when the user sorts on another column", async () => {
123
+ const user = userEvent.setup();
124
+ renderWithProviders(
125
+ <DataTable
126
+ data={postings}
127
+ columns={columns}
128
+ rowAccumulator={runningBalance}
129
+ layout="table"
130
+ initialState={{ pagination: { pageIndex: 0, pageSize: 12 } }}
131
+ />,
132
+ );
133
+ await waitFor(() => expect(screen.getByText("P-01")).toBeTruthy());
134
+
135
+ // Payee runs backwards against date, so a payee sort puts the LAST
136
+ // posting first. Its balance stays 1200 — the fold order is the date
137
+ // order it was computed in, and sorting reorders rows for display only.
138
+ await user.click(screen.getByText("Payee"));
139
+ await waitFor(() => expect(screen.getByText("bal 1200")).toBeTruthy());
140
+ const rendered = screen.getAllByText(/^bal \d+$/).map((el) => el.textContent);
141
+ expect(rendered, "every folded figure is still on screen, attached to its own row").toContain(
142
+ "bal 1200",
143
+ );
144
+ expect(rendered).toContain("bal 100");
145
+ });
146
+ });
147
+
148
+ describe("the RunningBalance story", () => {
149
+ it("renders the register across two pages with figures that continue", async () => {
150
+ // The story is the documented example (AC: MPO-250). Rendering it here
151
+ // means it cannot rot into something that does not paginate.
152
+ const { RunningBalance } = await import("./DataTable.stories");
153
+ const user = userEvent.setup();
154
+ renderWithProviders(<>{(RunningBalance.render as () => React.ReactNode)()}</>);
155
+
156
+ await waitFor(() => expect(screen.getByText("Opening deposit")).toBeTruthy());
157
+ // Page one is P-01..P-05 and ends on Utilities: 2500.00 - 1800.00 - 84.50
158
+ // + 4123.00 - 146.00.
159
+ expect(screen.getByText("$4,592.50")).toBeTruthy();
160
+ expect(screen.queryByText("$4,573.75"), "page two's first balance is not here yet").toBeNull();
161
+
162
+ await user.click(screen.getByLabelText("Go to next page"));
163
+ await waitFor(() => expect(screen.getByText("Coffee roaster")).toBeTruthy());
164
+ // Page two picks the fold up rather than restarting: -18.75 on 4592.50.
165
+ expect(screen.getByText("$4,573.75"), "page two continues the fold").toBeTruthy();
166
+ expect(screen.getByText("$5,005.85")).toBeTruthy();
167
+ // The footer total is the same fold, read once at the end — every posting,
168
+ // not just this page.
169
+ expect(screen.getAllByText("$6,078.85").length).toBeGreaterThan(0);
170
+ });
171
+ });
@@ -159,7 +159,6 @@ export function DataTableBulkActions<T = any>(props: DataTableBulkActionsProps<T
159
159
  {showSelectAllMatching && (
160
160
  <Button
161
161
  size={knobProps.sizeToken}
162
- variant="outlined"
163
162
  onPress={() => ctx?.table.toggleAllRowsSelected(true)}
164
163
  aria-label={`Select all ${matchingCount} matching rows`}
165
164
  {...knobProps.borderRadius}
@@ -173,7 +172,6 @@ export function DataTableBulkActions<T = any>(props: DataTableBulkActionsProps<T
173
172
  <Button
174
173
  key={action.id}
175
174
  size={knobProps.sizeToken}
176
- variant="outlined"
177
175
  theme={action.variant === "destructive" ? "red" : undefined}
178
176
  disabled={action.disabled}
179
177
  // DG-ST-03: reason renders via the Button explain affordance;
@@ -199,7 +197,6 @@ export function DataTableBulkActions<T = any>(props: DataTableBulkActionsProps<T
199
197
 
200
198
  <Button
201
199
  size={knobProps.sizeToken}
202
- variant="outlined"
203
200
  onPress={onClearSelection}
204
201
  aria-label="Clear selection"
205
202
  {...knobProps.borderRadius}
@@ -265,7 +262,6 @@ function BulkActionsOverflowMenu<T = any>({
265
262
  >
266
263
  <Button
267
264
  size={knobProps.sizeToken}
268
- variant="outlined"
269
265
  icon={<DotsThreeIcon size={iconPx} />}
270
266
  aria-label="More bulk actions"
271
267
  {...knobProps.borderRadius}
@@ -53,9 +53,10 @@ import { Star } from "@phosphor-icons/react";
53
53
  import { RichTextEditor } from "@multiplatform.one/rich-text";
54
54
  import { useResolvedKnobs, ensureCompositeFocusRing } from "@multiplatform.one/theme";
55
55
  import React, { useCallback, useEffect, useRef, useState } from "react";
56
- import { View, isWeb } from "tamagui";
56
+ import { Text, View, isWeb } from "tamagui";
57
57
  import type { DataTableFieldProps, DataTableField } from "../../types";
58
58
  import { alignToFlex, getDefaultColumnAlign } from "../../utils/columnAlign";
59
+ import { formatMoney } from "../../utils/money";
59
60
  import { t } from "../../shared/t";
60
61
 
61
62
  export interface DataTableCellProps {
@@ -125,7 +126,9 @@ export function DataTableCell({
125
126
  max,
126
127
  step,
127
128
  precision,
128
- currency: _currency,
129
+ currency,
130
+ currencyField,
131
+ locale,
129
132
  defaultCountry,
130
133
  otpLength,
131
134
  mentions,
@@ -171,6 +174,29 @@ export function DataTableCell({
171
174
  );
172
175
  }
173
176
 
177
+ // A money cell is DISPLAY, in both modes. A Currency field is a decimal
178
+ // STRING (a float would round it) and a grid row is not where a ledger
179
+ // figure gets retyped — money is edited through the form surfaces (MPO-252).
180
+ //
181
+ // Formatting reads the ROW's own currency first, because a ledger genuinely
182
+ // mixes currencies row by row; the column-level `currency` constant is the
183
+ // fallback for rows carrying none. Nothing here converts between codes —
184
+ // two currencies are two figures. See utils/money.ts.
185
+ if (field === "money") {
186
+ const rowCurrency = currencyField
187
+ ? (rowData as Record<string, unknown> | undefined)?.[currencyField]
188
+ : undefined;
189
+ const resolvedCurrency =
190
+ typeof rowCurrency === "string" && rowCurrency ? rowCurrency : currency;
191
+ return (
192
+ <CellWrapper justify={cellJustify}>
193
+ <Text numberOfLines={1} aria-label={cellName}>
194
+ {formatMoney(value, resolvedCurrency, locale)}
195
+ </Text>
196
+ </CellWrapper>
197
+ );
198
+ }
199
+
174
200
  // Read-only cells route through FieldDisplay (lightweight, view-only) rather
175
201
  // than mounting the full Edit component with disabled={true}. This keeps
176
202
  // large tables performant and gives heavy fields a purpose-built compact
@@ -227,7 +227,6 @@ function BasicPinningExample() {
227
227
  onPress={() => pinning.togglePin(header.column.id, "left")}
228
228
  disabled={!isPinned && position !== "left" && !pinning.canPin("left")}
229
229
  circular
230
- variant={position === "left" ? "outlined" : undefined}
231
230
  aria-label={
232
231
  isPinned && position === "left"
233
232
  ? `Unpin ${headerLabel} column`
@@ -240,7 +239,6 @@ function BasicPinningExample() {
240
239
  onPress={() => pinning.togglePin(header.column.id, "right")}
241
240
  disabled={!isPinned && position !== "right" && !pinning.canPin("right")}
242
241
  circular
243
- variant={position === "right" ? "outlined" : undefined}
244
242
  aria-label={
245
243
  isPinned && position === "right"
246
244
  ? `Unpin ${headerLabel} column`
@@ -483,7 +481,6 @@ export const MaxPinnedColumns: Story = {
483
481
  size="$2"
484
482
  onPress={() => pinning.togglePin(column.id, "left")}
485
483
  disabled={!pinning.isPinned(column.id) && !pinning.canPin("left")}
486
- variant={pinning.getPinPosition(column.id) === "left" ? "outlined" : undefined}
487
484
  >
488
485
  {pinning.getPinPosition(column.id) === "left" ? "Unpin" : "Pin Left"}
489
486
  </Button>
@@ -491,7 +488,6 @@ export const MaxPinnedColumns: Story = {
491
488
  size="$2"
492
489
  onPress={() => pinning.togglePin(column.id, "right")}
493
490
  disabled={!pinning.isPinned(column.id) && !pinning.canPin("right")}
494
- variant={pinning.getPinPosition(column.id) === "right" ? "outlined" : undefined}
495
491
  >
496
492
  {pinning.getPinPosition(column.id) === "right" ? "Unpin" : "Pin Right"}
497
493
  </Button>