@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
@@ -0,0 +1,50 @@
1
+ import type { DocTypeMeta } from "@multiplatform.one/frappe";
2
+
3
+ /**
4
+ * A doctype whose rows point at one record of another doctype through a Link
5
+ * field: the desk's Connections, one DocType Link row (`link_doctype` +
6
+ * `link_fieldname`). Pokemon's types, abilities and moves are three of them,
7
+ * each a standalone doctype with a `pokemon` Link, not a child table.
8
+ */
9
+ export interface FrappeConnection {
10
+ /** The linked doctype (DocType Link `link_doctype`). */
11
+ doctype: string;
12
+ /** Its Link field naming the record (DocType Link `link_fieldname`). */
13
+ fieldname: string;
14
+ /** Columns to read from the linked rows; the link field is always read. */
15
+ fields?: readonly string[];
16
+ /** Section heading; defaults to the linked doctype. */
17
+ label?: string;
18
+ /** DocType Link `group`, for desk parity. */
19
+ group?: string;
20
+ /** Row ceiling per record; 0 (the default) reads every linked row. */
21
+ limit?: number;
22
+ }
23
+
24
+ /**
25
+ * `meta.links` as connections. Hidden rows and links that resolve through a
26
+ * child table (`is_child_table`) are left out: neither names the record by a
27
+ * Link field on the linked doctype itself.
28
+ */
29
+ export function connectionsFromMeta(
30
+ meta: Pick<DocTypeMeta, "links"> | null | undefined,
31
+ ): FrappeConnection[] {
32
+ return (meta?.links ?? [])
33
+ .filter(
34
+ (link) => !link.hidden && !link.is_child_table && link.link_doctype && link.link_fieldname,
35
+ )
36
+ .map((link) => ({
37
+ doctype: link.link_doctype,
38
+ fieldname: link.link_fieldname,
39
+ ...(link.group ? { group: link.group } : {}),
40
+ }));
41
+ }
42
+
43
+ /**
44
+ * The fields a connection read asks for: `name`, the link field, then its
45
+ * own; every field (`*`) when it names none, as a meta.links row does.
46
+ */
47
+ export function connectionFields(connection: FrappeConnection): string[] {
48
+ if (!connection.fields?.length) return ["*"];
49
+ return Array.from(new Set(["name", connection.fieldname, ...connection.fields]));
50
+ }
@@ -0,0 +1,112 @@
1
+ import { createContext, useContext, type ReactNode } from "react";
2
+
3
+ /**
4
+ * The host router as frappe-ui sees it. frappe-ui imports no router: the
5
+ * host app hands this to `FrappeProvider` once (`@multiplatform.one/core`
6
+ * does it from One's router), and a routed `FrappeListView` or `FrappeForm`
7
+ * (`route` prop) reads its record from the path and moves with it.
8
+ *
9
+ * Both members are hooks, called where the routed view renders, so they see
10
+ * that page's route. A host keeps the same navigation for the life of the
11
+ * tree.
12
+ */
13
+ export interface FrappeNavigation {
14
+ /** The pathname of the route the caller renders in. */
15
+ usePathname(): string;
16
+ /** A function that pushes an href onto the history. */
17
+ useNavigate(): (href: string) => void;
18
+ }
19
+
20
+ /** A routed view's handle on the host router. */
21
+ export interface FrappeRouter {
22
+ pathname: string;
23
+ navigate(href: string): void;
24
+ }
25
+
26
+ const FrappeNavigationContext = createContext<FrappeNavigation | undefined>(undefined);
27
+
28
+ export function FrappeNavigationProvider({
29
+ navigation,
30
+ children,
31
+ }: {
32
+ navigation: FrappeNavigation | undefined;
33
+ children: ReactNode;
34
+ }) {
35
+ const inherited = useContext(FrappeNavigationContext);
36
+ return (
37
+ <FrappeNavigationContext.Provider value={navigation ?? inherited}>
38
+ {children}
39
+ </FrappeNavigationContext.Provider>
40
+ );
41
+ }
42
+
43
+ export function useFrappeNavigation(): FrappeNavigation | undefined {
44
+ return useContext(FrappeNavigationContext);
45
+ }
46
+
47
+ /** The host router for a routed view, or undefined when the host gave none. */
48
+ export function useFrappeRouter(): FrappeRouter | undefined {
49
+ const navigation = useFrappeNavigation();
50
+ if (!navigation) return undefined;
51
+ // oxlint-disable-next-line rules-of-hooks -- one navigation per tree
52
+ const pathname = navigation.usePathname();
53
+ // oxlint-disable-next-line rules-of-hooks -- one navigation per tree
54
+ const navigate = navigation.useNavigate();
55
+ return { pathname, navigate };
56
+ }
57
+
58
+ /**
59
+ * What a path under a doctype's route names, desk style: the list
60
+ * (`/route`), a new record (`/route/new`), a record (`/route/<name>`) or its
61
+ * edit form (`/route/<name>/edit`).
62
+ */
63
+ export type FrappeRouteTarget =
64
+ | { mode: "list" }
65
+ | { mode: "create" }
66
+ | { mode: "view"; name: string }
67
+ | { mode: "edit"; name: string };
68
+
69
+ function trimPath(path: string): string {
70
+ const bare = path.split(/[?#]/)[0] ?? "";
71
+ return bare.length > 1 ? bare.replace(/\/+$/, "") : bare;
72
+ }
73
+
74
+ function decodeSegment(segment: string): string {
75
+ try {
76
+ return decodeURIComponent(segment);
77
+ } catch {
78
+ return segment;
79
+ }
80
+ }
81
+
82
+ export function frappeRouteTarget(route: string, pathname: string): FrappeRouteTarget | undefined {
83
+ const base = trimPath(route);
84
+ const path = trimPath(pathname);
85
+ if (path === base) return { mode: "list" };
86
+ if (!path.startsWith(`${base}/`)) return undefined;
87
+ const segments = path.slice(base.length + 1).split("/");
88
+ const [first, second] = segments;
89
+ if (!first) return undefined;
90
+ if (segments.length === 1) {
91
+ return first === "new" ? { mode: "create" } : { mode: "view", name: decodeSegment(first) };
92
+ }
93
+ if (segments.length === 2 && second === "edit") {
94
+ return { mode: "edit", name: decodeSegment(first) };
95
+ }
96
+ return undefined;
97
+ }
98
+
99
+ /** The href of a doctype route's list, new record, record or record edit form. */
100
+ export function frappeRouteHref(route: string, target: FrappeRouteTarget): string {
101
+ const base = trimPath(route);
102
+ switch (target.mode) {
103
+ case "list":
104
+ return base;
105
+ case "create":
106
+ return `${base}/new`;
107
+ case "view":
108
+ return `${base}/${encodeURIComponent(target.name)}`;
109
+ case "edit":
110
+ return `${base}/${encodeURIComponent(target.name)}/edit`;
111
+ }
112
+ }
@@ -0,0 +1,335 @@
1
+ // A doctype's list, record, edit and new pages from a declaration alone
2
+ // (MPO-333): `<FrappeListView doctype route>` and `<FrappeForm doctype
3
+ // route>` read the record from the host router's path, draw the page, and
4
+ // move between the pages on row press, Add, Edit, Save, Cancel and Delete.
5
+ // The bench below is a stock one: getdoctype for meta, /api/resource for
6
+ // reads, the live app's create_doc / update_doc / delete_doc for writes, and
7
+ // a hash name the server picks on create. Poke Move is the doctype.
8
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
9
+ import { cleanup, fireEvent, screen, waitFor } from "@testing-library/react";
10
+ import i18next from "i18next";
11
+ import { initReactI18next } from "react-i18next";
12
+ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
13
+ import { FrappeForm } from "./FrappeForm";
14
+ import { FrappeListView } from "./FrappeListView";
15
+ import { frappeRouteHref, frappeRouteTarget, type FrappeNavigation } from "./frappeNavigation";
16
+ import { FrappeProvider } from "./FrappeProvider";
17
+
18
+ const doctype = "Poke Move";
19
+ const route = "/moves";
20
+
21
+ const pokeMoveMeta = {
22
+ name: doctype,
23
+ doctype: "DocType",
24
+ title_field: "move_name",
25
+ fields: [
26
+ { fieldname: "move_name", fieldtype: "Data", label: "Move Name", reqd: 1 },
27
+ { fieldname: "power", fieldtype: "Int", label: "Power" },
28
+ { fieldname: "pp", fieldtype: "Int", label: "PP" },
29
+ {
30
+ fieldname: "damage_class",
31
+ fieldtype: "Select",
32
+ label: "Damage Class",
33
+ options: "\nphysical\nspecial\nstatus",
34
+ },
35
+ ],
36
+ permissions: [
37
+ { role: "System Manager", read: 1, write: 1, create: 1, delete: 1 },
38
+ { role: "Guest", read: 1 },
39
+ ],
40
+ };
41
+
42
+ interface Move {
43
+ name: string;
44
+ doctype: string;
45
+ move_name?: string;
46
+ power?: number;
47
+ pp?: number;
48
+ damage_class?: string;
49
+ }
50
+
51
+ interface Write {
52
+ method: string;
53
+ body: Record<string, unknown>;
54
+ }
55
+
56
+ let run = 0;
57
+ let bench = "";
58
+ let moves = new Map<string, Move>();
59
+ let writes: Write[] = [];
60
+ let guest = false;
61
+ let confirmDelete = vi.fn(() => true);
62
+
63
+ function json(body: unknown, status = 200) {
64
+ return new Response(JSON.stringify(body), {
65
+ status,
66
+ headers: { "content-type": "application/json" },
67
+ });
68
+ }
69
+
70
+ function matchesNameFilter(row: Move, filters: string | null): boolean {
71
+ if (!filters) return true;
72
+ const parsed = JSON.parse(filters) as unknown;
73
+ const clauses = Array.isArray(parsed)
74
+ ? (parsed as Array<[string, string, unknown]>)
75
+ : Object.entries(parsed as Record<string, unknown>).map(([key, value]) => [key, "=", value]);
76
+ return clauses.every(([field, operator, value]) =>
77
+ field === "name" && operator === "=" ? row.name === value : true,
78
+ );
79
+ }
80
+
81
+ function stubBench() {
82
+ vi.stubGlobal("fetch", async (input: string | URL | Request, init?: RequestInit) => {
83
+ const url = new URL(String(input instanceof Request ? input.url : input));
84
+ const method = url.pathname.replace(/^\/api\/method\//, "");
85
+ if (method === "frappe.desk.form.load.getdoctype") {
86
+ if (guest) return json({ exc_type: "PermissionError" }, 403);
87
+ return json({ docs: [pokeMoveMeta] });
88
+ }
89
+ const resource = `/api/resource/${encodeURIComponent(doctype)}`;
90
+ if (url.pathname === resource) {
91
+ const rows = [...moves.values()].filter((row) =>
92
+ matchesNameFilter(row, url.searchParams.get("filters")),
93
+ );
94
+ return json({ data: rows });
95
+ }
96
+ if (url.pathname.startsWith(`${resource}/`)) {
97
+ const row = moves.get(decodeURIComponent(url.pathname.slice(resource.length + 1)));
98
+ return row ? json({ data: row }) : json({ exc_type: "DoesNotExistError" }, 404);
99
+ }
100
+ if (/^live\.live\.api\.(create|update|delete)_doc$/.test(method)) {
101
+ const body = JSON.parse(String(init?.body ?? "{}")) as Record<string, unknown>;
102
+ writes.push({ method, body });
103
+ if (method === "live.live.api.create_doc") {
104
+ const created = {
105
+ ...(body as unknown as Move),
106
+ name: `move-${moves.size + 1}-hash`,
107
+ doctype,
108
+ };
109
+ moves.set(created.name, created);
110
+ return json({ message: created });
111
+ }
112
+ if (method === "live.live.api.update_doc") {
113
+ const name = String(body.name);
114
+ const updated = { ...moves.get(name), ...(body.data as object) } as Move;
115
+ moves.set(name, updated);
116
+ return json({ message: updated });
117
+ }
118
+ if (method === "live.live.api.delete_doc") {
119
+ moves.delete(String(body.name));
120
+ return json({ message: "ok" });
121
+ }
122
+ }
123
+ return json({ data: [], message: [] });
124
+ });
125
+ }
126
+
127
+ function stubNavigation(pathname: string) {
128
+ const pushed: string[] = [];
129
+ const navigation: FrappeNavigation = {
130
+ usePathname: () => pathname,
131
+ useNavigate: () => (href) => pushed.push(href),
132
+ };
133
+ return { navigation, pushed };
134
+ }
135
+
136
+ function renderAt(pathname: string, page: "list" | "form") {
137
+ const { navigation, pushed } = stubNavigation(pathname);
138
+ const view = renderWithProviders(
139
+ <FrappeProvider baseURL={bench} realtime={false} navigation={navigation}>
140
+ {page === "list" ? (
141
+ <FrappeListView<Move> doctype={doctype} route={route} />
142
+ ) : (
143
+ <FrappeForm<Move> doctype={doctype} route={route} />
144
+ )}
145
+ </FrappeProvider>,
146
+ );
147
+ return { ...view, pushed };
148
+ }
149
+
150
+ function button(container: HTMLElement, label: string): HTMLElement {
151
+ const found = Array.from(container.querySelectorAll('[role="button"], button')).find(
152
+ (node) => node.textContent?.trim() === label || node.getAttribute("aria-label") === label,
153
+ );
154
+ if (!found) throw new Error(`no ${label} button`);
155
+ return found as HTMLElement;
156
+ }
157
+
158
+ function input(container: HTMLElement, label: string): HTMLInputElement {
159
+ const field = screen.getByLabelText(label, { exact: false });
160
+ if (field instanceof HTMLInputElement) return field;
161
+ const nested = field.querySelector("input") ?? container.querySelector(`input[name="${label}"]`);
162
+ if (!nested) throw new Error(`no ${label} input`);
163
+ return nested as HTMLInputElement;
164
+ }
165
+
166
+ beforeAll(async () => {
167
+ await i18next.use(initReactI18next).init({
168
+ lng: "en",
169
+ interpolation: { escapeValue: false },
170
+ resources: { en: { translation: {} } },
171
+ });
172
+ });
173
+
174
+ beforeEach(() => {
175
+ run += 1;
176
+ bench = `http://routes-${run}.spec.test`;
177
+ guest = false;
178
+ writes = [];
179
+ moves = new Map([
180
+ ["a1b2c3", { name: "a1b2c3", doctype, move_name: "tackle", power: 40, pp: 35 }],
181
+ ["d4e5f6", { name: "d4e5f6", doctype, move_name: "vine-whip", power: 45, pp: 25 }],
182
+ ]);
183
+ stubBench();
184
+ confirmDelete = vi.fn(() => true);
185
+ vi.stubGlobal("confirm", confirmDelete);
186
+ window.confirm = confirmDelete;
187
+ });
188
+
189
+ afterEach(() => {
190
+ cleanup();
191
+ vi.restoreAllMocks();
192
+ vi.unstubAllGlobals();
193
+ });
194
+
195
+ describe("frappeRouteTarget", () => {
196
+ it("reads the list, a new record, a record and its edit form off the path", () => {
197
+ expect(frappeRouteTarget(route, "/moves")).toEqual({ mode: "list" });
198
+ expect(frappeRouteTarget(route, "/moves/")).toEqual({ mode: "list" });
199
+ expect(frappeRouteTarget(route, "/moves/new")).toEqual({ mode: "create" });
200
+ expect(frappeRouteTarget(route, "/moves/a1b2c3")).toEqual({ mode: "view", name: "a1b2c3" });
201
+ expect(frappeRouteTarget(route, "/moves/Hyper%20Beam/edit")).toEqual({
202
+ mode: "edit",
203
+ name: "Hyper Beam",
204
+ });
205
+ expect(frappeRouteTarget(route, "/pokemon/bulbasaur")).toBeUndefined();
206
+ expect(frappeRouteTarget(route, "/moves/a/b/c")).toBeUndefined();
207
+ });
208
+
209
+ it("writes the href for each page, the record name escaped", () => {
210
+ expect(frappeRouteHref(route, { mode: "list" })).toBe("/moves");
211
+ expect(frappeRouteHref(route, { mode: "create" })).toBe("/moves/new");
212
+ expect(frappeRouteHref(route, { mode: "view", name: "Hyper Beam" })).toBe(
213
+ "/moves/Hyper%20Beam",
214
+ );
215
+ expect(frappeRouteHref(`${route}/`, { mode: "edit", name: "a1b2c3" })).toBe(
216
+ "/moves/a1b2c3/edit",
217
+ );
218
+ });
219
+ });
220
+
221
+ describe("FrappeListView with a route", () => {
222
+ it("is the doctype's list page: its rows, a row opens the record, Add opens a new one", async () => {
223
+ const { container, pushed } = renderAt("/moves", "list");
224
+ await waitFor(() => expect(screen.getByText("tackle")).toBeTruthy());
225
+ expect(screen.getByText("vine-whip")).toBeTruthy();
226
+ expect(screen.getByRole("heading", { name: doctype })).toBeTruthy();
227
+
228
+ fireEvent.click(screen.getByText("tackle"));
229
+ expect(pushed).toEqual(["/moves/a1b2c3"]);
230
+
231
+ await waitFor(() => expect(button(container, `Add ${doctype}`)).toBeTruthy());
232
+ fireEvent.click(button(container, `Add ${doctype}`));
233
+ expect(pushed).toEqual(["/moves/a1b2c3", "/moves/new"]);
234
+ });
235
+
236
+ it("offers no Add to a reader who cannot load the doctype's meta", async () => {
237
+ guest = true;
238
+ const { container } = renderAt("/moves", "list");
239
+ await waitFor(() => expect(screen.getByText("tackle")).toBeTruthy());
240
+ expect(() => button(container, `Add ${doctype}`)).toThrow();
241
+ });
242
+ });
243
+
244
+ describe("FrappeForm with a route", () => {
245
+ it("shows the record read-only on its page, with Edit and Delete", async () => {
246
+ const { container, pushed } = renderAt("/moves/a1b2c3", "form");
247
+ await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
248
+ expect(screen.getByRole("heading", { name: "tackle" })).toBeTruthy();
249
+ expect(input(container, "Move Name").disabled || input(container, "Move Name").readOnly).toBe(
250
+ true,
251
+ );
252
+ expect(() => button(container, "Save")).toThrow();
253
+
254
+ fireEvent.click(button(container, "Edit"));
255
+ expect(pushed).toEqual(["/moves/a1b2c3/edit"]);
256
+
257
+ fireEvent.click(button(container, `Back to ${doctype} list`));
258
+ expect(pushed).toEqual(["/moves/a1b2c3/edit", "/moves"]);
259
+ });
260
+
261
+ it("edits the record through update_doc and returns to it", async () => {
262
+ const { container, pushed } = renderAt("/moves/a1b2c3/edit", "form");
263
+ await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
264
+ fireEvent.change(input(container, "Power"), { target: { value: "50" } });
265
+ fireEvent.click(button(container, "Save"));
266
+
267
+ await waitFor(() => expect(pushed).toEqual(["/moves/a1b2c3"]));
268
+ expect(writes).toEqual([
269
+ {
270
+ method: "live.live.api.update_doc",
271
+ body: { doctype, name: "a1b2c3", data: { power: 50 } },
272
+ },
273
+ ]);
274
+ expect(moves.get("a1b2c3")?.power).toBe(50);
275
+ });
276
+
277
+ it("cancels an edit back to the record", async () => {
278
+ const { container, pushed } = renderAt("/moves/a1b2c3/edit", "form");
279
+ await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
280
+ fireEvent.click(button(container, "Cancel"));
281
+ expect(pushed).toEqual(["/moves/a1b2c3"]);
282
+ expect(writes).toEqual([]);
283
+ });
284
+
285
+ it("creates through create_doc and opens the record under the name the server gave it", async () => {
286
+ const { container, pushed } = renderAt("/moves/new", "form");
287
+ await waitFor(() => expect(input(container, "Move Name")).toBeTruthy());
288
+ expect(screen.getByRole("heading", { name: `New ${doctype}` })).toBeTruthy();
289
+ fireEvent.change(input(container, "Move Name"), { target: { value: "hyper-beam" } });
290
+ fireEvent.change(input(container, "Power"), { target: { value: "150" } });
291
+ fireEvent.click(button(container, "Save"));
292
+
293
+ await waitFor(() => expect(pushed).toEqual(["/moves/move-3-hash"]));
294
+ expect(writes).toHaveLength(1);
295
+ expect(writes[0]?.method).toBe("live.live.api.create_doc");
296
+ expect(writes[0]?.body).toMatchObject({ doctype, move_name: "hyper-beam", power: 150 });
297
+ expect(moves.get("move-3-hash")?.move_name).toBe("hyper-beam");
298
+ });
299
+
300
+ it("deletes through delete_doc and returns to the list", async () => {
301
+ const { container, pushed } = renderAt("/moves/d4e5f6", "form");
302
+ await waitFor(() => expect(input(container, "Move Name").value).toBe("vine-whip"));
303
+ fireEvent.click(button(container, "Delete"));
304
+
305
+ await waitFor(() => expect(pushed).toEqual(["/moves"]));
306
+ expect(confirmDelete).toHaveBeenCalledTimes(1);
307
+ expect(writes).toEqual([
308
+ { method: "live.live.api.delete_doc", body: { doctype, name: "d4e5f6" } },
309
+ ]);
310
+ expect(moves.has("d4e5f6")).toBe(false);
311
+ });
312
+
313
+ it("shows a reader without meta the record's own fields, read-only and with no actions", async () => {
314
+ guest = true;
315
+ const { container } = renderAt("/moves/a1b2c3", "form");
316
+ await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
317
+ expect(input(container, "Power").value).toBe("40");
318
+ expect(screen.queryByText(`Failed to load ${doctype} form`)).toBeNull();
319
+ expect(() => button(container, "Edit")).toThrow();
320
+ expect(() => button(container, "Delete")).toThrow();
321
+ });
322
+
323
+ it("draws no field for the collection's own $ keys on a reader's record", async () => {
324
+ guest = true;
325
+ const { container } = renderAt("/moves/a1b2c3", "form");
326
+ await waitFor(() => expect(input(container, "Move Name").value).toBe("tackle"));
327
+ const labels = Array.from(container.querySelectorAll("label"), (node) =>
328
+ node.textContent?.trim(),
329
+ );
330
+ expect(labels.filter((label) => label?.startsWith("$"))).toEqual([]);
331
+ for (const key of ["$origin", "$key", "$collectionId"]) {
332
+ expect(screen.queryByText(key)).toBeNull();
333
+ }
334
+ });
335
+ });
package/src/index.ts CHANGED
@@ -24,6 +24,16 @@ export {
24
24
  type FrappeConfig,
25
25
  type FrappeProviderProps,
26
26
  } from "./FrappeProvider";
27
+ export {
28
+ FrappeNavigationProvider,
29
+ frappeRouteHref,
30
+ frappeRouteTarget,
31
+ useFrappeNavigation,
32
+ useFrappeRouter,
33
+ type FrappeNavigation,
34
+ type FrappeRouter,
35
+ type FrappeRouteTarget,
36
+ } from "./frappeNavigation";
27
37
  export {
28
38
  FrappeReconnectIndicator,
29
39
  type FrappeReconnectIndicatorProps,
@@ -43,7 +53,13 @@ export {
43
53
 
44
54
  export { FrappeTable, filterValueToFrappeTuple, type FrappeTableProps } from "./FrappeTable";
45
55
  export { docFieldsToFilterFields } from "./filterFields";
46
- export { FrappeForm, type FrappeFormProps } from "./FrappeForm";
56
+ export { FrappeForm, type FrappeConnectionRows, type FrappeFormProps } from "./FrappeForm";
57
+ export {
58
+ FrappeFormConnectionSection,
59
+ FrappeFormConnections,
60
+ type FrappeFormConnectionSectionProps,
61
+ type FrappeFormConnectionsProps,
62
+ } from "./FrappeFormConnections";
47
63
  export {
48
64
  FormSidebar,
49
65
  type FormSidebarProps,
@@ -53,6 +69,13 @@ export {
53
69
  } from "./FormSidebar";
54
70
  export { FrappeList, type FrappeListProps } from "./FrappeList";
55
71
  export { FrappeListView, type FrappeListViewProps, type ListViewMode } from "./FrappeListView";
72
+ export {
73
+ useConnectionFilters,
74
+ type ConnectionFiltersResult,
75
+ type FrappeDoctypeFilter,
76
+ type FrappeFieldFilter,
77
+ type FrappeFilters,
78
+ } from "./useConnectionFilters";
56
79
 
57
80
  export {
58
81
  FrappeKanban,
@@ -206,6 +229,8 @@ export {
206
229
  getFieldComponent,
207
230
  initDefaultRegistry,
208
231
  childFieldsToColumns,
232
+ childFieldsToDetailColumns,
233
+ childCellHidden,
209
234
  fieldtypeToTableField,
210
235
  } from "./fieldRegistry";
211
236
  export { FieldOverridesProvider, useFieldResolver } from "./fieldOverrides";
@@ -437,6 +462,7 @@ export {
437
462
  setCreatePrefill,
438
463
  takeCreatePrefill,
439
464
  useFormEngine,
465
+ useSessionRoles,
440
466
  } from "./formEngine";
441
467
  export type {
442
468
  BuildTabsOptions,
@@ -451,3 +477,4 @@ export type {
451
477
  LevelPerm,
452
478
  PermLevelMatrix,
453
479
  } from "./formEngine";
480
+ export { connectionFields, connectionsFromMeta, type FrappeConnection } from "./frappeConnections";
@@ -254,6 +254,7 @@ export function SchemaForm({
254
254
  multiple: field.multiple,
255
255
  commit: field.commit,
256
256
  control: field.control,
257
+ ...(field.type === "Table" ? { parentDoc: doc } : null),
257
258
  };
258
259
 
259
260
  // Check overrides