@multiplatform.one/table 7.10.0 → 7.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/README.md +61 -1
  2. package/dist/cjs/components/ChildTable/index.cjs +8 -2
  3. package/dist/cjs/components/ChildTable/index.native.js +8 -2
  4. package/dist/cjs/components/ChildTable/index.native.js.map +1 -1
  5. package/dist/cjs/components/ColumnHeaderFilter/index.cjs +77 -111
  6. package/dist/cjs/components/ColumnHeaderFilter/index.native.js +89 -129
  7. package/dist/cjs/components/ColumnHeaderFilter/index.native.js.map +1 -1
  8. package/dist/cjs/components/DataTable/DataTableCardControls.cjs +90 -54
  9. package/dist/cjs/components/DataTable/DataTableCardControls.native.js +93 -56
  10. package/dist/cjs/components/DataTable/DataTableCardControls.native.js.map +1 -1
  11. package/dist/cjs/components/DataTable/DataTableRoot.cjs +68 -9
  12. package/dist/cjs/components/DataTable/DataTableRoot.native.js +78 -11
  13. package/dist/cjs/components/DataTable/DataTableRoot.native.js.map +1 -1
  14. package/dist/cjs/components/DataTableCell/CellModalEditor.cjs +72 -70
  15. package/dist/cjs/components/DataTableCell/index.cjs +2 -2
  16. package/dist/cjs/components/DataTableCell/index.native.js +2 -11
  17. package/dist/cjs/components/DataTableCell/index.native.js.map +1 -1
  18. package/dist/cjs/components/DataTableDateFilter/index.cjs +135 -158
  19. package/dist/cjs/components/DataTableDateFilter/index.native.js +153 -183
  20. package/dist/cjs/components/DataTableDateFilter/index.native.js.map +1 -1
  21. package/dist/cjs/components/DataTableExport/index.cjs +122 -139
  22. package/dist/cjs/components/DataTableExport/index.native.js +135 -157
  23. package/dist/cjs/components/DataTableExport/index.native.js.map +1 -1
  24. package/dist/cjs/components/DataTableFilterButton/index.cjs +14 -0
  25. package/dist/cjs/components/DataTableFilterButton/index.native.js +15 -0
  26. package/dist/cjs/components/DataTableFilterButton/index.native.js.map +1 -1
  27. package/dist/cjs/components/DataTableGrouping/index.cjs +192 -212
  28. package/dist/cjs/components/DataTableGrouping/index.native.js +209 -230
  29. package/dist/cjs/components/DataTableGrouping/index.native.js.map +1 -1
  30. package/dist/cjs/components/DataTableSearch/index.cjs +103 -115
  31. package/dist/cjs/components/DataTableSearch/index.native.js +111 -123
  32. package/dist/cjs/components/DataTableSearch/index.native.js.map +1 -1
  33. package/dist/cjs/components/DataTableToolbar/index.cjs +161 -101
  34. package/dist/cjs/components/DataTableToolbar/index.native.js +168 -109
  35. package/dist/cjs/components/DataTableToolbar/index.native.js.map +1 -1
  36. package/dist/cjs/components/DataTableViewOptions/index.cjs +38 -54
  37. package/dist/cjs/components/DataTableViewOptions/index.native.js +40 -56
  38. package/dist/cjs/components/DataTableViewOptions/index.native.js.map +1 -1
  39. package/dist/cjs/components/FilterOperatorValueEditor/index.cjs +3 -11
  40. package/dist/cjs/components/FilterOperatorValueEditor/index.native.js +3 -11
  41. package/dist/cjs/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  42. package/dist/cjs/components/SimpleTable/index.cjs +2 -6
  43. package/dist/cjs/components/SimpleTable/index.native.js +2 -6
  44. package/dist/cjs/components/SimpleTable/index.native.js.map +1 -1
  45. package/dist/cjs/components/TableInput/RowDetailEditor.cjs +190 -0
  46. package/dist/cjs/components/TableInput/RowDetailEditor.native.js +172 -0
  47. package/dist/cjs/components/TableInput/RowDetailEditor.native.js.map +1 -0
  48. package/dist/cjs/components/TableInput/index.cjs +42 -5
  49. package/dist/cjs/components/TableInput/index.native.js +64 -1
  50. package/dist/cjs/components/TableInput/index.native.js.map +1 -1
  51. package/dist/cjs/components/TableInput/shared.cjs +8 -0
  52. package/dist/cjs/components/TableInput/shared.native.js +10 -0
  53. package/dist/cjs/components/TableInput/shared.native.js.map +1 -1
  54. package/dist/cjs/hooks/useDataTable.cjs +43 -2
  55. package/dist/cjs/hooks/useDataTable.native.js +49 -3
  56. package/dist/cjs/hooks/useDataTable.native.js.map +1 -1
  57. package/dist/cjs/index.native.js.map +1 -1
  58. package/dist/cjs/utils/rowPress.cjs +4 -1
  59. package/dist/cjs/utils/rowPress.native.js +4 -1
  60. package/dist/cjs/utils/rowPress.native.js.map +1 -1
  61. package/dist/esm/components/ChildTable/index.mjs +8 -2
  62. package/dist/esm/components/ChildTable/index.mjs.map +1 -1
  63. package/dist/esm/components/ChildTable/index.native.js +8 -2
  64. package/dist/esm/components/ChildTable/index.native.js.map +1 -1
  65. package/dist/esm/components/ColumnHeaderFilter/index.mjs +80 -114
  66. package/dist/esm/components/ColumnHeaderFilter/index.mjs.map +1 -1
  67. package/dist/esm/components/ColumnHeaderFilter/index.native.js +92 -132
  68. package/dist/esm/components/ColumnHeaderFilter/index.native.js.map +1 -1
  69. package/dist/esm/components/DataTable/DataTableCardControls.mjs +89 -54
  70. package/dist/esm/components/DataTable/DataTableCardControls.mjs.map +1 -1
  71. package/dist/esm/components/DataTable/DataTableCardControls.native.js +92 -56
  72. package/dist/esm/components/DataTable/DataTableCardControls.native.js.map +1 -1
  73. package/dist/esm/components/DataTable/DataTableRoot.mjs +70 -11
  74. package/dist/esm/components/DataTable/DataTableRoot.mjs.map +1 -1
  75. package/dist/esm/components/DataTable/DataTableRoot.native.js +80 -13
  76. package/dist/esm/components/DataTable/DataTableRoot.native.js.map +1 -1
  77. package/dist/esm/components/DataTableCell/CellModalEditor.mjs +76 -74
  78. package/dist/esm/components/DataTableCell/CellModalEditor.mjs.map +1 -1
  79. package/dist/esm/components/DataTableCell/index.mjs +3 -3
  80. package/dist/esm/components/DataTableCell/index.mjs.map +1 -1
  81. package/dist/esm/components/DataTableCell/index.native.js +3 -12
  82. package/dist/esm/components/DataTableCell/index.native.js.map +1 -1
  83. package/dist/esm/components/DataTableDateFilter/index.mjs +137 -160
  84. package/dist/esm/components/DataTableDateFilter/index.mjs.map +1 -1
  85. package/dist/esm/components/DataTableDateFilter/index.native.js +155 -185
  86. package/dist/esm/components/DataTableDateFilter/index.native.js.map +1 -1
  87. package/dist/esm/components/DataTableExport/index.mjs +124 -141
  88. package/dist/esm/components/DataTableExport/index.mjs.map +1 -1
  89. package/dist/esm/components/DataTableExport/index.native.js +137 -159
  90. package/dist/esm/components/DataTableExport/index.native.js.map +1 -1
  91. package/dist/esm/components/DataTableFilterButton/index.mjs +14 -1
  92. package/dist/esm/components/DataTableFilterButton/index.mjs.map +1 -1
  93. package/dist/esm/components/DataTableFilterButton/index.native.js +15 -1
  94. package/dist/esm/components/DataTableFilterButton/index.native.js.map +1 -1
  95. package/dist/esm/components/DataTableGrouping/index.mjs +194 -214
  96. package/dist/esm/components/DataTableGrouping/index.mjs.map +1 -1
  97. package/dist/esm/components/DataTableGrouping/index.native.js +211 -232
  98. package/dist/esm/components/DataTableGrouping/index.native.js.map +1 -1
  99. package/dist/esm/components/DataTableSearch/index.mjs +105 -117
  100. package/dist/esm/components/DataTableSearch/index.mjs.map +1 -1
  101. package/dist/esm/components/DataTableSearch/index.native.js +113 -125
  102. package/dist/esm/components/DataTableSearch/index.native.js.map +1 -1
  103. package/dist/esm/components/DataTableToolbar/index.mjs +165 -106
  104. package/dist/esm/components/DataTableToolbar/index.mjs.map +1 -1
  105. package/dist/esm/components/DataTableToolbar/index.native.js +172 -114
  106. package/dist/esm/components/DataTableToolbar/index.native.js.map +1 -1
  107. package/dist/esm/components/DataTableViewOptions/index.mjs +40 -56
  108. package/dist/esm/components/DataTableViewOptions/index.mjs.map +1 -1
  109. package/dist/esm/components/DataTableViewOptions/index.native.js +42 -58
  110. package/dist/esm/components/DataTableViewOptions/index.native.js.map +1 -1
  111. package/dist/esm/components/FilterOperatorValueEditor/index.mjs +3 -11
  112. package/dist/esm/components/FilterOperatorValueEditor/index.mjs.map +1 -1
  113. package/dist/esm/components/FilterOperatorValueEditor/index.native.js +3 -11
  114. package/dist/esm/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  115. package/dist/esm/components/SimpleTable/index.mjs +3 -7
  116. package/dist/esm/components/SimpleTable/index.mjs.map +1 -1
  117. package/dist/esm/components/SimpleTable/index.native.js +3 -7
  118. package/dist/esm/components/SimpleTable/index.native.js.map +1 -1
  119. package/dist/esm/components/TableInput/RowDetailEditor.mjs +165 -0
  120. package/dist/esm/components/TableInput/RowDetailEditor.mjs.map +1 -0
  121. package/dist/esm/components/TableInput/RowDetailEditor.native.js +144 -0
  122. package/dist/esm/components/TableInput/RowDetailEditor.native.js.map +1 -0
  123. package/dist/esm/components/TableInput/index.mjs +42 -5
  124. package/dist/esm/components/TableInput/index.mjs.map +1 -1
  125. package/dist/esm/components/TableInput/index.native.js +64 -1
  126. package/dist/esm/components/TableInput/index.native.js.map +1 -1
  127. package/dist/esm/components/TableInput/shared.mjs +7 -1
  128. package/dist/esm/components/TableInput/shared.mjs.map +1 -1
  129. package/dist/esm/components/TableInput/shared.native.js +9 -1
  130. package/dist/esm/components/TableInput/shared.native.js.map +1 -1
  131. package/dist/esm/hooks/useDataTable.mjs +43 -2
  132. package/dist/esm/hooks/useDataTable.mjs.map +1 -1
  133. package/dist/esm/hooks/useDataTable.native.js +49 -3
  134. package/dist/esm/hooks/useDataTable.native.js.map +1 -1
  135. package/dist/esm/index.mjs.map +1 -1
  136. package/dist/esm/index.native.js.map +1 -1
  137. package/dist/esm/utils/rowPress.mjs +4 -1
  138. package/dist/esm/utils/rowPress.mjs.map +1 -1
  139. package/dist/esm/utils/rowPress.native.js +4 -1
  140. package/dist/esm/utils/rowPress.native.js.map +1 -1
  141. package/dist/jsx/components/ChildTable/index.mjs +8 -2
  142. package/dist/jsx/components/ChildTable/index.mjs.map +1 -1
  143. package/dist/jsx/components/ChildTable/index.native.js +8 -2
  144. package/dist/jsx/components/ChildTable/index.native.js.map +1 -1
  145. package/dist/jsx/components/ColumnHeaderFilter/index.mjs +80 -114
  146. package/dist/jsx/components/ColumnHeaderFilter/index.mjs.map +1 -1
  147. package/dist/jsx/components/ColumnHeaderFilter/index.native.js +89 -129
  148. package/dist/jsx/components/ColumnHeaderFilter/index.native.js.map +1 -1
  149. package/dist/jsx/components/DataTable/DataTableCardControls.mjs +89 -54
  150. package/dist/jsx/components/DataTable/DataTableCardControls.mjs.map +1 -1
  151. package/dist/jsx/components/DataTable/DataTableCardControls.native.js +93 -56
  152. package/dist/jsx/components/DataTable/DataTableCardControls.native.js.map +1 -1
  153. package/dist/jsx/components/DataTable/DataTableRoot.mjs +70 -11
  154. package/dist/jsx/components/DataTable/DataTableRoot.mjs.map +1 -1
  155. package/dist/jsx/components/DataTable/DataTableRoot.native.js +78 -11
  156. package/dist/jsx/components/DataTable/DataTableRoot.native.js.map +1 -1
  157. package/dist/jsx/components/DataTableCell/CellModalEditor.mjs +76 -74
  158. package/dist/jsx/components/DataTableCell/CellModalEditor.mjs.map +1 -1
  159. package/dist/jsx/components/DataTableCell/index.mjs +3 -3
  160. package/dist/jsx/components/DataTableCell/index.mjs.map +1 -1
  161. package/dist/jsx/components/DataTableCell/index.native.js +2 -11
  162. package/dist/jsx/components/DataTableCell/index.native.js.map +1 -1
  163. package/dist/jsx/components/DataTableDateFilter/index.mjs +137 -160
  164. package/dist/jsx/components/DataTableDateFilter/index.mjs.map +1 -1
  165. package/dist/jsx/components/DataTableDateFilter/index.native.js +153 -183
  166. package/dist/jsx/components/DataTableDateFilter/index.native.js.map +1 -1
  167. package/dist/jsx/components/DataTableExport/index.mjs +124 -141
  168. package/dist/jsx/components/DataTableExport/index.mjs.map +1 -1
  169. package/dist/jsx/components/DataTableExport/index.native.js +135 -157
  170. package/dist/jsx/components/DataTableExport/index.native.js.map +1 -1
  171. package/dist/jsx/components/DataTableFilterButton/index.mjs +14 -1
  172. package/dist/jsx/components/DataTableFilterButton/index.mjs.map +1 -1
  173. package/dist/jsx/components/DataTableFilterButton/index.native.js +15 -0
  174. package/dist/jsx/components/DataTableFilterButton/index.native.js.map +1 -1
  175. package/dist/jsx/components/DataTableGrouping/index.mjs +194 -214
  176. package/dist/jsx/components/DataTableGrouping/index.mjs.map +1 -1
  177. package/dist/jsx/components/DataTableGrouping/index.native.js +209 -230
  178. package/dist/jsx/components/DataTableGrouping/index.native.js.map +1 -1
  179. package/dist/jsx/components/DataTableSearch/index.mjs +105 -117
  180. package/dist/jsx/components/DataTableSearch/index.mjs.map +1 -1
  181. package/dist/jsx/components/DataTableSearch/index.native.js +111 -123
  182. package/dist/jsx/components/DataTableSearch/index.native.js.map +1 -1
  183. package/dist/jsx/components/DataTableToolbar/index.mjs +165 -106
  184. package/dist/jsx/components/DataTableToolbar/index.mjs.map +1 -1
  185. package/dist/jsx/components/DataTableToolbar/index.native.js +168 -109
  186. package/dist/jsx/components/DataTableToolbar/index.native.js.map +1 -1
  187. package/dist/jsx/components/DataTableViewOptions/index.mjs +40 -56
  188. package/dist/jsx/components/DataTableViewOptions/index.mjs.map +1 -1
  189. package/dist/jsx/components/DataTableViewOptions/index.native.js +40 -56
  190. package/dist/jsx/components/DataTableViewOptions/index.native.js.map +1 -1
  191. package/dist/jsx/components/FilterOperatorValueEditor/index.mjs +3 -11
  192. package/dist/jsx/components/FilterOperatorValueEditor/index.mjs.map +1 -1
  193. package/dist/jsx/components/FilterOperatorValueEditor/index.native.js +3 -11
  194. package/dist/jsx/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  195. package/dist/jsx/components/SimpleTable/index.mjs +3 -7
  196. package/dist/jsx/components/SimpleTable/index.mjs.map +1 -1
  197. package/dist/jsx/components/SimpleTable/index.native.js +2 -6
  198. package/dist/jsx/components/SimpleTable/index.native.js.map +1 -1
  199. package/dist/jsx/components/TableInput/RowDetailEditor.mjs +165 -0
  200. package/dist/jsx/components/TableInput/RowDetailEditor.mjs.map +1 -0
  201. package/dist/jsx/components/TableInput/RowDetailEditor.native.js +172 -0
  202. package/dist/jsx/components/TableInput/RowDetailEditor.native.js.map +1 -0
  203. package/dist/jsx/components/TableInput/index.mjs +42 -5
  204. package/dist/jsx/components/TableInput/index.mjs.map +1 -1
  205. package/dist/jsx/components/TableInput/index.native.js +64 -1
  206. package/dist/jsx/components/TableInput/index.native.js.map +1 -1
  207. package/dist/jsx/components/TableInput/shared.mjs +7 -1
  208. package/dist/jsx/components/TableInput/shared.mjs.map +1 -1
  209. package/dist/jsx/components/TableInput/shared.native.js +10 -0
  210. package/dist/jsx/components/TableInput/shared.native.js.map +1 -1
  211. package/dist/jsx/hooks/useDataTable.mjs +43 -2
  212. package/dist/jsx/hooks/useDataTable.mjs.map +1 -1
  213. package/dist/jsx/hooks/useDataTable.native.js +49 -3
  214. package/dist/jsx/hooks/useDataTable.native.js.map +1 -1
  215. package/dist/jsx/index.js.map +1 -1
  216. package/dist/jsx/index.mjs.map +1 -1
  217. package/dist/jsx/index.native.js.map +1 -1
  218. package/dist/jsx/utils/rowPress.mjs +4 -1
  219. package/dist/jsx/utils/rowPress.mjs.map +1 -1
  220. package/dist/jsx/utils/rowPress.native.js +4 -1
  221. package/dist/jsx/utils/rowPress.native.js.map +1 -1
  222. package/package.json +16 -16
  223. package/src/components/ChildTable/childTable.cellHidden.spec.tsx +57 -0
  224. package/src/components/ChildTable/childTable.rowDetail.spec.tsx +120 -0
  225. package/src/components/ChildTable/index.tsx +16 -0
  226. package/src/components/ColumnHeaderFilter/index.tsx +89 -116
  227. package/src/components/DataTable/DataTable.stories.tsx +130 -6
  228. package/src/components/DataTable/DataTableCardControls.stories.tsx +61 -2
  229. package/src/components/DataTable/DataTableCardControls.tsx +92 -66
  230. package/src/components/DataTable/DataTableRoot.tsx +109 -18
  231. package/src/components/DataTable/cardLayout.spec.tsx +4 -3
  232. package/src/components/DataTable/cardToolbar.spec.tsx +238 -0
  233. package/src/components/DataTable/chromeInterplay.spec.tsx +7 -5
  234. package/src/components/DataTable/columnWidthLayout.spec.tsx +189 -0
  235. package/src/components/DataTable/dataTableCardControls.spec.tsx +3 -2
  236. package/src/components/DataTable/dataTableMobileCards.spec.tsx +1 -4
  237. package/src/components/DataTable/footerLayout.spec.tsx +250 -0
  238. package/src/components/DataTable/headerContainment.spec.tsx +91 -0
  239. package/src/components/DataTable/pinningGeometry.spec.tsx +66 -1
  240. package/src/components/DataTable/rowPress.spec.tsx +2 -6
  241. package/src/components/DataTableBulkActions/DataTableBulkActions.stories.tsx +5 -4
  242. package/src/components/DataTableCell/CellModalEditor.tsx +95 -75
  243. package/src/components/DataTableCell/cellDraftPolicy.spec.tsx +12 -2
  244. package/src/components/DataTableCell/cellModalEditor.spec.tsx +75 -49
  245. package/src/components/DataTableCell/dataTableCell.spec.tsx +31 -14
  246. package/src/components/DataTableCell/index.tsx +3 -2
  247. package/src/components/DataTableDateFilter/DataTableDateFilter.spec.tsx +6 -3
  248. package/src/components/DataTableDateFilter/index.tsx +151 -180
  249. package/src/components/DataTableExport/DataTableExport.spec.tsx +42 -18
  250. package/src/components/DataTableExport/index.tsx +135 -142
  251. package/src/components/DataTableFilterButton/index.tsx +9 -0
  252. package/src/components/DataTableGrouping/index.tsx +193 -195
  253. package/src/components/DataTableSearch/index.tsx +111 -109
  254. package/src/components/DataTableToolbar/index.tsx +179 -107
  255. package/src/components/DataTableViewOptions/DataTableViewOptions.stories.tsx +52 -0
  256. package/src/components/DataTableViewOptions/index.tsx +32 -40
  257. package/src/components/FilterOperatorValueEditor/FilterOperatorValueEditor.spec.tsx +64 -2
  258. package/src/components/FilterOperatorValueEditor/index.tsx +0 -8
  259. package/src/components/SimpleTable/index.tsx +3 -2
  260. package/src/components/SimpleTable/simpleTable.spec.tsx +15 -0
  261. package/src/components/Table/accessibility.spec.tsx +7 -19
  262. package/src/components/TableInput/RowDetailEditor.native.tsx +134 -0
  263. package/src/components/TableInput/RowDetailEditor.tsx +173 -0
  264. package/src/components/TableInput/index.native.tsx +49 -1
  265. package/src/components/TableInput/index.tsx +65 -10
  266. package/src/components/TableInput/shared.ts +29 -0
  267. package/src/components/tableDropdowns.spec.tsx +172 -0
  268. package/src/hooks/useDataTable.spec.ts +153 -0
  269. package/src/hooks/useDataTable.ts +38 -2
  270. package/src/index.ts +1 -0
  271. package/src/types.ts +14 -12
  272. package/src/utils/rowPress.ts +3 -0
  273. package/types/components/ChildTable/index.d.ts +10 -0
  274. package/types/components/ChildTable/index.d.ts.map +1 -1
  275. package/types/components/ColumnHeaderFilter/index.d.ts +1 -1
  276. package/types/components/ColumnHeaderFilter/index.d.ts.map +1 -1
  277. package/types/components/DataTable/DataTableCardControls.d.ts +3 -0
  278. package/types/components/DataTable/DataTableCardControls.d.ts.map +1 -1
  279. package/types/components/DataTable/DataTableRoot.d.ts.map +1 -1
  280. package/types/components/DataTableCell/CellModalEditor.d.ts +10 -7
  281. package/types/components/DataTableCell/CellModalEditor.d.ts.map +1 -1
  282. package/types/components/DataTableCell/index.d.ts.map +1 -1
  283. package/types/components/DataTableDateFilter/index.d.ts.map +1 -1
  284. package/types/components/DataTableFilterButton/index.d.ts +2 -1
  285. package/types/components/DataTableFilterButton/index.d.ts.map +1 -1
  286. package/types/components/DataTableGrouping/index.d.ts.map +1 -1
  287. package/types/components/DataTableSearch/index.d.ts.map +1 -1
  288. package/types/components/DataTableToolbar/index.d.ts +1 -0
  289. package/types/components/DataTableToolbar/index.d.ts.map +1 -1
  290. package/types/components/FilterOperatorValueEditor/index.d.ts.map +1 -1
  291. package/types/components/SimpleTable/index.d.ts.map +1 -1
  292. package/types/components/TableInput/RowDetailEditor.d.ts +24 -0
  293. package/types/components/TableInput/RowDetailEditor.d.ts.map +1 -0
  294. package/types/components/TableInput/RowDetailEditor.native.d.ts +11 -0
  295. package/types/components/TableInput/RowDetailEditor.native.d.ts.map +1 -0
  296. package/types/components/TableInput/index.d.ts +11 -2
  297. package/types/components/TableInput/index.d.ts.map +1 -1
  298. package/types/components/TableInput/index.native.d.ts +8 -1
  299. package/types/components/TableInput/index.native.d.ts.map +1 -1
  300. package/types/components/TableInput/shared.d.ts +11 -0
  301. package/types/components/TableInput/shared.d.ts.map +1 -1
  302. package/types/hooks/useDataTable.d.ts.map +1 -1
  303. package/types/index.d.ts +1 -0
  304. package/types/index.d.ts.map +1 -1
  305. package/types/types.d.ts +14 -12
  306. package/types/types.d.ts.map +1 -1
  307. package/types/utils/rowPress.d.ts.map +1 -1
@@ -11,12 +11,34 @@
11
11
  * render. The combination warns instead, naming `columnLayout` as the reason.
12
12
  */
13
13
 
14
+ import { fireEvent, waitFor } from "@testing-library/react";
14
15
  import { renderWithProviders } from "@multiplatform.one/test-utils";
15
16
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
16
17
  import type { DataTableColumn } from "../../types";
17
18
  import { __resetTableDevWarnSeen, warnColumnWidthIgnored } from "../../shared/devWarn";
18
19
  import { DataTable } from "./index";
19
20
 
21
+ vi.mock("@tanstack/react-virtual", async (importOriginal) => {
22
+ const actual = await importOriginal<typeof import("@tanstack/react-virtual")>();
23
+ return {
24
+ ...actual,
25
+ useVirtualizer: (options: Parameters<typeof actual.useVirtualizer>[0]) =>
26
+ actual.useVirtualizer({
27
+ ...options,
28
+ initialRect: { width: 800, height: 240 },
29
+ observeElementRect: (_instance, callback) => {
30
+ callback({ width: 800, height: 240 });
31
+ return () => {};
32
+ },
33
+ observeElementOffset: (_instance, callback) => {
34
+ callback(0, false);
35
+ return () => {};
36
+ },
37
+ scrollToFn: () => {},
38
+ }),
39
+ };
40
+ });
41
+
20
42
  type Pokemon = { id: number; name: string; xp: number };
21
43
 
22
44
  const sampleData: Pokemon[] = [
@@ -193,3 +215,170 @@ describe("DataTable columnLayout=equal with per-column widths", () => {
193
215
  expect(widthWarnings()).toHaveLength(0);
194
216
  });
195
217
  });
218
+
219
+ function hasSizingStyle(element: HTMLElement, property: string, value: string, token: string) {
220
+ return (
221
+ element.style.getPropertyValue(property) === value || String(element.className).includes(token)
222
+ );
223
+ }
224
+
225
+ function expectCellWidth(element: HTMLElement | null, width: number) {
226
+ expect(element).not.toBeNull();
227
+ expect(hasSizingStyle(element!, "width", `${width}px`, `_w-${width}px`)).toBe(true);
228
+ }
229
+
230
+ describe("DataTable declared column sizes", () => {
231
+ beforeEach(startCapture);
232
+ afterEach(stopCapture);
233
+
234
+ it.each(["alias", "canonical"])(
235
+ "applies %s widths to scroll headers, body and footer below the old floor",
236
+ (spelling) => {
237
+ const sizingColumns = plainColumns.map((column, index) => ({
238
+ ...column,
239
+ [spelling === "alias" ? "width" : "size"]: index === 0 ? 320 : 48,
240
+ footer: index === 0 ? "Totals" : "126",
241
+ }));
242
+ const { container } = renderTable(sizingColumns, "scroll");
243
+ expectCellWidth(container.querySelector("#name-header"), 320);
244
+ expectCellWidth(container.querySelector("#xp-header"), 48);
245
+ const cells = container.querySelectorAll('[aria-label="Table data"] [role="cell"]');
246
+ expect(cells.length).toBeGreaterThanOrEqual(4);
247
+ expectCellWidth(cells[0] as HTMLElement, 320);
248
+ expectCellWidth(cells[1] as HTMLElement, 48);
249
+ expect(hasSizingStyle(cells[1] as HTMLElement, "min-width", "0px", "_miw-0px")).toBe(true);
250
+ const footer = container.querySelectorAll('tfoot [role="cell"]');
251
+ expect(footer).toHaveLength(2);
252
+ expectCellWidth(footer[0] as HTMLElement, 320);
253
+ expectCellWidth(footer[1] as HTMLElement, 48);
254
+ expect(widthWarnings()).toHaveLength(0);
255
+ },
256
+ );
257
+
258
+ it("gives unpinned equal cells the same flex policy for canonical and alias declarations", () => {
259
+ const canonical = plainColumns.map((column, index) => ({
260
+ ...column,
261
+ size: index === 0 ? 320 : 80,
262
+ }));
263
+ const withSizes = renderTable(canonical);
264
+ const sizeMarkup = stripGeneratedIds(withSizes.container.innerHTML);
265
+ withSizes.unmount();
266
+ const withoutSizes = renderTable(plainColumns);
267
+ expect(stripGeneratedIds(withoutSizes.container.innerHTML)).toBe(sizeMarkup);
268
+ });
269
+
270
+ it("lets resizing use aliases in equal mode while preserving the fixed flex basis", () => {
271
+ const { container } = renderWithProviders(
272
+ <DataTable
273
+ data={sampleData}
274
+ columns={widthColumns}
275
+ columnLayout="equal"
276
+ layout="table"
277
+ enableColumnResizing
278
+ enablePagination={false}
279
+ readOnly
280
+ />,
281
+ );
282
+ const header = container.querySelector("#name-header") as HTMLElement;
283
+ expectCellWidth(header, 320);
284
+ expect(hasSizingStyle(header, "flex-basis", "auto", "_fb-auto")).toBe(true);
285
+ expect(hasSizingStyle(header, "flex-grow", "0", "_fg-0")).toBe(true);
286
+ expect(widthWarnings()).toHaveLength(0);
287
+ });
288
+
289
+ it("does not inflate a 48px resizable equal-layout cell with the primitive width floor", () => {
290
+ const { container } = renderWithProviders(
291
+ <DataTable
292
+ data={sampleData}
293
+ columns={[{ accessorKey: "xp", header: "XP", width: 48 }]}
294
+ columnLayout="equal"
295
+ layout="table"
296
+ enableColumnResizing
297
+ enablePagination={false}
298
+ readOnly
299
+ />,
300
+ );
301
+ const header = container.querySelector("#xp-header") as HTMLElement;
302
+ expectCellWidth(header, 48);
303
+ expect(hasSizingStyle(header, "min-width", "0px", "_miw-0px")).toBe(true);
304
+ });
305
+
306
+ it("passes size inputs to auto layout without changing its table algorithm", () => {
307
+ const { container } = renderTable(widthColumns, "auto");
308
+ expectCellWidth(container.querySelector("#name-header"), 320);
309
+ expect((container.querySelector('[role="table"]') as HTMLElement).style.tableLayout).toBe(
310
+ "fixed",
311
+ );
312
+ });
313
+
314
+ it("resizes an aliased column through the real header handle within its bounds", () => {
315
+ const columns = widthColumns.map((column) => ({ ...column, minWidth: 50, maxWidth: 350 }));
316
+ const { container, getByRole } = renderWithProviders(
317
+ <DataTable
318
+ data={sampleData}
319
+ columns={columns}
320
+ columnLayout="scroll"
321
+ layout="table"
322
+ enableColumnResizing
323
+ enablePagination={false}
324
+ readOnly
325
+ />,
326
+ );
327
+ const handle = getByRole("separator", { name: "Resize Name column" });
328
+ expect(handle.getAttribute("aria-valuenow")).toBe("320");
329
+ fireEvent.mouseDown(handle, { clientX: 100 });
330
+ fireEvent.mouseMove(document, { clientX: 120 });
331
+ fireEvent.mouseUp(document, { clientX: 120 });
332
+ expectCellWidth(container.querySelector("#name-header"), 340);
333
+ fireEvent.mouseDown(handle, { clientX: 100 });
334
+ fireEvent.mouseMove(document, { clientX: 300 });
335
+ fireEvent.mouseUp(document, { clientX: 300 });
336
+ expectCellWidth(container.querySelector("#name-header"), 350);
337
+ expect(handle.getAttribute("aria-valuenow")).toBe("350");
338
+ const cell = container.querySelector<HTMLElement>('[aria-label="Table data"] [role="cell"]');
339
+ expectCellWidth(cell, 350);
340
+ });
341
+
342
+ it("exposes the normalized size to the view-options display on mount", async () => {
343
+ const { getByText } = renderWithProviders(
344
+ <DataTable
345
+ data={sampleData}
346
+ columns={widthColumns}
347
+ columnLayout="scroll"
348
+ layout="table"
349
+ enableColumnResizing
350
+ enablePagination={false}
351
+ readOnly
352
+ >
353
+ <DataTable.ViewOptions showColumnResizing showColumnVisibility={false} />
354
+ </DataTable>,
355
+ );
356
+ await waitFor(() => expect(getByText("320px")).toBeDefined());
357
+ expect(getByText("80px")).toBeDefined();
358
+ });
359
+
360
+ it("uses the real virtual row renderer with the same declared widths", async () => {
361
+ const { container } = renderWithProviders(
362
+ <DataTable
363
+ data={sampleData}
364
+ columns={widthColumns}
365
+ columnLayout="scroll"
366
+ layout="table"
367
+ enableVirtualization
368
+ virtualizationHeight={240}
369
+ enablePagination={false}
370
+ readOnly
371
+ />,
372
+ );
373
+ await waitFor(() => {
374
+ const virtualBody = container.querySelector('tbody[role="presentation"] [role="rowgroup"]');
375
+ expect(virtualBody).not.toBeNull();
376
+ expect(virtualBody!.querySelectorAll('[role="row"]')).toHaveLength(2);
377
+ const cells = virtualBody!.querySelectorAll('[role="cell"]');
378
+ expect(cells.length).toBeGreaterThanOrEqual(4);
379
+ expectCellWidth(cells[0] as HTMLElement, 320);
380
+ expectCellWidth(cells[1] as HTMLElement, 80);
381
+ });
382
+ expectCellWidth(container.querySelector("#name-header"), 320);
383
+ });
384
+ });
@@ -42,7 +42,7 @@ function renderCards(props: Record<string, any> = {}) {
42
42
  describe("DataTableCardControls", () => {
43
43
  it("is a labelled toolbar above the card list", () => {
44
44
  renderCards({ enableSorting: true, enableFiltering: true });
45
- const toolbar = screen.getByRole("toolbar", { name: "Card list controls" });
45
+ const toolbar = screen.getByRole("toolbar", { name: "Table toolbar" });
46
46
  expect(toolbar).toBeTruthy();
47
47
  expect(screen.getByLabelText("Sort rows")).toBeTruthy();
48
48
  });
@@ -61,7 +61,8 @@ describe("DataTableCardControls", () => {
61
61
  it("filtering only: the filter chrome is there, the sort menu is not", () => {
62
62
  renderCards({ enableSorting: false, enableFiltering: true });
63
63
  expect(screen.queryByLabelText("Sort rows")).toBeNull();
64
- expect(screen.getByText("Add Filter")).toBeTruthy();
64
+ expect(screen.getByRole("button", { name: "Filter" })).toBeTruthy();
65
+ expect(screen.queryByText("Add Filter")).toBeNull();
65
66
  });
66
67
 
67
68
  it("with no sort applied the trigger reads the neutral label", () => {
@@ -102,12 +102,9 @@ describe("DataTableMobileCards", () => {
102
102
  });
103
103
 
104
104
  it("a control keeps a row of its own and prints no label", () => {
105
- // Explicit id on BOTH columns: DataTable seeds columnOrder from
106
- // `col.id || ""`, so an id-less accessor column beside an id'd one would
107
- // put `_actions` first and make the control the card title instead.
108
105
  const { container } = renderCards({
109
106
  columns: [
110
- { id: "firstName", accessorKey: "firstName", header: "First Name" },
107
+ { accessorKey: "firstName", header: "First Name" },
111
108
  { id: "_actions", accessorKey: "lastName", header: "Actions", meta: { role: "actions" } },
112
109
  ],
113
110
  });
@@ -0,0 +1,250 @@
1
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
2
+ import { Preset } from "@multiplatform.one/theme";
3
+ import { fireEvent, waitFor } from "@testing-library/react";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import type { DataTableColumn } from "../../types";
6
+ import { DataTable } from "./index";
7
+
8
+ // Happy DOM has no viewport measurements. Keep the actual virtualizer and row renderer.
9
+ vi.mock("@tanstack/react-virtual", async (importOriginal) => {
10
+ const actual = await importOriginal<typeof import("@tanstack/react-virtual")>();
11
+ return {
12
+ ...actual,
13
+ useVirtualizer: (options: Parameters<typeof actual.useVirtualizer>[0]) =>
14
+ actual.useVirtualizer({
15
+ ...options,
16
+ initialRect: { width: 800, height: 240 },
17
+ observeElementRect: (_instance, callback) => {
18
+ callback({ width: 800, height: 240 });
19
+ return () => {};
20
+ },
21
+ observeElementOffset: (_instance, callback) => {
22
+ callback(0, false);
23
+ return () => {};
24
+ },
25
+ scrollToFn: () => {},
26
+ }),
27
+ };
28
+ });
29
+
30
+ const data = [{ id: "1", account: "Account 1", balance: "10.00" }];
31
+ const columns: DataTableColumn<(typeof data)[number]>[] = [
32
+ { accessorKey: "account", header: "Account", size: 320, footer: "Totals" },
33
+ { accessorKey: "balance", header: "Balance", size: 80, footer: "10.00" },
34
+ ];
35
+ const allAuxiliaries = {
36
+ enableRowSelection: true,
37
+ showRowNumbers: true,
38
+ enableRowExpansion: true,
39
+ showDeleteRow: true,
40
+ };
41
+
42
+ function renderTable(props = {}, size: "small" | "medium" | "large" = "medium") {
43
+ return renderWithProviders(
44
+ <Preset overrides={{ size }}>
45
+ <DataTable
46
+ data={data}
47
+ columns={columns}
48
+ layout="table"
49
+ columnLayout="scroll"
50
+ enablePagination={false}
51
+ {...props}
52
+ />
53
+ </Preset>,
54
+ );
55
+ }
56
+
57
+ function cells(row: Element): HTMLElement[] {
58
+ return [...row.children].filter((element) =>
59
+ ["cell", "columnheader"].includes(element.getAttribute("role") ?? ""),
60
+ ) as HTMLElement[];
61
+ }
62
+
63
+ function width(cell: HTMLElement): number {
64
+ return Number.parseFloat(
65
+ cell.style.width || /(?:^| )_w-([\d.]+)px(?: |$)/.exec(cell.className)?.[1] || "NaN",
66
+ );
67
+ }
68
+
69
+ function rows(container: HTMLElement) {
70
+ const header = container.querySelector('thead [role="row"]')!;
71
+ const body = container.querySelector('[aria-label="Row 1"]')!;
72
+ const footer = container.querySelector('tfoot [role="row"]')!;
73
+ expect(header).not.toBeNull();
74
+ expect(body).not.toBeNull();
75
+ expect(footer).not.toBeNull();
76
+ return { header: cells(header), body: cells(body), footer: cells(footer) };
77
+ }
78
+
79
+ function expectPinned(cell: HTMLElement, side: "start" | "end", offset: number) {
80
+ expect(cell.style.position).toBe("sticky");
81
+ expect(cell.style[side === "start" ? "insetInlineStart" : "insetInlineEnd"]).toBe(`${offset}px`);
82
+ expect(cell.style.zIndex).toBe("2");
83
+ expect(cell.style.backgroundColor).toBe("var(--color2)");
84
+ }
85
+
86
+ let previousDirection: string | null;
87
+ beforeEach(() => {
88
+ previousDirection = document.documentElement.getAttribute("dir");
89
+ });
90
+ afterEach(() => {
91
+ if (previousDirection === null) document.documentElement.removeAttribute("dir");
92
+ else document.documentElement.setAttribute("dir", previousDirection);
93
+ });
94
+
95
+ describe.each(["ltr", "rtl"])("footer layout in %s", (direction) => {
96
+ beforeEach(() => document.documentElement.setAttribute("dir", direction));
97
+
98
+ it.each([
99
+ { name: "no auxiliaries", props: {}, leading: 0, trailing: 0 },
100
+ { name: "selection", props: { enableRowSelection: true }, leading: 1, trailing: 0 },
101
+ { name: "row numbers", props: { showRowNumbers: true }, leading: 1, trailing: 0 },
102
+ { name: "expansion", props: { enableRowExpansion: true }, leading: 1, trailing: 0 },
103
+ { name: "delete", props: { showDeleteRow: true }, leading: 0, trailing: 1 },
104
+ { name: "all auxiliaries", props: allAuxiliaries, leading: 3, trailing: 1 },
105
+ {
106
+ name: "read-only auxiliaries",
107
+ props: { ...allAuxiliaries, readOnly: true },
108
+ leading: 3,
109
+ trailing: 0,
110
+ },
111
+ {
112
+ name: "read-only delete suppressed",
113
+ props: { showDeleteRow: true, readOnly: true },
114
+ leading: 0,
115
+ trailing: 0,
116
+ },
117
+ {
118
+ name: "compact auxiliaries",
119
+ props: { ...allAuxiliaries, compact: true },
120
+ leading: 3,
121
+ trailing: 1,
122
+ },
123
+ ])("matches resolved header/body slots with $name", ({ props, leading, trailing }) => {
124
+ const { container } = renderTable(props);
125
+ const row = rows(container);
126
+ const headerWidths = row.header.map(width);
127
+ expect(row.header).toHaveLength(leading + 2 + trailing);
128
+ expect(headerWidths.every((value) => Number.isFinite(value) && value > 0)).toBe(true);
129
+ expect(headerWidths.slice(leading, leading + 2)).toEqual([320, 80]);
130
+ expect(row.body.map(width)).toEqual(headerWidths);
131
+ expect(row.footer.map(width)).toEqual(headerWidths);
132
+ expect(row.footer[leading]).toHaveTextContent("Totals");
133
+ expect(row.footer[leading + 1]).toHaveTextContent("10.00");
134
+ const auxiliaryCells = [...row.footer.slice(0, leading), ...row.footer.slice(leading + 2)];
135
+ for (const cell of auxiliaryCells) {
136
+ expect(cell.textContent).toBe("");
137
+ expect(cell.tabIndex).toBe(-1);
138
+ expect(
139
+ cell.querySelector('button, input, [role="button"], [role="checkbox"], [tabindex]'),
140
+ ).toBeNull();
141
+ }
142
+ });
143
+
144
+ it.each(["left", "right"] as const)(
145
+ "resolves pinned %s footer paint like header/body",
146
+ async (side) => {
147
+ const utils = renderTable({ enableColumnPinning: true });
148
+ fireEvent.click(utils.getByLabelText("Pin Account column left"));
149
+ if (side === "right") fireEvent.click(utils.getByLabelText("Pin Account column right"));
150
+ await waitFor(() =>
151
+ expect(
152
+ (utils.container.querySelector("#account-header") as HTMLElement).style.position,
153
+ ).toBe("sticky"),
154
+ );
155
+ const row = rows(utils.container);
156
+ const index = row.header.findIndex((cell) => cell.id === "account-header");
157
+ const logicalSide = side === "left" ? "start" : "end";
158
+ expectPinned(row.header[index], logicalSide, 0);
159
+ expectPinned(row.body[index], logicalSide, 0);
160
+ expectPinned(row.footer[index], logicalSide, 0);
161
+ expect(row.footer[1 - index].style.position).not.toBe("sticky");
162
+ },
163
+ );
164
+
165
+ it("follows resolved auxiliary widths at small and large preset sizes", () => {
166
+ const widthsBySize = ["small", "large"].map((size) => {
167
+ const utils = renderTable(allAuxiliaries, size as "small" | "large");
168
+ const row = rows(utils.container);
169
+ const headerWidths = row.header.map(width);
170
+ expect(headerWidths).toHaveLength(6);
171
+ expect(headerWidths.slice(3, 5)).toEqual([320, 80]);
172
+ expect(row.body.map(width)).toEqual(headerWidths);
173
+ expect(row.footer.map(width)).toEqual(headerWidths);
174
+ utils.unmount();
175
+ return headerWidths;
176
+ });
177
+ for (const index of [0, 1, 2, 5]) {
178
+ expect(widthsBySize[0][index]).toBeLessThan(widthsBySize[1][index]);
179
+ }
180
+ });
181
+
182
+ it.each([false, true])(
183
+ "reserves auxiliary flow around both pin sides, readOnly=%s",
184
+ async (readOnly) => {
185
+ const utils = renderTable({ ...allAuxiliaries, readOnly, enableColumnPinning: true });
186
+ fireEvent.click(utils.getByLabelText("Pin Account column left"));
187
+ fireEvent.click(utils.getByLabelText("Pin Balance column left"));
188
+ fireEvent.click(utils.getByLabelText("Pin Balance column right"));
189
+ await waitFor(() => expect(rows(utils.container).header[3].style.position).toBe("sticky"));
190
+ const row = rows(utils.container);
191
+ const headerWidths = row.header.map(width);
192
+ const leadingWidth = headerWidths.slice(0, 3).reduce((sum, value) => sum + value, 0);
193
+ const trailingWidth = readOnly ? 0 : headerWidths[5];
194
+ expect(leadingWidth).toBeGreaterThan(0);
195
+ expect(row.body.map(width)).toEqual(headerWidths);
196
+ expect(row.footer.map(width)).toEqual(headerWidths);
197
+ for (const section of [row.header, row.body, row.footer]) {
198
+ expectPinned(section[3], "start", leadingWidth);
199
+ expectPinned(section[4], "end", trailingWidth);
200
+ }
201
+ },
202
+ );
203
+
204
+ it("keeps actual virtual rows and auxiliary footer slots aligned after pinned resize", async () => {
205
+ const utils = renderWithProviders(
206
+ <DataTable
207
+ data={[
208
+ { id: "1", rank: 1, label: "Record 1" },
209
+ { id: "2", rank: 2, label: "Record 2" },
210
+ ]}
211
+ columns={[
212
+ { accessorKey: "rank", header: "Rank", size: 48, minSize: 32, maxSize: 64, footer: "2" },
213
+ { accessorKey: "label", header: "Label", size: 320, footer: "Total" },
214
+ ]}
215
+ layout="table"
216
+ columnLayout="scroll"
217
+ enableColumnResizing
218
+ enableColumnPinning
219
+ enableVirtualization
220
+ virtualizationHeight={240}
221
+ enablePagination={false}
222
+ {...allAuxiliaries}
223
+ />,
224
+ );
225
+ const virtualRows = () =>
226
+ utils.container.querySelectorAll('tbody[role="presentation"] [role="rowgroup"] [role="row"]');
227
+ await waitFor(() => expect(virtualRows()).toHaveLength(2));
228
+ fireEvent.click(utils.getByLabelText("Pin Rank column left"));
229
+ fireEvent.click(utils.getByLabelText("Pin Label column left"));
230
+ const header = () => cells(utils.container.querySelector('thead [role="row"]')!);
231
+ const leadingWidth = header()
232
+ .slice(0, 3)
233
+ .reduce((sum, cell) => sum + width(cell), 0);
234
+ expect(width(header()[3])).toBe(48);
235
+ expectPinned(header()[4], "start", leadingWidth + 48);
236
+ const handle = utils.getByRole("separator", { name: "Resize Rank column" });
237
+ fireEvent.mouseDown(handle, { clientX: 100 });
238
+ fireEvent.mouseMove(document, { clientX: 130 });
239
+ fireEvent.mouseUp(document, { clientX: 130 });
240
+ expect(width(header()[3])).toBe(64);
241
+ expectPinned(header()[4], "start", leadingWidth + 64);
242
+ const footer = cells(utils.container.querySelector('tfoot [role="row"]')!);
243
+ expect(virtualRows()).toHaveLength(2);
244
+ for (const section of [...virtualRows()].map(cells).concat([footer])) {
245
+ expect(section.map(width)).toEqual(header().map(width));
246
+ expectPinned(section[3], "start", leadingWidth);
247
+ expectPinned(section[4], "start", leadingWidth + 64);
248
+ }
249
+ });
250
+ });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * MPO-336 / LC-10 FRAME-CONTAINMENT at the header grain.
3
+ *
4
+ * A header label wider than its column painted over the next column: the
5
+ * label run would not shrink below its max-content width, so a start-aligned
6
+ * label pushed its sort and filter glyphs into the neighbour and an
7
+ * end-aligned (number) label spilled left over the previous column. The
8
+ * label is now the part that yields: it shrinks to the column and wraps,
9
+ * breaking a long word if it must, while the glyphs keep their size.
10
+ *
11
+ * happy-dom does no layout, so this pins the flex contract that produces the
12
+ * containment. The painted geometry is measured in Storybook by the LC-10
13
+ * probe (`table-datatable--narrow-headers`, scripts/storybook-rubric).
14
+ */
15
+
16
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
17
+ import { describe, expect, it } from "vitest";
18
+ import type { DataTableColumn } from "../../types";
19
+ import { DataTable } from "./index";
20
+
21
+ type Row = { id: string; account: string; quantity: number };
22
+
23
+ const rows: Row[] = [
24
+ { id: "1", account: "acme-website", quantity: 142 },
25
+ { id: "2", account: "tamagui-docs", quantity: 89 },
26
+ ];
27
+
28
+ const columns: DataTableColumn<Row>[] = [
29
+ { id: "account", accessorKey: "account", header: "Customer account (input)", width: 96 },
30
+ {
31
+ id: "quantity",
32
+ accessorKey: "quantity",
33
+ header: "Quantity (stepper)",
34
+ align: "right",
35
+ width: 96,
36
+ },
37
+ ];
38
+
39
+ function style(el: Element, prop: "flexShrink" | "minWidth" | "overflowWrap"): string {
40
+ const inline = (el as HTMLElement).style[prop];
41
+ return inline || getComputedStyle(el)[prop];
42
+ }
43
+
44
+ function renderNarrow() {
45
+ return renderWithProviders(
46
+ <DataTable<Row>
47
+ data={rows}
48
+ columns={columns}
49
+ layout="table"
50
+ columnLayout="scroll"
51
+ enableSorting
52
+ enableFiltering
53
+ enablePagination={false}
54
+ readOnly
55
+ />,
56
+ );
57
+ }
58
+
59
+ describe("DataTable header labels stay inside their column (MPO-336)", () => {
60
+ it.each(["account", "quantity"])("the %s label shrinks and wraps", (id) => {
61
+ const { container } = renderNarrow();
62
+ const header = container.querySelector(`#${id}-header`) as HTMLElement;
63
+ expect(header).toBeTruthy();
64
+ const label = header.querySelector("[data-header-label]");
65
+ expect(label, "header label run is marked").not.toBeNull();
66
+ expect(style(label!, "flexShrink")).toBe("1");
67
+ expect(style(label!, "minWidth")).toMatch(/^0(px)?$/);
68
+ expect(style(label!, "overflowWrap")).toBe("anywhere");
69
+ // Every ancestor between the label and the cell yields too, or the
70
+ // label's min-content width comes back through its parent.
71
+ for (let node = label!.parentElement; node && node !== header; node = node.parentElement) {
72
+ if (getComputedStyle(node).display === "contents") continue;
73
+ expect(style(node, "minWidth"), "ancestor min-width").toMatch(/^0(px)?$/);
74
+ }
75
+ });
76
+
77
+ it("the sort and filter glyphs keep their size", () => {
78
+ const { container } = renderNarrow();
79
+ const header = container.querySelector("#account-header") as HTMLElement;
80
+ const label = header.querySelector("[data-header-label]")!;
81
+ const glyphs = [...header.querySelectorAll("svg")];
82
+ expect(glyphs.length).toBeGreaterThanOrEqual(2);
83
+ for (const svg of glyphs) {
84
+ let node: Element | null = svg;
85
+ while (node && node.parentElement && !node.parentElement.contains(label)) {
86
+ node = node.parentElement;
87
+ }
88
+ expect(style(node!, "flexShrink"), "glyph slot").toBe("0");
89
+ }
90
+ });
91
+ });
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { renderWithProviders } from "@multiplatform.one/test-utils";
18
18
  import { fireEvent, waitFor } from "@testing-library/react";
19
- import { describe, expect, it, vi } from "vitest";
19
+ import { afterEach, describe, expect, it, vi } from "vitest";
20
20
  import type { DataTableColumn } from "../../types";
21
21
  import { DataTable } from "./index";
22
22
 
@@ -142,3 +142,68 @@ describe("column pinning geometry", () => {
142
142
  });
143
143
  });
144
144
  });
145
+
146
+ afterEach(() => document.documentElement.removeAttribute("dir"));
147
+
148
+ it.each(["ltr", "rtl"])(
149
+ "keeps declared small pinned boxes and logical offsets aligned in %s",
150
+ async (direction) => {
151
+ document.documentElement.setAttribute("dir", direction);
152
+ const utils = renderPinned({
153
+ columns: columns.map((column, index) => ({
154
+ ...column,
155
+ footer: String(column.header),
156
+ width: index === 0 ? 48 : 320,
157
+ })),
158
+ });
159
+ await pinIdColumn(utils);
160
+ fireEvent.click(utils.getByLabelText("Pin First Name column left"));
161
+ await waitFor(() => {
162
+ const first = utils.container.querySelector("#id-header") as HTMLElement;
163
+ const second = utils.container.querySelector("#firstName-header") as HTMLElement;
164
+ expect(hasStyle(first, "width", "48px", "_w-48px")).toBe(true);
165
+ expect(hasStyle(first, "minWidth", "48px", "_miw-48px")).toBe(true);
166
+ expect(hasStyle(first, "maxWidth", "48px", "_maw-48px")).toBe(true);
167
+ expect(second.style.insetInlineStart).toBe("48px");
168
+ const body = utils.container.querySelector('[role="row"][aria-label="Row 1"]')!;
169
+ const cells = body.querySelectorAll('[role="cell"]');
170
+ expect(hasStyle(cells[0] as HTMLElement, "width", "48px", "_w-48px")).toBe(true);
171
+ expect((cells[1] as HTMLElement).style.insetInlineStart).toBe("48px");
172
+ const footer = utils.container.querySelectorAll('tfoot [role="cell"]');
173
+ expect(hasStyle(footer[0] as HTMLElement, "width", "48px", "_w-48px")).toBe(true);
174
+ expect((footer[1] as HTMLElement).style.insetInlineStart).toBe("48px");
175
+ });
176
+ },
177
+ );
178
+
179
+ it.each(["ltr", "rtl"])(
180
+ "aligns right-pinned headers, rows and footers in %s",
181
+ async (direction) => {
182
+ document.documentElement.setAttribute("dir", direction);
183
+ const utils = renderPinned({
184
+ columns: columns.map((column, index) => ({
185
+ ...column,
186
+ footer: String(column.header),
187
+ width: index === 3 ? 76 : 320,
188
+ })),
189
+ });
190
+ for (const label of ["Last Name", "Age"]) {
191
+ fireEvent.click(utils.getByLabelText(`Pin ${label} column left`));
192
+ fireEvent.click(utils.getByLabelText(`Pin ${label} column right`));
193
+ }
194
+ await waitFor(() => {
195
+ const last = utils.container.querySelector("#age-header") as HTMLElement;
196
+ const previous = utils.container.querySelector("#lastName-header") as HTMLElement;
197
+ expect(hasStyle(last, "width", "76px", "_w-76px")).toBe(true);
198
+ expect(last.style.insetInlineEnd).toBe("0px");
199
+ expect(previous.style.insetInlineEnd).toBe("76px");
200
+ const body = utils.container.querySelector('[role="row"][aria-label="Row 1"]')!;
201
+ const cells = body.querySelectorAll('[role="cell"]');
202
+ expect(hasStyle(cells[3] as HTMLElement, "width", "76px", "_w-76px")).toBe(true);
203
+ expect((cells[2] as HTMLElement).style.insetInlineEnd).toBe("76px");
204
+ const footer = utils.container.querySelectorAll('tfoot [role="cell"]');
205
+ expect(hasStyle(footer[3] as HTMLElement, "width", "76px", "_w-76px")).toBe(true);
206
+ expect((footer[2] as HTMLElement).style.insetInlineEnd).toBe("76px");
207
+ });
208
+ },
209
+ );
@@ -31,13 +31,9 @@ const sampleData: Person[] = [
31
31
  { id: 3, firstName: "Carol", age: 20 },
32
32
  ];
33
33
 
34
- // Explicit ids: DataTable seeds columnOrder from `col.id || ""`, so mixing
35
- // id-less accessor columns with an id'd display column would reorder the
36
- // display column first (and identity fallbacks would read it as the "first
37
- // visible cell").
38
34
  const columns: DataTableColumn<Person>[] = [
39
- { id: "firstName", accessorKey: "firstName", header: "First Name" },
40
- { id: "age", accessorKey: "age", header: "Age" },
35
+ { accessorKey: "firstName", header: "First Name" },
36
+ { accessorKey: "age", header: "Age" },
41
37
  ];
42
38
 
43
39
  function pressRows(container: HTMLElement): HTMLElement[] {