@multiplatform.one/table 7.11.0 → 7.16.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 (277) hide show
  1. package/README.md +1 -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/DataTableRoot.cjs +17 -1
  9. package/dist/cjs/components/DataTable/DataTableRoot.native.js +25 -2
  10. package/dist/cjs/components/DataTable/DataTableRoot.native.js.map +1 -1
  11. package/dist/cjs/components/DataTableBulkActions/index.cjs +1 -1
  12. package/dist/cjs/components/DataTableBulkActions/index.native.js +1 -1
  13. package/dist/cjs/components/DataTableBulkActions/index.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/DataTableGrouping/index.cjs +192 -212
  25. package/dist/cjs/components/DataTableGrouping/index.native.js +209 -230
  26. package/dist/cjs/components/DataTableGrouping/index.native.js.map +1 -1
  27. package/dist/cjs/components/DataTableSearch/index.cjs +103 -115
  28. package/dist/cjs/components/DataTableSearch/index.native.js +111 -123
  29. package/dist/cjs/components/DataTableSearch/index.native.js.map +1 -1
  30. package/dist/cjs/components/DataTableToolbar/index.cjs +44 -61
  31. package/dist/cjs/components/DataTableToolbar/index.native.js +46 -65
  32. package/dist/cjs/components/DataTableToolbar/index.native.js.map +1 -1
  33. package/dist/cjs/components/DataTableViewOptions/index.cjs +38 -54
  34. package/dist/cjs/components/DataTableViewOptions/index.native.js +40 -56
  35. package/dist/cjs/components/DataTableViewOptions/index.native.js.map +1 -1
  36. package/dist/cjs/components/FilterOperatorValueEditor/index.cjs +3 -11
  37. package/dist/cjs/components/FilterOperatorValueEditor/index.native.js +3 -11
  38. package/dist/cjs/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  39. package/dist/cjs/components/SimpleTable/index.cjs +2 -6
  40. package/dist/cjs/components/SimpleTable/index.native.js +2 -6
  41. package/dist/cjs/components/SimpleTable/index.native.js.map +1 -1
  42. package/dist/cjs/components/TableInput/RowDetailEditor.cjs +190 -0
  43. package/dist/cjs/components/TableInput/RowDetailEditor.native.js +172 -0
  44. package/dist/cjs/components/TableInput/RowDetailEditor.native.js.map +1 -0
  45. package/dist/cjs/components/TableInput/index.cjs +42 -5
  46. package/dist/cjs/components/TableInput/index.native.js +64 -1
  47. package/dist/cjs/components/TableInput/index.native.js.map +1 -1
  48. package/dist/cjs/components/TableInput/shared.cjs +8 -0
  49. package/dist/cjs/components/TableInput/shared.native.js +10 -0
  50. package/dist/cjs/components/TableInput/shared.native.js.map +1 -1
  51. package/dist/cjs/hooks/useDataTable.cjs +11 -1
  52. package/dist/cjs/hooks/useDataTable.native.js +12 -2
  53. package/dist/cjs/hooks/useDataTable.native.js.map +1 -1
  54. package/dist/cjs/index.native.js.map +1 -1
  55. package/dist/cjs/utils/rowPress.cjs +4 -1
  56. package/dist/cjs/utils/rowPress.native.js +4 -1
  57. package/dist/cjs/utils/rowPress.native.js.map +1 -1
  58. package/dist/esm/components/ChildTable/index.mjs +8 -2
  59. package/dist/esm/components/ChildTable/index.mjs.map +1 -1
  60. package/dist/esm/components/ChildTable/index.native.js +8 -2
  61. package/dist/esm/components/ChildTable/index.native.js.map +1 -1
  62. package/dist/esm/components/ColumnHeaderFilter/index.mjs +80 -114
  63. package/dist/esm/components/ColumnHeaderFilter/index.mjs.map +1 -1
  64. package/dist/esm/components/ColumnHeaderFilter/index.native.js +92 -132
  65. package/dist/esm/components/ColumnHeaderFilter/index.native.js.map +1 -1
  66. package/dist/esm/components/DataTable/DataTableRoot.mjs +17 -1
  67. package/dist/esm/components/DataTable/DataTableRoot.mjs.map +1 -1
  68. package/dist/esm/components/DataTable/DataTableRoot.native.js +25 -2
  69. package/dist/esm/components/DataTable/DataTableRoot.native.js.map +1 -1
  70. package/dist/esm/components/DataTableBulkActions/index.mjs +1 -1
  71. package/dist/esm/components/DataTableBulkActions/index.mjs.map +1 -1
  72. package/dist/esm/components/DataTableBulkActions/index.native.js +1 -1
  73. package/dist/esm/components/DataTableBulkActions/index.native.js.map +1 -1
  74. package/dist/esm/components/DataTableCell/CellModalEditor.mjs +76 -74
  75. package/dist/esm/components/DataTableCell/CellModalEditor.mjs.map +1 -1
  76. package/dist/esm/components/DataTableCell/index.mjs +3 -3
  77. package/dist/esm/components/DataTableCell/index.mjs.map +1 -1
  78. package/dist/esm/components/DataTableCell/index.native.js +3 -12
  79. package/dist/esm/components/DataTableCell/index.native.js.map +1 -1
  80. package/dist/esm/components/DataTableDateFilter/index.mjs +137 -160
  81. package/dist/esm/components/DataTableDateFilter/index.mjs.map +1 -1
  82. package/dist/esm/components/DataTableDateFilter/index.native.js +155 -185
  83. package/dist/esm/components/DataTableDateFilter/index.native.js.map +1 -1
  84. package/dist/esm/components/DataTableExport/index.mjs +124 -141
  85. package/dist/esm/components/DataTableExport/index.mjs.map +1 -1
  86. package/dist/esm/components/DataTableExport/index.native.js +137 -159
  87. package/dist/esm/components/DataTableExport/index.native.js.map +1 -1
  88. package/dist/esm/components/DataTableGrouping/index.mjs +194 -214
  89. package/dist/esm/components/DataTableGrouping/index.mjs.map +1 -1
  90. package/dist/esm/components/DataTableGrouping/index.native.js +211 -232
  91. package/dist/esm/components/DataTableGrouping/index.native.js.map +1 -1
  92. package/dist/esm/components/DataTableSearch/index.mjs +105 -117
  93. package/dist/esm/components/DataTableSearch/index.mjs.map +1 -1
  94. package/dist/esm/components/DataTableSearch/index.native.js +113 -125
  95. package/dist/esm/components/DataTableSearch/index.native.js.map +1 -1
  96. package/dist/esm/components/DataTableToolbar/index.mjs +46 -63
  97. package/dist/esm/components/DataTableToolbar/index.mjs.map +1 -1
  98. package/dist/esm/components/DataTableToolbar/index.native.js +48 -67
  99. package/dist/esm/components/DataTableToolbar/index.native.js.map +1 -1
  100. package/dist/esm/components/DataTableViewOptions/index.mjs +40 -56
  101. package/dist/esm/components/DataTableViewOptions/index.mjs.map +1 -1
  102. package/dist/esm/components/DataTableViewOptions/index.native.js +42 -58
  103. package/dist/esm/components/DataTableViewOptions/index.native.js.map +1 -1
  104. package/dist/esm/components/FilterOperatorValueEditor/index.mjs +3 -11
  105. package/dist/esm/components/FilterOperatorValueEditor/index.mjs.map +1 -1
  106. package/dist/esm/components/FilterOperatorValueEditor/index.native.js +3 -11
  107. package/dist/esm/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  108. package/dist/esm/components/SimpleTable/index.mjs +3 -7
  109. package/dist/esm/components/SimpleTable/index.mjs.map +1 -1
  110. package/dist/esm/components/SimpleTable/index.native.js +3 -7
  111. package/dist/esm/components/SimpleTable/index.native.js.map +1 -1
  112. package/dist/esm/components/TableInput/RowDetailEditor.mjs +165 -0
  113. package/dist/esm/components/TableInput/RowDetailEditor.mjs.map +1 -0
  114. package/dist/esm/components/TableInput/RowDetailEditor.native.js +144 -0
  115. package/dist/esm/components/TableInput/RowDetailEditor.native.js.map +1 -0
  116. package/dist/esm/components/TableInput/index.mjs +42 -5
  117. package/dist/esm/components/TableInput/index.mjs.map +1 -1
  118. package/dist/esm/components/TableInput/index.native.js +64 -1
  119. package/dist/esm/components/TableInput/index.native.js.map +1 -1
  120. package/dist/esm/components/TableInput/shared.mjs +7 -1
  121. package/dist/esm/components/TableInput/shared.mjs.map +1 -1
  122. package/dist/esm/components/TableInput/shared.native.js +9 -1
  123. package/dist/esm/components/TableInput/shared.native.js.map +1 -1
  124. package/dist/esm/hooks/useDataTable.mjs +11 -1
  125. package/dist/esm/hooks/useDataTable.mjs.map +1 -1
  126. package/dist/esm/hooks/useDataTable.native.js +12 -2
  127. package/dist/esm/hooks/useDataTable.native.js.map +1 -1
  128. package/dist/esm/index.mjs.map +1 -1
  129. package/dist/esm/index.native.js.map +1 -1
  130. package/dist/esm/utils/rowPress.mjs +4 -1
  131. package/dist/esm/utils/rowPress.mjs.map +1 -1
  132. package/dist/esm/utils/rowPress.native.js +4 -1
  133. package/dist/esm/utils/rowPress.native.js.map +1 -1
  134. package/dist/jsx/components/ChildTable/index.mjs +8 -2
  135. package/dist/jsx/components/ChildTable/index.mjs.map +1 -1
  136. package/dist/jsx/components/ChildTable/index.native.js +8 -2
  137. package/dist/jsx/components/ChildTable/index.native.js.map +1 -1
  138. package/dist/jsx/components/ColumnHeaderFilter/index.mjs +80 -114
  139. package/dist/jsx/components/ColumnHeaderFilter/index.mjs.map +1 -1
  140. package/dist/jsx/components/ColumnHeaderFilter/index.native.js +89 -129
  141. package/dist/jsx/components/ColumnHeaderFilter/index.native.js.map +1 -1
  142. package/dist/jsx/components/DataTable/DataTableRoot.mjs +17 -1
  143. package/dist/jsx/components/DataTable/DataTableRoot.mjs.map +1 -1
  144. package/dist/jsx/components/DataTable/DataTableRoot.native.js +25 -2
  145. package/dist/jsx/components/DataTable/DataTableRoot.native.js.map +1 -1
  146. package/dist/jsx/components/DataTableBulkActions/index.mjs +1 -1
  147. package/dist/jsx/components/DataTableBulkActions/index.mjs.map +1 -1
  148. package/dist/jsx/components/DataTableBulkActions/index.native.js +1 -1
  149. package/dist/jsx/components/DataTableBulkActions/index.native.js.map +1 -1
  150. package/dist/jsx/components/DataTableCell/CellModalEditor.mjs +76 -74
  151. package/dist/jsx/components/DataTableCell/CellModalEditor.mjs.map +1 -1
  152. package/dist/jsx/components/DataTableCell/index.mjs +3 -3
  153. package/dist/jsx/components/DataTableCell/index.mjs.map +1 -1
  154. package/dist/jsx/components/DataTableCell/index.native.js +2 -11
  155. package/dist/jsx/components/DataTableCell/index.native.js.map +1 -1
  156. package/dist/jsx/components/DataTableDateFilter/index.mjs +137 -160
  157. package/dist/jsx/components/DataTableDateFilter/index.mjs.map +1 -1
  158. package/dist/jsx/components/DataTableDateFilter/index.native.js +153 -183
  159. package/dist/jsx/components/DataTableDateFilter/index.native.js.map +1 -1
  160. package/dist/jsx/components/DataTableExport/index.mjs +124 -141
  161. package/dist/jsx/components/DataTableExport/index.mjs.map +1 -1
  162. package/dist/jsx/components/DataTableExport/index.native.js +135 -157
  163. package/dist/jsx/components/DataTableExport/index.native.js.map +1 -1
  164. package/dist/jsx/components/DataTableGrouping/index.mjs +194 -214
  165. package/dist/jsx/components/DataTableGrouping/index.mjs.map +1 -1
  166. package/dist/jsx/components/DataTableGrouping/index.native.js +209 -230
  167. package/dist/jsx/components/DataTableGrouping/index.native.js.map +1 -1
  168. package/dist/jsx/components/DataTableSearch/index.mjs +105 -117
  169. package/dist/jsx/components/DataTableSearch/index.mjs.map +1 -1
  170. package/dist/jsx/components/DataTableSearch/index.native.js +111 -123
  171. package/dist/jsx/components/DataTableSearch/index.native.js.map +1 -1
  172. package/dist/jsx/components/DataTableToolbar/index.mjs +46 -63
  173. package/dist/jsx/components/DataTableToolbar/index.mjs.map +1 -1
  174. package/dist/jsx/components/DataTableToolbar/index.native.js +46 -65
  175. package/dist/jsx/components/DataTableToolbar/index.native.js.map +1 -1
  176. package/dist/jsx/components/DataTableViewOptions/index.mjs +40 -56
  177. package/dist/jsx/components/DataTableViewOptions/index.mjs.map +1 -1
  178. package/dist/jsx/components/DataTableViewOptions/index.native.js +40 -56
  179. package/dist/jsx/components/DataTableViewOptions/index.native.js.map +1 -1
  180. package/dist/jsx/components/FilterOperatorValueEditor/index.mjs +3 -11
  181. package/dist/jsx/components/FilterOperatorValueEditor/index.mjs.map +1 -1
  182. package/dist/jsx/components/FilterOperatorValueEditor/index.native.js +3 -11
  183. package/dist/jsx/components/FilterOperatorValueEditor/index.native.js.map +1 -1
  184. package/dist/jsx/components/SimpleTable/index.mjs +3 -7
  185. package/dist/jsx/components/SimpleTable/index.mjs.map +1 -1
  186. package/dist/jsx/components/SimpleTable/index.native.js +2 -6
  187. package/dist/jsx/components/SimpleTable/index.native.js.map +1 -1
  188. package/dist/jsx/components/TableInput/RowDetailEditor.mjs +165 -0
  189. package/dist/jsx/components/TableInput/RowDetailEditor.mjs.map +1 -0
  190. package/dist/jsx/components/TableInput/RowDetailEditor.native.js +172 -0
  191. package/dist/jsx/components/TableInput/RowDetailEditor.native.js.map +1 -0
  192. package/dist/jsx/components/TableInput/index.mjs +42 -5
  193. package/dist/jsx/components/TableInput/index.mjs.map +1 -1
  194. package/dist/jsx/components/TableInput/index.native.js +64 -1
  195. package/dist/jsx/components/TableInput/index.native.js.map +1 -1
  196. package/dist/jsx/components/TableInput/shared.mjs +7 -1
  197. package/dist/jsx/components/TableInput/shared.mjs.map +1 -1
  198. package/dist/jsx/components/TableInput/shared.native.js +10 -0
  199. package/dist/jsx/components/TableInput/shared.native.js.map +1 -1
  200. package/dist/jsx/hooks/useDataTable.mjs +11 -1
  201. package/dist/jsx/hooks/useDataTable.mjs.map +1 -1
  202. package/dist/jsx/hooks/useDataTable.native.js +12 -2
  203. package/dist/jsx/hooks/useDataTable.native.js.map +1 -1
  204. package/dist/jsx/index.js.map +1 -1
  205. package/dist/jsx/index.mjs.map +1 -1
  206. package/dist/jsx/index.native.js.map +1 -1
  207. package/dist/jsx/utils/rowPress.mjs +4 -1
  208. package/dist/jsx/utils/rowPress.mjs.map +1 -1
  209. package/dist/jsx/utils/rowPress.native.js +4 -1
  210. package/dist/jsx/utils/rowPress.native.js.map +1 -1
  211. package/package.json +14 -14
  212. package/src/components/ChildTable/childTable.cellHidden.spec.tsx +57 -0
  213. package/src/components/ChildTable/childTable.rowDetail.spec.tsx +120 -0
  214. package/src/components/ChildTable/index.tsx +16 -0
  215. package/src/components/ColumnHeaderFilter/index.tsx +89 -116
  216. package/src/components/DataTable/DataTable.stories.tsx +71 -6
  217. package/src/components/DataTable/DataTableRoot.tsx +27 -4
  218. package/src/components/DataTable/dataTableMobileCards.spec.tsx +1 -4
  219. package/src/components/DataTable/headerContainment.spec.tsx +91 -0
  220. package/src/components/DataTable/rowPress.spec.tsx +2 -6
  221. package/src/components/DataTableBulkActions/DataTableBulkActions.stories.tsx +5 -4
  222. package/src/components/DataTableBulkActions/index.tsx +1 -1
  223. package/src/components/DataTableCell/CellModalEditor.tsx +95 -75
  224. package/src/components/DataTableCell/cellDraftPolicy.spec.tsx +12 -2
  225. package/src/components/DataTableCell/cellModalEditor.spec.tsx +75 -49
  226. package/src/components/DataTableCell/dataTableCell.spec.tsx +31 -14
  227. package/src/components/DataTableCell/index.tsx +3 -2
  228. package/src/components/DataTableDateFilter/DataTableDateFilter.spec.tsx +6 -3
  229. package/src/components/DataTableDateFilter/index.tsx +151 -180
  230. package/src/components/DataTableExport/index.tsx +135 -142
  231. package/src/components/DataTableGrouping/index.tsx +193 -195
  232. package/src/components/DataTableSearch/index.tsx +111 -109
  233. package/src/components/DataTableToolbar/index.tsx +39 -46
  234. package/src/components/DataTableViewOptions/DataTableViewOptions.stories.tsx +52 -0
  235. package/src/components/DataTableViewOptions/index.tsx +32 -40
  236. package/src/components/FilterOperatorValueEditor/FilterOperatorValueEditor.spec.tsx +64 -2
  237. package/src/components/FilterOperatorValueEditor/index.tsx +0 -8
  238. package/src/components/SimpleTable/index.tsx +3 -2
  239. package/src/components/SimpleTable/simpleTable.spec.tsx +15 -0
  240. package/src/components/Table/accessibility.spec.tsx +7 -19
  241. package/src/components/TableInput/RowDetailEditor.native.tsx +134 -0
  242. package/src/components/TableInput/RowDetailEditor.tsx +173 -0
  243. package/src/components/TableInput/index.native.tsx +49 -1
  244. package/src/components/TableInput/index.tsx +65 -10
  245. package/src/components/TableInput/shared.ts +29 -0
  246. package/src/components/tableDropdowns.spec.tsx +172 -0
  247. package/src/hooks/useDataTable.spec.ts +39 -0
  248. package/src/hooks/useDataTable.ts +15 -1
  249. package/src/index.ts +1 -0
  250. package/src/utils/rowPress.ts +3 -0
  251. package/types/components/ChildTable/index.d.ts +10 -0
  252. package/types/components/ChildTable/index.d.ts.map +1 -1
  253. package/types/components/ColumnHeaderFilter/index.d.ts +1 -1
  254. package/types/components/ColumnHeaderFilter/index.d.ts.map +1 -1
  255. package/types/components/DataTable/DataTableRoot.d.ts.map +1 -1
  256. package/types/components/DataTableCell/CellModalEditor.d.ts +10 -7
  257. package/types/components/DataTableCell/CellModalEditor.d.ts.map +1 -1
  258. package/types/components/DataTableCell/index.d.ts.map +1 -1
  259. package/types/components/DataTableDateFilter/index.d.ts.map +1 -1
  260. package/types/components/DataTableGrouping/index.d.ts.map +1 -1
  261. package/types/components/DataTableSearch/index.d.ts.map +1 -1
  262. package/types/components/FilterOperatorValueEditor/index.d.ts.map +1 -1
  263. package/types/components/SimpleTable/index.d.ts.map +1 -1
  264. package/types/components/TableInput/RowDetailEditor.d.ts +24 -0
  265. package/types/components/TableInput/RowDetailEditor.d.ts.map +1 -0
  266. package/types/components/TableInput/RowDetailEditor.native.d.ts +11 -0
  267. package/types/components/TableInput/RowDetailEditor.native.d.ts.map +1 -0
  268. package/types/components/TableInput/index.d.ts +11 -2
  269. package/types/components/TableInput/index.d.ts.map +1 -1
  270. package/types/components/TableInput/index.native.d.ts +8 -1
  271. package/types/components/TableInput/index.native.d.ts.map +1 -1
  272. package/types/components/TableInput/shared.d.ts +11 -0
  273. package/types/components/TableInput/shared.d.ts.map +1 -1
  274. package/types/hooks/useDataTable.d.ts.map +1 -1
  275. package/types/index.d.ts +1 -0
  276. package/types/index.d.ts.map +1 -1
  277. package/types/utils/rowPress.d.ts.map +1 -1
@@ -14,6 +14,7 @@ import {
14
14
  ensureFocusVisibleRing,
15
15
  useResolvedKnobs,
16
16
  type DisabledRecipe,
17
+ transitionProps,
17
18
  } from "@multiplatform.one/theme";
18
19
  import { useCallback, useState, type ReactNode } from "react";
19
20
  import { useTranslation } from "react-i18next";
@@ -331,7 +332,7 @@ function SimpleTableMobileCards({
331
332
  const tableZebraOn = knobProps.tableZebra === "on";
332
333
  const [titleCol, ...detailCols] = columns;
333
334
  const typeSize = knobProps.label.fontSize;
334
- const motion = knobProps.transition ? { transition: knobProps.transition } : {};
335
+ const motion = transitionProps(knobProps.transition);
335
336
 
336
337
  if (data.length === 0) {
337
338
  return (
@@ -478,7 +479,7 @@ export function SimpleTableDesktop({
478
479
  const tableZebraOn = knobProps.tableZebra === "on";
479
480
  const ariaLabel = caption ? undefined : props["aria-label"];
480
481
  const typeSize = knobProps.label.fontSize;
481
- const motion = knobProps.transition ? { transition: knobProps.transition } : {};
482
+ const motion = transitionProps(knobProps.transition);
482
483
  const pad = cellPad(knobProps.gap.gap);
483
484
  const frameBorder = knobProps.surface.borderWidth;
484
485
 
@@ -417,4 +417,19 @@ describe("SimpleTable", () => {
417
417
  fireEvent.click(container.querySelector("tbody tr, [role='row']") as Element);
418
418
  expect(onRowPress).not.toHaveBeenCalled();
419
419
  });
420
+ it("survives the animation knob flipping live from none to bouncy (MPO-327)", () => {
421
+ const columns: SimpleTableColumn[] = [{ accessorKey: "name", header: "Name" }];
422
+ const data = [{ name: "Alice" }, { name: "Bob" }];
423
+ const Flip = ({ animation }: { animation: "none" | "bouncy" }) => (
424
+ <Preset overrides={{ animation }}>
425
+ <SimpleTableDesktop data={data} columns={columns} onRowPress={() => {}} />
426
+ </Preset>
427
+ );
428
+ const { container, rerender } = renderWithProviders(<Flip animation="none" />);
429
+ expect(container.querySelector("table")?.getAttribute("data-animation")).toBe("none");
430
+
431
+ expect(() => rerender(<Flip animation="bouncy" />)).not.toThrow();
432
+ expect(container.querySelector("table")?.getAttribute("data-animation")).toBe("on");
433
+ expect(container.textContent).toContain("Bob");
434
+ });
420
435
  });
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { renderWithProviders, fireEvent } from "@multiplatform.one/test-utils";
11
+ import { waitFor } from "@testing-library/react";
11
12
  import { describe, expect, it, vi } from "vitest";
12
13
  import { Table } from "@multiplatform.one/table-primitives";
13
14
  import { useTableKeyboard } from "../../hooks/useTableKeyboard";
@@ -111,14 +112,7 @@ describe("Table Accessibility", () => {
111
112
  expect(true).toBe(true);
112
113
  });
113
114
 
114
- it("Escape key behavior: Tamagui Popover handles Escape natively", () => {
115
- // Tamagui's Popover component handles Escape key natively to close.
116
- // We verify the popover can be opened (controlled state) and that
117
- // the Escape key handler exists in the DOM event chain.
118
- //
119
- // Note: In happy-dom test environment, Tamagui popover portals may
120
- // not fully render, so we test the controlled open/close pattern.
121
-
115
+ it("Escape closes the column filter panel", async () => {
122
116
  const onFilterChange = vi.fn();
123
117
 
124
118
  const { container } = renderWithProviders(
@@ -130,22 +124,16 @@ describe("Table Accessibility", () => {
130
124
  />,
131
125
  );
132
126
 
133
- // The filter trigger should be present and clickable
134
127
  const trigger = container.querySelector('[aria-label="Filter column Status"]');
135
128
  expect(trigger).not.toBeNull();
136
129
 
137
- // Click to open the popover
138
130
  fireEvent.click(trigger!);
131
+ const panel = () => document.querySelector('[data-testid="floating-panel-viewport"]');
132
+ await waitFor(() => expect(panel()).not.toBeNull());
139
133
 
140
- // The popover content should now be controlled by internal state.
141
- // In happy-dom, the Popover.Content may or may not portal into the DOM.
142
- // The key verification is that the component structure supports Escape:
143
- // ColumnHeaderFilter uses Tamagui Popover with controlled open/onOpenChange,
144
- // which natively handles Escape key to call onOpenChange(false).
145
- //
146
- // We verify the pattern is correct by checking the trigger exists
147
- // and the component renders without error after click interaction.
148
- expect(trigger).not.toBeNull();
134
+ fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
135
+ await waitFor(() => expect(panel()).toBeNull());
136
+ expect(onFilterChange).not.toHaveBeenCalled();
149
137
  });
150
138
 
151
139
  it("semantic HTML and ARIA roles on table primitives", () => {
@@ -0,0 +1,134 @@
1
+ /**
2
+ * RowDetailEditor (native) — the row form as a FloatingPanel bottom sheet,
3
+ * the same dialog/sheet split CellModalEditor.native makes. Props, staging
4
+ * and footer match ./RowDetailEditor.tsx; backdrop tap and Android back
5
+ * commit like "Done".
6
+ */
7
+
8
+ import { Button, FloatingPanel } from "@multiplatform.one/forms";
9
+ import { useResolvedKnobs } from "@multiplatform.one/theme";
10
+ import { PencilSimpleIcon } from "@phosphor-icons/react";
11
+ import { useCallback, useRef, useState } from "react";
12
+ import { Text, View, XStack, YStack } from "tamagui";
13
+ import type { DataTableData, DataTableField, DataTableFieldProps } from "../../types";
14
+ import { t } from "../../shared/t";
15
+ import { withInterp } from "../../utils/withInterp";
16
+ import { DataTableCell } from "../DataTableCell";
17
+ import type { RowDetailEditorProps } from "./RowDetailEditor";
18
+ import { changedColumns, columnKey } from "./shared";
19
+
20
+ export type { RowDetailEditorProps } from "./RowDetailEditor";
21
+
22
+ export function RowDetailEditor<TData extends DataTableData>({
23
+ row,
24
+ rowIndex,
25
+ columns,
26
+ readOnly,
27
+ compact,
28
+ isCellHidden,
29
+ onCommit,
30
+ }: RowDetailEditorProps<TData>) {
31
+ const { knobProps } = useResolvedKnobs({ compact });
32
+ const [draft, setDraft] = useState<TData | null>(null);
33
+ const draftRef = useRef(draft);
34
+ draftRef.current = draft;
35
+ const rowRef = useRef(row);
36
+ rowRef.current = row;
37
+ const onCommitRef = useRef(onCommit);
38
+ onCommitRef.current = onCommit;
39
+ const discardRef = useRef(false);
40
+ const rowNumber = rowIndex + 1;
41
+
42
+ const handleOpenChange = useCallback(
43
+ (next: boolean) => {
44
+ if (next) {
45
+ discardRef.current = false;
46
+ const seeded = { ...rowRef.current };
47
+ draftRef.current = seeded;
48
+ setDraft(seeded);
49
+ return;
50
+ }
51
+ const active = draftRef.current;
52
+ if (!discardRef.current && !readOnly && active) {
53
+ const changed = changedColumns(rowRef.current, active, columns);
54
+ if (changed.length > 0) onCommitRef.current(active, changed);
55
+ }
56
+ discardRef.current = false;
57
+ draftRef.current = null;
58
+ setDraft(null);
59
+ },
60
+ [columns, readOnly],
61
+ );
62
+
63
+ const handleCancel = useCallback(() => {
64
+ discardRef.current = true;
65
+ handleOpenChange(false);
66
+ }, [handleOpenChange]);
67
+
68
+ const setDraftValue = useCallback((columnId: string, value: unknown) => {
69
+ const current = draftRef.current;
70
+ if (!current) return;
71
+ const next = { ...current, [columnId]: value } as TData;
72
+ draftRef.current = next;
73
+ setDraft(next);
74
+ }, []);
75
+
76
+ const triggerLabel = withInterp(
77
+ t(readOnly ? "View row {{row}}" : "Edit row {{row}}", { row: rowNumber }),
78
+ { row: rowNumber },
79
+ );
80
+ const current = draft ?? row;
81
+
82
+ return (
83
+ <FloatingPanel
84
+ open={draft !== null}
85
+ onOpenChange={handleOpenChange}
86
+ scrollable
87
+ triggerSizing="content"
88
+ triggerA11y={{ label: triggerLabel }}
89
+ trigger={
90
+ <View alignItems="center" justifyContent="center">
91
+ <PencilSimpleIcon size={16} />
92
+ </View>
93
+ }
94
+ >
95
+ <YStack {...knobProps.gapLg}>
96
+ <Text {...knobProps.heading}>
97
+ {withInterp(t("Row {{row}}", { row: rowNumber }), { row: rowNumber })}
98
+ </Text>
99
+ {columns.map((col) => {
100
+ const columnId = columnKey(col);
101
+ if (isCellHidden?.(current, columnId, rowIndex)) return null;
102
+ const label = typeof col.header === "string" ? col.header : columnId;
103
+ return (
104
+ <YStack key={columnId} {...knobProps.gap}>
105
+ <Text {...knobProps.label}>{label}</Text>
106
+ <DataTableCell
107
+ value={current[columnId as keyof TData]}
108
+ onChange={(value) => setDraftValue(columnId, value)}
109
+ field={(col.field || "input") as DataTableField}
110
+ fieldProps={col.fieldProps as DataTableFieldProps | undefined}
111
+ readOnly={readOnly}
112
+ compact={false}
113
+ columnId={columnId}
114
+ columnLabel={label}
115
+ rowIndex={rowIndex}
116
+ rowData={current}
117
+ />
118
+ </YStack>
119
+ );
120
+ })}
121
+ <XStack {...knobProps.gap} justifyContent="flex-end">
122
+ {readOnly ? (
123
+ <Button onPress={handleCancel}>{t("Close")}</Button>
124
+ ) : (
125
+ <>
126
+ <Button onPress={handleCancel}>{t("Cancel")}</Button>
127
+ <Button onPress={() => handleOpenChange(false)}>{t("Done")}</Button>
128
+ </>
129
+ )}
130
+ </XStack>
131
+ </YStack>
132
+ </FloatingPanel>
133
+ );
134
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * RowDetailEditor — the child-table row opened as a form (Frappe's grid
3
+ * row "Edit" form). The grid shows the list-view columns; the detail editor
4
+ * shows every `columns` entry for one row, full-size, in a dialog.
5
+ *
6
+ * Same staging contract as CellModalEditor (UX-023): the fields edit a draft
7
+ * copy of the row seeded on open. "Done" and an outside click commit the
8
+ * draft once; "Cancel" and Escape discard it. A read-only row opens for
9
+ * viewing with a single "Close".
10
+ */
11
+
12
+ import { DialogContent, DialogOverlay } from "@multiplatform.one/components";
13
+ import { Button } from "@multiplatform.one/forms";
14
+ import { TableCellContext } from "@multiplatform.one/table-primitives";
15
+ import { useResolvedKnobs } from "@multiplatform.one/theme";
16
+ import { PencilSimpleIcon } from "@phosphor-icons/react";
17
+ import { useCallback, useRef, useState } from "react";
18
+ import { Dialog, ScrollView, Text, XStack, YStack } from "tamagui";
19
+ import type {
20
+ DataTableColumn,
21
+ DataTableData,
22
+ DataTableField,
23
+ DataTableFieldProps,
24
+ } from "../../types";
25
+ import { t } from "../../shared/t";
26
+ import { withInterp } from "../../utils/withInterp";
27
+ import { DataTableCell } from "../DataTableCell";
28
+ import { changedColumns, columnKey, type ChildCellHidden } from "./shared";
29
+
30
+ export interface RowDetailEditorProps<TData extends DataTableData> {
31
+ row: TData;
32
+ rowIndex: number;
33
+ columns: DataTableColumn<TData>[];
34
+ readOnly: boolean;
35
+ compact: boolean;
36
+ isCellHidden?: ChildCellHidden<TData>;
37
+ /** Receives the edited row once per session, only when a value changed. */
38
+ onCommit: (row: TData, changed: string[]) => void;
39
+ }
40
+
41
+ export function RowDetailEditor<TData extends DataTableData>({
42
+ row,
43
+ rowIndex,
44
+ columns,
45
+ readOnly,
46
+ compact,
47
+ isCellHidden,
48
+ onCommit,
49
+ }: RowDetailEditorProps<TData>) {
50
+ const { knobProps } = useResolvedKnobs({ compact });
51
+ const [draft, setDraft] = useState<TData | null>(null);
52
+ const draftRef = useRef(draft);
53
+ draftRef.current = draft;
54
+ const rowRef = useRef(row);
55
+ rowRef.current = row;
56
+ const onCommitRef = useRef(onCommit);
57
+ onCommitRef.current = onCommit;
58
+ const discardRef = useRef(false);
59
+ const rowNumber = rowIndex + 1;
60
+
61
+ const handleOpenChange = useCallback(
62
+ (next: boolean) => {
63
+ if (next) {
64
+ discardRef.current = false;
65
+ const seeded = { ...rowRef.current };
66
+ draftRef.current = seeded;
67
+ setDraft(seeded);
68
+ return;
69
+ }
70
+ const active = draftRef.current;
71
+ if (!discardRef.current && !readOnly && active) {
72
+ const changed = changedColumns(rowRef.current, active, columns);
73
+ if (changed.length > 0) onCommitRef.current(active, changed);
74
+ }
75
+ discardRef.current = false;
76
+ draftRef.current = null;
77
+ setDraft(null);
78
+ },
79
+ [columns, readOnly],
80
+ );
81
+
82
+ const handleCancel = useCallback(() => {
83
+ discardRef.current = true;
84
+ handleOpenChange(false);
85
+ }, [handleOpenChange]);
86
+
87
+ const setDraftValue = useCallback((columnId: string, value: unknown) => {
88
+ const current = draftRef.current;
89
+ if (!current) return;
90
+ const next = { ...current, [columnId]: value } as TData;
91
+ draftRef.current = next;
92
+ setDraft(next);
93
+ }, []);
94
+
95
+ const triggerLabel = withInterp(
96
+ t(readOnly ? "View row {{row}}" : "Edit row {{row}}", { row: rowNumber }),
97
+ { row: rowNumber },
98
+ );
99
+ const current = draft ?? row;
100
+
101
+ return (
102
+ <>
103
+ <Button
104
+ chromeless
105
+ compact={compact}
106
+ onPress={() => handleOpenChange(true)}
107
+ hitSlop={knobProps.nestedControl.hitSlop}
108
+ aria-label={triggerLabel}
109
+ aria-haspopup="dialog"
110
+ aria-expanded={draft !== null}
111
+ >
112
+ <PencilSimpleIcon size={16} />
113
+ </Button>
114
+ <Dialog open={draft !== null} onOpenChange={handleOpenChange} modal>
115
+ <Dialog.Portal>
116
+ <DialogOverlay opacity={0.5} />
117
+ <DialogContent
118
+ maxWidth={720}
119
+ maxHeight="80vh"
120
+ width="90vw"
121
+ onEscapeKeyDown={() => {
122
+ discardRef.current = true;
123
+ }}
124
+ >
125
+ <Dialog.Title {...knobProps.heading}>
126
+ {withInterp(t("Row {{row}}", { row: rowNumber }), { row: rowNumber })}
127
+ </Dialog.Title>
128
+ <ScrollView flexShrink={1} minHeight={0}>
129
+ <TableCellContext.Provider value={{ inTableCell: false }}>
130
+ <YStack {...knobProps.gapLg}>
131
+ {columns.map((col) => {
132
+ const columnId = columnKey(col);
133
+ if (isCellHidden?.(current, columnId, rowIndex)) return null;
134
+ const label = typeof col.header === "string" ? col.header : columnId;
135
+ return (
136
+ <YStack key={columnId} {...knobProps.gap}>
137
+ <Text {...knobProps.label}>{label}</Text>
138
+ <DataTableCell
139
+ value={current[columnId as keyof TData]}
140
+ onChange={(value) => setDraftValue(columnId, value)}
141
+ field={(col.field || "input") as DataTableField}
142
+ fieldProps={col.fieldProps as DataTableFieldProps | undefined}
143
+ readOnly={readOnly}
144
+ compact={false}
145
+ columnId={columnId}
146
+ columnLabel={label}
147
+ rowIndex={rowIndex}
148
+ rowData={current}
149
+ />
150
+ </YStack>
151
+ );
152
+ })}
153
+ </YStack>
154
+ </TableCellContext.Provider>
155
+ </ScrollView>
156
+ <XStack {...knobProps.gap} justifyContent="flex-end">
157
+ {readOnly ? (
158
+ <Button onPress={handleCancel}>{t("Close")}</Button>
159
+ ) : (
160
+ <>
161
+ <Button onPress={handleCancel}>{t("Cancel")}</Button>
162
+ <Button theme="accent" onPress={() => handleOpenChange(false)}>
163
+ {t("Done")}
164
+ </Button>
165
+ </>
166
+ )}
167
+ </XStack>
168
+ </DialogContent>
169
+ </Dialog.Portal>
170
+ </Dialog>
171
+ </>
172
+ );
173
+ }
@@ -32,7 +32,8 @@ import type {
32
32
  } from "../../types";
33
33
  import { DataTableCell } from "../DataTableCell";
34
34
  import { withInterp } from "../../utils/withInterp";
35
- import { generateRowId, isEmptyRows, SKELETON_ROW_COUNT } from "./shared";
35
+ import { RowDetailEditor } from "./RowDetailEditor";
36
+ import { generateRowId, isEmptyRows, SKELETON_ROW_COUNT, type ChildCellHidden } from "./shared";
36
37
 
37
38
  export interface TableInputProps<TData extends DataTableData = DataTableData> {
38
39
  /** Data array for the table (ignored in favor of form state in the form branch) */
@@ -80,6 +81,12 @@ export interface TableInputProps<TData extends DataTableData = DataTableData> {
80
81
  compact?: boolean;
81
82
  /** Leading grab handle + keyboard reorder via kanbanDnd (web implements). */
82
83
  enableReorder?: boolean;
84
+ /** Per-row button that opens the row in a detail editor (MPO-22). */
85
+ rowDetail?: boolean;
86
+ /** Fields the detail editor shows. Defaults to `columns`. */
87
+ detailColumns?: DataTableColumn<TData>[];
88
+ /** Hides one row's cell, in the grid and in the detail editor. */
89
+ isCellHidden?: ChildCellHidden<TData>;
83
90
  }
84
91
 
85
92
  export function TableInput<TData extends DataTableData = DataTableData>({
@@ -103,6 +110,9 @@ export function TableInput<TData extends DataTableData = DataTableData>({
103
110
  emptyMessage,
104
111
  "aria-label": ariaLabel,
105
112
  compact = true,
113
+ rowDetail = false,
114
+ detailColumns,
115
+ isCellHidden,
106
116
  }: TableInputProps<TData>) {
107
117
  const { t } = useTranslation();
108
118
  const { resolvedForm } = useFormField({ form: formProp });
@@ -138,6 +148,9 @@ export function TableInput<TData extends DataTableData = DataTableData>({
138
148
  emptyMessage: emptyMessage ?? t("No rows"),
139
149
  ariaLabel: resolvedAriaLabel,
140
150
  compact,
151
+ rowDetail,
152
+ detailColumns: detailColumns ?? columns,
153
+ isCellHidden,
141
154
  t,
142
155
  };
143
156
 
@@ -191,6 +204,9 @@ interface TableInputInnerProps<TData extends DataTableData> {
191
204
  emptyMessage: string;
192
205
  ariaLabel: string;
193
206
  compact: boolean;
207
+ rowDetail: boolean;
208
+ detailColumns: DataTableColumn<TData>[];
209
+ isCellHidden?: ChildCellHidden<TData>;
194
210
  t: (key: string, options?: Record<string, unknown>) => string;
195
211
  }
196
212
 
@@ -210,6 +226,9 @@ function TableInputInner<TData extends DataTableData>({
210
226
  emptyMessage,
211
227
  ariaLabel,
212
228
  compact,
229
+ rowDetail,
230
+ detailColumns,
231
+ isCellHidden,
213
232
  t,
214
233
  }: TableInputInnerProps<TData>) {
215
234
  const theme = useTheme();
@@ -219,6 +238,7 @@ function TableInputInner<TData extends DataTableData>({
219
238
  const indexWidth = Math.max(36, chromePx);
220
239
  const actionWidth = chromePx + 16;
221
240
  const [activeRowId, setActiveRowId] = useState<string | null>(null);
241
+ const Detail = RowDetailEditor<TData>;
222
242
 
223
243
  const handleCellChange = useCallback(
224
244
  (rowIndex: number, columnId: string, value: unknown) => {
@@ -250,6 +270,16 @@ function TableInputInner<TData extends DataTableData>({
250
270
  [rows, commit, minRows],
251
271
  );
252
272
 
273
+ const handleRowDetailCommit = useCallback(
274
+ (rowIndex: number, next: TData, changed: string[]) => {
275
+ for (const columnId of changed) {
276
+ onCellChange?.(rowIndex, columnId, next[columnId as keyof TData]);
277
+ }
278
+ commit(rows.map((row, index) => (index === rowIndex ? next : row)));
279
+ },
280
+ [rows, commit, onCellChange],
281
+ );
282
+
253
283
  const canAddRow = !maxRows || rows.length < maxRows;
254
284
  const canDelete = !minRows || rows.length > (minRows || 0);
255
285
 
@@ -277,6 +307,7 @@ function TableInputInner<TData extends DataTableData>({
277
307
  </Text>
278
308
  </View>
279
309
  ))}
310
+ {rowDetail && <View width={actionWidth} />}
280
311
  {showDeleteRow && !readOnly && <View width={actionWidth} />}
281
312
  </XStack>
282
313
  );
@@ -304,6 +335,9 @@ function TableInputInner<TData extends DataTableData>({
304
335
  const field = (col.field || "input") as DataTableField;
305
336
  const fieldProps = col.fieldProps as DataTableFieldProps | undefined;
306
337
 
338
+ if (isCellHidden?.(item, columnId, index)) {
339
+ return <View key={columnId} flex={1} paddingHorizontal="$1" />;
340
+ }
307
341
  return (
308
342
  <View key={columnId} flex={1} paddingHorizontal="$1">
309
343
  <DataTableCell
@@ -321,6 +355,19 @@ function TableInputInner<TData extends DataTableData>({
321
355
  </View>
322
356
  );
323
357
  })}
358
+ {rowDetail && (
359
+ <View width={actionWidth} justifyContent="center" alignItems="center">
360
+ <Detail
361
+ row={item}
362
+ rowIndex={index}
363
+ columns={detailColumns}
364
+ readOnly={readOnly}
365
+ compact={compact}
366
+ isCellHidden={isCellHidden}
367
+ onCommit={(next, changed) => handleRowDetailCommit(index, next, changed)}
368
+ />
369
+ </View>
370
+ )}
324
371
  {showDeleteRow && !readOnly && (
325
372
  <View width={actionWidth} justifyContent="center" alignItems="center">
326
373
  <Button
@@ -355,6 +402,7 @@ function TableInputInner<TData extends DataTableData>({
355
402
  <Skeleton variant="rounded" width="70%" height={16} />
356
403
  </View>
357
404
  ))}
405
+ {rowDetail && <View width={actionWidth} />}
358
406
  {showDeleteRow && !readOnly && <View width={actionWidth} />}
359
407
  </XStack>
360
408
  ))}