@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
@@ -1,33 +1,17 @@
1
1
  /**
2
- * CellModalEditor design-law tests — the modal is SF-OVERLAY / E-OVERLAY.
3
- *
4
- * The house DialogContent owns elevatedSurface + containerRadius + transition
5
- * (LC-18). A fixed 1px border on Dialog.Content was the recorded GAP; this
6
- * spec locks the recipe path so it cannot regress to a handwritten frame.
2
+ * CellModalEditor on web (MPO-221): the tall-field editor is a FloatingPanel
3
+ * attached to the cell it was opened from, the same primitive the native
4
+ * variant uses, not a centered Dialog. The panel owns the SF-OVERLAY /
5
+ * E-OVERLAY surface (elevatedSurface); the staging contract rides on top.
7
6
  */
8
7
 
9
8
  import React from "react";
9
+ import { readFileSync } from "node:fs";
10
+ import { join } from "node:path";
10
11
  import { renderWithProviders } from "@multiplatform.one/test-utils";
11
12
  import { fireEvent, screen, waitFor } from "@testing-library/react";
12
13
  import { describe, expect, it, vi } from "vitest";
13
14
 
14
- const dialogContentProps: Record<string, unknown>[] = [];
15
-
16
- vi.mock("@multiplatform.one/components", async (importOriginal) => {
17
- const actual = await importOriginal<typeof import("@multiplatform.one/components")>();
18
- return {
19
- ...actual,
20
- DialogContent: (props: Record<string, unknown>) => {
21
- dialogContentProps.push(props);
22
- return React.createElement(
23
- "div",
24
- { "data-testid": "cell-modal-dialog-content" },
25
- props.children as React.ReactNode,
26
- );
27
- },
28
- };
29
- });
30
-
31
15
  vi.mock("@multiplatform.one/forms", async (importOriginal) => ({
32
16
  ...(await importOriginal<typeof import("@multiplatform.one/forms")>()),
33
17
  FieldDisplay: (props: { field?: string }) =>
@@ -47,36 +31,78 @@ vi.mock("@multiplatform.one/forms", async (importOriginal) => ({
47
31
 
48
32
  import { CellModalEditor } from "./CellModalEditor";
49
33
 
50
- describe("CellModalEditor — overlay recipes", () => {
51
- it("opens DialogContent without a fixed 1px border (SF-OVERLAY recipe path)", async () => {
52
- dialogContentProps.length = 0;
53
- renderWithProviders(
54
- <CellModalEditor
55
- field="textarea"
56
- value="hello"
57
- onCommit={vi.fn()}
58
- title="Edit text"
59
- label="Notes"
60
- >
61
- {(draft, onDraftChange) =>
62
- React.createElement("textarea", {
63
- "data-testid": "draft-editor",
64
- value: draft,
65
- onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) =>
66
- onDraftChange(event.target.value),
67
- })
68
- }
69
- </CellModalEditor>,
70
- );
34
+ const panel = () =>
35
+ document.querySelector('[data-testid="floating-panel-viewport"]') as HTMLElement | null;
71
36
 
72
- fireEvent.click(screen.getByTestId("mock-FieldDisplay"));
73
- await waitFor(() => expect(screen.getByTestId("cell-modal-dialog-content")).toBeTruthy());
37
+ function renderEditor(onCommit = vi.fn()) {
38
+ const utils = renderWithProviders(
39
+ <CellModalEditor
40
+ field="textarea"
41
+ value="hello"
42
+ onCommit={onCommit}
43
+ title="Edit text"
44
+ label="Notes"
45
+ >
46
+ {(draft, onDraftChange) =>
47
+ React.createElement("textarea", {
48
+ "data-testid": "draft-editor",
49
+ value: draft,
50
+ onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) =>
51
+ onDraftChange(event.target.value),
52
+ })
53
+ }
54
+ </CellModalEditor>,
55
+ );
56
+ return { ...utils, onCommit };
57
+ }
74
58
 
75
- const frame = dialogContentProps.at(-1);
76
- expect(frame).toBeTruthy();
77
- expect(frame?.borderWidth).toBeUndefined();
78
- expect(frame?.borderColor).toBeUndefined();
59
+ describe("CellModalEditor on web — FloatingPanel, not a Dialog (MPO-221)", () => {
60
+ it("opens the editor inside the FloatingPanel viewport", async () => {
61
+ renderEditor();
62
+ fireEvent.click(screen.getByTestId("mock-FieldDisplay"));
63
+ await waitFor(() => expect(panel()).not.toBeNull());
64
+ expect(panel()!.contains(screen.getByTestId("draft-editor"))).toBe(true);
65
+ expect(panel()!.textContent).toContain("Edit text");
66
+ expect(document.querySelector('[role="dialog"]')).toBeNull();
79
67
  expect(screen.getByRole("button", { name: "Cancel" })).not.toBeDisabled();
80
68
  expect(screen.getByRole("button", { name: "Done" })).toBeTruthy();
81
69
  });
70
+
71
+ it("anchors to the cell trigger: the panel's reference wraps the cell face", async () => {
72
+ renderEditor();
73
+ const face = screen.getByTestId("mock-FieldDisplay");
74
+ expect(face.closest("[data-fp-reference]")).not.toBeNull();
75
+ fireEvent.click(face);
76
+ await waitFor(() => expect(panel()).not.toBeNull());
77
+ expect(panel()!.style.position).toBe("fixed");
78
+ });
79
+
80
+ it("an outside click commits the draft (LC-21)", async () => {
81
+ const { onCommit } = renderEditor();
82
+ fireEvent.click(screen.getByTestId("mock-FieldDisplay"));
83
+ await waitFor(() => expect(panel()).not.toBeNull());
84
+ fireEvent.change(screen.getByTestId("draft-editor"), { target: { value: "changed" } });
85
+ fireEvent.click(document.body);
86
+ await waitFor(() => expect(panel()).toBeNull());
87
+ expect(onCommit).toHaveBeenCalledTimes(1);
88
+ expect(onCommit).toHaveBeenCalledWith("changed");
89
+ });
90
+
91
+ it("Escape discards the draft", async () => {
92
+ const { onCommit } = renderEditor();
93
+ fireEvent.click(screen.getByTestId("mock-FieldDisplay"));
94
+ await waitFor(() => expect(panel()).not.toBeNull());
95
+ const editor = screen.getByTestId("draft-editor");
96
+ fireEvent.change(editor, { target: { value: "changed" } });
97
+ fireEvent.keyDown(editor, { key: "Escape" });
98
+ await waitFor(() => expect(panel()).toBeNull());
99
+ expect(onCommit).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it("the web source no longer mounts a Tamagui Dialog", () => {
103
+ const source = readFileSync(join(__dirname, "CellModalEditor.tsx"), "utf8");
104
+ expect(source).not.toMatch(/\bDialog\b.*from "tamagui"/);
105
+ expect(source).not.toMatch(/DialogContent|DialogOverlay/);
106
+ expect(source).toMatch(/<FloatingPanel/);
107
+ });
82
108
  });
@@ -19,10 +19,11 @@ import { describe, expect, it, vi } from "vitest";
19
19
  // ── Mock form components ──────────────────────────────────────
20
20
 
21
21
  function createMock(name: string) {
22
- const Comp = React.forwardRef((_props: any, ref: any) =>
22
+ const Comp = React.forwardRef((props: any, ref: any) =>
23
23
  React.createElement("div", {
24
24
  "data-testid": `mock-${name}`,
25
25
  "data-component": name,
26
+ "data-currency": props.currency,
26
27
  ref,
27
28
  }),
28
29
  );
@@ -187,6 +188,17 @@ describe("DataTableCell field type parity", () => {
187
188
  expectNotRendered(container, "Input");
188
189
  });
189
190
 
191
+ it("a currency stepper cell hands the currency to Stepper (MPO-221)", () => {
192
+ const { container } = renderCell({
193
+ field: "stepper",
194
+ value: 42,
195
+ fieldProps: { currency: "USD" },
196
+ });
197
+ expect(
198
+ container.querySelector('[data-component="Stepper"]')?.getAttribute("data-currency"),
199
+ ).toBe("USD");
200
+ });
201
+
190
202
  it("rating renders Rating, not custom star icons", () => {
191
203
  const { container } = renderCell({
192
204
  field: "rating",
@@ -217,10 +229,15 @@ describe("DataTableCell field type parity", () => {
217
229
  expect(display?.getAttribute("data-field")).toBe(fieldType);
218
230
  // Editor is not mounted until the cell is clicked
219
231
  expectNotRendered(baseElement, editorName);
220
- // Clicking the cell opens the modal containing the real editor
232
+ // Clicking the cell opens the panel containing the real editor. The
233
+ // panel renders inline (no portal), so the cell FACE is the trigger
234
+ // subtree and the editor lives in the panel content beside it.
221
235
  fireEvent.click(display!);
222
236
  await waitFor(() => expectRendered(baseElement, editorName));
223
- return { container, baseElement };
237
+ const face = container.querySelector('[data-testid="floating-panel-trigger"]') as HTMLElement;
238
+ const panel = container.querySelector('[data-testid="floating-panel-content"]') as HTMLElement;
239
+ expectRendered(panel, editorName);
240
+ return { container, baseElement, face, panel };
224
241
  }
225
242
 
226
243
  it("fileupload renders FileUpload, not custom FileUploadCell", async () => {
@@ -234,14 +251,14 @@ describe("DataTableCell field type parity", () => {
234
251
  });
235
252
 
236
253
  it("signature renders Signature, not status text", async () => {
237
- const { container } = await expectModalEditor(
254
+ const { face } = await expectModalEditor(
238
255
  { field: "signature", value: "" },
239
256
  "signature",
240
257
  "Signature",
241
258
  );
242
259
  // Old implementation showed "Not signed" / "Sign" in the cell
243
- expect(container.textContent).not.toContain("Not signed");
244
- expect(container.textContent).not.toContain("Sign");
260
+ expect(face.textContent).not.toContain("Not signed");
261
+ expect(face.textContent).not.toContain("Sign");
245
262
  });
246
263
 
247
264
  it("geolocation renders Geolocation, not two plain Inputs", async () => {
@@ -271,16 +288,16 @@ describe("DataTableCell field type parity", () => {
271
288
  });
272
289
 
273
290
  it("barcode routes through the modal editor (LC-06), never inline in the cell", async () => {
274
- const { container, baseElement } = await expectModalEditor(
291
+ const { face, panel } = await expectModalEditor(
275
292
  { field: "barcode", value: "SKU-001234" },
276
293
  "barcode",
277
294
  "Barcode",
278
295
  );
279
296
  // The full editor (with its camera scanner surface) must not mount inline
280
- // in the cell — only inside the modal after click.
281
- expectNotRendered(container, "Barcode");
282
- // The modal also carries the large scannable preview.
283
- expectRendered(baseElement, "BarcodePreview");
297
+ // in the cell face — only inside the panel after click.
298
+ expectNotRendered(face, "Barcode");
299
+ // The panel also carries the large scannable preview.
300
+ expectRendered(panel, "BarcodePreview");
284
301
  });
285
302
 
286
303
  it("custom render receives the cell's honest state as the render context (D-18.4)", () => {
@@ -372,13 +389,13 @@ describe("DataTableCell field type parity", () => {
372
389
  });
373
390
 
374
391
  it("textarea routes through the modal editor (LC-06), never inline in the cell", async () => {
375
- const { container, baseElement } = await expectModalEditor(
392
+ const { face, panel } = await expectModalEditor(
376
393
  { field: "textarea", value: "line one\nline two" },
377
394
  "textarea",
378
395
  "TextArea",
379
396
  );
380
- expectNotRendered(container, "TextArea");
381
- expectRendered(baseElement, "TextArea");
397
+ expectNotRendered(face, "TextArea");
398
+ expectRendered(panel, "TextArea");
382
399
  });
383
400
 
384
401
  it("cell chrome carries the LC-71 composite ring class (keyboard-only, not mouse-within)", () => {
@@ -46,6 +46,7 @@ import {
46
46
  ColorPicker,
47
47
  // Read-only display variants
48
48
  FieldDisplay,
49
+ containsAcrossPanels,
49
50
  } from "@multiplatform.one/forms";
50
51
  import { CellModalEditor } from "./CellModalEditor";
51
52
  import { CellOptionsEditor } from "./CellOptionsEditor";
@@ -380,6 +381,7 @@ export function DataTableCell({
380
381
  max={max}
381
382
  step={step ?? 1}
382
383
  precision={precision ?? 0}
384
+ currency={currency}
383
385
  disabled={disabled}
384
386
  readOnly={readOnly}
385
387
  placeholder={placeholder || "0"}
@@ -1167,8 +1169,7 @@ function DraftFieldEditor({
1167
1169
  if (event.key === "Escape") cancelDraft();
1168
1170
  };
1169
1171
  const handleDocPointerDown = (event: PointerEvent) => {
1170
- const node = containerRef.current;
1171
- if (node && event.target instanceof Node && node.contains(event.target)) return;
1172
+ if (containsAcrossPanels(containerRef.current, event.target)) return;
1172
1173
  setSessionActive(false);
1173
1174
  commitDraft();
1174
1175
  };
@@ -156,12 +156,15 @@ describe("DataTableDateFilter - DatePicker integration", () => {
156
156
  /**
157
157
  * Open the picker behind `placeholder` and click a day in its calendar.
158
158
  * A just-closed picker panel lingers mounted (with live handlers!) until
159
- * its close timer fires, so first wait until no panel is mounted — then
160
- * the day query can safely be global (forms DatePicker spec pattern).
159
+ * its close timer fires, so first wait until the filter's own panel is the
160
+ * only one mounted — then the day query can safely be global (forms
161
+ * DatePicker spec pattern). Panels portal to the page (MPO-335), so a
162
+ * picker panel is a sibling of the filter panel, not inside it.
161
163
  */
164
+ const panels = '[data-testid="floating-panel-viewport"]';
162
165
  const pickDay = async (placeholder: string, day: number) => {
163
166
  await waitFor(() => {
164
- expect(document.querySelectorAll('[data-testid="floating-panel-viewport"]').length).toBe(0);
167
+ expect(document.querySelectorAll(panels).length).toBe(1);
165
168
  });
166
169
  fireEvent.click(screen.getByPlaceholderText(placeholder));
167
170
  await waitFor(() => {
@@ -8,9 +8,8 @@ import {
8
8
  } from "@phosphor-icons/react";
9
9
  import { formatAbsoluteDate, parseCalendarDate, useResolvedKnobs } from "@multiplatform.one/theme";
10
10
  import React, { useState, useMemo } from "react";
11
- import { Button, DatePicker } from "@multiplatform.one/forms";
11
+ import { Button, DatePicker, FloatingPanel } from "@multiplatform.one/forms";
12
12
  import {
13
- Popover,
14
13
  ScrollView,
15
14
  Separator,
16
15
  Text,
@@ -311,194 +310,166 @@ export function DataTableDateFilter({
311
310
  }
312
311
  };
313
312
 
314
- return (
315
- // LC-06: anchored editor — start-aligned to the trigger edge with
316
- // collision handling (flip + clamp + resize inside the viewport).
317
- <Popover
318
- open={isOpen}
319
- onOpenChange={setIsOpen}
320
- placement="bottom-start"
321
- offset={4}
322
- stayInFrame
323
- allowFlip
324
- resize
313
+ const trigger = (
314
+ <Button
315
+ size={knobProps.sizeToken}
316
+ disabled={disabled}
317
+ icon={<CalendarIcon size={iconPx} />}
318
+ justifyContent="flex-start"
319
+ minWidth={triggerMinWidth}
325
320
  >
326
- <Popover.Trigger asChild>
327
- <Button
328
- size={knobProps.sizeToken}
329
- disabled={disabled}
330
- icon={<CalendarIcon size={iconPx} />}
331
- justifyContent="flex-start"
332
- minWidth={triggerMinWidth}
333
- >
334
- <Text
335
- flex={1}
336
- textAlign="left"
337
- // LC-40: while disabled, the placeholder tier ($color10) drops
338
- // below AA on the washed chrome — pin to the lowest AA step.
339
- color={disabled ? "$color11" : value ? undefined : "$color10"}
340
- fontSize="$3"
341
- numberOfLines={1}
342
- >
343
- {getDisplayValue()}
344
- </Text>
345
- <CaretDownIcon size={iconPx} />
346
- </Button>
347
- </Popover.Trigger>
348
-
349
- {/* Overlay contract: elevatedSurface + transition-paired enter/exit
350
- motion (LC-24) — same fragments as the shared components
351
- PopoverContent. The popper resize middleware caps the height to
352
- the viewport; the ScrollView keeps every section reachable
353
- instead of painting past the frame (LC-10). */}
354
- <Popover.Content
355
- {...knobProps.elevatedSurface}
356
- transition={knobProps.transition}
357
- enterStyle={{ opacity: 0, y: -4, scale: 0.97 }}
358
- exitStyle={{ opacity: 0, y: -4, scale: 0.97 }}
359
- minWidth={popoverMinWidth}
360
- padding={0}
361
- overflow="hidden"
321
+ <Text
322
+ flex={1}
323
+ textAlign="left"
324
+ // LC-40: while disabled, the placeholder tier ($color10) drops
325
+ // below AA on the washed chrome — pin to the lowest AA step.
326
+ color={disabled ? "$color11" : value ? undefined : "$color10"}
327
+ fontSize="$3"
328
+ numberOfLines={1}
362
329
  >
363
- <Popover.ScrollView minHeight={0}>
364
- <YStack {...knobProps.panelPadding} {...knobProps.gapLg}>
365
- <XStack justifyContent="space-between" alignItems="center">
366
- <Text {...knobProps.label} fontWeight="400">
367
- {t("Date Filter")}
368
- </Text>
369
- {value && (
370
- <Button
371
- size={knobProps.sizeToken}
372
- onPress={handleClear}
373
- icon={<XIcon size={iconPx} />}
374
- >
375
- {t("Clear")}
376
- </Button>
377
- )}
378
- </XStack>
330
+ {getDisplayValue()}
331
+ </Text>
332
+ <CaretDownIcon size={iconPx} />
333
+ </Button>
334
+ );
379
335
 
380
- {/* Quick Presets */}
381
- {showPresets && (
382
- <YStack gap="$2">
383
- <Text {...knobProps.label} fontWeight="400">
384
- {t("Quick Filters")}
385
- </Text>
386
- <ScrollView maxHeight={presetListMaxHeight}>
387
- <YStack gap={PRESET_ROW_GAP_TOKEN}>
388
- {allPresets.map((preset) => (
389
- <Button
390
- key={preset.id}
391
- size={knobProps.sizeToken}
392
- justifyContent="flex-start"
393
- onPress={() => handlePresetSelect(preset)}
394
- {...(activePresetId === preset.id
395
- ? { theme: "accent" as any, backgroundColor: "$background" }
396
- : undefined)}
397
- >
398
- <Text fontSize="$3">{preset.label}</Text>
399
- </Button>
400
- ))}
401
- </YStack>
402
- </ScrollView>
403
- </YStack>
404
- )}
405
-
406
- <Separator />
407
-
408
- {/* Custom Date Selection */}
409
- <YStack {...knobProps.gap}>
410
- <Text {...knobProps.label} fontWeight="400">
411
- {t("Custom Date")}
412
- </Text>
413
-
414
- <XStack gap="$2">
415
- <Button
416
- flex={1}
417
- size={knobProps.sizeToken}
418
- onPress={() => handleCustomDateSelect(new Date().toISOString(), "is")}
419
- >
420
- {t("Today")}
421
- </Button>
422
- <Button
423
- flex={1}
424
- size={knobProps.sizeToken}
425
- onPress={() =>
426
- handleCustomDateSelect(
427
- new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
428
- "is",
429
- )
430
- }
431
- >
432
- {t("Yesterday")}
433
- </Button>
434
- </XStack>
435
-
436
- {/* Date Range */}
437
- <YStack gap="$2">
438
- <Text {...knobProps.label} fontWeight="400">
439
- {t("Date Range")}
440
- </Text>
441
- <XStack gap="$2">
442
- <Button
443
- flex={1}
444
- size={knobProps.sizeToken}
445
- onPress={() =>
446
- handleDateRangeSelect(
447
- new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
448
- new Date().toISOString(),
449
- )
450
- }
451
- >
452
- {t("Last 7 days")}
453
- </Button>
336
+ return (
337
+ // LC-06: anchored editor. FloatingPanel attaches flush to the trigger
338
+ // (MPO-38 cover contract), clamps inside the viewport and becomes a sheet
339
+ // at 640 and below.
340
+ <FloatingPanel open={isOpen} onOpenChange={setIsOpen} trigger={trigger} scrollable>
341
+ <YStack {...knobProps.gapLg} minWidth={popoverMinWidth} data-dismiss-class="compose">
342
+ <XStack justifyContent="space-between" alignItems="center">
343
+ <Text {...knobProps.label} fontWeight="400">
344
+ {t("Date Filter")}
345
+ </Text>
346
+ {value && (
347
+ <Button size={knobProps.sizeToken} onPress={handleClear} icon={<XIcon size={iconPx} />}>
348
+ {t("Clear")}
349
+ </Button>
350
+ )}
351
+ </XStack>
352
+
353
+ {/* Quick Presets */}
354
+ {showPresets && (
355
+ <YStack gap="$2">
356
+ <Text {...knobProps.label} fontWeight="400">
357
+ {t("Quick Filters")}
358
+ </Text>
359
+ <ScrollView maxHeight={presetListMaxHeight}>
360
+ <YStack gap={PRESET_ROW_GAP_TOKEN}>
361
+ {allPresets.map((preset) => (
454
362
  <Button
455
- flex={1}
363
+ key={preset.id}
456
364
  size={knobProps.sizeToken}
457
- onPress={() =>
458
- handleDateRangeSelect(
459
- new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
460
- new Date().toISOString(),
461
- )
462
- }
365
+ justifyContent="flex-start"
366
+ onPress={() => handlePresetSelect(preset)}
367
+ {...(activePresetId === preset.id
368
+ ? { theme: "accent" as any, backgroundColor: "$background" }
369
+ : undefined)}
463
370
  >
464
- {t("Last 30 days")}
371
+ <Text fontSize="$3">{preset.label}</Text>
465
372
  </Button>
466
- </XStack>
373
+ ))}
467
374
  </YStack>
375
+ </ScrollView>
376
+ </YStack>
377
+ )}
468
378
 
469
- {/* DatePicker-based Custom Range Selection */}
470
- <YStack gap="$2">
471
- <Text {...knobProps.label} fontWeight="400">
472
- {t("Custom Range")}
473
- </Text>
474
- <XStack gap="$2">
475
- <View flex={1} data-testid="start-date-picker">
476
- <DatePicker
477
- placeholder={t("Start date")}
478
- label={t("From")}
479
- minDate={minDate}
480
- maxDate={maxDate}
481
- value={pickerRange.start ?? null}
482
- onChange={handleStartDatePick}
483
- />
484
- </View>
485
- <View flex={1} data-testid="end-date-picker">
486
- <DatePicker
487
- placeholder={t("End date")}
488
- label={t("To")}
489
- minDate={minDate}
490
- maxDate={maxDate}
491
- value={pickerRange.end ?? null}
492
- onChange={handleEndDatePick}
493
- />
494
- </View>
495
- </XStack>
496
- </YStack>
497
- </YStack>
379
+ <Separator />
380
+
381
+ {/* Custom Date Selection */}
382
+ <YStack {...knobProps.gap}>
383
+ <Text {...knobProps.label} fontWeight="400">
384
+ {t("Custom Date")}
385
+ </Text>
386
+
387
+ <XStack gap="$2">
388
+ <Button
389
+ flex={1}
390
+ size={knobProps.sizeToken}
391
+ onPress={() => handleCustomDateSelect(new Date().toISOString(), "is")}
392
+ >
393
+ {t("Today")}
394
+ </Button>
395
+ <Button
396
+ flex={1}
397
+ size={knobProps.sizeToken}
398
+ onPress={() =>
399
+ handleCustomDateSelect(
400
+ new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
401
+ "is",
402
+ )
403
+ }
404
+ >
405
+ {t("Yesterday")}
406
+ </Button>
407
+ </XStack>
408
+
409
+ {/* Date Range */}
410
+ <YStack gap="$2">
411
+ <Text {...knobProps.label} fontWeight="400">
412
+ {t("Date Range")}
413
+ </Text>
414
+ <XStack gap="$2">
415
+ <Button
416
+ flex={1}
417
+ size={knobProps.sizeToken}
418
+ onPress={() =>
419
+ handleDateRangeSelect(
420
+ new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
421
+ new Date().toISOString(),
422
+ )
423
+ }
424
+ >
425
+ {t("Last 7 days")}
426
+ </Button>
427
+ <Button
428
+ flex={1}
429
+ size={knobProps.sizeToken}
430
+ onPress={() =>
431
+ handleDateRangeSelect(
432
+ new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
433
+ new Date().toISOString(),
434
+ )
435
+ }
436
+ >
437
+ {t("Last 30 days")}
438
+ </Button>
439
+ </XStack>
440
+ </YStack>
441
+
442
+ {/* DatePicker-based Custom Range Selection */}
443
+ <YStack gap="$2">
444
+ <Text {...knobProps.label} fontWeight="400">
445
+ {t("Custom Range")}
446
+ </Text>
447
+ <XStack gap="$2">
448
+ <View flex={1} data-testid="start-date-picker">
449
+ <DatePicker
450
+ placeholder={t("Start date")}
451
+ label={t("From")}
452
+ minDate={minDate}
453
+ maxDate={maxDate}
454
+ value={pickerRange.start ?? null}
455
+ onChange={handleStartDatePick}
456
+ />
457
+ </View>
458
+ <View flex={1} data-testid="end-date-picker">
459
+ <DatePicker
460
+ placeholder={t("End date")}
461
+ label={t("To")}
462
+ minDate={minDate}
463
+ maxDate={maxDate}
464
+ value={pickerRange.end ?? null}
465
+ onChange={handleEndDatePick}
466
+ />
467
+ </View>
468
+ </XStack>
498
469
  </YStack>
499
- </Popover.ScrollView>
500
- </Popover.Content>
501
- </Popover>
470
+ </YStack>
471
+ </YStack>
472
+ </FloatingPanel>
502
473
  );
503
474
  }
504
475