@multiplatform.one/frappe-ui 6.7.0 → 7.0.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 (516) hide show
  1. package/dist/cjs/FrappeAssignment.cjs +2 -0
  2. package/dist/cjs/FrappeAssignment.native.js +2 -0
  3. package/dist/cjs/FrappeAssignment.native.js.map +1 -1
  4. package/dist/cjs/FrappeCalendar.cjs +5 -7
  5. package/dist/cjs/FrappeCalendar.native.js +5 -9
  6. package/dist/cjs/FrappeCalendar.native.js.map +1 -1
  7. package/dist/cjs/FrappeChartCard.cjs +2 -0
  8. package/dist/cjs/FrappeChartCard.native.js +2 -0
  9. package/dist/cjs/FrappeChartCard.native.js.map +1 -1
  10. package/dist/cjs/FrappeDashboard.cjs +11 -22
  11. package/dist/cjs/FrappeDashboard.native.js +20 -22
  12. package/dist/cjs/FrappeDashboard.native.js.map +1 -1
  13. package/dist/cjs/FrappeForm.cjs +3 -1
  14. package/dist/cjs/FrappeForm.native.js +3 -1
  15. package/dist/cjs/FrappeForm.native.js.map +1 -1
  16. package/dist/cjs/FrappeGantt.cjs +5 -7
  17. package/dist/cjs/FrappeGantt.native.js +5 -9
  18. package/dist/cjs/FrappeGantt.native.js.map +1 -1
  19. package/dist/cjs/FrappeImageView.cjs +3 -1
  20. package/dist/cjs/FrappeImageView.native.js +3 -1
  21. package/dist/cjs/FrappeImageView.native.js.map +1 -1
  22. package/dist/cjs/FrappeKPICard.cjs +2 -0
  23. package/dist/cjs/FrappeKPICard.native.js +2 -0
  24. package/dist/cjs/FrappeKPICard.native.js.map +1 -1
  25. package/dist/cjs/FrappeKanban.cjs +3 -1
  26. package/dist/cjs/FrappeKanban.native.js +3 -1
  27. package/dist/cjs/FrappeKanban.native.js.map +1 -1
  28. package/dist/cjs/FrappeLikedBy.cjs +2 -0
  29. package/dist/cjs/FrappeLikedBy.native.js +2 -0
  30. package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
  31. package/dist/cjs/FrappeList.cjs +3 -1
  32. package/dist/cjs/FrappeList.native.js +3 -1
  33. package/dist/cjs/FrappeList.native.js.map +1 -1
  34. package/dist/cjs/FrappeListView.cjs +6 -12
  35. package/dist/cjs/FrappeListView.native.js +13 -12
  36. package/dist/cjs/FrappeListView.native.js.map +1 -1
  37. package/dist/cjs/FrappeProvider.cjs +5 -1
  38. package/dist/cjs/FrappeProvider.native.js +5 -1
  39. package/dist/cjs/FrappeProvider.native.js.map +1 -1
  40. package/dist/cjs/FrappeReportBuilder.cjs +2 -0
  41. package/dist/cjs/FrappeReportBuilder.native.js +2 -0
  42. package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
  43. package/dist/cjs/FrappeReportView.cjs +23 -45
  44. package/dist/cjs/FrappeReportView.native.js +35 -50
  45. package/dist/cjs/FrappeReportView.native.js.map +1 -1
  46. package/dist/cjs/FrappeTable.cjs +105 -31
  47. package/dist/cjs/FrappeTable.native.js +140 -42
  48. package/dist/cjs/FrappeTable.native.js.map +1 -1
  49. package/dist/cjs/FrappeTreeView.cjs +3 -1
  50. package/dist/cjs/FrappeTreeView.native.js +3 -1
  51. package/dist/cjs/FrappeTreeView.native.js.map +1 -1
  52. package/dist/cjs/SyncErrorNotice.cjs +76 -0
  53. package/dist/cjs/SyncErrorNotice.native.js +92 -0
  54. package/dist/cjs/SyncErrorNotice.native.js.map +1 -0
  55. package/dist/cjs/commentActions.cjs +120 -0
  56. package/dist/cjs/commentActions.native.js +129 -0
  57. package/dist/cjs/commentActions.native.js.map +1 -0
  58. package/dist/cjs/components/attachments.cjs +2 -0
  59. package/dist/cjs/components/attachments.native.js +2 -0
  60. package/dist/cjs/components/attachments.native.js.map +1 -1
  61. package/dist/cjs/components/tags.cjs +5 -0
  62. package/dist/cjs/components/tags.native.js +5 -0
  63. package/dist/cjs/components/tags.native.js.map +1 -1
  64. package/dist/cjs/components/timeline.cjs +29 -2
  65. package/dist/cjs/components/timeline.native.js +33 -2
  66. package/dist/cjs/components/timeline.native.js.map +1 -1
  67. package/dist/cjs/desk/DeskFormSidebar.cjs +2 -0
  68. package/dist/cjs/desk/DeskFormSidebar.native.js +2 -0
  69. package/dist/cjs/desk/DeskFormSidebar.native.js.map +1 -1
  70. package/dist/cjs/fieldRegistry.cjs +2 -0
  71. package/dist/cjs/fieldRegistry.native.js +2 -0
  72. package/dist/cjs/fieldRegistry.native.js.map +1 -1
  73. package/dist/cjs/fields/Date/index.cjs +71 -7
  74. package/dist/cjs/fields/Date/index.native.js +325 -37
  75. package/dist/cjs/fields/Date/index.native.js.map +1 -1
  76. package/dist/cjs/fields/Date/types.cjs +18 -0
  77. package/dist/cjs/fields/Date/types.native.js +21 -0
  78. package/dist/cjs/fields/Date/types.native.js.map +1 -0
  79. package/dist/cjs/fields/Date/wire.cjs +4 -8
  80. package/dist/cjs/fields/Date/wire.native.js +4 -11
  81. package/dist/cjs/fields/Date/wire.native.js.map +1 -1
  82. package/dist/cjs/fields/DynamicLink.cjs +2 -0
  83. package/dist/cjs/fields/DynamicLink.native.js +2 -0
  84. package/dist/cjs/fields/DynamicLink.native.js.map +1 -1
  85. package/dist/cjs/fields/FileUpload.cjs +2 -0
  86. package/dist/cjs/fields/FileUpload.native.js +2 -0
  87. package/dist/cjs/fields/FileUpload.native.js.map +1 -1
  88. package/dist/cjs/fields/TableMultiSelect.cjs +6 -1
  89. package/dist/cjs/fields/TableMultiSelect.native.js +6 -1
  90. package/dist/cjs/fields/TableMultiSelect.native.js.map +1 -1
  91. package/dist/cjs/frappeStoryTarget.cjs +88 -0
  92. package/dist/cjs/frappeStoryTarget.native.js +100 -0
  93. package/dist/cjs/frappeStoryTarget.native.js.map +1 -1
  94. package/dist/cjs/frappeUsers.cjs +2 -0
  95. package/dist/cjs/frappeUsers.native.js +2 -0
  96. package/dist/cjs/frappeUsers.native.js.map +1 -1
  97. package/dist/cjs/index.cjs +17 -4
  98. package/dist/cjs/index.native.js +17 -4
  99. package/dist/cjs/index.native.js.map +1 -1
  100. package/dist/cjs/liveErrors.cjs +5 -1
  101. package/dist/cjs/liveErrors.native.js +5 -1
  102. package/dist/cjs/liveErrors.native.js.map +1 -1
  103. package/dist/cjs/useFrappeAggregate.cjs +13 -1
  104. package/dist/cjs/useFrappeAggregate.native.js +14 -1
  105. package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
  106. package/dist/cjs/useFrappeFacets.cjs +329 -0
  107. package/dist/cjs/useFrappeFacets.native.js +632 -0
  108. package/dist/cjs/useFrappeFacets.native.js.map +1 -0
  109. package/dist/esm/FrappeAssignment.mjs +2 -0
  110. package/dist/esm/FrappeAssignment.mjs.map +1 -1
  111. package/dist/esm/FrappeAssignment.native.js +2 -0
  112. package/dist/esm/FrappeAssignment.native.js.map +1 -1
  113. package/dist/esm/FrappeCalendar.mjs +6 -8
  114. package/dist/esm/FrappeCalendar.mjs.map +1 -1
  115. package/dist/esm/FrappeCalendar.native.js +6 -10
  116. package/dist/esm/FrappeCalendar.native.js.map +1 -1
  117. package/dist/esm/FrappeChartCard.mjs +2 -0
  118. package/dist/esm/FrappeChartCard.mjs.map +1 -1
  119. package/dist/esm/FrappeChartCard.native.js +2 -0
  120. package/dist/esm/FrappeChartCard.native.js.map +1 -1
  121. package/dist/esm/FrappeDashboard.mjs +12 -19
  122. package/dist/esm/FrappeDashboard.mjs.map +1 -1
  123. package/dist/esm/FrappeDashboard.native.js +23 -21
  124. package/dist/esm/FrappeDashboard.native.js.map +1 -1
  125. package/dist/esm/FrappeForm.mjs +3 -1
  126. package/dist/esm/FrappeForm.mjs.map +1 -1
  127. package/dist/esm/FrappeForm.native.js +3 -1
  128. package/dist/esm/FrappeForm.native.js.map +1 -1
  129. package/dist/esm/FrappeGantt.mjs +6 -8
  130. package/dist/esm/FrappeGantt.mjs.map +1 -1
  131. package/dist/esm/FrappeGantt.native.js +6 -10
  132. package/dist/esm/FrappeGantt.native.js.map +1 -1
  133. package/dist/esm/FrappeImageView.mjs +3 -1
  134. package/dist/esm/FrappeImageView.mjs.map +1 -1
  135. package/dist/esm/FrappeImageView.native.js +3 -1
  136. package/dist/esm/FrappeImageView.native.js.map +1 -1
  137. package/dist/esm/FrappeKPICard.mjs +2 -0
  138. package/dist/esm/FrappeKPICard.mjs.map +1 -1
  139. package/dist/esm/FrappeKPICard.native.js +2 -0
  140. package/dist/esm/FrappeKPICard.native.js.map +1 -1
  141. package/dist/esm/FrappeKanban.mjs +3 -1
  142. package/dist/esm/FrappeKanban.mjs.map +1 -1
  143. package/dist/esm/FrappeKanban.native.js +3 -1
  144. package/dist/esm/FrappeKanban.native.js.map +1 -1
  145. package/dist/esm/FrappeLikedBy.mjs +2 -0
  146. package/dist/esm/FrappeLikedBy.mjs.map +1 -1
  147. package/dist/esm/FrappeLikedBy.native.js +2 -0
  148. package/dist/esm/FrappeLikedBy.native.js.map +1 -1
  149. package/dist/esm/FrappeList.mjs +3 -1
  150. package/dist/esm/FrappeList.mjs.map +1 -1
  151. package/dist/esm/FrappeList.native.js +3 -1
  152. package/dist/esm/FrappeList.native.js.map +1 -1
  153. package/dist/esm/FrappeListView.mjs +7 -13
  154. package/dist/esm/FrappeListView.mjs.map +1 -1
  155. package/dist/esm/FrappeListView.native.js +15 -14
  156. package/dist/esm/FrappeListView.native.js.map +1 -1
  157. package/dist/esm/FrappeProvider.mjs +5 -1
  158. package/dist/esm/FrappeProvider.mjs.map +1 -1
  159. package/dist/esm/FrappeProvider.native.js +5 -1
  160. package/dist/esm/FrappeProvider.native.js.map +1 -1
  161. package/dist/esm/FrappeReportBuilder.mjs +2 -0
  162. package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
  163. package/dist/esm/FrappeReportBuilder.native.js +2 -0
  164. package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
  165. package/dist/esm/FrappeReportView.mjs +26 -49
  166. package/dist/esm/FrappeReportView.mjs.map +1 -1
  167. package/dist/esm/FrappeReportView.native.js +38 -54
  168. package/dist/esm/FrappeReportView.native.js.map +1 -1
  169. package/dist/esm/FrappeTable.mjs +107 -33
  170. package/dist/esm/FrappeTable.mjs.map +1 -1
  171. package/dist/esm/FrappeTable.native.js +142 -44
  172. package/dist/esm/FrappeTable.native.js.map +1 -1
  173. package/dist/esm/FrappeTreeView.mjs +3 -1
  174. package/dist/esm/FrappeTreeView.mjs.map +1 -1
  175. package/dist/esm/FrappeTreeView.native.js +3 -1
  176. package/dist/esm/FrappeTreeView.native.js.map +1 -1
  177. package/dist/esm/SyncErrorNotice.mjs +51 -0
  178. package/dist/esm/SyncErrorNotice.mjs.map +1 -0
  179. package/dist/esm/SyncErrorNotice.native.js +53 -0
  180. package/dist/esm/SyncErrorNotice.native.js.map +1 -0
  181. package/dist/esm/commentActions.mjs +95 -0
  182. package/dist/esm/commentActions.mjs.map +1 -0
  183. package/dist/esm/commentActions.native.js +101 -0
  184. package/dist/esm/commentActions.native.js.map +1 -0
  185. package/dist/esm/components/attachments.mjs +2 -0
  186. package/dist/esm/components/attachments.mjs.map +1 -1
  187. package/dist/esm/components/attachments.native.js +2 -0
  188. package/dist/esm/components/attachments.native.js.map +1 -1
  189. package/dist/esm/components/tags.mjs +5 -0
  190. package/dist/esm/components/tags.mjs.map +1 -1
  191. package/dist/esm/components/tags.native.js +5 -0
  192. package/dist/esm/components/tags.native.js.map +1 -1
  193. package/dist/esm/components/timeline.mjs +31 -4
  194. package/dist/esm/components/timeline.mjs.map +1 -1
  195. package/dist/esm/components/timeline.native.js +35 -4
  196. package/dist/esm/components/timeline.native.js.map +1 -1
  197. package/dist/esm/desk/DeskFormSidebar.mjs +2 -0
  198. package/dist/esm/desk/DeskFormSidebar.mjs.map +1 -1
  199. package/dist/esm/desk/DeskFormSidebar.native.js +2 -0
  200. package/dist/esm/desk/DeskFormSidebar.native.js.map +1 -1
  201. package/dist/esm/fieldRegistry.mjs +2 -0
  202. package/dist/esm/fieldRegistry.mjs.map +1 -1
  203. package/dist/esm/fieldRegistry.native.js +2 -0
  204. package/dist/esm/fieldRegistry.native.js.map +1 -1
  205. package/dist/esm/fields/Date/index.mjs +73 -9
  206. package/dist/esm/fields/Date/index.mjs.map +1 -1
  207. package/dist/esm/fields/Date/index.native.js +315 -38
  208. package/dist/esm/fields/Date/index.native.js.map +1 -1
  209. package/dist/esm/fields/Date/types.mjs +2 -0
  210. package/dist/esm/fields/Date/types.mjs.map +1 -0
  211. package/dist/esm/fields/Date/types.native.js +2 -0
  212. package/dist/esm/fields/Date/types.native.js.map +1 -0
  213. package/dist/esm/fields/Date/wire.mjs +4 -8
  214. package/dist/esm/fields/Date/wire.mjs.map +1 -1
  215. package/dist/esm/fields/Date/wire.native.js +4 -11
  216. package/dist/esm/fields/Date/wire.native.js.map +1 -1
  217. package/dist/esm/fields/DynamicLink.mjs +2 -0
  218. package/dist/esm/fields/DynamicLink.mjs.map +1 -1
  219. package/dist/esm/fields/DynamicLink.native.js +2 -0
  220. package/dist/esm/fields/DynamicLink.native.js.map +1 -1
  221. package/dist/esm/fields/FileUpload.mjs +2 -0
  222. package/dist/esm/fields/FileUpload.mjs.map +1 -1
  223. package/dist/esm/fields/FileUpload.native.js +2 -0
  224. package/dist/esm/fields/FileUpload.native.js.map +1 -1
  225. package/dist/esm/fields/TableMultiSelect.mjs +7 -2
  226. package/dist/esm/fields/TableMultiSelect.mjs.map +1 -1
  227. package/dist/esm/fields/TableMultiSelect.native.js +7 -2
  228. package/dist/esm/fields/TableMultiSelect.native.js.map +1 -1
  229. package/dist/esm/frappeStoryTarget.mjs +88 -2
  230. package/dist/esm/frappeStoryTarget.mjs.map +1 -1
  231. package/dist/esm/frappeStoryTarget.native.js +100 -2
  232. package/dist/esm/frappeStoryTarget.native.js.map +1 -1
  233. package/dist/esm/frappeUsers.mjs +2 -0
  234. package/dist/esm/frappeUsers.mjs.map +1 -1
  235. package/dist/esm/frappeUsers.native.js +2 -0
  236. package/dist/esm/frappeUsers.native.js.map +1 -1
  237. package/dist/esm/index.mjs +7 -3
  238. package/dist/esm/index.mjs.map +1 -1
  239. package/dist/esm/index.native.js +7 -3
  240. package/dist/esm/index.native.js.map +1 -1
  241. package/dist/esm/liveErrors.mjs +5 -1
  242. package/dist/esm/liveErrors.mjs.map +1 -1
  243. package/dist/esm/liveErrors.native.js +5 -1
  244. package/dist/esm/liveErrors.native.js.map +1 -1
  245. package/dist/esm/useFrappeAggregate.mjs +13 -1
  246. package/dist/esm/useFrappeAggregate.mjs.map +1 -1
  247. package/dist/esm/useFrappeAggregate.native.js +14 -1
  248. package/dist/esm/useFrappeAggregate.native.js.map +1 -1
  249. package/dist/esm/useFrappeFacets.mjs +299 -0
  250. package/dist/esm/useFrappeFacets.mjs.map +1 -0
  251. package/dist/esm/useFrappeFacets.native.js +599 -0
  252. package/dist/esm/useFrappeFacets.native.js.map +1 -0
  253. package/dist/jsx/FrappeAssignment.mjs +2 -0
  254. package/dist/jsx/FrappeAssignment.mjs.map +1 -1
  255. package/dist/jsx/FrappeAssignment.native.js +2 -0
  256. package/dist/jsx/FrappeAssignment.native.js.map +1 -1
  257. package/dist/jsx/FrappeCalendar.mjs +6 -8
  258. package/dist/jsx/FrappeCalendar.mjs.map +1 -1
  259. package/dist/jsx/FrappeCalendar.native.js +5 -9
  260. package/dist/jsx/FrappeCalendar.native.js.map +1 -1
  261. package/dist/jsx/FrappeChartCard.mjs +2 -0
  262. package/dist/jsx/FrappeChartCard.mjs.map +1 -1
  263. package/dist/jsx/FrappeChartCard.native.js +2 -0
  264. package/dist/jsx/FrappeChartCard.native.js.map +1 -1
  265. package/dist/jsx/FrappeDashboard.mjs +12 -19
  266. package/dist/jsx/FrappeDashboard.mjs.map +1 -1
  267. package/dist/jsx/FrappeDashboard.native.js +20 -22
  268. package/dist/jsx/FrappeDashboard.native.js.map +1 -1
  269. package/dist/jsx/FrappeForm.mjs +3 -1
  270. package/dist/jsx/FrappeForm.mjs.map +1 -1
  271. package/dist/jsx/FrappeForm.native.js +3 -1
  272. package/dist/jsx/FrappeForm.native.js.map +1 -1
  273. package/dist/jsx/FrappeGantt.mjs +6 -8
  274. package/dist/jsx/FrappeGantt.mjs.map +1 -1
  275. package/dist/jsx/FrappeGantt.native.js +5 -9
  276. package/dist/jsx/FrappeGantt.native.js.map +1 -1
  277. package/dist/jsx/FrappeImageView.mjs +3 -1
  278. package/dist/jsx/FrappeImageView.mjs.map +1 -1
  279. package/dist/jsx/FrappeImageView.native.js +3 -1
  280. package/dist/jsx/FrappeImageView.native.js.map +1 -1
  281. package/dist/jsx/FrappeKPICard.mjs +2 -0
  282. package/dist/jsx/FrappeKPICard.mjs.map +1 -1
  283. package/dist/jsx/FrappeKPICard.native.js +2 -0
  284. package/dist/jsx/FrappeKPICard.native.js.map +1 -1
  285. package/dist/jsx/FrappeKanban.mjs +3 -1
  286. package/dist/jsx/FrappeKanban.mjs.map +1 -1
  287. package/dist/jsx/FrappeKanban.native.js +3 -1
  288. package/dist/jsx/FrappeKanban.native.js.map +1 -1
  289. package/dist/jsx/FrappeLikedBy.mjs +2 -0
  290. package/dist/jsx/FrappeLikedBy.mjs.map +1 -1
  291. package/dist/jsx/FrappeLikedBy.native.js +2 -0
  292. package/dist/jsx/FrappeLikedBy.native.js.map +1 -1
  293. package/dist/jsx/FrappeList.mjs +3 -1
  294. package/dist/jsx/FrappeList.mjs.map +1 -1
  295. package/dist/jsx/FrappeList.native.js +3 -1
  296. package/dist/jsx/FrappeList.native.js.map +1 -1
  297. package/dist/jsx/FrappeListView.mjs +7 -13
  298. package/dist/jsx/FrappeListView.mjs.map +1 -1
  299. package/dist/jsx/FrappeListView.native.js +13 -12
  300. package/dist/jsx/FrappeListView.native.js.map +1 -1
  301. package/dist/jsx/FrappeProvider.mjs +5 -1
  302. package/dist/jsx/FrappeProvider.mjs.map +1 -1
  303. package/dist/jsx/FrappeProvider.native.js +5 -1
  304. package/dist/jsx/FrappeProvider.native.js.map +1 -1
  305. package/dist/jsx/FrappeReportBuilder.mjs +2 -0
  306. package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
  307. package/dist/jsx/FrappeReportBuilder.native.js +2 -0
  308. package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
  309. package/dist/jsx/FrappeReportView.mjs +26 -49
  310. package/dist/jsx/FrappeReportView.mjs.map +1 -1
  311. package/dist/jsx/FrappeReportView.native.js +35 -50
  312. package/dist/jsx/FrappeReportView.native.js.map +1 -1
  313. package/dist/jsx/FrappeTable.mjs +107 -33
  314. package/dist/jsx/FrappeTable.mjs.map +1 -1
  315. package/dist/jsx/FrappeTable.native.js +140 -42
  316. package/dist/jsx/FrappeTable.native.js.map +1 -1
  317. package/dist/jsx/FrappeTreeView.mjs +3 -1
  318. package/dist/jsx/FrappeTreeView.mjs.map +1 -1
  319. package/dist/jsx/FrappeTreeView.native.js +3 -1
  320. package/dist/jsx/FrappeTreeView.native.js.map +1 -1
  321. package/dist/jsx/SyncErrorNotice.mjs +51 -0
  322. package/dist/jsx/SyncErrorNotice.mjs.map +1 -0
  323. package/dist/jsx/SyncErrorNotice.native.js +92 -0
  324. package/dist/jsx/SyncErrorNotice.native.js.map +1 -0
  325. package/dist/jsx/commentActions.mjs +95 -0
  326. package/dist/jsx/commentActions.mjs.map +1 -0
  327. package/dist/jsx/commentActions.native.js +129 -0
  328. package/dist/jsx/commentActions.native.js.map +1 -0
  329. package/dist/jsx/components/attachments.mjs +2 -0
  330. package/dist/jsx/components/attachments.mjs.map +1 -1
  331. package/dist/jsx/components/attachments.native.js +2 -0
  332. package/dist/jsx/components/attachments.native.js.map +1 -1
  333. package/dist/jsx/components/tags.mjs +5 -0
  334. package/dist/jsx/components/tags.mjs.map +1 -1
  335. package/dist/jsx/components/tags.native.js +5 -0
  336. package/dist/jsx/components/tags.native.js.map +1 -1
  337. package/dist/jsx/components/timeline.mjs +31 -4
  338. package/dist/jsx/components/timeline.mjs.map +1 -1
  339. package/dist/jsx/components/timeline.native.js +33 -2
  340. package/dist/jsx/components/timeline.native.js.map +1 -1
  341. package/dist/jsx/desk/DeskFormSidebar.mjs +2 -0
  342. package/dist/jsx/desk/DeskFormSidebar.mjs.map +1 -1
  343. package/dist/jsx/desk/DeskFormSidebar.native.js +2 -0
  344. package/dist/jsx/desk/DeskFormSidebar.native.js.map +1 -1
  345. package/dist/jsx/fieldRegistry.mjs +2 -0
  346. package/dist/jsx/fieldRegistry.mjs.map +1 -1
  347. package/dist/jsx/fieldRegistry.native.js +2 -0
  348. package/dist/jsx/fieldRegistry.native.js.map +1 -1
  349. package/dist/jsx/fields/Date/index.mjs +73 -9
  350. package/dist/jsx/fields/Date/index.mjs.map +1 -1
  351. package/dist/jsx/fields/Date/index.native.js +325 -37
  352. package/dist/jsx/fields/Date/index.native.js.map +1 -1
  353. package/dist/jsx/fields/Date/types.mjs +2 -0
  354. package/dist/jsx/fields/Date/types.mjs.map +1 -0
  355. package/dist/jsx/fields/Date/types.native.js +21 -0
  356. package/dist/jsx/fields/Date/types.native.js.map +1 -0
  357. package/dist/jsx/fields/Date/wire.mjs +4 -8
  358. package/dist/jsx/fields/Date/wire.mjs.map +1 -1
  359. package/dist/jsx/fields/Date/wire.native.js +4 -11
  360. package/dist/jsx/fields/Date/wire.native.js.map +1 -1
  361. package/dist/jsx/fields/DynamicLink.mjs +2 -0
  362. package/dist/jsx/fields/DynamicLink.mjs.map +1 -1
  363. package/dist/jsx/fields/DynamicLink.native.js +2 -0
  364. package/dist/jsx/fields/DynamicLink.native.js.map +1 -1
  365. package/dist/jsx/fields/FileUpload.mjs +2 -0
  366. package/dist/jsx/fields/FileUpload.mjs.map +1 -1
  367. package/dist/jsx/fields/FileUpload.native.js +2 -0
  368. package/dist/jsx/fields/FileUpload.native.js.map +1 -1
  369. package/dist/jsx/fields/TableMultiSelect.mjs +7 -2
  370. package/dist/jsx/fields/TableMultiSelect.mjs.map +1 -1
  371. package/dist/jsx/fields/TableMultiSelect.native.js +6 -1
  372. package/dist/jsx/fields/TableMultiSelect.native.js.map +1 -1
  373. package/dist/jsx/frappeStoryTarget.mjs +88 -2
  374. package/dist/jsx/frappeStoryTarget.mjs.map +1 -1
  375. package/dist/jsx/frappeStoryTarget.native.js +100 -0
  376. package/dist/jsx/frappeStoryTarget.native.js.map +1 -1
  377. package/dist/jsx/frappeUsers.mjs +2 -0
  378. package/dist/jsx/frappeUsers.mjs.map +1 -1
  379. package/dist/jsx/frappeUsers.native.js +2 -0
  380. package/dist/jsx/frappeUsers.native.js.map +1 -1
  381. package/dist/jsx/index.js +7 -3
  382. package/dist/jsx/index.js.map +1 -1
  383. package/dist/jsx/index.mjs +7 -3
  384. package/dist/jsx/index.mjs.map +1 -1
  385. package/dist/jsx/index.native.js +17 -4
  386. package/dist/jsx/index.native.js.map +1 -1
  387. package/dist/jsx/liveErrors.mjs +5 -1
  388. package/dist/jsx/liveErrors.mjs.map +1 -1
  389. package/dist/jsx/liveErrors.native.js +5 -1
  390. package/dist/jsx/liveErrors.native.js.map +1 -1
  391. package/dist/jsx/useFrappeAggregate.mjs +13 -1
  392. package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
  393. package/dist/jsx/useFrappeAggregate.native.js +14 -1
  394. package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
  395. package/dist/jsx/useFrappeFacets.mjs +299 -0
  396. package/dist/jsx/useFrappeFacets.mjs.map +1 -0
  397. package/dist/jsx/useFrappeFacets.native.js +632 -0
  398. package/dist/jsx/useFrappeFacets.native.js.map +1 -0
  399. package/package.json +20 -20
  400. package/src/FormSidebar.stories.tsx +88 -10
  401. package/src/FrappeAssignment.tsx +2 -0
  402. package/src/FrappeCalendar.tsx +12 -7
  403. package/src/FrappeChartCard.tsx +9 -2
  404. package/src/FrappeDashboard.spec.tsx +95 -0
  405. package/src/FrappeDashboard.tsx +20 -41
  406. package/src/FrappeForm.stories.tsx +53 -46
  407. package/src/FrappeForm.tsx +11 -1
  408. package/src/FrappeGantt.tsx +12 -7
  409. package/src/FrappeImageView.tsx +11 -1
  410. package/src/FrappeKPICard.stories.tsx +4 -1
  411. package/src/FrappeKPICard.tsx +2 -0
  412. package/src/FrappeKanban.tsx +4 -0
  413. package/src/FrappeLikedBy.stories.tsx +26 -10
  414. package/src/FrappeLikedBy.tsx +2 -0
  415. package/src/FrappeList.tsx +11 -1
  416. package/src/FrappeListView.spec.tsx +144 -0
  417. package/src/FrappeListView.tsx +11 -23
  418. package/src/FrappeProvider.tsx +30 -2
  419. package/src/FrappeReportBuilder.tsx +2 -0
  420. package/src/FrappeReportView.spec.tsx +140 -16
  421. package/src/FrappeReportView.stories.tsx +12 -6
  422. package/src/FrappeReportView.tsx +62 -61
  423. package/src/FrappeTable.keepRows.spec.tsx +200 -0
  424. package/src/FrappeTable.retryStorm.spec.tsx +140 -0
  425. package/src/FrappeTable.spec.tsx +222 -0
  426. package/src/FrappeTable.tsx +211 -54
  427. package/src/FrappeTreeView.tsx +11 -1
  428. package/src/SyncErrorNotice.spec.tsx +60 -0
  429. package/src/SyncErrorNotice.tsx +81 -0
  430. package/src/commentActions.ts +157 -0
  431. package/src/components/attachments.tsx +2 -0
  432. package/src/components/tags.tsx +14 -1
  433. package/src/components/timeline.optimistic.spec.tsx +106 -0
  434. package/src/components/timeline.tsx +36 -4
  435. package/src/desk/DeskFormSidebar.tsx +2 -0
  436. package/src/fieldRegistry.ts +2 -0
  437. package/src/fields/Date/index.native.tsx +410 -0
  438. package/src/fields/Date/index.tsx +138 -57
  439. package/src/fields/Date/nativePresentation.spec.tsx +251 -0
  440. package/src/fields/Date/typeParity.spec.tsx +480 -0
  441. package/src/fields/Date/types.ts +53 -0
  442. package/src/fields/Date/wire.spec.ts +63 -1
  443. package/src/fields/Date/wire.ts +17 -17
  444. package/src/fields/DynamicLink.tsx +2 -0
  445. package/src/fields/FileUpload.tsx +2 -0
  446. package/src/fields/TableMultiSelect.tsx +6 -3
  447. package/src/frappeStoryTarget.tsx +104 -1
  448. package/src/frappeUsers.ts +2 -0
  449. package/src/index.spec.ts +62 -0
  450. package/src/index.ts +36 -2
  451. package/src/liveErrors.spec.ts +50 -0
  452. package/src/liveErrors.ts +11 -1
  453. package/src/useFrappeAggregate.ts +20 -1
  454. package/src/useFrappeFacets.spec.tsx +436 -0
  455. package/src/useFrappeFacets.ts +568 -0
  456. package/types/FrappeAssignment.d.ts.map +1 -1
  457. package/types/FrappeCalendar.d.ts.map +1 -1
  458. package/types/FrappeChartCard.d.ts +1 -2
  459. package/types/FrappeChartCard.d.ts.map +1 -1
  460. package/types/FrappeDashboard.d.ts +0 -1
  461. package/types/FrappeDashboard.d.ts.map +1 -1
  462. package/types/FrappeForm.d.ts.map +1 -1
  463. package/types/FrappeGantt.d.ts.map +1 -1
  464. package/types/FrappeKPICard.d.ts.map +1 -1
  465. package/types/FrappeKanban.d.ts.map +1 -1
  466. package/types/FrappeLikedBy.d.ts.map +1 -1
  467. package/types/FrappeList.d.ts.map +1 -1
  468. package/types/FrappeListView.d.ts.map +1 -1
  469. package/types/FrappeProvider.d.ts +18 -1
  470. package/types/FrappeProvider.d.ts.map +1 -1
  471. package/types/FrappeReportBuilder.d.ts.map +1 -1
  472. package/types/FrappeReportView.d.ts +22 -15
  473. package/types/FrappeReportView.d.ts.map +1 -1
  474. package/types/FrappeTable.d.ts +21 -2
  475. package/types/FrappeTable.d.ts.map +1 -1
  476. package/types/FrappeTreeView.d.ts.map +1 -1
  477. package/types/SyncErrorNotice.d.ts +38 -0
  478. package/types/SyncErrorNotice.d.ts.map +1 -0
  479. package/types/commentActions.d.ts +46 -0
  480. package/types/commentActions.d.ts.map +1 -0
  481. package/types/components/attachments.d.ts.map +1 -1
  482. package/types/components/tags.d.ts.map +1 -1
  483. package/types/components/timeline.d.ts.map +1 -1
  484. package/types/desk/DeskFormSidebar.d.ts.map +1 -1
  485. package/types/fieldRegistry.d.ts.map +1 -1
  486. package/types/fields/Date/index.d.ts +22 -12
  487. package/types/fields/Date/index.d.ts.map +1 -1
  488. package/types/fields/Date/index.native.d.ts +14 -0
  489. package/types/fields/Date/index.native.d.ts.map +1 -0
  490. package/types/fields/Date/types.d.ts +27 -0
  491. package/types/fields/Date/types.d.ts.map +1 -0
  492. package/types/fields/Date/wire.d.ts +13 -9
  493. package/types/fields/Date/wire.d.ts.map +1 -1
  494. package/types/fields/DynamicLink.d.ts.map +1 -1
  495. package/types/fields/FileUpload.d.ts.map +1 -1
  496. package/types/fields/TableMultiSelect.d.ts.map +1 -1
  497. package/types/frappeStoryTarget.d.ts +19 -1
  498. package/types/frappeStoryTarget.d.ts.map +1 -1
  499. package/types/frappeUsers.d.ts.map +1 -1
  500. package/types/index.d.ts +6 -2
  501. package/types/index.d.ts.map +1 -1
  502. package/types/liveErrors.d.ts +4 -0
  503. package/types/liveErrors.d.ts.map +1 -1
  504. package/types/useFrappeAggregate.d.ts +4 -0
  505. package/types/useFrappeAggregate.d.ts.map +1 -1
  506. package/types/useFrappeFacets.d.ts +143 -0
  507. package/types/useFrappeFacets.d.ts.map +1 -0
  508. package/dist/cjs/fields/Date/date.native.js +0 -277
  509. package/dist/cjs/fields/Date/date.native.js.map +0 -1
  510. package/dist/esm/fields/Date/date.native.js +0 -238
  511. package/dist/esm/fields/Date/date.native.js.map +0 -1
  512. package/dist/jsx/fields/Date/date.native.js +0 -277
  513. package/dist/jsx/fields/Date/date.native.js.map +0 -1
  514. package/src/fields/Date/date.native.tsx +0 -299
  515. package/types/fields/Date/date.native.d.ts +0 -19
  516. package/types/fields/Date/date.native.d.ts.map +0 -1
@@ -2,14 +2,17 @@
2
2
  // through the house DataTable. These specs cover the pure payload
3
3
  // normalization (dict/string columns, dict/array rows incl. the server's
4
4
  // array-shaped total row), the fieldtype → DataTableColumn mapping
5
- // (DG-TBL-01 align-by-type), the filter refetch flow (debounced runs,
5
+ // (DG-TBL-01 align-by-type on the typed meta channel), row activation via
6
+ // DataTable's first-class `onRowPress`/`rowLabel` (the replacement contract
7
+ // for the deleted per-cell pressable layer: one pressable row unit, one
8
+ // roving tab stop per LC-49, Enter AND Space per LC-50, honest row names
9
+ // per DG-A11Y-06), the filter → refetch flow (debounced runs,
6
10
  // controlled/uncontrolled values, defaults seeding, refresh), and the honest
7
11
  // empty / error states.
8
12
  import { renderWithProviders } from "@multiplatform.one/test-utils";
9
13
  import { getDefaultColumnAlign } from "@multiplatform.one/table";
10
14
  import { cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
11
15
  import i18next from "i18next";
12
- import type { ReactElement } from "react";
13
16
  import { initReactI18next } from "react-i18next";
14
17
  import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
15
18
  import {
@@ -20,6 +23,7 @@ import {
20
23
  normalizeReportRows,
21
24
  reportColumnToDataTableColumn,
22
25
  reportFieldtypeToTableField,
26
+ reportRowLabel,
23
27
  scrubFieldname,
24
28
  type FrappeReportColumn,
25
29
  type FrappeReportRunResponse,
@@ -261,20 +265,25 @@ describe("reportColumnToDataTableColumn", () => {
261
265
  expect(col.width).toBeUndefined();
262
266
  });
263
267
 
264
- it("wraps cells in a pressable custom renderer when onRowPress is set", () => {
265
- const onRowPress = vi.fn();
266
- const rows = [{ size: 1 }, { size: 3.09 }];
267
- const col = reportColumnToDataTableColumn(
268
- { label: "Size (MB)", fieldname: "size", fieldtype: "Float" },
269
- { onRowPress, rowsRef: { current: rows } },
270
- );
271
- // DG-TBL-01 alignment survives the custom escape hatch.
272
- expect(col.field).toBe("custom");
273
- expect(col.align).toBe("right");
274
- const render = (col.fieldProps as { render: (v: unknown, r: unknown) => ReactElement }).render;
275
- renderWithProviders(render(3.09, rows[1]));
276
- fireEvent.click(screen.getByText("3.09"));
277
- expect(onRowPress).toHaveBeenCalledWith(rows[1], 1);
268
+ it("pins DG-TBL-01 alignment on the typed meta channel (numerics right)", () => {
269
+ const alignFor = (fieldtype: string) =>
270
+ reportColumnToDataTableColumn({ label: "X", fieldname: "x", fieldtype }).meta?.align;
271
+ expect(alignFor("Int")).toBe("right");
272
+ expect(alignFor("Float")).toBe("right");
273
+ expect(alignFor("Currency")).toBe("right");
274
+ expect(alignFor("Check")).toBe("center");
275
+ expect(alignFor("Data")).toBe("left");
276
+ });
277
+ });
278
+
279
+ describe("reportRowLabel", () => {
280
+ it("names rows by name, then title, and stays empty otherwise", () => {
281
+ expect(reportRowLabel({ name: "SINV-0001", title: "Acme" })).toBe("SINV-0001");
282
+ expect(reportRowLabel({ title: "Acme Corp" })).toBe("Acme Corp");
283
+ expect(reportRowLabel({ name: 42 })).toBe("42");
284
+ // Empty defers to DataTable's fallbacks (first cell → "Row N").
285
+ expect(reportRowLabel({ table: "tabDocField" })).toBe("");
286
+ expect(reportRowLabel({ name: "" })).toBe("");
278
287
  });
279
288
  });
280
289
 
@@ -325,6 +334,27 @@ describe("FrappeReportView", () => {
325
334
  expect(await screen.findByText("Error running Broken Report")).toBeTruthy();
326
335
  });
327
336
 
337
+ it("permission chrome does not render a python traceback", async () => {
338
+ const err = Object.assign(
339
+ new Error(
340
+ '["Traceback (most recent call last):\\n File "apps/frappe/frappe/app.py", line 120, in application',
341
+ ),
342
+ { status: 403 },
343
+ );
344
+ renderWithProviders(
345
+ <FrappeReportView
346
+ reportName="Database Storage Usage By Tables"
347
+ fixture={() => {
348
+ throw err;
349
+ }}
350
+ />,
351
+ );
352
+ expect(
353
+ await screen.findByText("You do not have permission to run Database Storage Usage By Tables"),
354
+ ).toBeTruthy();
355
+ expect(screen.queryByText(/Traceback/)).toBeNull();
356
+ });
357
+
328
358
  it("re-runs the report when a filter value changes (filter → refetch)", async () => {
329
359
  const loader = vi.fn(async ({ filters }: { filters: Record<string, unknown> }) => ({
330
360
  columns: storageReport.columns,
@@ -415,3 +445,97 @@ describe("FrappeReportView", () => {
415
445
  expect(await screen.findByText("Nothing to see here")).toBeTruthy();
416
446
  });
417
447
  });
448
+
449
+ // ---------------------------------------------------------------------------
450
+ // Row activation — DataTable onRowPress (the replacement contract for the
451
+ // deleted per-cell pressable layer)
452
+ // ---------------------------------------------------------------------------
453
+
454
+ const pressableReport: FrappeReportRunResponse = {
455
+ columns: [
456
+ { label: "Name", fieldname: "name", fieldtype: "Link", options: "ToDo", width: 140 },
457
+ { label: "Table", fieldname: "table", fieldtype: "Data", width: 200 },
458
+ { label: "Size (MB)", fieldname: "size", fieldtype: "Float" },
459
+ ],
460
+ result: [
461
+ { name: "ROW-0001", table: "tabDocField", size: 3.09 },
462
+ { name: "ROW-0002", table: "tabError Log", size: 0.89 },
463
+ { name: "ROW-0003", table: "tabSeries", size: 0.02 },
464
+ ],
465
+ };
466
+
467
+ function pressRows(container: HTMLElement): HTMLElement[] {
468
+ return Array.from(container.querySelectorAll('[data-row-press="true"]')) as HTMLElement[];
469
+ }
470
+
471
+ function renderPressableReport(extraProps: Record<string, unknown> = {}) {
472
+ const onRowPress = vi.fn();
473
+ const result = renderWithProviders(
474
+ <FrappeReportView
475
+ reportName="Pressable Report"
476
+ fixture={pressableReport}
477
+ onRowPress={onRowPress}
478
+ tableProps={{ layout: "table", enablePagination: false }}
479
+ {...extraProps}
480
+ />,
481
+ );
482
+ return { ...result, onRowPress };
483
+ }
484
+
485
+ describe("FrappeReportView onRowPress → DataTable onRowPress", () => {
486
+ it("activates on mouse click anywhere in the row and on Enter AND Space", async () => {
487
+ const { container, onRowPress } = renderPressableReport();
488
+ await screen.findByText("tabDocField");
489
+ const rows = pressRows(container);
490
+ expect(rows).toHaveLength(3);
491
+
492
+ // Mouse: any cell in the row is the row's press target (index keeps
493
+ // its full-row-set meaning).
494
+ fireEvent.click(screen.getByText("tabError Log"));
495
+ expect(onRowPress).toHaveBeenCalledTimes(1);
496
+ expect(onRowPress).toHaveBeenCalledWith(expect.objectContaining({ name: "ROW-0002" }), 1);
497
+
498
+ // Keyboard: Enter AND Space activate the focused row (LC-50).
499
+ rows[0].focus();
500
+ fireEvent.keyDown(rows[0], { key: "Enter" });
501
+ expect(onRowPress).toHaveBeenCalledTimes(2);
502
+ expect(onRowPress).toHaveBeenLastCalledWith(expect.objectContaining({ name: "ROW-0001" }), 0);
503
+ fireEvent.keyDown(rows[0], { key: " " });
504
+ expect(onRowPress).toHaveBeenCalledTimes(3);
505
+ });
506
+
507
+ it("exposes ONE roving tab stop, names rows honestly, and keeps cells pressable-free", async () => {
508
+ const { container } = renderPressableReport();
509
+ await screen.findByText("tabDocField");
510
+ const rows = pressRows(container);
511
+
512
+ // LC-49: exactly one tab stop across the row set; arrows rove it.
513
+ expect(rows.map((row) => row.getAttribute("tabindex"))).toEqual(["0", "-1", "-1"]);
514
+ rows[0].focus();
515
+ fireEvent.keyDown(rows[0], { key: "ArrowDown" });
516
+ expect(document.activeElement).toBe(rows[1]);
517
+
518
+ // DG-A11Y-06: rows named by report identity (the name column).
519
+ expect(rows.map((row) => row.getAttribute("aria-label"))).toEqual([
520
+ "ROW-0001",
521
+ "ROW-0002",
522
+ "ROW-0003",
523
+ ]);
524
+
525
+ // The per-cell pressable layer is GONE: no cell-level buttons or
526
+ // extra tab stops inside pressable rows.
527
+ for (const row of rows) {
528
+ expect(row.querySelectorAll('[role="button"]')).toHaveLength(0);
529
+ expect(row.querySelectorAll('[tabindex="0"]')).toHaveLength(0);
530
+ }
531
+ });
532
+
533
+ it("falls back to the first visible cell for rows without a name/title column", async () => {
534
+ const { container } = renderPressableReport({ fixture: storageReport });
535
+ await screen.findByText("tabDocField");
536
+ expect(pressRows(container).map((row) => row.getAttribute("aria-label"))).toEqual([
537
+ "tabDocField",
538
+ "tabError Log",
539
+ ]);
540
+ });
541
+ });
@@ -154,20 +154,26 @@ const storageReportFixture: FrappeReportRunResponse = {
154
154
  add_total_row: true,
155
155
  };
156
156
 
157
- export const Live = () => (
158
- <FrappeStoryProvider>
157
+ export const Live = () =>
158
+ frappeStoriesLive ? (
159
+ <FrappeStoryProvider>
160
+ <FrappeReportView
161
+ reportName="Database Storage Usage By Tables"
162
+ onRowPress={action("onRowPress")}
163
+ />
164
+ </FrappeStoryProvider>
165
+ ) : (
159
166
  <FrappeReportView
160
167
  reportName="Database Storage Usage By Tables"
161
- fixture={frappeStoriesLive ? undefined : storageReportFixture}
168
+ fixture={storageReportFixture}
162
169
  onRowPress={action("onRowPress")}
163
170
  />
164
- </FrappeStoryProvider>
165
- );
171
+ );
166
172
  Live.parameters = {
167
173
  docs: {
168
174
  description: {
169
175
  story:
170
- "Runs the stock 'Database Storage Usage By Tables' Script Report against the real bench through the Storybook proxy (frappe.desk.query_report.runverified live on a vanilla bench; System Manager report perms ride the proxy's Administrator token). When the bench is down the story renders a fixture payload mirroring the same column contract instead.",
176
+ "Runs the stock 'Database Storage Usage By Tables' Script Report against the real bench through the Storybook proxy. A session without Report permission (a plain Desk User) sees the permission EmptyState 'You do not have permission to run …' plus the server's sentence — not a python traceback; same honesty as QueryReportScreen Live. System Manager / Administrator sees the table. When the bench is down the story renders a fixture payload mirroring the same column contract.",
171
177
  },
172
178
  },
173
179
  };
@@ -25,9 +25,13 @@
25
25
  * anything else falls back to Data.
26
26
  *
27
27
  * Column mapping targets read-only display: fieldtypes map to DataTable
28
- * `field` types so DG-TBL-01 align-by-type defaults apply automatically
29
- * (numerics right via `stepper`, Check centered via `checkbox`, text left)
30
- * and cells format through the forms `FieldDisplay` pipeline (`readOnly`).
28
+ * `field` types and cells format through the forms `FieldDisplay` pipeline
29
+ * (`readOnly`); the DG-TBL-01 align-by-type resolution (numerics right via
30
+ * `stepper`, Check centered via `checkbox`, text left) is pinned once per
31
+ * column on the typed `meta.align` channel. Row activation rides
32
+ * DataTable's first-class `onRowPress`/`rowLabel` contract — the whole row
33
+ * is ONE pressable unit (no per-cell pressable wrappers), named by the
34
+ * report row's `name`/`title` value when present.
31
35
  *
32
36
  * CSV/JSON export reuses `DataTableExport` from `@multiplatform.one/table`
33
37
  * (no new export machinery), capability-gated like FrappeReportBuilder (B12):
@@ -35,7 +39,7 @@
35
39
  */
36
40
  import { AsyncDebouncer, CallModule, HttpClient, type AuthConfig } from "@multiplatform.one/frappe";
37
41
  import { AsyncBoundary } from "@multiplatform.one/components";
38
- import { DatePicker, FieldDisplay, Spinner } from "@multiplatform.one/forms";
42
+ import { DatePicker, Spinner } from "@multiplatform.one/forms";
39
43
  import {
40
44
  DataTable,
41
45
  DataTableExport,
@@ -44,11 +48,11 @@ import {
44
48
  type DataTableProps,
45
49
  type DataTableField,
46
50
  } from "@multiplatform.one/table";
47
- import { useResolvedKnobs } from "@multiplatform.one/theme";
51
+ import { formatCalendarDate, useResolvedKnobs } from "@multiplatform.one/theme";
48
52
  import { ArrowClockwiseIcon } from "@phosphor-icons/react";
49
53
  import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
50
54
  import { useTranslation } from "react-i18next";
51
- import { Button, Text, View, XStack, YStack, type YStackProps } from "tamagui";
55
+ import { Button, Text, XStack, YStack, type YStackProps } from "tamagui";
52
56
  import { useFrappeConfig } from "./FrappeProvider";
53
57
  import { Check } from "./fields/Check";
54
58
  import { Data } from "./fields/Data";
@@ -269,62 +273,47 @@ export function reportFieldtypeToTableField(column: FrappeReportColumn): {
269
273
  }
270
274
  }
271
275
 
272
- export interface ReportColumnMapOptions {
273
- /** Row press handler — cells wrap in a pressable surface when set. */
274
- onRowPress?: (row: FrappeReportRow, index: number) => void;
275
- /** Current rows (index lookup for onRowPress). Read at press time. */
276
- rowsRef?: { current: FrappeReportRow[] };
277
- }
278
-
279
276
  /**
280
277
  * Map a normalized report column to a DataTableColumn.
281
278
  *
282
- * Without `onRowPress` the column carries a plain `field` (FieldDisplay via
283
- * the table's readOnly path; alignment fully automatic). With `onRowPress`
284
- * the cell renders through the `custom` escape hatch (pressable wrapper
285
- * around the same FieldDisplay) and the DG-TBL-01 alignment is preserved by
286
- * resolving it explicitly from the underlying field type.
279
+ * The column carries a plain `field` (FieldDisplay via the table's
280
+ * readOnly path) and pins its DG-TBL-01 alignment on the typed
281
+ * `meta.align` channel (numerics right, Check centered, text left) so the
282
+ * resolved alignment survives every downstream cell surface, custom
283
+ * renderers included (DataTable's aligned cell wrapper reads it).
287
284
  */
288
285
  export function reportColumnToDataTableColumn(
289
286
  column: FrappeReportColumn,
290
- { onRowPress, rowsRef }: ReportColumnMapOptions = {},
291
287
  ): DataTableColumn<FrappeReportRow> {
292
288
  const { field, fieldProps } = reportFieldtypeToTableField(column);
293
289
  const width = column.width != null ? Number(column.width) : undefined;
294
- const base: DataTableColumn<FrappeReportRow> = {
290
+ return {
295
291
  id: column.fieldname,
296
292
  accessorKey: column.fieldname,
297
293
  header: column.label,
298
294
  ...(width && Number.isFinite(width) ? { width } : {}),
299
295
  field,
300
296
  ...(fieldProps ? { fieldProps } : {}),
301
- };
302
- if (!onRowPress) return base;
303
- return {
304
- ...base,
305
- // Explicit align: the custom field would otherwise default to left.
306
- align: getDefaultColumnAlign({ field, fieldProps }),
307
- field: "custom",
308
- fieldProps: {
309
- ...fieldProps,
310
- render: (value: unknown, rowData: unknown) => (
311
- <View
312
- onPress={() => {
313
- const rows = rowsRef?.current ?? [];
314
- const row = rowData as FrappeReportRow;
315
- onRowPress(row, rows.indexOf(row));
316
- }}
317
- cursor="pointer"
318
- minWidth={0}
319
- flexShrink={1}
320
- >
321
- <FieldDisplay field={field} value={value} fieldProps={fieldProps} />
322
- </View>
323
- ),
324
- },
297
+ meta: { align: getDefaultColumnAlign({ id: column.fieldname, field, fieldProps }) },
325
298
  };
326
299
  }
327
300
 
301
+ /**
302
+ * Accessible row identity for pressable report rows (DG-A11Y-06): report
303
+ * rows carry no doctype meta, so identity is the conventional report
304
+ * identity columns — the linked document `name`, else `title`. An empty
305
+ * result defers to DataTable's own fallbacks (first visible cell value →
306
+ * positional "Row N").
307
+ */
308
+ export function reportRowLabel(row: FrappeReportRow): string {
309
+ for (const key of ["name", "title"] as const) {
310
+ const value = row[key];
311
+ if (typeof value === "string" && value) return value;
312
+ if (typeof value === "number") return String(value);
313
+ }
314
+ return "";
315
+ }
316
+
328
317
  /**
329
318
  * Download capability gate for the built-in export path (B12 precedent from
330
319
  * FrappeReportBuilder): `downloadBlob` rides a DOM anchor + object URL and
@@ -565,13 +554,6 @@ export function useFrappeReport(
565
554
  // Filter bar
566
555
  // ---------------------------------------------------------------------------
567
556
 
568
- function toFrappeDate(date: Date): string {
569
- const y = date.getFullYear();
570
- const m = String(date.getMonth() + 1).padStart(2, "0");
571
- const d = String(date.getDate()).padStart(2, "0");
572
- return `${y}-${m}-${d}`;
573
- }
574
-
575
557
  interface ReportFilterFieldProps {
576
558
  def: FrappeReportFilterDef;
577
559
  value: unknown;
@@ -602,7 +584,7 @@ function ReportFilterField({ def, value, onChange }: ReportFilterFieldProps) {
602
584
  label={def.label}
603
585
  compact
604
586
  value={value == null ? null : String(value)}
605
- onChange={(date) => onChange(date ? toFrappeDate(date) : "")}
587
+ onChange={(date) => onChange(date ? formatCalendarDate(date) : "")}
606
588
  />
607
589
  );
608
590
  case "Check":
@@ -648,7 +630,14 @@ export interface FrappeReportViewProps extends Omit<YStackProps, "children"> {
648
630
  * only the desk evaluates.
649
631
  */
650
632
  filterFields?: FrappeReportFilterDef[];
651
- /** Row press handler (row, index). */
633
+ /**
634
+ * Callback when a row is activated (row, index into the full row set).
635
+ * Rides DataTable's first-class `onRowPress` contract: the whole row is
636
+ * ONE pressable unit (no per-cell pressable wrappers), the row set
637
+ * exposes a single roving tab stop (LC-49), Enter AND Space activate
638
+ * the focused row (LC-50), and presses on inner interactive controls
639
+ * never fire the row press.
640
+ */
652
641
  onRowPress?: (row: FrappeReportRow, index: number) => void;
653
642
  /** Fixture payload or loader — renders without any network. */
654
643
  fixture?: FrappeReportFixture;
@@ -758,15 +747,10 @@ export function FrappeReportView({
758
747
  }, [isControlled, reportName, filterDefs]);
759
748
 
760
749
  const rows = payload?.rows ?? [];
761
- const rowsRef = useRef<FrappeReportRow[]>(rows);
762
- rowsRef.current = rows;
763
750
 
764
751
  const columns = useMemo(
765
- () =>
766
- (payload?.columns ?? []).map((col) =>
767
- reportColumnToDataTableColumn(col, onRowPress ? { onRowPress, rowsRef } : {}),
768
- ),
769
- [payload?.columns, onRowPress],
752
+ () => (payload?.columns ?? []).map((col) => reportColumnToDataTableColumn(col)),
753
+ [payload?.columns],
770
754
  );
771
755
 
772
756
  const exportColumns = useMemo(
@@ -881,7 +865,24 @@ export function FrappeReportView({
881
865
  {...boundaryState}
882
866
  />
883
867
  ) : (
884
- <ReportDataTable data={rows} columns={columns} readOnly {...tableProps} />
868
+ <ReportDataTable
869
+ data={rows}
870
+ columns={columns}
871
+ readOnly
872
+ // onRowPress (the stable FrappeReportView prop) rides DataTable's
873
+ // first-class row activation: whole-row press target, roving tab
874
+ // stop (LC-49), Enter/Space activation (LC-50), inner interactive
875
+ // controls win. The forwarded index keeps its full-row-set
876
+ // meaning (DataTable reports page-local indexes). rowLabel names
877
+ // each pressable row by report identity. Both spread BEFORE
878
+ // {...tableProps} so an explicit consumer onRowPress/rowLabel
879
+ // still ejects (consumer-last house rule).
880
+ {...(onRowPress
881
+ ? { onRowPress: (row: FrappeReportRow) => onRowPress(row, rows.indexOf(row)) }
882
+ : {})}
883
+ rowLabel={reportRowLabel}
884
+ {...tableProps}
885
+ />
885
886
  )}
886
887
  </YStack>
887
888
  );
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Keep-rows-and-reconcile (the read arm of the ONE optimistic pattern):
3
+ * changing the table's filters must NOT tear the list down and re-render
4
+ * from empty. The current rows stay mounted, the new filter applies
5
+ * locally to what is already on screen (optimistic narrowing), and the
6
+ * real query reconciles when data returns — no blanking, no skeleton
7
+ * flash, no scroll reset.
8
+ *
9
+ * The named reproducer was the backoffice list: every filter toggle
10
+ * remounted FrappeTable (list keyed on a filter generation), resetting
11
+ * `hasLoadedOnce` and flashing the AsyncBoundary skeleton for the full
12
+ * round-trip (~1.6s of blank list against the live bench). These specs pin
13
+ * the replacement contract at the FrappeTable layer: the `filters` prop
14
+ * adopts in place (content-keyed), held rows render while the swapped
15
+ * collection loads, and the loading boundary is reserved for the FIRST
16
+ * load only.
17
+ */
18
+
19
+ import { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
20
+ import type { DataTableColumn } from "@multiplatform.one/table";
21
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
22
+ import { cleanup, screen, waitFor } from "@testing-library/react";
23
+ import i18next from "i18next";
24
+ import { initReactI18next } from "react-i18next";
25
+ import { afterEach, beforeAll, describe, expect, it } from "vitest";
26
+ import { FrappeProvider } from "./FrappeProvider";
27
+ import { FrappeTable } from "./FrappeTable";
28
+
29
+ beforeAll(async () => {
30
+ if (!i18next.isInitialized) {
31
+ await i18next.use(initReactI18next).init({
32
+ lng: "en",
33
+ interpolation: { escapeValue: false },
34
+ resources: { en: { translation: {} } },
35
+ });
36
+ }
37
+ });
38
+
39
+ afterEach(() => {
40
+ cleanup();
41
+ });
42
+
43
+ interface Customer {
44
+ name: string;
45
+ doctype: string;
46
+ customer_name: string;
47
+ status: string;
48
+ }
49
+
50
+ const customers: Customer[] = [
51
+ { name: "CUST-001", doctype: "Customer", customer_name: "Acme Corp", status: "Active" },
52
+ { name: "CUST-002", doctype: "Customer", customer_name: "Globex", status: "Active" },
53
+ { name: "CUST-003", doctype: "Customer", customer_name: "Initech", status: "Lead" },
54
+ ];
55
+
56
+ const columns: DataTableColumn<Partial<Customer>>[] = [
57
+ { id: "name", accessorKey: "name", header: "ID" },
58
+ { id: "customer_name", accessorKey: "customer_name", header: "Customer Name" },
59
+ { id: "status", accessorKey: "status", header: "Status" },
60
+ ];
61
+
62
+ const filterFields = [{ id: "status", label: "Status", type: "text" as const }];
63
+
64
+ /**
65
+ * Fixture provider whose getList can be gated: while `gate` is set, list
66
+ * fetches wait on it — models the server round-trip a filter change causes,
67
+ * so the specs can assert what renders DURING the gap.
68
+ */
69
+ class GatedFixtureProvider extends InMemoryFixtureProvider {
70
+ gate: Promise<void> | null = null;
71
+
72
+ override async getList<TDoc extends { name: string; doctype: string }>(
73
+ ...args: Parameters<InMemoryFixtureProvider["getList"]>
74
+ ) {
75
+ if (this.gate) await this.gate;
76
+ return super.getList(...args) as Promise<Partial<TDoc>[]>;
77
+ }
78
+ }
79
+
80
+ function renderTable(
81
+ fixtures: InMemoryFixtureProvider,
82
+ filters: Array<[keyof Customer & string, string, unknown]>,
83
+ ) {
84
+ return renderWithProviders(
85
+ <FrappeProvider baseURL="https://frappetable-keeprows.spec.test" fixtures={fixtures}>
86
+ <FrappeTable<Customer>
87
+ doctype="Customer"
88
+ columns={columns}
89
+ filterableFields={filterFields}
90
+ filters={filters}
91
+ layout="table"
92
+ enablePagination={false}
93
+ />
94
+ </FrappeProvider>,
95
+ );
96
+ }
97
+
98
+ function rerenderTable(
99
+ rerender: (ui: React.ReactElement) => void,
100
+ fixtures: InMemoryFixtureProvider,
101
+ filters: Array<[keyof Customer & string, string, unknown]>,
102
+ ) {
103
+ rerender(
104
+ <FrappeProvider baseURL="https://frappetable-keeprows.spec.test" fixtures={fixtures}>
105
+ <FrappeTable<Customer>
106
+ doctype="Customer"
107
+ columns={columns}
108
+ filterableFields={filterFields}
109
+ filters={filters}
110
+ layout="table"
111
+ enablePagination={false}
112
+ />
113
+ </FrappeProvider>,
114
+ );
115
+ }
116
+
117
+ describe("FrappeTable keep-rows-and-reconcile on filter change", () => {
118
+ it("keeps current rows rendered and locally narrows them while the new query is in flight, then reconciles", async () => {
119
+ const fixtures = new GatedFixtureProvider({ Customer: customers });
120
+ const { rerender } = renderTable(fixtures, []);
121
+ await screen.findByText("Acme Corp");
122
+ expect(screen.getByText("Initech")).toBeTruthy();
123
+
124
+ // Gate the round-trip the filter change will cause.
125
+ let release: () => void = () => {};
126
+ fixtures.gate = new Promise<void>((resolve) => {
127
+ release = resolve;
128
+ });
129
+
130
+ rerenderTable(rerender, fixtures, [["status", "=", "Active"]]);
131
+
132
+ // DURING the gap: the matching rows never left the screen, the filter
133
+ // already applied to what was cached locally, and no loading boundary
134
+ // replaced the table.
135
+ await waitFor(() => expect(screen.queryByText("Initech")).toBeNull());
136
+ expect(screen.getByText("Acme Corp")).toBeTruthy();
137
+ expect(screen.getByText("Globex")).toBeTruthy();
138
+ expect(screen.queryByText(/No Customer records found/i)).toBeNull();
139
+
140
+ // Reconcile: the server (fixture) result replaces the held rows.
141
+ release();
142
+ fixtures.gate = null;
143
+ await waitFor(() => {
144
+ expect(screen.getByText("Acme Corp")).toBeTruthy();
145
+ expect(screen.getByText("Globex")).toBeTruthy();
146
+ expect(screen.queryByText("Initech")).toBeNull();
147
+ });
148
+ });
149
+
150
+ it("a filter that matches no current rows keeps the last-good rows mounted (no empty boundary)", async () => {
151
+ const fixtures = new GatedFixtureProvider({ Customer: customers });
152
+ const { rerender } = renderTable(fixtures, []);
153
+ await screen.findByText("Acme Corp");
154
+
155
+ let release: () => void = () => {};
156
+ fixtures.gate = new Promise<void>((resolve) => {
157
+ release = resolve;
158
+ });
159
+
160
+ rerenderTable(rerender, fixtures, [["status", "=", "Closed"]]);
161
+
162
+ // DURING the gap: local apply would empty the page — previous rows stay
163
+ // as placeholderData. The empty AsyncBoundary must not replace the table.
164
+ expect(screen.getByText("Acme Corp")).toBeTruthy();
165
+ expect(screen.getByText("Initech")).toBeTruthy();
166
+ expect(screen.queryByText(/No Customer records found/i)).toBeNull();
167
+
168
+ release();
169
+ fixtures.gate = null;
170
+ await waitFor(() => {
171
+ expect(screen.queryByText("Acme Corp")).toBeNull();
172
+ expect(screen.getByText(/No Customer records found/i)).toBeTruthy();
173
+ });
174
+ });
175
+
176
+ it("clearing the filter keeps the narrowed rows rendered until the wider set reconciles in", async () => {
177
+ const fixtures = new GatedFixtureProvider({ Customer: customers });
178
+ const { rerender } = renderTable(fixtures, [["status", "=", "Active"]]);
179
+ await screen.findByText("Acme Corp");
180
+ expect(screen.queryByText("Initech")).toBeNull();
181
+
182
+ let release: () => void = () => {};
183
+ fixtures.gate = new Promise<void>((resolve) => {
184
+ release = resolve;
185
+ });
186
+
187
+ rerenderTable(rerender, fixtures, []);
188
+
189
+ // DURING the gap: last-good rows stay (no filter to narrow with).
190
+ expect(screen.getByText("Acme Corp")).toBeTruthy();
191
+ expect(screen.getByText("Globex")).toBeTruthy();
192
+ expect(screen.queryByText(/No Customer records found/i)).toBeNull();
193
+
194
+ release();
195
+ fixtures.gate = null;
196
+ // Reconcile: the wider set (including Initech) arrives.
197
+ await screen.findByText("Initech");
198
+ expect(screen.getByText("Acme Corp")).toBeTruthy();
199
+ });
200
+ });