@multiplatform.one/frappe-ui 7.10.0 → 7.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/FrappeDashboard.cjs +8 -10
  3. package/dist/cjs/FrappeDashboard.native.js +8 -14
  4. package/dist/cjs/FrappeDashboard.native.js.map +1 -1
  5. package/dist/cjs/FrappeForm.cjs +465 -175
  6. package/dist/cjs/FrappeForm.native.js +611 -207
  7. package/dist/cjs/FrappeForm.native.js.map +1 -1
  8. package/dist/cjs/FrappeFormConnections.cjs +125 -0
  9. package/dist/cjs/FrappeFormConnections.native.js +149 -0
  10. package/dist/cjs/FrappeFormConnections.native.js.map +1 -0
  11. package/dist/cjs/FrappeImageView.cjs +7 -9
  12. package/dist/cjs/FrappeImageView.native.js +7 -13
  13. package/dist/cjs/FrappeImageView.native.js.map +1 -1
  14. package/dist/cjs/FrappeList.cjs +29 -4
  15. package/dist/cjs/FrappeList.native.js +37 -5
  16. package/dist/cjs/FrappeList.native.js.map +1 -1
  17. package/dist/cjs/FrappeListView.cjs +102 -29
  18. package/dist/cjs/FrappeListView.native.js +109 -29
  19. package/dist/cjs/FrappeListView.native.js.map +1 -1
  20. package/dist/cjs/FrappeProvider.cjs +7 -2
  21. package/dist/cjs/FrappeProvider.native.js +7 -2
  22. package/dist/cjs/FrappeProvider.native.js.map +1 -1
  23. package/dist/cjs/FrappeReportBuilder.cjs +10 -11
  24. package/dist/cjs/FrappeReportBuilder.native.js +10 -16
  25. package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
  26. package/dist/cjs/FrappeRowsReader.cjs +49 -0
  27. package/dist/cjs/FrappeRowsReader.native.js +53 -0
  28. package/dist/cjs/FrappeRowsReader.native.js.map +1 -0
  29. package/dist/cjs/FrappeTable.cjs +18 -6
  30. package/dist/cjs/FrappeTable.native.js +24 -6
  31. package/dist/cjs/FrappeTable.native.js.map +1 -1
  32. package/dist/cjs/fieldRegistry.cjs +36 -11
  33. package/dist/cjs/fieldRegistry.native.js +59 -11
  34. package/dist/cjs/fieldRegistry.native.js.map +1 -1
  35. package/dist/cjs/fields/Date/index.native.js +5 -2
  36. package/dist/cjs/fields/Date/index.native.js.map +1 -1
  37. package/dist/cjs/fields/Date/sheetKeyboard.cjs +2 -2
  38. package/dist/cjs/fields/Date/sheetKeyboard.native.js +2 -2
  39. package/dist/cjs/fields/Date/sheetKeyboard.native.js.map +1 -1
  40. package/dist/cjs/fields/DateRange/index.cjs +2 -1
  41. package/dist/cjs/fields/DateRange/index.native.js +4 -1
  42. package/dist/cjs/fields/DateRange/index.native.js.map +1 -1
  43. package/dist/cjs/formEngine/index.cjs +4 -2
  44. package/dist/cjs/formEngine/index.native.js +3 -1
  45. package/dist/cjs/formEngine/index.native.js.map +1 -1
  46. package/dist/cjs/formEngine/useSessionRoles.cjs +77 -0
  47. package/dist/cjs/formEngine/useSessionRoles.native.js +84 -0
  48. package/dist/cjs/formEngine/useSessionRoles.native.js.map +1 -0
  49. package/dist/cjs/frappeConnections.cjs +41 -0
  50. package/dist/cjs/frappeConnections.native.js +50 -0
  51. package/dist/cjs/frappeConnections.native.js.map +1 -0
  52. package/dist/cjs/frappeNavigation.cjs +107 -0
  53. package/dist/cjs/frappeNavigation.native.js +112 -0
  54. package/dist/cjs/frappeNavigation.native.js.map +1 -0
  55. package/dist/cjs/index.cjs +18 -1
  56. package/dist/cjs/index.native.js +18 -1
  57. package/dist/cjs/index.native.js.map +1 -1
  58. package/dist/cjs/schemaForm/index.cjs +4 -1
  59. package/dist/cjs/schemaForm/index.native.js +4 -1
  60. package/dist/cjs/schemaForm/index.native.js.map +1 -1
  61. package/dist/cjs/useConnectionFilters.cjs +146 -0
  62. package/dist/cjs/useConnectionFilters.native.js +213 -0
  63. package/dist/cjs/useConnectionFilters.native.js.map +1 -0
  64. package/dist/cjs/useFrappePagination.cjs +37 -18
  65. package/dist/cjs/useFrappePagination.native.js +59 -23
  66. package/dist/cjs/useFrappePagination.native.js.map +1 -1
  67. package/dist/cjs/useFrappeRows.native.js.map +1 -1
  68. package/dist/esm/FrappeDashboard.mjs +9 -11
  69. package/dist/esm/FrappeDashboard.mjs.map +1 -1
  70. package/dist/esm/FrappeDashboard.native.js +9 -15
  71. package/dist/esm/FrappeDashboard.native.js.map +1 -1
  72. package/dist/esm/FrappeForm.mjs +471 -181
  73. package/dist/esm/FrappeForm.mjs.map +1 -1
  74. package/dist/esm/FrappeForm.native.js +599 -195
  75. package/dist/esm/FrappeForm.native.js.map +1 -1
  76. package/dist/esm/FrappeFormConnections.mjs +99 -0
  77. package/dist/esm/FrappeFormConnections.mjs.map +1 -0
  78. package/dist/esm/FrappeFormConnections.native.js +120 -0
  79. package/dist/esm/FrappeFormConnections.native.js.map +1 -0
  80. package/dist/esm/FrappeImageView.mjs +8 -10
  81. package/dist/esm/FrappeImageView.mjs.map +1 -1
  82. package/dist/esm/FrappeImageView.native.js +8 -14
  83. package/dist/esm/FrappeImageView.native.js.map +1 -1
  84. package/dist/esm/FrappeList.mjs +31 -6
  85. package/dist/esm/FrappeList.mjs.map +1 -1
  86. package/dist/esm/FrappeList.native.js +39 -7
  87. package/dist/esm/FrappeList.native.js.map +1 -1
  88. package/dist/esm/FrappeListView.mjs +103 -30
  89. package/dist/esm/FrappeListView.mjs.map +1 -1
  90. package/dist/esm/FrappeListView.native.js +110 -30
  91. package/dist/esm/FrappeListView.native.js.map +1 -1
  92. package/dist/esm/FrappeProvider.mjs +7 -2
  93. package/dist/esm/FrappeProvider.mjs.map +1 -1
  94. package/dist/esm/FrappeProvider.native.js +7 -2
  95. package/dist/esm/FrappeProvider.native.js.map +1 -1
  96. package/dist/esm/FrappeReportBuilder.mjs +11 -12
  97. package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
  98. package/dist/esm/FrappeReportBuilder.native.js +11 -17
  99. package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
  100. package/dist/esm/FrappeRowsReader.mjs +24 -0
  101. package/dist/esm/FrappeRowsReader.mjs.map +1 -0
  102. package/dist/esm/FrappeRowsReader.native.js +25 -0
  103. package/dist/esm/FrappeRowsReader.native.js.map +1 -0
  104. package/dist/esm/FrappeTable.mjs +18 -6
  105. package/dist/esm/FrappeTable.mjs.map +1 -1
  106. package/dist/esm/FrappeTable.native.js +24 -6
  107. package/dist/esm/FrappeTable.native.js.map +1 -1
  108. package/dist/esm/fieldRegistry.mjs +35 -12
  109. package/dist/esm/fieldRegistry.mjs.map +1 -1
  110. package/dist/esm/fieldRegistry.native.js +58 -12
  111. package/dist/esm/fieldRegistry.native.js.map +1 -1
  112. package/dist/esm/fields/Date/index.native.js +6 -3
  113. package/dist/esm/fields/Date/index.native.js.map +1 -1
  114. package/dist/esm/fields/Date/sheetKeyboard.mjs +2 -2
  115. package/dist/esm/fields/Date/sheetKeyboard.mjs.map +1 -1
  116. package/dist/esm/fields/Date/sheetKeyboard.native.js +2 -2
  117. package/dist/esm/fields/Date/sheetKeyboard.native.js.map +1 -1
  118. package/dist/esm/fields/DateRange/index.mjs +2 -1
  119. package/dist/esm/fields/DateRange/index.mjs.map +1 -1
  120. package/dist/esm/fields/DateRange/index.native.js +4 -1
  121. package/dist/esm/fields/DateRange/index.native.js.map +1 -1
  122. package/dist/esm/formEngine/index.mjs +2 -1
  123. package/dist/esm/formEngine/index.mjs.map +1 -1
  124. package/dist/esm/formEngine/index.native.js +2 -1
  125. package/dist/esm/formEngine/index.native.js.map +1 -1
  126. package/dist/esm/formEngine/useSessionRoles.mjs +52 -0
  127. package/dist/esm/formEngine/useSessionRoles.mjs.map +1 -0
  128. package/dist/esm/formEngine/useSessionRoles.native.js +56 -0
  129. package/dist/esm/formEngine/useSessionRoles.native.js.map +1 -0
  130. package/dist/esm/frappeConnections.mjs +15 -0
  131. package/dist/esm/frappeConnections.mjs.map +1 -0
  132. package/dist/esm/frappeConnections.native.js +21 -0
  133. package/dist/esm/frappeConnections.native.js.map +1 -0
  134. package/dist/esm/frappeNavigation.mjs +78 -0
  135. package/dist/esm/frappeNavigation.mjs.map +1 -0
  136. package/dist/esm/frappeNavigation.native.js +80 -0
  137. package/dist/esm/frappeNavigation.native.js.map +1 -0
  138. package/dist/esm/index.mjs +7 -3
  139. package/dist/esm/index.mjs.map +1 -1
  140. package/dist/esm/index.native.js +7 -3
  141. package/dist/esm/index.native.js.map +1 -1
  142. package/dist/esm/schemaForm/index.mjs +4 -1
  143. package/dist/esm/schemaForm/index.mjs.map +1 -1
  144. package/dist/esm/schemaForm/index.native.js +4 -1
  145. package/dist/esm/schemaForm/index.native.js.map +1 -1
  146. package/dist/esm/useConnectionFilters.mjs +121 -0
  147. package/dist/esm/useConnectionFilters.mjs.map +1 -0
  148. package/dist/esm/useConnectionFilters.native.js +185 -0
  149. package/dist/esm/useConnectionFilters.native.js.map +1 -0
  150. package/dist/esm/useFrappePagination.mjs +38 -19
  151. package/dist/esm/useFrappePagination.mjs.map +1 -1
  152. package/dist/esm/useFrappePagination.native.js +60 -24
  153. package/dist/esm/useFrappePagination.native.js.map +1 -1
  154. package/dist/esm/useFrappeRows.mjs.map +1 -1
  155. package/dist/esm/useFrappeRows.native.js.map +1 -1
  156. package/dist/jsx/FrappeDashboard.mjs +9 -11
  157. package/dist/jsx/FrappeDashboard.mjs.map +1 -1
  158. package/dist/jsx/FrappeDashboard.native.js +8 -14
  159. package/dist/jsx/FrappeDashboard.native.js.map +1 -1
  160. package/dist/jsx/FrappeForm.mjs +471 -181
  161. package/dist/jsx/FrappeForm.mjs.map +1 -1
  162. package/dist/jsx/FrappeForm.native.js +611 -207
  163. package/dist/jsx/FrappeForm.native.js.map +1 -1
  164. package/dist/jsx/FrappeFormConnections.mjs +99 -0
  165. package/dist/jsx/FrappeFormConnections.mjs.map +1 -0
  166. package/dist/jsx/FrappeFormConnections.native.js +149 -0
  167. package/dist/jsx/FrappeFormConnections.native.js.map +1 -0
  168. package/dist/jsx/FrappeImageView.mjs +8 -10
  169. package/dist/jsx/FrappeImageView.mjs.map +1 -1
  170. package/dist/jsx/FrappeImageView.native.js +7 -13
  171. package/dist/jsx/FrappeImageView.native.js.map +1 -1
  172. package/dist/jsx/FrappeList.mjs +31 -6
  173. package/dist/jsx/FrappeList.mjs.map +1 -1
  174. package/dist/jsx/FrappeList.native.js +37 -5
  175. package/dist/jsx/FrappeList.native.js.map +1 -1
  176. package/dist/jsx/FrappeListView.mjs +103 -30
  177. package/dist/jsx/FrappeListView.mjs.map +1 -1
  178. package/dist/jsx/FrappeListView.native.js +109 -29
  179. package/dist/jsx/FrappeListView.native.js.map +1 -1
  180. package/dist/jsx/FrappeProvider.mjs +7 -2
  181. package/dist/jsx/FrappeProvider.mjs.map +1 -1
  182. package/dist/jsx/FrappeProvider.native.js +7 -2
  183. package/dist/jsx/FrappeProvider.native.js.map +1 -1
  184. package/dist/jsx/FrappeReportBuilder.mjs +11 -12
  185. package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
  186. package/dist/jsx/FrappeReportBuilder.native.js +10 -16
  187. package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
  188. package/dist/jsx/FrappeRowsReader.mjs +24 -0
  189. package/dist/jsx/FrappeRowsReader.mjs.map +1 -0
  190. package/dist/jsx/FrappeRowsReader.native.js +53 -0
  191. package/dist/jsx/FrappeRowsReader.native.js.map +1 -0
  192. package/dist/jsx/FrappeTable.mjs +18 -6
  193. package/dist/jsx/FrappeTable.mjs.map +1 -1
  194. package/dist/jsx/FrappeTable.native.js +24 -6
  195. package/dist/jsx/FrappeTable.native.js.map +1 -1
  196. package/dist/jsx/fieldRegistry.mjs +35 -12
  197. package/dist/jsx/fieldRegistry.mjs.map +1 -1
  198. package/dist/jsx/fieldRegistry.native.js +59 -11
  199. package/dist/jsx/fieldRegistry.native.js.map +1 -1
  200. package/dist/jsx/fields/Date/index.native.js +5 -2
  201. package/dist/jsx/fields/Date/index.native.js.map +1 -1
  202. package/dist/jsx/fields/Date/sheetKeyboard.mjs +2 -2
  203. package/dist/jsx/fields/Date/sheetKeyboard.mjs.map +1 -1
  204. package/dist/jsx/fields/Date/sheetKeyboard.native.js +2 -2
  205. package/dist/jsx/fields/Date/sheetKeyboard.native.js.map +1 -1
  206. package/dist/jsx/fields/DateRange/index.mjs +2 -1
  207. package/dist/jsx/fields/DateRange/index.mjs.map +1 -1
  208. package/dist/jsx/fields/DateRange/index.native.js +4 -1
  209. package/dist/jsx/fields/DateRange/index.native.js.map +1 -1
  210. package/dist/jsx/formEngine/index.mjs +2 -1
  211. package/dist/jsx/formEngine/index.mjs.map +1 -1
  212. package/dist/jsx/formEngine/index.native.js +3 -1
  213. package/dist/jsx/formEngine/index.native.js.map +1 -1
  214. package/dist/jsx/formEngine/useSessionRoles.mjs +52 -0
  215. package/dist/jsx/formEngine/useSessionRoles.mjs.map +1 -0
  216. package/dist/jsx/formEngine/useSessionRoles.native.js +84 -0
  217. package/dist/jsx/formEngine/useSessionRoles.native.js.map +1 -0
  218. package/dist/jsx/frappeConnections.mjs +15 -0
  219. package/dist/jsx/frappeConnections.mjs.map +1 -0
  220. package/dist/jsx/frappeConnections.native.js +50 -0
  221. package/dist/jsx/frappeConnections.native.js.map +1 -0
  222. package/dist/jsx/frappeNavigation.mjs +78 -0
  223. package/dist/jsx/frappeNavigation.mjs.map +1 -0
  224. package/dist/jsx/frappeNavigation.native.js +112 -0
  225. package/dist/jsx/frappeNavigation.native.js.map +1 -0
  226. package/dist/jsx/index.js +7 -3
  227. package/dist/jsx/index.js.map +1 -1
  228. package/dist/jsx/index.mjs +7 -3
  229. package/dist/jsx/index.mjs.map +1 -1
  230. package/dist/jsx/index.native.js +18 -1
  231. package/dist/jsx/index.native.js.map +1 -1
  232. package/dist/jsx/schemaForm/index.mjs +4 -1
  233. package/dist/jsx/schemaForm/index.mjs.map +1 -1
  234. package/dist/jsx/schemaForm/index.native.js +4 -1
  235. package/dist/jsx/schemaForm/index.native.js.map +1 -1
  236. package/dist/jsx/useConnectionFilters.mjs +121 -0
  237. package/dist/jsx/useConnectionFilters.mjs.map +1 -0
  238. package/dist/jsx/useConnectionFilters.native.js +213 -0
  239. package/dist/jsx/useConnectionFilters.native.js.map +1 -0
  240. package/dist/jsx/useFrappePagination.mjs +38 -19
  241. package/dist/jsx/useFrappePagination.mjs.map +1 -1
  242. package/dist/jsx/useFrappePagination.native.js +59 -23
  243. package/dist/jsx/useFrappePagination.native.js.map +1 -1
  244. package/dist/jsx/useFrappeRows.mjs.map +1 -1
  245. package/dist/jsx/useFrappeRows.native.js.map +1 -1
  246. package/package.json +26 -22
  247. package/src/FileBrowser/AttachLibrary.stories.tsx +23 -0
  248. package/src/FrappeDashboard.tsx +11 -16
  249. package/src/FrappeForm.connections.spec.tsx +280 -0
  250. package/src/FrappeForm.connections.stories.tsx +93 -0
  251. package/src/FrappeForm.deskSemantics.spec.tsx +284 -0
  252. package/src/FrappeForm.tsx +560 -145
  253. package/src/FrappeFormConnections.tsx +148 -0
  254. package/src/FrappeGuestMeta.spec.tsx +143 -0
  255. package/src/FrappeImageView.tsx +8 -12
  256. package/src/FrappeList.tsx +71 -15
  257. package/src/FrappeListView.cards.spec.tsx +175 -0
  258. package/src/FrappeListView.cards.stories.tsx +131 -0
  259. package/src/FrappeListView.connections.spec.tsx +192 -0
  260. package/src/FrappeListView.connections.stories.tsx +99 -0
  261. package/src/FrappeListView.seed.spec.tsx +228 -0
  262. package/src/FrappeListView.tsx +177 -31
  263. package/src/FrappeProvider.catchUp.spec.tsx +502 -0
  264. package/src/FrappeProvider.tsx +15 -3
  265. package/src/FrappeReportBuilder.tsx +11 -19
  266. package/src/FrappeRowsReader.tsx +38 -0
  267. package/src/FrappeTable.optionalProps.spec.tsx +249 -0
  268. package/src/FrappeTable.spec.tsx +32 -42
  269. package/src/FrappeTable.stories.spec.tsx +55 -0
  270. package/src/FrappeTable.stories.tsx +28 -37
  271. package/src/FrappeTable.tsx +108 -45
  272. package/src/childTableDependsOn.spec.tsx +85 -0
  273. package/src/desk/DeskFormSidebar.spec.tsx +20 -0
  274. package/src/desk/DeskFormSidebar.stories.tsx +42 -0
  275. package/src/fieldRegistry.spec.tsx +27 -64
  276. package/src/fieldRegistry.ts +71 -14
  277. package/src/fields/Date/index.native.tsx +4 -2
  278. package/src/fields/Date/sheetKeyboard.spec.ts +14 -5
  279. package/src/fields/Date/sheetKeyboard.ts +12 -4
  280. package/src/fields/DateRange/DateRange.spec.tsx +67 -0
  281. package/src/fields/DateRange/index.tsx +5 -1
  282. package/src/fields/LinkTree.spec.tsx +8 -0
  283. package/src/fields/LinkTree.stories.tsx +28 -5
  284. package/src/fields/Select/Select.hydration.spec.tsx +177 -0
  285. package/src/formEngine/doctypeMeta.ts +15 -114
  286. package/src/formEngine/index.ts +2 -0
  287. package/src/formEngine/seam.spec.ts +23 -4
  288. package/src/formEngine/useSessionRoles.ts +73 -0
  289. package/src/frappeConnections.ts +50 -0
  290. package/src/frappeNavigation.tsx +112 -0
  291. package/src/frappeRoutes.spec.tsx +335 -0
  292. package/src/index.ts +28 -1
  293. package/src/schemaForm/index.tsx +1 -0
  294. package/src/useConnectionFilters.tsx +157 -0
  295. package/src/useFrappePagination.ts +70 -26
  296. package/src/useFrappeRows.sharedPath.spec.tsx +206 -0
  297. package/src/useFrappeRows.ts +5 -4
  298. package/types/FrappeDashboard.d.ts +3 -4
  299. package/types/FrappeDashboard.d.ts.map +1 -1
  300. package/types/FrappeForm.d.ts +57 -5
  301. package/types/FrappeForm.d.ts.map +1 -1
  302. package/types/FrappeFormConnections.d.ts +18 -0
  303. package/types/FrappeFormConnections.d.ts.map +1 -0
  304. package/types/FrappeImageView.d.ts +2 -3
  305. package/types/FrappeImageView.d.ts.map +1 -1
  306. package/types/FrappeList.d.ts +20 -2
  307. package/types/FrappeList.d.ts.map +1 -1
  308. package/types/FrappeListView.d.ts +53 -3
  309. package/types/FrappeListView.d.ts.map +1 -1
  310. package/types/FrappeProvider.d.ts +10 -3
  311. package/types/FrappeProvider.d.ts.map +1 -1
  312. package/types/FrappeReportBuilder.d.ts.map +1 -1
  313. package/types/FrappeRowsReader.d.ts +26 -0
  314. package/types/FrappeRowsReader.d.ts.map +1 -0
  315. package/types/FrappeTable.d.ts +39 -8
  316. package/types/FrappeTable.d.ts.map +1 -1
  317. package/types/fieldRegistry.d.ts +17 -3
  318. package/types/fieldRegistry.d.ts.map +1 -1
  319. package/types/fields/Date/index.native.d.ts.map +1 -1
  320. package/types/fields/Date/sheetKeyboard.d.ts +4 -3
  321. package/types/fields/Date/sheetKeyboard.d.ts.map +1 -1
  322. package/types/fields/DateRange/index.d.ts.map +1 -1
  323. package/types/formEngine/doctypeMeta.d.ts +10 -114
  324. package/types/formEngine/doctypeMeta.d.ts.map +1 -1
  325. package/types/formEngine/index.d.ts +1 -0
  326. package/types/formEngine/index.d.ts.map +1 -1
  327. package/types/formEngine/useSessionRoles.d.ts +19 -0
  328. package/types/formEngine/useSessionRoles.d.ts.map +1 -0
  329. package/types/frappeConnections.d.ts +33 -0
  330. package/types/frappeConnections.d.ts.map +1 -0
  331. package/types/frappeNavigation.d.ts +49 -0
  332. package/types/frappeNavigation.d.ts.map +1 -0
  333. package/types/index.d.ts +7 -3
  334. package/types/index.d.ts.map +1 -1
  335. package/types/schemaForm/index.d.ts.map +1 -1
  336. package/types/useConnectionFilters.d.ts +34 -0
  337. package/types/useConnectionFilters.d.ts.map +1 -0
  338. package/types/useFrappePagination.d.ts +18 -5
  339. package/types/useFrappePagination.d.ts.map +1 -1
  340. package/types/useFrappeRows.d.ts +5 -4
  341. package/types/useFrappeRows.d.ts.map +1 -1
  342. package/src/FileBrowser/AttachLibrary.spec.tsx +0 -74
@@ -12,6 +12,7 @@ import {
12
12
  type DataTableProps,
13
13
  type DataTableState,
14
14
  type FilterValue,
15
+ type FilterableField,
15
16
  type PageLoadQuery,
16
17
  } from "@multiplatform.one/table";
17
18
  import { AsyncBoundary } from "@multiplatform.one/components";
@@ -70,7 +71,17 @@ export interface FrappeTableProps<TDoc extends { name: string; doctype: string }
70
71
  doctype: string;
71
72
  /** Column definitions for the table */
72
73
  columns: DataTableColumn<Partial<TDoc>>[];
73
- /** Fields to fetch from the server */
74
+ /**
75
+ * Fields to fetch. Omitted, a read from the bench (`serverSide`, or client
76
+ * mode under a provider without `fixtures`) requests `name` plus every
77
+ * column's `accessorKey`, and client mode under a provider carrying
78
+ * `fixtures` reads whole documents from the corpus. A column keyed on
79
+ * something that is not a doctype field (a rowAccumulator's derived key,
80
+ * say) makes the bench refuse that read, so name the list to leave such a
81
+ * key out. Name it, too, to share one collection with another reader of
82
+ * the same doctype (see useFrappeRows) or to fetch fields no column shows,
83
+ * such as a title field or a filter-only field.
84
+ */
74
85
  fields?: readonly (keyof TDoc & string)[];
75
86
  /** Initial filter criteria */
76
87
  filters?: Partial<TDoc> | Array<[keyof TDoc & string, string, any]>;
@@ -116,45 +127,20 @@ export interface FrappeTableProps<TDoc extends { name: string; doctype: string }
116
127
  * FrappeTable has to name it or callers (backoffice list) fail the build.
117
128
  */
118
129
  compact?: boolean;
130
+ /**
131
+ * Filters the toolbar offers. Omitted, the list is derived from the
132
+ * doctype's meta: under a provider carrying `fixtures` that is the
133
+ * corpus's "DocType" entry and never the network, whatever `baseURL` the
134
+ * host also carries (a corpus without the entry yields no menu);
135
+ * otherwise it is a `frappe.desk.form.load.getdoctype` call. The
136
+ * meta-derived menu offers the doctype's fields, including ones the
137
+ * server refuses to filter on, so a live app should pass the honoured
138
+ * set by hand. Passing it skips the meta read entirely, which also drops
139
+ * title-field row labels back to the document name.
140
+ */
141
+ filterableFields?: FilterableField[];
119
142
  }
120
143
 
121
- /**
122
- * A data table component connected to Frappe Live data.
123
- *
124
- * Displays documents from a Frappe DocType in a sortable, filterable table
125
- * with real-time updates via WebSocket. Integrates with the FrappeProvider
126
- * for configuration.
127
- *
128
- * @typeParam TDoc - The document type with at least name and doctype fields
129
- *
130
- * @example
131
- * ```tsx
132
- * interface ToDo {
133
- * name: string;
134
- * doctype: string;
135
- * description: string;
136
- * status: 'Open' | 'Closed';
137
- * }
138
- *
139
- * function ToDoTable() {
140
- * return (
141
- * <FrappeProvider baseURL="https://mysite.frappe.cloud">
142
- * <FrappeTable<ToDo>
143
- * doctype="ToDo"
144
- * columns={[
145
- * { accessorKey: 'description', header: 'Description' },
146
- * { accessorKey: 'status', header: 'Status' }
147
- * ]}
148
- * fields={['name', 'description', 'status']}
149
- * onRowClick={(row) => console.log('Clicked:', row)}
150
- * />
151
- * </FrappeProvider>
152
- * );
153
- * }
154
- * ```
155
- *
156
- * @category Components
157
- */
158
144
  /**
159
145
  * Map one table FilterValue to a Frappe filter tuple. Returns null when the
160
146
  * filter carries no usable value (e.g. "contains" with an empty string) so
@@ -274,6 +260,52 @@ function stateToFrappeParams(state: DataTableState) {
274
260
  return { limitStart, limit, filters: frappeFilters, orderBy };
275
261
  }
276
262
 
263
+ /**
264
+ * A data table component connected to Frappe Live data.
265
+ *
266
+ * Displays documents from a Frappe DocType in a sortable, filterable table
267
+ * with real-time updates via WebSocket. Integrates with the FrappeProvider
268
+ * for configuration.
269
+ *
270
+ * Two props read as optional and carry defaults that matter (MPO-230).
271
+ * Without `fields`, a read from the bench (`serverSide`, or client mode with
272
+ * no `fixtures`) requests `name` plus every column's `accessorKey`, and
273
+ * client mode under a fixture corpus reads whole documents. Without
274
+ * `filterableFields`, the filter menu comes from the doctype's meta: the
275
+ * corpus's "DocType" entry under a provider carrying `fixtures`, never the
276
+ * network whatever `baseURL` the host also carries, and a `getdoctype` call
277
+ * otherwise. Each prop's own entry has the detail.
278
+ *
279
+ * @typeParam TDoc - The document type with at least name and doctype fields
280
+ *
281
+ * @example
282
+ * ```tsx
283
+ * interface ToDo {
284
+ * name: string;
285
+ * doctype: string;
286
+ * description: string;
287
+ * status: 'Open' | 'Closed';
288
+ * }
289
+ *
290
+ * function ToDoTable() {
291
+ * return (
292
+ * <FrappeProvider baseURL="https://mysite.frappe.cloud">
293
+ * <FrappeTable<ToDo>
294
+ * doctype="ToDo"
295
+ * columns={[
296
+ * { accessorKey: 'description', header: 'Description' },
297
+ * { accessorKey: 'status', header: 'Status' }
298
+ * ]}
299
+ * fields={['name', 'description', 'status']}
300
+ * onRowClick={(row) => console.log('Clicked:', row)}
301
+ * />
302
+ * </FrappeProvider>
303
+ * );
304
+ * }
305
+ * ```
306
+ *
307
+ * @category Components
308
+ */
277
309
  export function FrappeTable<TDoc extends { name: string; doctype: string }>({
278
310
  baseURL,
279
311
  auth,
@@ -363,11 +395,42 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
363
395
  return new FrappeApp(finalBaseURL, (finalAuth ?? {}) as Record<string, unknown>);
364
396
  }, [serverSide, finalBaseURL, finalAuth]);
365
397
 
398
+ // Keyed on content, not on the columns array, so an inline `columns`
399
+ // literal cannot re-key the fetch every render and refetch in a loop.
400
+ const columnFieldsKey = fields
401
+ ? undefined
402
+ : JSON.stringify(
403
+ columns
404
+ .map((column): unknown => column.accessorKey)
405
+ .filter((key): key is string => typeof key === "string"),
406
+ );
407
+ const serverFields = useMemo<string[]>(
408
+ () =>
409
+ fields
410
+ ? (fields as string[])
411
+ : Array.from(new Set<string>(["name", ...JSON.parse(columnFieldsKey ?? "[]")])),
412
+ [fields, columnFieldsKey],
413
+ );
414
+ // Client mode over a fixture corpus keeps whole documents: narrowing it
415
+ // would drop the non-column fields (a title field, a filter-only field)
416
+ // fixture hosts read today.
417
+ const clientFields = (fields ??
418
+ (connectionConfig.fixtures ? undefined : serverFields)) as typeof fields;
419
+
366
420
  // Full doctype field list → table filterableFields: hidden / non-column
367
421
  // fields become filterable through the toolbar Filter surface. An explicit
368
422
  // filterableFields prop overrides the metadata-derived list (fixtures,
369
- // stories, custom subsets).
370
- const { meta } = useDocTypeMeta(filterableFieldsProp ? undefined : connectionConfig, doctype);
423
+ // stories, custom subsets). A fixture corpus is the whole world for meta
424
+ // too: the read goes to its "DocType" collection with the baseURL a
425
+ // fixture host may still carry withheld, so a corpus without the entry
426
+ // yields no menu instead of dialling getdoctype (MPO-230).
427
+ const skipMeta = !!filterableFieldsProp;
428
+ const metaConnection = useMemo(() => {
429
+ if (skipMeta) return undefined;
430
+ if (!connectionConfig.fixtures) return connectionConfig;
431
+ return { ...connectionConfig, baseURL: undefined };
432
+ }, [skipMeta, connectionConfig]);
433
+ const { meta } = useDocTypeMeta(metaConnection, doctype);
371
434
  const metaFilterFields = useMemo(
372
435
  () => (meta?.fields ? docFieldsToFilterFields(meta.fields) : undefined),
373
436
  [meta],
@@ -465,7 +528,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
465
528
  try {
466
529
  const [data, count] = await Promise.all([
467
530
  frappeApp.db().getDocList<TDoc>(doctype, {
468
- fields: (fields as string[]) ?? ["name"],
531
+ fields: serverFields,
469
532
  filters: params.filters.length ? params.filters : undefined,
470
533
  limitStart: params.limitStart,
471
534
  limit: params.limit,
@@ -507,7 +570,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
507
570
  }
508
571
  }
509
572
  },
510
- [frappeApp, serverSide, doctype, fields],
573
+ [frappeApp, serverSide, doctype, serverFields],
511
574
  );
512
575
 
513
576
  // Live invalidation for serverSide mode: any realtime event for this
@@ -565,7 +628,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
565
628
  } as DataTableState);
566
629
  const [rows, count] = await Promise.all([
567
630
  frappeApp.db().getDocList<TDoc>(doctype, {
568
- fields: (fields as string[]) ?? ["name"],
631
+ fields: serverFields,
569
632
  filters: params.filters.length ? params.filters : undefined,
570
633
  limitStart: params.limitStart,
571
634
  limit: params.limit,
@@ -577,7 +640,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
577
640
  ]);
578
641
  return { rows, totalCount: count ?? undefined };
579
642
  },
580
- [frappeApp, doctype, fields],
643
+ [frappeApp, doctype, serverFields],
581
644
  );
582
645
 
583
646
  const resolvedLoadPage = loadPageProp ?? (infiniteServerMode ? frappeLoadPage : undefined);
@@ -632,7 +695,7 @@ export function FrappeTable<TDoc extends { name: string; doctype: string }>({
632
695
  isQueryLoading: isRowsLoading,
633
696
  error: liveError,
634
697
  } = useFrappeRows<TDoc>(serverSide ? undefined : connectionConfig, doctype, {
635
- fields,
698
+ fields: clientFields,
636
699
  filters: activeFilters,
637
700
  limit: null,
638
701
  enabled: !serverSide,
@@ -0,0 +1,85 @@
1
+ import { fireEvent, screen, waitFor, within } from "@testing-library/react";
2
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import {
5
+ childCellHidden,
6
+ childFieldsToDetailColumns,
7
+ type ChildTableFieldDef,
8
+ type FieldSchema,
9
+ } from "./fieldRegistry";
10
+ import { SchemaForm, type FormSchema } from "./schemaForm";
11
+
12
+ const childFields: ChildTableFieldDef[] = [
13
+ { fieldname: "item_code", fieldtype: "Data", label: "Item Code", in_list_view: 1 },
14
+ { fieldname: "has_serial", fieldtype: "Check", label: "Has Serial", in_list_view: 1 },
15
+ {
16
+ fieldname: "serial_no",
17
+ fieldtype: "Data",
18
+ label: "Serial No",
19
+ in_list_view: 1,
20
+ depends_on: "eval:doc.has_serial == 1",
21
+ },
22
+ {
23
+ fieldname: "warranty",
24
+ fieldtype: "Data",
25
+ label: "Warranty",
26
+ depends_on: 'eval:parent.status == "Draft"',
27
+ },
28
+ { fieldname: "sec", fieldtype: "Section Break" },
29
+ ];
30
+
31
+ function renderOrder(status: string) {
32
+ const fields: FieldSchema[] = [
33
+ { type: "Data", name: "status", label: "Status" },
34
+ { type: "Table", name: "items", label: "Items", options: "Order Item", fields: childFields },
35
+ ];
36
+ const schema: FormSchema = { fields, sections: [{ columns: [{ fields }] }] };
37
+ return renderWithProviders(
38
+ <SchemaForm
39
+ schema={schema}
40
+ doc={{
41
+ status,
42
+ items: [
43
+ { name: "r1", item_code: "SKU-1", has_serial: 1, serial_no: "SN-1", warranty: "1y" },
44
+ { name: "r2", item_code: "SKU-2", has_serial: 0, serial_no: "SN-2", warranty: "2y" },
45
+ ],
46
+ }}
47
+ setValue={vi.fn()}
48
+ />,
49
+ );
50
+ }
51
+
52
+ describe("child-table per-row depends_on (MPO-22)", () => {
53
+ it("compiles child depends_on against the row, with the parent doc in scope", () => {
54
+ const hidden = childCellHidden(childFields, { status: "Draft" })!;
55
+ expect(hidden({ has_serial: 1 }, "serial_no", 0)).toBe(false);
56
+ expect(hidden({ has_serial: 0 }, "serial_no", 1)).toBe(true);
57
+ expect(hidden({ has_serial: 0 }, "item_code", 1)).toBe(false);
58
+ expect(hidden({}, "warranty", 0)).toBe(false);
59
+ expect(childCellHidden(childFields, { status: "Submitted" })!({}, "warranty", 0)).toBe(true);
60
+ expect(childCellHidden([{ fieldname: "a", fieldtype: "Data" }])).toBeUndefined();
61
+ });
62
+
63
+ it("hides the cell on the row whose depends_on is false and keeps it on the other", () => {
64
+ renderOrder("Draft");
65
+ const serials = screen.getAllByLabelText("Serial No") as HTMLInputElement[];
66
+ expect(serials.map((input) => input.value)).toEqual(["SN-1"]);
67
+ expect(screen.getAllByLabelText("Item Code")).toHaveLength(2);
68
+ });
69
+
70
+ it("the row detail editor shows every child field and applies the same rule", async () => {
71
+ expect(childFieldsToDetailColumns(childFields).map((c) => c.id)).toEqual([
72
+ "item_code",
73
+ "has_serial",
74
+ "serial_no",
75
+ "warranty",
76
+ ]);
77
+
78
+ renderOrder("Submitted");
79
+ fireEvent.click(screen.getByRole("button", { name: "Edit row 2" }));
80
+ const dialog = await waitFor(() => screen.getByRole("dialog", { name: "Row 2" }));
81
+ expect((within(dialog).getByLabelText("Item Code") as HTMLInputElement).value).toBe("SKU-2");
82
+ expect(within(dialog).queryByLabelText("Serial No")).toBeNull();
83
+ expect(within(dialog).queryByLabelText("Warranty")).toBeNull();
84
+ });
85
+ });
@@ -87,4 +87,24 @@ describe("DeskFormSidebar (MPO-111)", () => {
87
87
  renderSidebar();
88
88
  expect(await screen.findByTestId("desk-form-sidebar")).toBeTruthy();
89
89
  });
90
+
91
+ it("hands FormSidebar the Assigned To and Shared With sections even when both are empty", async () => {
92
+ const doc = {
93
+ name: "TODO-002",
94
+ doctype: "Todo",
95
+ description: "File",
96
+ owner: "sam@example.com",
97
+ };
98
+ let call = 0;
99
+ mockUseLiveQuery.mockImplementation(() => {
100
+ const slot = call++ % 3;
101
+ return { data: slot === 0 ? [doc] : [] };
102
+ });
103
+ renderSidebar("TODO-002");
104
+ const sidebar = await screen.findByTestId("desk-form-sidebar");
105
+ // buildSidebarSections always emits assignments; shares only when the
106
+ // DocShare subscription exists (it does here). An empty list keeps its
107
+ // section and renders emptyText — the pane never loses a row.
108
+ expect(sidebar.getAttribute("data-section-keys")).toBe("assignments,shares");
109
+ });
90
110
  });
@@ -126,6 +126,31 @@ function makeFixtures() {
126
126
  });
127
127
  }
128
128
 
129
+ /**
130
+ * Same doc, nothing hanging off it: no `_assign` mirror, no DocShare row, no
131
+ * image. Every section still renders — with its emptyText — so the pane
132
+ * geometry is identical to the populated board.
133
+ */
134
+ const bareTodoDocs = [
135
+ {
136
+ name: "TODO-002",
137
+ doctype: "Todo",
138
+ description: "File the expense report",
139
+ status: "Open",
140
+ owner: "sam@example.com",
141
+ modified_by: "sam@example.com",
142
+ creation: "2026-02-03",
143
+ },
144
+ ];
145
+
146
+ function makeBareFixtures() {
147
+ return new InMemoryFixtureProvider({
148
+ Todo: bareTodoDocs,
149
+ DocShare: [],
150
+ ToDo: [],
151
+ });
152
+ }
153
+
129
154
  export const Default = () => (
130
155
  <FrappeProvider baseURL={MOCK_HOST} fixtures={makeFixtures()}>
131
156
  <YStack width={320} height={520}>
@@ -135,6 +160,23 @@ export const Default = () => (
135
160
  );
136
161
  Default.storyName = "Main";
137
162
 
163
+ export const EmptySections = () => (
164
+ <FrappeProvider baseURL={MOCK_HOST} fixtures={makeBareFixtures()}>
165
+ <YStack width={320} height={520}>
166
+ <DeskFormSidebar doctype="Todo" name="TODO-002" />
167
+ </YStack>
168
+ </FrappeProvider>
169
+ );
170
+ EmptySections.storyName = "Empty Sections";
171
+ EmptySections.parameters = {
172
+ docs: {
173
+ description: {
174
+ story:
175
+ "Doc with no assignees, no shares and no image: Assigned To reads its emptyText (No one assigned), Shared With reads Not shared, and the metadata rows still come from the doc.",
176
+ },
177
+ },
178
+ };
179
+
138
180
  export const Loading = () => (
139
181
  <FrappeProvider baseURL={MOCK_HOST} fixtures={makeFixtures()}>
140
182
  <YStack width={320} height={200}>
@@ -9,13 +9,19 @@
9
9
  * are plain functions without hooks, so calling them directly is safe).
10
10
  */
11
11
  import { Button, Combobox, ImageField } from "@multiplatform.one/forms";
12
- import type { FrappeFieldType } from "@multiplatform.one/frappe";
12
+ import {
13
+ customFieldTypes,
14
+ frappeFieldTypes,
15
+ type FrappeFieldType,
16
+ } from "@multiplatform.one/frappe";
13
17
  import type React from "react";
14
- import { describe, expect, it } from "vitest";
18
+ import { describe, expect, expectTypeOf, it } from "vitest";
19
+ import { registeredFieldtypesFromSource } from "../tests/registrySource";
15
20
  import {
16
21
  childFieldsToColumns,
17
22
  fieldtypeToTableField,
18
23
  getFieldComponent,
24
+ registerField,
19
25
  type ChildTableFieldDef,
20
26
  type FieldRenderProps,
21
27
  } from "./fieldRegistry";
@@ -274,69 +280,26 @@ describe("form adapters (registry context)", () => {
274
280
  expect(element.props.value).toBe("mail");
275
281
  });
276
282
 
277
- it("union-minus-registry holds no real fieldtype (layout breaks only)", () => {
278
- // Every FrappeFieldType member from public/frappe/src/doctype.ts. A real
279
- // fieldtype missing from the registry renders the unsupported-type string
280
- // (the MPO-51 Icon hole, reopened once already by 035867446's registry
281
- // rewrite), so this list is asserted against the registry wholesale.
282
- const frappeFieldTypes: readonly FrappeFieldType[] = [
283
- "Autocomplete",
284
- "Attach",
285
- "Attach Image",
286
- "Barcode",
287
- "Button",
288
- "Check",
289
- "Code",
290
- "Color",
291
- "Column Break",
292
- "Currency",
293
- "Data",
294
- "Date",
295
- "Datetime",
296
- "Duration",
297
- "Dynamic Link",
298
- "Float",
299
- "Geolocation",
300
- "HTML",
301
- "HTML Editor",
302
- "Heading",
303
- "Icon",
304
- "Image",
305
- "Int",
306
- "JSON",
307
- "Link",
308
- "Long Text",
309
- "Markdown Editor",
310
- "Password",
311
- "Percent",
312
- "Phone",
313
- "Rating",
314
- "Read Only",
315
- "Section Break",
316
- "Select",
317
- "Signature",
318
- "Small Text",
319
- "Tab Break",
320
- "Table",
321
- "Table MultiSelect",
322
- "Text",
323
- "Text Editor",
324
- "Time",
325
- ];
326
- const unregistered = frappeFieldTypes.filter((fieldtype) => !getFieldComponent(fieldtype));
327
- // The three layout breaks never get adapters — FormSection / FormGrid /
283
+ it("union-minus-registry holds no real fieldtype (structural types only)", () => {
284
+ // A real fieldtype missing from the registry renders the unsupported-type
285
+ // string (the MPO-51 Icon hole, reopened once already by 035867446's
286
+ // registry rewrite), so the whole union is asserted against the registry.
287
+ // The three breaks and Fold never get adapters: FormSection / FormGrid /
328
288
  // Tabs serve them structurally.
329
- const layoutBreaks = ["Column Break", "Section Break", "Tab Break"];
330
- for (const layoutBreak of layoutBreaks) {
331
- expect(unregistered).toContain(layoutBreak);
332
- }
333
- expect(unregistered).not.toContain("Icon");
334
- // "Button" is transiently tolerated here: its adapter (a8f8f52bd) was
335
- // reverted by the same 035867446 rewrite and is being restored on
336
- // lane/MPO-51-button-fieldtype. Drop it from this allowlist when that
337
- // lands — the Button spec below pins the adapter itself.
338
- for (const fieldtype of unregistered) {
339
- expect([...layoutBreaks, "Button"]).toContain(fieldtype);
289
+ const unregistered = frappeFieldTypes.filter((fieldtype) => !getFieldComponent(fieldtype));
290
+ expect([...unregistered].sort()).toEqual(
291
+ ["Column Break", "Fold", "Section Break", "Tab Break"].sort(),
292
+ );
293
+ });
294
+
295
+ it("registers nothing outside the FrappeFieldType union (MPO-15)", () => {
296
+ expectTypeOf(registerField).parameter(0).toEqualTypeOf<FrappeFieldType>();
297
+ const union = new Set<string>(frappeFieldTypes);
298
+ const registered = [...registeredFieldtypesFromSource()];
299
+ expect(registered.length).toBeGreaterThan(40);
300
+ expect(registered.filter((fieldtype) => !union.has(fieldtype))).toEqual([]);
301
+ for (const custom of customFieldTypes) {
302
+ expect(getFieldComponent(custom), custom).toBeDefined();
340
303
  }
341
304
  });
342
305
 
@@ -8,10 +8,11 @@
8
8
 
9
9
  import React, { useMemo } from "react";
10
10
  import type { ComponentType } from "react";
11
- import { useDocTypeMeta } from "@multiplatform.one/frappe";
11
+ import { useDocTypeMeta, type FrappeFieldType } from "@multiplatform.one/frappe";
12
12
  import { Button, CheckboxGroup, Combobox, ImageField, Skeleton } from "@multiplatform.one/forms";
13
13
 
14
14
  import { Barcode } from "./fields/Barcode";
15
+ import { compileDependsOn, type DependsDoc, type DependsEvaluator } from "./dependsOn";
15
16
  import { useFrappeConfig } from "./FrappeProvider";
16
17
  import { Check, type CheckProps } from "./fields/Check";
17
18
  import { CodeEditorField, frappeCodeLanguage } from "./fields/CodeEditor";
@@ -47,6 +48,7 @@ import { SwitchField } from "./fields/Switch";
47
48
  import { TableMultiSelect } from "./fields/TableMultiSelect";
48
49
  import {
49
50
  ChildTable,
51
+ type ChildCellHidden,
50
52
  type DataTableCellRenderContext,
51
53
  type DataTableField,
52
54
  } from "@multiplatform.one/table";
@@ -112,6 +114,8 @@ export interface FieldRenderProps {
112
114
  doctype?: string;
113
115
  /** For Select fields: newline-separated options string or other config */
114
116
  options?: string;
117
+ /** For Table fields: the form doc child `depends_on` reads as `parent`. */
118
+ parentDoc?: Record<string, unknown>;
115
119
  /** Allow pass-through props */
116
120
  [key: string]: any;
117
121
  }
@@ -129,9 +133,13 @@ const registry = new Map<string, ComponentType<FieldRenderProps>>();
129
133
  * override channel: writing it per provider instance clobbers every other
130
134
  * provider on the page (last render wins, silently). Per-subtree overrides
131
135
  * belong on `FieldOverridesProvider` (fieldOverrides.tsx), which SchemaForm
132
- * consults before this table.
136
+ * consults before this table. Only a `FrappeFieldType` registers: the union
137
+ * is the authority on what a fieldtype may be (MPO-15).
133
138
  */
134
- export function registerField(fieldtype: string, component: ComponentType<FieldRenderProps>): void {
139
+ export function registerField(
140
+ fieldtype: FrappeFieldType,
141
+ component: ComponentType<FieldRenderProps>,
142
+ ): void {
135
143
  registry.set(fieldtype, component);
136
144
  }
137
145
 
@@ -955,6 +963,8 @@ export interface ChildTableFieldDef {
955
963
  hidden?: boolean | number;
956
964
  precision?: number;
957
965
  default?: string;
966
+ /** Per-row visibility, evaluated against the row with the parent doc in scope. */
967
+ depends_on?: string;
958
968
  }
959
969
 
960
970
  type TableColumnDef = {
@@ -1189,22 +1199,49 @@ function isVisibleChildField(f: ChildTableFieldDef): boolean {
1189
1199
  return !f.hidden && !TABLE_LAYOUT_TYPES.has(f.fieldtype) && !f.fieldtype.startsWith("Table");
1190
1200
  }
1191
1201
 
1202
+ function fieldToColumn(f: ChildTableFieldDef): TableColumnDef {
1203
+ const mapped = fieldtypeToTableField(f.fieldtype, f.options);
1204
+ return {
1205
+ id: f.fieldname,
1206
+ header: f.label || f.fieldname,
1207
+ accessorKey: f.fieldname,
1208
+ field: mapped.field,
1209
+ fieldProps: mapped.fieldProps,
1210
+ readOnly: !!f.read_only,
1211
+ };
1212
+ }
1213
+
1192
1214
  /** Build editable ChildTable columns from child DocType field defs. */
1193
1215
  export function childFieldsToColumns(fields: ChildTableFieldDef[]): TableColumnDef[] {
1194
1216
  const visible = fields.filter(isVisibleChildField);
1195
1217
  const flagged = visible.filter((f) => !!f.in_list_view);
1196
1218
  const source = flagged.length > 0 ? flagged : visible.slice(0, 8);
1197
- return source.map((f) => {
1198
- const mapped = fieldtypeToTableField(f.fieldtype, f.options);
1199
- return {
1200
- id: f.fieldname,
1201
- header: f.label || f.fieldname,
1202
- accessorKey: f.fieldname,
1203
- field: mapped.field,
1204
- fieldProps: mapped.fieldProps,
1205
- readOnly: !!f.read_only,
1206
- };
1207
- });
1219
+ return source.map(fieldToColumn);
1220
+ }
1221
+
1222
+ /** Every editable child field, for the row detail editor (desk's row form). */
1223
+ export function childFieldsToDetailColumns(fields: ChildTableFieldDef[]): TableColumnDef[] {
1224
+ return fields.filter(isVisibleChildField).map(fieldToColumn);
1225
+ }
1226
+
1227
+ /**
1228
+ * Per-row cell visibility from child `depends_on`: the row is `doc`, the
1229
+ * parent form doc is `parent`. Undefined when no child field declares one.
1230
+ */
1231
+ export function childCellHidden(
1232
+ fields: ChildTableFieldDef[],
1233
+ parent?: DependsDoc,
1234
+ ): ChildCellHidden | undefined {
1235
+ const evaluators = new Map<string, DependsEvaluator>();
1236
+ for (const f of fields) {
1237
+ const evaluator = compileDependsOn(f.depends_on, true);
1238
+ if (evaluator) evaluators.set(f.fieldname, evaluator);
1239
+ }
1240
+ if (evaluators.size === 0) return undefined;
1241
+ return (row, columnId) => {
1242
+ const evaluator = evaluators.get(columnId);
1243
+ return evaluator ? !evaluator(row, parent) : false;
1244
+ };
1208
1245
  }
1209
1246
 
1210
1247
  function defaultRowFromFields(fields: ChildTableFieldDef[]): Record<string, unknown> {
@@ -1305,11 +1342,31 @@ function TableAdapter(props: FieldRenderProps) {
1305
1342
  return undefined;
1306
1343
  }, [resolvedFields]);
1307
1344
 
1345
+ const detailColumns = useMemo(
1346
+ () =>
1347
+ resolvedFields?.length
1348
+ ? childFieldsToDetailColumns(resolvedFields as ChildTableFieldDef[])
1349
+ : undefined,
1350
+ [resolvedFields],
1351
+ );
1352
+
1353
+ const parentDoc = props.parentDoc;
1354
+ const isCellHidden = useMemo(
1355
+ () =>
1356
+ resolvedFields?.length
1357
+ ? childCellHidden(resolvedFields as ChildTableFieldDef[], parentDoc)
1358
+ : undefined,
1359
+ [resolvedFields, parentDoc],
1360
+ );
1361
+
1308
1362
  return React.createElement(ChildTable, {
1309
1363
  label: props.label,
1310
1364
  value: Array.isArray(props.value) ? props.value : [],
1311
1365
  onChange: props.onChange,
1312
1366
  columns,
1367
+ detailColumns: detailColumns?.length ? detailColumns : undefined,
1368
+ rowDetail: true,
1369
+ isCellHidden,
1313
1370
  defaultRow,
1314
1371
  disabled: props.disabled,
1315
1372
  error: props.error,
@@ -14,7 +14,7 @@ import {
14
14
  useFormField,
15
15
  useResolvedValidators,
16
16
  } from "@multiplatform.one/forms";
17
- import { useState } from "react";
17
+ import { useContext, useState } from "react";
18
18
  import {
19
19
  Modal,
20
20
  Pressable,
@@ -23,6 +23,7 @@ import {
23
23
  useWindowDimensions,
24
24
  View as RNView,
25
25
  } from "react-native";
26
+ import { SafeAreaInsetsContext, initialWindowMetrics } from "react-native-safe-area-context";
26
27
  import { platform } from "@multiplatform.one/platform";
27
28
  import DateTimePicker, { type DateTimePickerEvent } from "@react-native-community/datetimepicker";
28
29
  import { Text, View, XStack, YStack, useProps, useTheme, useThemeName } from "tamagui";
@@ -143,7 +144,8 @@ function DateField<
143
144
  // MPO-64 leftover: the sheet must end ABOVE the keyboard, not run under it.
144
145
  const keyboardInset = useKeyboardInset();
145
146
  const { height: windowHeight } = useWindowDimensions();
146
- const maxHeight = dateSheetMaxHeight(windowHeight, keyboardInset);
147
+ const topInset = useContext(SafeAreaInsetsContext)?.top ?? initialWindowMetrics?.insets.top ?? 0;
148
+ const maxHeight = dateSheetMaxHeight(windowHeight, keyboardInset, topInset);
147
149
 
148
150
  const [showPicker, setShowPicker] = useState(false);
149
151
  const [showTimePicker, setShowTimePicker] = useState(false);