@multiplatform.one/frappe-ui 7.11.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 (341) 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 +24 -20
  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.tsx +13 -1
  264. package/src/FrappeReportBuilder.tsx +11 -19
  265. package/src/FrappeRowsReader.tsx +38 -0
  266. package/src/FrappeTable.optionalProps.spec.tsx +249 -0
  267. package/src/FrappeTable.spec.tsx +32 -42
  268. package/src/FrappeTable.stories.spec.tsx +55 -0
  269. package/src/FrappeTable.stories.tsx +28 -37
  270. package/src/FrappeTable.tsx +108 -45
  271. package/src/childTableDependsOn.spec.tsx +85 -0
  272. package/src/desk/DeskFormSidebar.spec.tsx +20 -0
  273. package/src/desk/DeskFormSidebar.stories.tsx +42 -0
  274. package/src/fieldRegistry.spec.tsx +27 -64
  275. package/src/fieldRegistry.ts +71 -14
  276. package/src/fields/Date/index.native.tsx +4 -2
  277. package/src/fields/Date/sheetKeyboard.spec.ts +14 -5
  278. package/src/fields/Date/sheetKeyboard.ts +12 -4
  279. package/src/fields/DateRange/DateRange.spec.tsx +67 -0
  280. package/src/fields/DateRange/index.tsx +5 -1
  281. package/src/fields/LinkTree.spec.tsx +8 -0
  282. package/src/fields/LinkTree.stories.tsx +28 -5
  283. package/src/fields/Select/Select.hydration.spec.tsx +177 -0
  284. package/src/formEngine/doctypeMeta.ts +15 -114
  285. package/src/formEngine/index.ts +2 -0
  286. package/src/formEngine/seam.spec.ts +23 -4
  287. package/src/formEngine/useSessionRoles.ts +73 -0
  288. package/src/frappeConnections.ts +50 -0
  289. package/src/frappeNavigation.tsx +112 -0
  290. package/src/frappeRoutes.spec.tsx +335 -0
  291. package/src/index.ts +28 -1
  292. package/src/schemaForm/index.tsx +1 -0
  293. package/src/useConnectionFilters.tsx +157 -0
  294. package/src/useFrappePagination.ts +70 -26
  295. package/src/useFrappeRows.sharedPath.spec.tsx +206 -0
  296. package/src/useFrappeRows.ts +5 -4
  297. package/types/FrappeDashboard.d.ts +3 -4
  298. package/types/FrappeDashboard.d.ts.map +1 -1
  299. package/types/FrappeForm.d.ts +57 -5
  300. package/types/FrappeForm.d.ts.map +1 -1
  301. package/types/FrappeFormConnections.d.ts +18 -0
  302. package/types/FrappeFormConnections.d.ts.map +1 -0
  303. package/types/FrappeImageView.d.ts +2 -3
  304. package/types/FrappeImageView.d.ts.map +1 -1
  305. package/types/FrappeList.d.ts +20 -2
  306. package/types/FrappeList.d.ts.map +1 -1
  307. package/types/FrappeListView.d.ts +53 -3
  308. package/types/FrappeListView.d.ts.map +1 -1
  309. package/types/FrappeProvider.d.ts +8 -1
  310. package/types/FrappeProvider.d.ts.map +1 -1
  311. package/types/FrappeReportBuilder.d.ts.map +1 -1
  312. package/types/FrappeRowsReader.d.ts +26 -0
  313. package/types/FrappeRowsReader.d.ts.map +1 -0
  314. package/types/FrappeTable.d.ts +39 -8
  315. package/types/FrappeTable.d.ts.map +1 -1
  316. package/types/fieldRegistry.d.ts +17 -3
  317. package/types/fieldRegistry.d.ts.map +1 -1
  318. package/types/fields/Date/index.native.d.ts.map +1 -1
  319. package/types/fields/Date/sheetKeyboard.d.ts +4 -3
  320. package/types/fields/Date/sheetKeyboard.d.ts.map +1 -1
  321. package/types/fields/DateRange/index.d.ts.map +1 -1
  322. package/types/formEngine/doctypeMeta.d.ts +10 -114
  323. package/types/formEngine/doctypeMeta.d.ts.map +1 -1
  324. package/types/formEngine/index.d.ts +1 -0
  325. package/types/formEngine/index.d.ts.map +1 -1
  326. package/types/formEngine/useSessionRoles.d.ts +19 -0
  327. package/types/formEngine/useSessionRoles.d.ts.map +1 -0
  328. package/types/frappeConnections.d.ts +33 -0
  329. package/types/frappeConnections.d.ts.map +1 -0
  330. package/types/frappeNavigation.d.ts +49 -0
  331. package/types/frappeNavigation.d.ts.map +1 -0
  332. package/types/index.d.ts +7 -3
  333. package/types/index.d.ts.map +1 -1
  334. package/types/schemaForm/index.d.ts.map +1 -1
  335. package/types/useConnectionFilters.d.ts +34 -0
  336. package/types/useConnectionFilters.d.ts.map +1 -0
  337. package/types/useFrappePagination.d.ts +18 -5
  338. package/types/useFrappePagination.d.ts.map +1 -1
  339. package/types/useFrappeRows.d.ts +5 -4
  340. package/types/useFrappeRows.d.ts.map +1 -1
  341. package/src/FileBrowser/AttachLibrary.spec.tsx +0 -74
@@ -6,12 +6,24 @@ import { useTranslation } from "react-i18next";
6
6
  import { Button, SearchInput, Spinner } from "@multiplatform.one/forms";
7
7
  import { Text, View, XStack, YStack, isWeb, useTheme, type YStackProps } from "tamagui";
8
8
  import { SavedFilterDropdown, useSavedFilters, type ParsedFilter } from "@multiplatform.one/table";
9
- import { Avatar, ViewSwitcher, componentColors } from "@multiplatform.one/components";
9
+ import {
10
+ AsyncBoundary,
11
+ Avatar,
12
+ ListPageLayout,
13
+ PageHeader,
14
+ ViewSwitcher,
15
+ componentColors,
16
+ type ImageGridProps,
17
+ } from "@multiplatform.one/components";
10
18
  import { pressTargetStyle, useResolvedKnobs } from "@multiplatform.one/theme";
11
19
  import { storage } from "@multiplatform.one/store";
12
20
  import { useFrappeConfig } from "./FrappeProvider";
13
- import { FrappeList } from "./FrappeList";
21
+ import { FrappeList, type FrappeListProps } from "./FrappeList";
14
22
  import { FrappeTable } from "./FrappeTable";
23
+ import { frappeRouteHref, useFrappeRouter } from "./frappeNavigation";
24
+ import { connectionsFromMeta, type FrappeConnection } from "./frappeConnections";
25
+ import { isPermissionError } from "./liveErrors";
26
+ import { useConnectionFilters, type FrappeDoctypeFilter } from "./useConnectionFilters";
15
27
 
16
28
  interface ListFieldConfig {
17
29
  key: string;
@@ -19,7 +31,7 @@ interface ListFieldConfig {
19
31
  render?: (value: any, doc: any) => React.ReactNode;
20
32
  }
21
33
 
22
- export type ListViewMode = "list" | "table";
34
+ export type ListViewMode = "list" | "table" | "grid";
23
35
 
24
36
  export interface FrappeListViewProps<TDoc extends { name: string; doctype: string }> extends Omit<
25
37
  YStackProps,
@@ -27,12 +39,58 @@ export interface FrappeListViewProps<TDoc extends { name: string; doctype: strin
27
39
  > {
28
40
  doctype: string;
29
41
  fields?: readonly (keyof TDoc & string)[];
30
- filters?: Partial<TDoc> | Array<[keyof TDoc & string, string, unknown]>;
42
+ /**
43
+ * Doctypes linking back to this one (desk Connections). A four-part filter
44
+ * on one of them, `[doctype, field, operator, value]`, narrows this list to
45
+ * the records its matching rows name. Defaults to the doctype's meta.links.
46
+ */
47
+ connections?: readonly FrappeConnection[];
48
+ filters?: Partial<TDoc> | Array<[keyof TDoc & string, string, unknown] | FrappeDoctypeFilter>;
31
49
  defaultViewMode?: ListViewMode;
50
+ /** Controlled view mode; the stored per-doctype choice is then ignored. */
51
+ viewMode?: ListViewMode;
52
+ onViewModeChange?: (mode: ListViewMode) => void;
32
53
  onItemClick?: (doc: Partial<TDoc>) => void;
33
54
  onAdd?: () => void;
55
+ /**
56
+ * The path the doctype's pages live under (`/moves`). Given, the view is
57
+ * the doctype's list page: it carries the list page template with the
58
+ * doctype as its title, a row opens `route/<name>` and Add opens
59
+ * `route/new` through the FrappeProvider's navigation. Add waits for the
60
+ * doctype's meta, so a reader who cannot load it is not offered a form.
61
+ */
62
+ route?: string;
63
+ /**
64
+ * The query the list reads, beyond fields and filters. Give a route loader
65
+ * the same order and limit (`loadDocList`) so its server page and the live
66
+ * page match; `initialRows` is that server page.
67
+ */
68
+ orderBy?: FrappeListProps<TDoc>["orderBy"];
69
+ limit?: number;
70
+ initialRows?: readonly Partial<TDoc>[];
34
71
  columns?: DataTableColumn<Partial<TDoc>>[];
35
72
  displayFields?: ListFieldConfig[];
73
+ /**
74
+ * Per-row card slot. Given, the view gains a card grid (`grid` mode, the
75
+ * catalog ImageGrid) that draws each row with it; loading, empty and error
76
+ * stay the view's own states.
77
+ */
78
+ renderCard?: (doc: Partial<TDoc>, index: number) => React.ReactNode;
79
+ /** Card grid: column count, tile rung and media aspect. */
80
+ gridProps?: Pick<ImageGridProps<Partial<TDoc>>, "columns" | "tileSize" | "mediaAspect">;
81
+ /** Replaces the default empty message in list and card modes. */
82
+ emptyState?: React.ReactNode;
83
+ /**
84
+ * Error-state retry. Defaults to remounting the list, which restarts its
85
+ * sync only when nothing else still holds the failed collection.
86
+ */
87
+ onRetry?: () => void;
88
+ /** Rows revealed per page in list and card modes (default 20). */
89
+ pageSize?: number;
90
+ /** False drops the list/card search band (a screen that owns its own search). */
91
+ showSearch?: boolean;
92
+ /** False renders the view without its card surface, flush in the page. */
93
+ surface?: boolean;
36
94
  showToolbar?: boolean;
37
95
  showFilterSidebar?: boolean;
38
96
  /** @deprecated The toolbar always renders the List/Table switcher now. */
@@ -90,7 +148,7 @@ function resolveImageUrl(value: unknown, baseURL: string): string {
90
148
  async function getStoredViewMode(doctype: string): Promise<ListViewMode | null> {
91
149
  try {
92
150
  const stored = await Promise.resolve(storage.getItem(`${storageKeyPrefix}${doctype}`));
93
- if (stored === "list" || stored === "table") return stored;
151
+ if (stored === "list" || stored === "table" || stored === "grid") return stored;
94
152
  } catch {
95
153
  // storage not available
96
154
  }
@@ -114,12 +172,26 @@ async function setStoredViewMode(doctype: string, mode: ListViewMode): Promise<v
114
172
  export function FrappeListView<TDoc extends { name: string; doctype: string }>({
115
173
  doctype,
116
174
  fields,
175
+ connections,
117
176
  filters,
118
177
  defaultViewMode = "list",
119
- onItemClick,
120
- onAdd,
178
+ viewMode: controlledViewMode,
179
+ onViewModeChange,
180
+ onItemClick: onItemClickProp,
181
+ onAdd: onAddProp,
182
+ route,
183
+ orderBy,
184
+ limit,
185
+ initialRows,
121
186
  columns,
122
187
  displayFields,
188
+ renderCard,
189
+ gridProps,
190
+ emptyState,
191
+ onRetry,
192
+ pageSize,
193
+ showSearch = true,
194
+ surface = true,
123
195
  showToolbar = true,
124
196
  showFilterSidebar = false,
125
197
  // deprecated: switcher always renders; destructured so it stays out of stackProps
@@ -130,6 +202,7 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
130
202
  ...stackProps
131
203
  }: FrappeListViewProps<TDoc>) {
132
204
  const config = useFrappeConfig();
205
+ const navigate = useFrappeRouter()?.navigate;
133
206
  const { knobProps } = useResolvedKnobs();
134
207
  const { t } = useTranslation();
135
208
  const theme = useTheme();
@@ -153,7 +226,10 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
153
226
  fixtures: config?.fixtures,
154
227
  };
155
228
 
156
- const [viewMode, setViewModeState] = useState<ListViewMode>(defaultViewMode);
229
+ const [storedViewMode, setViewModeState] = useState<ListViewMode>(defaultViewMode);
230
+ const requestedViewMode = controlledViewMode ?? storedViewMode;
231
+ const viewMode: ListViewMode =
232
+ requestedViewMode === "grid" && !renderCard ? "list" : requestedViewMode;
157
233
 
158
234
  useEffect(() => {
159
235
  let cancelled = false;
@@ -171,10 +247,12 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
171
247
 
172
248
  const setViewMode = useCallback(
173
249
  (mode: ListViewMode) => {
250
+ onViewModeChange?.(mode);
251
+ if (controlledViewMode !== undefined) return;
174
252
  setViewModeState(mode);
175
253
  void setStoredViewMode(doctype, mode);
176
254
  },
177
- [doctype],
255
+ [doctype, controlledViewMode, onViewModeChange],
178
256
  );
179
257
 
180
258
  const {
@@ -183,6 +261,27 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
183
261
  error: metaError,
184
262
  } = useDocTypeMeta(connectionConfig, doctype);
185
263
 
264
+ const onItemClick =
265
+ onItemClickProp ??
266
+ (route !== undefined && navigate
267
+ ? (doc: Partial<TDoc>) => {
268
+ if (doc.name) navigate(frappeRouteHref(route, { mode: "view", name: doc.name }));
269
+ }
270
+ : undefined);
271
+ const onAdd =
272
+ onAddProp ??
273
+ (route !== undefined && navigate && meta
274
+ ? () => navigate(frappeRouteHref(route, { mode: "create" }))
275
+ : undefined);
276
+ const asPage = (view: React.ReactElement) =>
277
+ route === undefined ? (
278
+ view
279
+ ) : (
280
+ <ListPageLayout scroll={false} header={<PageHeader title={doctype} divider={false} />}>
281
+ {view}
282
+ </ListPageLayout>
283
+ );
284
+
186
285
  // Guest fallback: DocType meta is permission-gated (Guests get 403) while
187
286
  // the documents themselves are often guest-readable. Peek one doc with
188
287
  // fields=["*"] and derive display fields from its keys so the views render
@@ -352,6 +451,18 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
352
451
  setActiveFilters(filters);
353
452
  }, [filters]);
354
453
 
454
+ const effectiveConnections = useMemo(
455
+ () => connections ?? (isMetaLoading ? undefined : connectionsFromMeta(meta)),
456
+ [connections, isMetaLoading, meta],
457
+ );
458
+ const linkFilters = useConnectionFilters(
459
+ connectionConfig,
460
+ doctype,
461
+ activeFilters as Parameters<typeof useConnectionFilters>[2],
462
+ effectiveConnections,
463
+ );
464
+ const resolvedFilters = linkFilters.filters as typeof activeFilters;
465
+
355
466
  // List-mode search: a debounced server-side `like` on the doctype's title
356
467
  // field, merged into the filters useFrappePagination already accepts (the
357
468
  // pagination layer has no orFilters plumbing, so one well-chosen field
@@ -368,21 +479,21 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
368
479
 
369
480
  const listFilters = useMemo(() => {
370
481
  const query = debouncedSearch.trim();
371
- if (!query) return activeFilters;
482
+ if (!query) return resolvedFilters;
372
483
  const searchFilter = [searchField, "like", `%${query}%`] as [
373
484
  keyof TDoc & string,
374
485
  string,
375
486
  unknown,
376
487
  ];
377
- if (!activeFilters) return [searchFilter];
378
- if (Array.isArray(activeFilters)) return [...activeFilters, searchFilter];
488
+ if (!resolvedFilters) return [searchFilter];
489
+ if (Array.isArray(resolvedFilters)) return [...resolvedFilters, searchFilter];
379
490
  return [
380
- ...Object.entries(activeFilters).map(
491
+ ...Object.entries(resolvedFilters).map(
381
492
  ([key, value]) => [key, "=", value] as [keyof TDoc & string, string, unknown],
382
493
  ),
383
494
  searchFilter,
384
495
  ];
385
- }, [activeFilters, debouncedSearch, searchField]);
496
+ }, [resolvedFilters, debouncedSearch, searchField]);
386
497
 
387
498
  // Axiom 6 / DG-TBL-06: zero rows under active filters or search must not
388
499
  // render the same chrome as a truly empty doctype — the no-results message
@@ -423,8 +534,10 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
423
534
  [savedFiltersHook],
424
535
  );
425
536
 
426
- if (isMetaLoading) {
427
- return (
537
+ // Named fields need no schema to read or draw: the view renders now (on
538
+ // the server too) and labels settle when meta lands.
539
+ if (isMetaLoading && !fields) {
540
+ return asPage(
428
541
  <YStack
429
542
  {...knobProps.panelPadding}
430
543
  alignItems="center"
@@ -435,7 +548,7 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
435
548
  <Text {...knobProps.body} color={knobProps.textAccentColor} marginTop="$2">
436
549
  {t("Loading {{doctype}} schema...", { doctype })}
437
550
  </Text>
438
- </YStack>
551
+ </YStack>,
439
552
  );
440
553
  }
441
554
 
@@ -456,6 +569,7 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
456
569
  <XStack alignItems="center" {...knobProps.gap}>
457
570
  <ViewSwitcher
458
571
  views={[
572
+ ...(renderCard ? [{ type: "grid", label: t("Cards") }] : []),
459
573
  { type: "list", label: t("List") },
460
574
  { type: "table", label: t("Table") },
461
575
  ]}
@@ -576,17 +690,23 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
576
690
  );
577
691
 
578
692
  const renderContent = () => {
579
- if (viewMode === "list") {
693
+ if (viewMode === "list" || viewMode === "grid") {
694
+ const isGrid = viewMode === "grid";
580
695
  return (
581
- <YStack flex={1}>
582
- {renderListSearch()}
696
+ <YStack flex={isGrid ? undefined : 1}>
697
+ {showSearch ? renderListSearch() : null}
583
698
  <FrappeList
584
- key={`list-${refreshKey}`}
699
+ key={`${viewMode}-${refreshKey}`}
700
+ layout={isGrid ? "grid" : "list"}
701
+ gridProps={gridProps}
702
+ emptyState={emptyState}
703
+ pageSize={pageSize}
704
+ onRetry={onRetry ?? handleRefresh}
585
705
  doctype={doctype}
586
706
  fields={effectiveFields as readonly ("name" | "doctype")[]}
587
707
  filters={listFilters as Partial<{ name: string; doctype: string }>}
588
708
  renderItem={
589
- renderListItem as unknown as (
709
+ (isGrid && renderCard ? renderCard : renderListItem) as unknown as (
590
710
  item: Partial<{ name: string; doctype: string }>,
591
711
  index: number,
592
712
  ) => React.ReactNode
@@ -599,6 +719,11 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
599
719
  baseURL={finalBaseURL}
600
720
  auth={finalAuth}
601
721
  syncOptions={finalSyncOptions}
722
+ orderBy={orderBy as FrappeListProps<{ name: string; doctype: string }>["orderBy"]}
723
+ limit={limit}
724
+ initialRows={
725
+ initialRows as FrappeListProps<{ name: string; doctype: string }>["initialRows"]
726
+ }
602
727
  // Only override when narrowing is active: FrappeList's own
603
728
  // "No {doctype} records found" stays the true-empty message.
604
729
  {...(noResultsMessage ? { emptyMessage: noResultsMessage } : {})}
@@ -612,7 +737,7 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
612
737
  key={`table-${refreshKey}`}
613
738
  doctype={doctype}
614
739
  fields={effectiveFields}
615
- filters={activeFilters}
740
+ filters={resolvedFilters as Parameters<typeof FrappeTable<TDoc>>[0]["filters"]}
616
741
  columns={effectiveColumns}
617
742
  onRowClick={onItemClick}
618
743
  baseURL={finalBaseURL}
@@ -622,24 +747,45 @@ export function FrappeListView<TDoc extends { name: string; doctype: string }>({
622
747
  );
623
748
  };
624
749
 
625
- return (
750
+ // A connection filter's matching records are known only once its linked
751
+ // rows are read; until then the view waits instead of listing everything.
752
+ const renderResolvedContent = () => {
753
+ if (linkFilters.error) {
754
+ return (
755
+ <AsyncBoundary
756
+ layout="list"
757
+ error={linkFilters.error}
758
+ errorTitle={
759
+ isPermissionError(linkFilters.error)
760
+ ? t("You do not have permission to view {{doctype}}", { doctype })
761
+ : t("Error loading {{doctype}}", { doctype })
762
+ }
763
+ />
764
+ );
765
+ }
766
+ if (linkFilters.pending) return <AsyncBoundary layout="list" loading />;
767
+ return renderContent();
768
+ };
769
+
770
+ return asPage(
626
771
  <YStack
627
- flex={1}
628
- {...knobProps.surface}
629
- {...knobProps.containerRadius}
630
- overflow="hidden"
772
+ flex={viewMode === "grid" ? undefined : 1}
773
+ {...(surface ? knobProps.surface : {})}
774
+ {...(surface ? knobProps.containerRadius : {})}
775
+ overflow={surface ? "hidden" : undefined}
631
776
  testID={`${doctype.toLowerCase()}-list`}
632
777
  {...stackProps}
633
778
  >
634
779
  {renderToolbar()}
780
+ {linkFilters.readers}
635
781
  {showFilterSidebar ? (
636
782
  <XStack flex={1}>
637
783
  {renderFilterSidebar()}
638
- <YStack flex={1}>{renderContent()}</YStack>
784
+ <YStack flex={1}>{renderResolvedContent()}</YStack>
639
785
  </XStack>
640
786
  ) : (
641
- renderContent()
787
+ renderResolvedContent()
642
788
  )}
643
- </YStack>
789
+ </YStack>,
644
790
  );
645
791
  }
@@ -1,5 +1,6 @@
1
1
  import type { FixtureProvider, UseFrappeCollectionConfig } from "@multiplatform.one/frappe";
2
2
  import React, { createContext, use, useContext } from "react";
3
+ import { FrappeNavigationProvider, type FrappeNavigation } from "./frappeNavigation";
3
4
 
4
5
  /**
5
6
  * Configuration for the Frappe connection.
@@ -101,6 +102,12 @@ export interface FrappeProviderProps {
101
102
  * `baseURL` to run the whole app off fixtures with no server.
102
103
  */
103
104
  fixtures?: FixtureProvider;
105
+ /**
106
+ * The host router. Given, a `FrappeListView` or `FrappeForm` with a
107
+ * `route` reads its record from the path and navigates between the
108
+ * doctype's list, record, edit and new pages on its own.
109
+ */
110
+ navigation?: FrappeNavigation;
104
111
  }
105
112
 
106
113
  /**
@@ -137,6 +144,7 @@ export function FrappeProvider({
137
144
  syncOptions,
138
145
  socketOptions,
139
146
  fixtures,
147
+ navigation,
140
148
  }: FrappeProviderProps) {
141
149
  const value = React.useMemo(
142
150
  () => ({
@@ -163,7 +171,11 @@ export function FrappeProvider({
163
171
  ],
164
172
  );
165
173
 
166
- return <FrappeContext.Provider value={value}>{children}</FrappeContext.Provider>;
174
+ return (
175
+ <FrappeContext.Provider value={value}>
176
+ <FrappeNavigationProvider navigation={navigation}>{children}</FrappeNavigationProvider>
177
+ </FrappeContext.Provider>
178
+ );
167
179
  }
168
180
 
169
181
  /**
@@ -1,9 +1,4 @@
1
- import {
2
- useDocTypeMeta,
3
- useFrappeCollection,
4
- useFrappeDoctypeEvents,
5
- useLiveQuery,
6
- } from "@multiplatform.one/frappe";
1
+ import { useDocTypeMeta, useFrappeDoctypeEvents } from "@multiplatform.one/frappe";
7
2
  import { downloadBlob } from "@multiplatform.one/platform";
8
3
  import {
9
4
  ReportBuilder,
@@ -20,6 +15,7 @@ import { Popover, Text, XStack, YStack, type YStackProps } from "tamagui";
20
15
  import { FloppyDiskIcon, FolderOpenIcon, TrashIcon } from "@phosphor-icons/react";
21
16
  import { useResolvedKnobs } from "@multiplatform.one/theme";
22
17
  import { useFrappeConfig } from "./FrappeProvider";
18
+ import { useFrappeRows } from "./useFrappeRows";
23
19
 
24
20
  // ============================================================================
25
21
  // Saved Report Types (Frappe-specific persisted storage)
@@ -164,7 +160,7 @@ export function FrappeReportBuilder({
164
160
  fieldname: f.fieldname,
165
161
  label: f.label || f.fieldname,
166
162
  fieldtype: f.fieldtype,
167
- enabled: f.in_list_view ?? false,
163
+ enabled: !!f.in_list_view,
168
164
  }));
169
165
  }, [initialColumns, meta]);
170
166
 
@@ -177,20 +173,16 @@ export function FrappeReportBuilder({
177
173
  return fieldNames as readonly string[];
178
174
  }, [allColumns]);
179
175
 
180
- const collection = useFrappeCollection<{ name: string; doctype: string }>(
181
- connectionConfig,
182
- doctype,
183
- {
184
- fields: fetchFields as readonly ("name" | "doctype")[],
185
- filters: filters as Partial<{ name: string; doctype: string }>,
186
- },
187
- );
188
176
  const {
189
- data: rawData,
190
- isLoading,
177
+ rows,
178
+ isQueryLoading: isLoading,
191
179
  isError,
192
- } = useLiveQuery((q) => (collection ? q.from({ _list: collection }) : null), [collection]);
193
- const data = useMemo(() => (rawData as Record<string, unknown>[] | undefined) ?? [], [rawData]);
180
+ } = useFrappeRows<{ name: string; doctype: string }>(connectionConfig, doctype, {
181
+ fields: fetchFields as readonly ("name" | "doctype")[],
182
+ filters: filters as Partial<{ name: string; doctype: string }>,
183
+ limit: null,
184
+ });
185
+ const data = rows as unknown as Record<string, unknown>[];
194
186
 
195
187
  // Live staleness: a realtime change on the doctype marks the current
196
188
  // report result stale until the collection reconciles (data identity
@@ -0,0 +1,38 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ useFrappeRows,
4
+ type FrappeRowsConnection,
5
+ type UseFrappeRowsOptions,
6
+ } from "./useFrappeRows";
7
+
8
+ export interface FrappeRowsSnapshot<TDoc> {
9
+ rows: TDoc[];
10
+ isLoading: boolean;
11
+ error: Error | null;
12
+ }
13
+
14
+ export interface FrappeRowsReaderProps<TDoc extends { name: string; doctype: string }> {
15
+ connection: FrappeRowsConnection | undefined;
16
+ doctype: string;
17
+ /** Memoize it: a new identity every render re-reports the same rows. */
18
+ options: UseFrappeRowsOptions<TDoc>;
19
+ onChange: (snapshot: FrappeRowsSnapshot<TDoc>) => void;
20
+ }
21
+
22
+ /**
23
+ * One doctype read as a component. A parent that needs a varying number of
24
+ * reads (one per connection, one per junction filter) mounts one reader per
25
+ * read instead of calling hooks in a loop. Renders nothing.
26
+ */
27
+ export function FrappeRowsReader<TDoc extends { name: string; doctype: string }>({
28
+ connection,
29
+ doctype,
30
+ options,
31
+ onChange,
32
+ }: FrappeRowsReaderProps<TDoc>) {
33
+ const { rows, isLoading, error } = useFrappeRows<TDoc>(connection, doctype, options);
34
+ useEffect(() => {
35
+ onChange({ rows, isLoading, error });
36
+ }, [rows, isLoading, error, onChange]);
37
+ return null;
38
+ }