@multiplatform.one/frappe-ui 7.7.6 → 7.8.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 (491) hide show
  1. package/dist/cjs/ChartCard.native.js.map +1 -1
  2. package/dist/cjs/FileBrowser/AttachLibrary.native.js.map +1 -1
  3. package/dist/cjs/FileBrowser/folderTree.native.js.map +1 -1
  4. package/dist/cjs/FileBrowser/index.native.js.map +1 -1
  5. package/dist/cjs/FileBrowser/types.native.js.map +1 -1
  6. package/dist/cjs/FileBrowser/viewPersistence.native.js.map +1 -1
  7. package/dist/cjs/FormSidebar.native.js.map +1 -1
  8. package/dist/cjs/FrappeAssignment.native.js.map +1 -1
  9. package/dist/cjs/FrappeCalendar.cjs +20 -17
  10. package/dist/cjs/FrappeCalendar.native.js +22 -21
  11. package/dist/cjs/FrappeCalendar.native.js.map +1 -1
  12. package/dist/cjs/FrappeChartCard.native.js.map +1 -1
  13. package/dist/cjs/FrappeConnectionIndicator.native.js.map +1 -1
  14. package/dist/cjs/FrappeDashboard.native.js.map +1 -1
  15. package/dist/cjs/FrappeForm.cjs +1 -1
  16. package/dist/cjs/FrappeForm.native.js +2 -2
  17. package/dist/cjs/FrappeForm.native.js.map +1 -1
  18. package/dist/cjs/FrappeGantt.cjs +25 -34
  19. package/dist/cjs/FrappeGantt.native.js +27 -38
  20. package/dist/cjs/FrappeGantt.native.js.map +1 -1
  21. package/dist/cjs/FrappeImageView.cjs +4 -2
  22. package/dist/cjs/FrappeImageView.native.js +8 -4
  23. package/dist/cjs/FrappeImageView.native.js.map +1 -1
  24. package/dist/cjs/FrappeKPICard.cjs +26 -1
  25. package/dist/cjs/FrappeKPICard.native.js +26 -1
  26. package/dist/cjs/FrappeKPICard.native.js.map +1 -1
  27. package/dist/cjs/FrappeKanban.cjs +16 -17
  28. package/dist/cjs/FrappeKanban.native.js +18 -21
  29. package/dist/cjs/FrappeKanban.native.js.map +1 -1
  30. package/dist/cjs/FrappeLikedBy.cjs +1 -1
  31. package/dist/cjs/FrappeLikedBy.native.js +1 -1
  32. package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
  33. package/dist/cjs/FrappeList.cjs +2 -2
  34. package/dist/cjs/FrappeList.native.js +2 -2
  35. package/dist/cjs/FrappeList.native.js.map +1 -1
  36. package/dist/cjs/FrappeListView.native.js.map +1 -1
  37. package/dist/cjs/FrappeProvider.native.js.map +1 -1
  38. package/dist/cjs/FrappeReconnectIndicator.native.js.map +1 -1
  39. package/dist/cjs/FrappeReportBuilder.cjs +2 -2
  40. package/dist/cjs/FrappeReportBuilder.native.js +2 -2
  41. package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
  42. package/dist/cjs/FrappeReportView.native.js +3 -3
  43. package/dist/cjs/FrappeReportView.native.js.map +1 -1
  44. package/dist/cjs/FrappeTable.cjs +35 -38
  45. package/dist/cjs/FrappeTable.native.js +38 -39
  46. package/dist/cjs/FrappeTable.native.js.map +1 -1
  47. package/dist/cjs/FrappeTreeView.cjs +85 -50
  48. package/dist/cjs/FrappeTreeView.native.js +129 -50
  49. package/dist/cjs/FrappeTreeView.native.js.map +1 -1
  50. package/dist/cjs/SyncErrorNotice.native.js.map +1 -1
  51. package/dist/cjs/commentActions.cjs +1 -1
  52. package/dist/cjs/commentActions.native.js +1 -1
  53. package/dist/cjs/commentActions.native.js.map +1 -1
  54. package/dist/cjs/components/attachments.native.js.map +1 -1
  55. package/dist/cjs/components/tags.native.js.map +1 -1
  56. package/dist/cjs/components/timeline.cjs +1 -1
  57. package/dist/cjs/components/timeline.native.js +1 -1
  58. package/dist/cjs/components/timeline.native.js.map +1 -1
  59. package/dist/cjs/dependsOn.native.js.map +1 -1
  60. package/dist/cjs/desk/DeskFormPage.native.js.map +1 -1
  61. package/dist/cjs/desk/DeskFormSidebar.native.js.map +1 -1
  62. package/dist/cjs/desk/DeskListPage.native.js.map +1 -1
  63. package/dist/cjs/desk/DeskShell.cjs +2 -0
  64. package/dist/cjs/desk/DeskShell.native.js +2 -0
  65. package/dist/cjs/desk/DeskShell.native.js.map +1 -1
  66. package/dist/cjs/desk/DeskSidebar.native.js.map +1 -1
  67. package/dist/cjs/desk/awesomebar.cjs +1 -1
  68. package/dist/cjs/desk/awesomebar.native.js +1 -1
  69. package/dist/cjs/desk/awesomebar.native.js.map +1 -1
  70. package/dist/cjs/fieldOverrides.native.js.map +1 -1
  71. package/dist/cjs/fieldRegistry.native.js +31 -31
  72. package/dist/cjs/fieldRegistry.native.js.map +1 -1
  73. package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
  74. package/dist/cjs/fields/Check/index.native.js.map +1 -1
  75. package/dist/cjs/fields/CodeEditor/index.native.js.map +1 -1
  76. package/dist/cjs/fields/Color/index.native.js.map +1 -1
  77. package/dist/cjs/fields/Data/index.native.js.map +1 -1
  78. package/dist/cjs/fields/Date/sheetKeyboard.native.js.map +1 -1
  79. package/dist/cjs/fields/Date/types.native.js.map +1 -1
  80. package/dist/cjs/fields/Date/wire.native.js.map +1 -1
  81. package/dist/cjs/fields/DateRange/index.native.js.map +1 -1
  82. package/dist/cjs/fields/Duration/index.native.js.map +1 -1
  83. package/dist/cjs/fields/DynamicLink.native.js.map +1 -1
  84. package/dist/cjs/fields/FileUpload.native.js.map +1 -1
  85. package/dist/cjs/fields/FileUploadField/index.native.js.map +1 -1
  86. package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
  87. package/dist/cjs/fields/HTML/index.native.js.map +1 -1
  88. package/dist/cjs/fields/Heading/index.native.js.map +1 -1
  89. package/dist/cjs/fields/Icon/index.native.js.map +1 -1
  90. package/dist/cjs/fields/Link.native.js.map +1 -1
  91. package/dist/cjs/fields/LinkTree.native.js.map +1 -1
  92. package/dist/cjs/fields/LongText/index.native.js.map +1 -1
  93. package/dist/cjs/fields/MentionInput/index.cjs +4 -1
  94. package/dist/cjs/fields/MentionInput/index.native.js +4 -1
  95. package/dist/cjs/fields/MentionInput/index.native.js.map +1 -1
  96. package/dist/cjs/fields/Numeric/index.native.js.map +1 -1
  97. package/dist/cjs/fields/OTPInput/index.native.js.map +1 -1
  98. package/dist/cjs/fields/Password/index.native.js.map +1 -1
  99. package/dist/cjs/fields/Phone/index.native.js.map +1 -1
  100. package/dist/cjs/fields/Progress/index.native.js.map +1 -1
  101. package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
  102. package/dist/cjs/fields/Rating/index.native.js.map +1 -1
  103. package/dist/cjs/fields/ReadOnly/index.native.js.map +1 -1
  104. package/dist/cjs/fields/Select/index.native.js.map +1 -1
  105. package/dist/cjs/fields/Signature/index.native.js.map +1 -1
  106. package/dist/cjs/fields/Slider/index.native.js.map +1 -1
  107. package/dist/cjs/fields/SmallText/index.native.js.map +1 -1
  108. package/dist/cjs/fields/Switch/index.native.js.map +1 -1
  109. package/dist/cjs/fields/TableMultiSelect.native.js.map +1 -1
  110. package/dist/cjs/fields/TextEditor/types.native.js.map +1 -1
  111. package/dist/cjs/fields/TimePicker/index.native.js.map +1 -1
  112. package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
  113. package/dist/cjs/fields/shared/EditorPreviewPanes.native.js.map +1 -1
  114. package/dist/cjs/filterFields.native.js.map +1 -1
  115. package/dist/cjs/formEngine/createPrefill.native.js.map +1 -1
  116. package/dist/cjs/formEngine/dirtyTracking.native.js.map +1 -1
  117. package/dist/cjs/formEngine/doctypeMeta.native.js.map +1 -1
  118. package/dist/cjs/formEngine/fetchFrom.native.js.map +1 -1
  119. package/dist/cjs/formEngine/fieldPermissions.native.js.map +1 -1
  120. package/dist/cjs/formEngine/formActions.native.js.map +1 -1
  121. package/dist/cjs/formEngine/formLayout.native.js.map +1 -1
  122. package/dist/cjs/formEngine/index.native.js.map +1 -1
  123. package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -1
  124. package/dist/cjs/frappeEventColor.native.js.map +1 -1
  125. package/dist/cjs/frappeStoryTarget.native.js.map +1 -1
  126. package/dist/cjs/frappeUsers.native.js.map +1 -1
  127. package/dist/cjs/index.cjs +4 -0
  128. package/dist/cjs/index.native.js +38 -34
  129. package/dist/cjs/index.native.js.map +1 -1
  130. package/dist/cjs/liveErrors.native.js.map +1 -1
  131. package/dist/cjs/resolveControl.native.js.map +1 -1
  132. package/dist/cjs/schemaForm/SchemaWizard.native.js.map +1 -1
  133. package/dist/cjs/schemaForm/applyFieldOrder.native.js.map +1 -1
  134. package/dist/cjs/schemaForm/index.cjs +10 -9
  135. package/dist/cjs/schemaForm/index.native.js +11 -10
  136. package/dist/cjs/schemaForm/index.native.js.map +1 -1
  137. package/dist/cjs/shared/devWarn.cjs +40 -0
  138. package/dist/cjs/shared/devWarn.native.js +45 -0
  139. package/dist/cjs/shared/devWarn.native.js.map +1 -0
  140. package/dist/cjs/shared/useKeyboardInset.native.js.map +1 -1
  141. package/dist/cjs/syncError.native.js.map +1 -1
  142. package/dist/cjs/useFrappeAggregate.cjs +15 -8
  143. package/dist/cjs/useFrappeAggregate.native.js +15 -9
  144. package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
  145. package/dist/cjs/useFrappeFacets.cjs +10 -9
  146. package/dist/cjs/useFrappeFacets.native.js +10 -10
  147. package/dist/cjs/useFrappeFacets.native.js.map +1 -1
  148. package/dist/cjs/useFrappePagination.native.js.map +1 -1
  149. package/dist/cjs/useFrappeRows.cjs +113 -0
  150. package/dist/cjs/useFrappeRows.native.js +129 -0
  151. package/dist/cjs/useFrappeRows.native.js.map +1 -0
  152. package/dist/esm/ChartCard.native.js.map +1 -1
  153. package/dist/esm/FileBrowser/AttachLibrary.native.js.map +1 -1
  154. package/dist/esm/FileBrowser/folderTree.native.js.map +1 -1
  155. package/dist/esm/FileBrowser/index.native.js.map +1 -1
  156. package/dist/esm/FileBrowser/types.native.js.map +1 -1
  157. package/dist/esm/FileBrowser/viewPersistence.native.js.map +1 -1
  158. package/dist/esm/FormSidebar.native.js.map +1 -1
  159. package/dist/esm/FrappeAssignment.native.js.map +1 -1
  160. package/dist/esm/FrappeCalendar.mjs +20 -17
  161. package/dist/esm/FrappeCalendar.mjs.map +1 -1
  162. package/dist/esm/FrappeCalendar.native.js +22 -21
  163. package/dist/esm/FrappeCalendar.native.js.map +1 -1
  164. package/dist/esm/FrappeChartCard.native.js.map +1 -1
  165. package/dist/esm/FrappeConnectionIndicator.native.js.map +1 -1
  166. package/dist/esm/FrappeDashboard.native.js.map +1 -1
  167. package/dist/esm/FrappeForm.mjs +1 -1
  168. package/dist/esm/FrappeForm.mjs.map +1 -1
  169. package/dist/esm/FrappeForm.native.js +1 -1
  170. package/dist/esm/FrappeForm.native.js.map +1 -1
  171. package/dist/esm/FrappeGantt.mjs +27 -36
  172. package/dist/esm/FrappeGantt.mjs.map +1 -1
  173. package/dist/esm/FrappeGantt.native.js +29 -40
  174. package/dist/esm/FrappeGantt.native.js.map +1 -1
  175. package/dist/esm/FrappeImageView.mjs +5 -3
  176. package/dist/esm/FrappeImageView.mjs.map +1 -1
  177. package/dist/esm/FrappeImageView.native.js +9 -5
  178. package/dist/esm/FrappeImageView.native.js.map +1 -1
  179. package/dist/esm/FrappeKPICard.mjs +27 -2
  180. package/dist/esm/FrappeKPICard.mjs.map +1 -1
  181. package/dist/esm/FrappeKPICard.native.js +27 -2
  182. package/dist/esm/FrappeKPICard.native.js.map +1 -1
  183. package/dist/esm/FrappeKanban.mjs +16 -17
  184. package/dist/esm/FrappeKanban.mjs.map +1 -1
  185. package/dist/esm/FrappeKanban.native.js +18 -21
  186. package/dist/esm/FrappeKanban.native.js.map +1 -1
  187. package/dist/esm/FrappeLikedBy.mjs +1 -1
  188. package/dist/esm/FrappeLikedBy.mjs.map +1 -1
  189. package/dist/esm/FrappeLikedBy.native.js +1 -1
  190. package/dist/esm/FrappeLikedBy.native.js.map +1 -1
  191. package/dist/esm/FrappeList.mjs +2 -2
  192. package/dist/esm/FrappeList.mjs.map +1 -1
  193. package/dist/esm/FrappeList.native.js +2 -2
  194. package/dist/esm/FrappeList.native.js.map +1 -1
  195. package/dist/esm/FrappeListView.native.js.map +1 -1
  196. package/dist/esm/FrappeProvider.mjs.map +1 -1
  197. package/dist/esm/FrappeProvider.native.js.map +1 -1
  198. package/dist/esm/FrappeReconnectIndicator.native.js.map +1 -1
  199. package/dist/esm/FrappeReportBuilder.mjs +2 -2
  200. package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
  201. package/dist/esm/FrappeReportBuilder.native.js +2 -2
  202. package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
  203. package/dist/esm/FrappeReportView.native.js.map +1 -1
  204. package/dist/esm/FrappeTable.mjs +36 -39
  205. package/dist/esm/FrappeTable.mjs.map +1 -1
  206. package/dist/esm/FrappeTable.native.js +39 -40
  207. package/dist/esm/FrappeTable.native.js.map +1 -1
  208. package/dist/esm/FrappeTreeView.mjs +89 -55
  209. package/dist/esm/FrappeTreeView.mjs.map +1 -1
  210. package/dist/esm/FrappeTreeView.native.js +133 -55
  211. package/dist/esm/FrappeTreeView.native.js.map +1 -1
  212. package/dist/esm/SyncErrorNotice.native.js.map +1 -1
  213. package/dist/esm/commentActions.mjs +1 -1
  214. package/dist/esm/commentActions.mjs.map +1 -1
  215. package/dist/esm/commentActions.native.js +1 -1
  216. package/dist/esm/commentActions.native.js.map +1 -1
  217. package/dist/esm/components/attachments.native.js.map +1 -1
  218. package/dist/esm/components/tags.native.js.map +1 -1
  219. package/dist/esm/components/timeline.mjs +1 -1
  220. package/dist/esm/components/timeline.mjs.map +1 -1
  221. package/dist/esm/components/timeline.native.js +1 -1
  222. package/dist/esm/components/timeline.native.js.map +1 -1
  223. package/dist/esm/dependsOn.native.js.map +1 -1
  224. package/dist/esm/desk/DeskFormPage.native.js.map +1 -1
  225. package/dist/esm/desk/DeskFormSidebar.native.js.map +1 -1
  226. package/dist/esm/desk/DeskListPage.native.js.map +1 -1
  227. package/dist/esm/desk/DeskShell.mjs +2 -0
  228. package/dist/esm/desk/DeskShell.mjs.map +1 -1
  229. package/dist/esm/desk/DeskShell.native.js +2 -0
  230. package/dist/esm/desk/DeskShell.native.js.map +1 -1
  231. package/dist/esm/desk/DeskSidebar.native.js.map +1 -1
  232. package/dist/esm/desk/awesomebar.mjs +1 -1
  233. package/dist/esm/desk/awesomebar.mjs.map +1 -1
  234. package/dist/esm/desk/awesomebar.native.js +1 -1
  235. package/dist/esm/desk/awesomebar.native.js.map +1 -1
  236. package/dist/esm/fieldOverrides.native.js.map +1 -1
  237. package/dist/esm/fieldRegistry.native.js.map +1 -1
  238. package/dist/esm/fields/Barcode/index.native.js.map +1 -1
  239. package/dist/esm/fields/Check/index.native.js.map +1 -1
  240. package/dist/esm/fields/CodeEditor/index.native.js.map +1 -1
  241. package/dist/esm/fields/Color/index.native.js.map +1 -1
  242. package/dist/esm/fields/Data/index.native.js.map +1 -1
  243. package/dist/esm/fields/Date/sheetKeyboard.native.js.map +1 -1
  244. package/dist/esm/fields/Date/types.native.js.map +1 -1
  245. package/dist/esm/fields/Date/wire.native.js.map +1 -1
  246. package/dist/esm/fields/DateRange/index.native.js.map +1 -1
  247. package/dist/esm/fields/Duration/index.native.js.map +1 -1
  248. package/dist/esm/fields/DynamicLink.native.js.map +1 -1
  249. package/dist/esm/fields/FileUpload.native.js.map +1 -1
  250. package/dist/esm/fields/FileUploadField/index.native.js.map +1 -1
  251. package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
  252. package/dist/esm/fields/HTML/index.native.js.map +1 -1
  253. package/dist/esm/fields/Heading/index.native.js.map +1 -1
  254. package/dist/esm/fields/Icon/index.native.js.map +1 -1
  255. package/dist/esm/fields/Link.native.js.map +1 -1
  256. package/dist/esm/fields/LinkTree.native.js.map +1 -1
  257. package/dist/esm/fields/LongText/index.native.js.map +1 -1
  258. package/dist/esm/fields/MentionInput/index.mjs +4 -1
  259. package/dist/esm/fields/MentionInput/index.mjs.map +1 -1
  260. package/dist/esm/fields/MentionInput/index.native.js +4 -1
  261. package/dist/esm/fields/MentionInput/index.native.js.map +1 -1
  262. package/dist/esm/fields/Numeric/index.native.js.map +1 -1
  263. package/dist/esm/fields/OTPInput/index.native.js.map +1 -1
  264. package/dist/esm/fields/Password/index.native.js.map +1 -1
  265. package/dist/esm/fields/Phone/index.native.js.map +1 -1
  266. package/dist/esm/fields/Progress/index.native.js.map +1 -1
  267. package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
  268. package/dist/esm/fields/Rating/index.native.js.map +1 -1
  269. package/dist/esm/fields/ReadOnly/index.native.js.map +1 -1
  270. package/dist/esm/fields/Select/index.native.js.map +1 -1
  271. package/dist/esm/fields/Signature/index.native.js.map +1 -1
  272. package/dist/esm/fields/Slider/index.native.js.map +1 -1
  273. package/dist/esm/fields/SmallText/index.native.js.map +1 -1
  274. package/dist/esm/fields/Switch/index.native.js.map +1 -1
  275. package/dist/esm/fields/TableMultiSelect.native.js.map +1 -1
  276. package/dist/esm/fields/TextEditor/types.native.js.map +1 -1
  277. package/dist/esm/fields/TimePicker/index.native.js.map +1 -1
  278. package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
  279. package/dist/esm/fields/shared/EditorPreviewPanes.native.js.map +1 -1
  280. package/dist/esm/filterFields.native.js.map +1 -1
  281. package/dist/esm/formEngine/createPrefill.native.js.map +1 -1
  282. package/dist/esm/formEngine/dirtyTracking.native.js.map +1 -1
  283. package/dist/esm/formEngine/doctypeMeta.native.js.map +1 -1
  284. package/dist/esm/formEngine/fetchFrom.native.js.map +1 -1
  285. package/dist/esm/formEngine/fieldPermissions.native.js.map +1 -1
  286. package/dist/esm/formEngine/formActions.native.js.map +1 -1
  287. package/dist/esm/formEngine/formLayout.native.js.map +1 -1
  288. package/dist/esm/formEngine/index.native.js.map +1 -1
  289. package/dist/esm/formEngine/useFormEngine.native.js.map +1 -1
  290. package/dist/esm/frappeEventColor.native.js.map +1 -1
  291. package/dist/esm/frappeStoryTarget.native.js.map +1 -1
  292. package/dist/esm/frappeUsers.native.js.map +1 -1
  293. package/dist/esm/index.mjs +2 -1
  294. package/dist/esm/index.mjs.map +1 -1
  295. package/dist/esm/index.native.js +2 -1
  296. package/dist/esm/index.native.js.map +1 -1
  297. package/dist/esm/liveErrors.native.js.map +1 -1
  298. package/dist/esm/resolveControl.native.js.map +1 -1
  299. package/dist/esm/schemaForm/SchemaWizard.native.js.map +1 -1
  300. package/dist/esm/schemaForm/applyFieldOrder.native.js.map +1 -1
  301. package/dist/esm/schemaForm/index.mjs +8 -7
  302. package/dist/esm/schemaForm/index.mjs.map +1 -1
  303. package/dist/esm/schemaForm/index.native.js +8 -7
  304. package/dist/esm/schemaForm/index.native.js.map +1 -1
  305. package/dist/esm/shared/devWarn.mjs +14 -0
  306. package/dist/esm/shared/devWarn.mjs.map +1 -0
  307. package/dist/esm/shared/devWarn.native.js +16 -0
  308. package/dist/esm/shared/devWarn.native.js.map +1 -0
  309. package/dist/esm/shared/useKeyboardInset.native.js.map +1 -1
  310. package/dist/esm/syncError.native.js.map +1 -1
  311. package/dist/esm/useFrappeAggregate.mjs +15 -8
  312. package/dist/esm/useFrappeAggregate.mjs.map +1 -1
  313. package/dist/esm/useFrappeAggregate.native.js +15 -9
  314. package/dist/esm/useFrappeAggregate.native.js.map +1 -1
  315. package/dist/esm/useFrappeFacets.mjs +10 -9
  316. package/dist/esm/useFrappeFacets.mjs.map +1 -1
  317. package/dist/esm/useFrappeFacets.native.js +10 -10
  318. package/dist/esm/useFrappeFacets.native.js.map +1 -1
  319. package/dist/esm/useFrappePagination.native.js.map +1 -1
  320. package/dist/esm/useFrappeRows.mjs +86 -0
  321. package/dist/esm/useFrappeRows.mjs.map +1 -0
  322. package/dist/esm/useFrappeRows.native.js +99 -0
  323. package/dist/esm/useFrappeRows.native.js.map +1 -0
  324. package/dist/jsx/FrappeCalendar.mjs +20 -17
  325. package/dist/jsx/FrappeCalendar.mjs.map +1 -1
  326. package/dist/jsx/FrappeCalendar.native.js +22 -21
  327. package/dist/jsx/FrappeCalendar.native.js.map +1 -1
  328. package/dist/jsx/FrappeForm.mjs +1 -1
  329. package/dist/jsx/FrappeForm.mjs.map +1 -1
  330. package/dist/jsx/FrappeForm.native.js +1 -1
  331. package/dist/jsx/FrappeForm.native.js.map +1 -1
  332. package/dist/jsx/FrappeGantt.mjs +27 -36
  333. package/dist/jsx/FrappeGantt.mjs.map +1 -1
  334. package/dist/jsx/FrappeGantt.native.js +27 -38
  335. package/dist/jsx/FrappeGantt.native.js.map +1 -1
  336. package/dist/jsx/FrappeImageView.mjs +5 -3
  337. package/dist/jsx/FrappeImageView.mjs.map +1 -1
  338. package/dist/jsx/FrappeImageView.native.js +8 -4
  339. package/dist/jsx/FrappeImageView.native.js.map +1 -1
  340. package/dist/jsx/FrappeKPICard.mjs +27 -2
  341. package/dist/jsx/FrappeKPICard.mjs.map +1 -1
  342. package/dist/jsx/FrappeKPICard.native.js +26 -1
  343. package/dist/jsx/FrappeKPICard.native.js.map +1 -1
  344. package/dist/jsx/FrappeKanban.mjs +16 -17
  345. package/dist/jsx/FrappeKanban.mjs.map +1 -1
  346. package/dist/jsx/FrappeKanban.native.js +18 -21
  347. package/dist/jsx/FrappeKanban.native.js.map +1 -1
  348. package/dist/jsx/FrappeLikedBy.mjs +1 -1
  349. package/dist/jsx/FrappeLikedBy.mjs.map +1 -1
  350. package/dist/jsx/FrappeLikedBy.native.js +1 -1
  351. package/dist/jsx/FrappeLikedBy.native.js.map +1 -1
  352. package/dist/jsx/FrappeList.mjs +2 -2
  353. package/dist/jsx/FrappeList.mjs.map +1 -1
  354. package/dist/jsx/FrappeList.native.js +2 -2
  355. package/dist/jsx/FrappeList.native.js.map +1 -1
  356. package/dist/jsx/FrappeProvider.mjs.map +1 -1
  357. package/dist/jsx/FrappeProvider.native.js.map +1 -1
  358. package/dist/jsx/FrappeReportBuilder.mjs +2 -2
  359. package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
  360. package/dist/jsx/FrappeReportBuilder.native.js +2 -2
  361. package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
  362. package/dist/jsx/FrappeTable.mjs +36 -39
  363. package/dist/jsx/FrappeTable.mjs.map +1 -1
  364. package/dist/jsx/FrappeTable.native.js +38 -39
  365. package/dist/jsx/FrappeTable.native.js.map +1 -1
  366. package/dist/jsx/FrappeTreeView.mjs +89 -55
  367. package/dist/jsx/FrappeTreeView.mjs.map +1 -1
  368. package/dist/jsx/FrappeTreeView.native.js +129 -50
  369. package/dist/jsx/FrappeTreeView.native.js.map +1 -1
  370. package/dist/jsx/commentActions.mjs +1 -1
  371. package/dist/jsx/commentActions.mjs.map +1 -1
  372. package/dist/jsx/commentActions.native.js +1 -1
  373. package/dist/jsx/commentActions.native.js.map +1 -1
  374. package/dist/jsx/components/timeline.mjs +1 -1
  375. package/dist/jsx/components/timeline.mjs.map +1 -1
  376. package/dist/jsx/components/timeline.native.js +1 -1
  377. package/dist/jsx/desk/DeskShell.mjs +2 -0
  378. package/dist/jsx/desk/DeskShell.mjs.map +1 -1
  379. package/dist/jsx/desk/DeskShell.native.js +2 -0
  380. package/dist/jsx/desk/DeskShell.native.js.map +1 -1
  381. package/dist/jsx/desk/awesomebar.mjs +1 -1
  382. package/dist/jsx/desk/awesomebar.mjs.map +1 -1
  383. package/dist/jsx/desk/awesomebar.native.js +1 -1
  384. package/dist/jsx/desk/awesomebar.native.js.map +1 -1
  385. package/dist/jsx/fields/MentionInput/index.mjs +4 -1
  386. package/dist/jsx/fields/MentionInput/index.mjs.map +1 -1
  387. package/dist/jsx/fields/MentionInput/index.native.js +4 -1
  388. package/dist/jsx/fields/MentionInput/index.native.js.map +1 -1
  389. package/dist/jsx/index.js +2 -1
  390. package/dist/jsx/index.js.map +1 -1
  391. package/dist/jsx/index.mjs +2 -1
  392. package/dist/jsx/index.mjs.map +1 -1
  393. package/dist/jsx/index.native.js +4 -0
  394. package/dist/jsx/index.native.js.map +1 -1
  395. package/dist/jsx/schemaForm/index.mjs +8 -7
  396. package/dist/jsx/schemaForm/index.mjs.map +1 -1
  397. package/dist/jsx/schemaForm/index.native.js +10 -9
  398. package/dist/jsx/schemaForm/index.native.js.map +1 -1
  399. package/dist/jsx/shared/devWarn.mjs +14 -0
  400. package/dist/jsx/shared/devWarn.mjs.map +1 -0
  401. package/dist/jsx/shared/devWarn.native.js +45 -0
  402. package/dist/jsx/shared/devWarn.native.js.map +1 -0
  403. package/dist/jsx/useFrappeAggregate.mjs +15 -8
  404. package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
  405. package/dist/jsx/useFrappeAggregate.native.js +15 -9
  406. package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
  407. package/dist/jsx/useFrappeFacets.mjs +10 -9
  408. package/dist/jsx/useFrappeFacets.mjs.map +1 -1
  409. package/dist/jsx/useFrappeFacets.native.js +10 -10
  410. package/dist/jsx/useFrappeFacets.native.js.map +1 -1
  411. package/dist/jsx/useFrappeRows.mjs +86 -0
  412. package/dist/jsx/useFrappeRows.mjs.map +1 -0
  413. package/dist/jsx/useFrappeRows.native.js +129 -0
  414. package/dist/jsx/useFrappeRows.native.js.map +1 -0
  415. package/package.json +17 -16
  416. package/src/FileBrowser/FileBrowser.spec.tsx +11 -1
  417. package/src/FrappeCalendar.tsx +25 -22
  418. package/src/FrappeForm.tsx +7 -3
  419. package/src/FrappeGantt.tsx +29 -39
  420. package/src/FrappeImageView.tsx +5 -3
  421. package/src/FrappeKPICard.spec.tsx +128 -0
  422. package/src/FrappeKPICard.tsx +44 -4
  423. package/src/FrappeKanban.tsx +24 -17
  424. package/src/FrappeLikedBy.tsx +1 -1
  425. package/src/FrappeList.tsx +3 -2
  426. package/src/FrappeProvider.fixturesOnly.spec.tsx +129 -0
  427. package/src/FrappeProvider.tsx +16 -6
  428. package/src/FrappeReportBuilder.tsx +4 -2
  429. package/src/FrappeTable.autoHeight.spec.tsx +142 -0
  430. package/src/FrappeTable.rowAccumulator.spec.tsx +110 -0
  431. package/src/FrappeTable.tsx +75 -48
  432. package/src/FrappeTreeView.stories.tsx +59 -0
  433. package/src/FrappeTreeView.tsx +166 -56
  434. package/src/commentActions.ts +1 -1
  435. package/src/components/timeline.tsx +1 -1
  436. package/src/desk/DeskShell.tsx +2 -0
  437. package/src/desk/awesomebar.tsx +5 -2
  438. package/src/fields/CodeEditor/CodeEditor.spec.tsx +10 -9
  439. package/src/fields/Date/typeParity.spec.tsx +19 -9
  440. package/src/fields/Duration/Duration.spec.tsx +73 -0
  441. package/src/fields/MentionInput/MentionInput.spec.tsx +110 -0
  442. package/src/fields/MentionInput/index.tsx +4 -1
  443. package/src/fields/Password/index.tsx +1 -1
  444. package/src/fields/Progress/Progress.spec.tsx +9 -0
  445. package/src/fields/RadioGroup/RadioGroup.spec.tsx +93 -0
  446. package/src/fields/Signature/Signature.spec.tsx +105 -0
  447. package/src/fields/Slider/Slider.spec.tsx +73 -0
  448. package/src/fields/Switch/Switch.spec.tsx +65 -0
  449. package/src/fields/TextEditor/TextEditor.spec.tsx +2 -3
  450. package/src/fields/TextEditor/TextEditor.stories.tsx +31 -1
  451. package/src/fields/ToggleGroup/ToggleGroup.spec.tsx +110 -0
  452. package/src/frappeViews.designLaw.spec.ts +11 -7
  453. package/src/index.ts +13 -0
  454. package/src/schemaForm/index.tsx +6 -7
  455. package/src/shared/devWarn.ts +42 -0
  456. package/src/useFrappeAggregate.ts +17 -11
  457. package/src/useFrappeFacets.ts +17 -10
  458. package/src/useFrappeRows.spec.tsx +176 -0
  459. package/src/useFrappeRows.ts +293 -0
  460. package/types/FrappeCalendar.d.ts.map +1 -1
  461. package/types/FrappeForm.d.ts +6 -2
  462. package/types/FrappeForm.d.ts.map +1 -1
  463. package/types/FrappeGantt.d.ts.map +1 -1
  464. package/types/FrappeImageView.d.ts.map +1 -1
  465. package/types/FrappeKPICard.d.ts +9 -1
  466. package/types/FrappeKPICard.d.ts.map +1 -1
  467. package/types/FrappeKanban.d.ts.map +1 -1
  468. package/types/FrappeList.d.ts.map +1 -1
  469. package/types/FrappeProvider.d.ts +16 -6
  470. package/types/FrappeProvider.d.ts.map +1 -1
  471. package/types/FrappeReportBuilder.d.ts.map +1 -1
  472. package/types/FrappeTable.d.ts +20 -1
  473. package/types/FrappeTable.d.ts.map +1 -1
  474. package/types/FrappeTreeView.d.ts +38 -1
  475. package/types/FrappeTreeView.d.ts.map +1 -1
  476. package/types/desk/DeskShell.d.ts.map +1 -1
  477. package/types/desk/awesomebar.d.ts.map +1 -1
  478. package/types/fields/MentionInput/index.d.ts.map +1 -1
  479. package/types/fields/Password/index.d.ts +1 -1
  480. package/types/fields/Password/index.d.ts.map +1 -1
  481. package/types/index.d.ts +1 -0
  482. package/types/index.d.ts.map +1 -1
  483. package/types/schemaForm/index.d.ts.map +1 -1
  484. package/types/shared/devWarn.d.ts +23 -0
  485. package/types/shared/devWarn.d.ts.map +1 -0
  486. package/types/useFrappeAggregate.d.ts +2 -1
  487. package/types/useFrappeAggregate.d.ts.map +1 -1
  488. package/types/useFrappeFacets.d.ts +2 -1
  489. package/types/useFrappeFacets.d.ts.map +1 -1
  490. package/types/useFrappeRows.d.ts +186 -0
  491. package/types/useFrappeRows.d.ts.map +1 -0
@@ -75,10 +75,23 @@ function regionMeta(name: string, isTree: boolean) {
75
75
  };
76
76
  }
77
77
 
78
+ /** A chart of accounts: no parent field, no is_tree, the name is the path. */
79
+ function accountMeta(name: string) {
80
+ return {
81
+ name,
82
+ module: "QA",
83
+ title_field: "name",
84
+ is_tree: 0,
85
+ fields: [{ fieldname: "balance", fieldtype: "Currency", label: "Balance", in_list_view: 1 }],
86
+ permissions: [{ role: "All", read: 1 }],
87
+ };
88
+ }
89
+
78
90
  function handleMetaRequest(url: URL): Response | null {
79
91
  const metaMatch = url.pathname.match(/^\/api\/resource\/DocType\/(.+)$/);
80
92
  if (!metaMatch) return null;
81
93
  const name = decodeURIComponent(metaMatch[1]);
94
+ if (name === "QaAccount") return jsonResponse({ data: accountMeta(name) });
82
95
  return jsonResponse({ data: regionMeta(name, name !== "QaFlatDoc") });
83
96
  }
84
97
 
@@ -143,6 +156,52 @@ export const NotATree = () => (
143
156
  </FrappeProvider>
144
157
  );
145
158
 
159
+ interface Account {
160
+ name: string;
161
+ doctype: string;
162
+ balance: string;
163
+ }
164
+
165
+ /**
166
+ * Seven rows, and every one of them is a leaf. "Assets", "Assets:Personal"
167
+ * and "Assets:Personal:Bank" are not documents at all.
168
+ */
169
+ const accountDocs: Account[] = [
170
+ { name: "Assets:Personal:Bank:Checking", doctype: "QaAccount", balance: "4,120.55" },
171
+ { name: "Assets:Personal:Bank:Savings", doctype: "QaAccount", balance: "18,300.00" },
172
+ { name: "Assets:Personal:Cash", doctype: "QaAccount", balance: "260.00" },
173
+ { name: "Expenses:Groceries", doctype: "QaAccount", balance: "612.40" },
174
+ { name: "Expenses:Utilities:Power", doctype: "QaAccount", balance: "148.90" },
175
+ { name: "Income:Salary", doctype: "QaAccount", balance: "9,000.00" },
176
+ { name: "Liabilities:Credit Card", doctype: "QaAccount", balance: "-732.10" },
177
+ ];
178
+
179
+ /** Colon-path doctype: is_tree is 0, there is no parent field, tree renders. */
180
+ export const ColonPath = () => (
181
+ <FrappeProvider
182
+ baseURL={MOCK_HOST}
183
+ fixtures={new InMemoryFixtureProvider({ QaAccount: accountDocs })}
184
+ >
185
+ <FrappeTreeView
186
+ doctype="QaAccount"
187
+ pathSeparator=":"
188
+ pathField="name"
189
+ valueField="balance"
190
+ expandByDefault
191
+ onNodeClick={action("onNodeClick")}
192
+ onPathSelect={action("onPathSelect")}
193
+ />
194
+ </FrappeProvider>
195
+ );
196
+ ColonPath.parameters = {
197
+ docs: {
198
+ description: {
199
+ story:
200
+ "The account IS its path (Assets:Personal:Bank:Checking). Meta reports is_tree 0 and the doctype has no parent field, so the parent-field path would paint the unsupported state instead. pathSeparator synthesises the branches client-side from each row's own key. A synthesised branch carries no document: clicking it fires onPathSelect with the path and no doc, and onNodeClick stays silent. The balances hang off the leaves, which are the only real rows.",
201
+ },
202
+ },
203
+ };
204
+
146
205
  /** Tree doctype with zero root nodes: empty message. */
147
206
  export const EmptyTree = () => (
148
207
  <FrappeProvider baseURL={MOCK_HOST} fixtures={new InMemoryFixtureProvider({ QaRegionEmpty: [] })}>
@@ -1,14 +1,14 @@
1
- import { useDocTypeMeta, useFrappeCollection, useLiveQuery } from "@multiplatform.one/frappe";
2
- import { AsyncBoundary, TreeView, type TreeNode } from "@multiplatform.one/components";
1
+ import { useDocTypeMeta } from "@multiplatform.one/frappe";
2
+ import { AsyncBoundary, EmptyState, TreeView, type TreeNode } from "@multiplatform.one/components";
3
3
  import { TreeStructureIcon } from "@phosphor-icons/react";
4
- import React, { useCallback, useEffect, useMemo, useState } from "react";
4
+ import React, { useCallback, useMemo, useState } from "react";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import { Text, YStack, type YStackProps } from "tamagui";
7
- import { useResolvedKnobs } from "@multiplatform.one/theme";
7
+ import { useGlyphColor, useResolvedKnobs } from "@multiplatform.one/theme";
8
8
  import { formCommonColors } from "@multiplatform.one/forms";
9
9
  import { useFrappeConfig } from "./FrappeProvider";
10
10
  import { FrappeReconnectIndicator } from "./FrappeReconnectIndicator";
11
- import { toLiveLoadError } from "./liveErrors";
11
+ import { useFrappeRows } from "./useFrappeRows";
12
12
 
13
13
  export interface FrappeTreeViewProps<TDoc extends { name: string; doctype: string }> extends Omit<
14
14
  YStackProps,
@@ -16,11 +16,32 @@ export interface FrappeTreeViewProps<TDoc extends { name: string; doctype: strin
16
16
  > {
17
17
  doctype: string;
18
18
  parentField?: string;
19
+ /**
20
+ * Separator that splits {@link FrappeTreeViewProps.pathField} into levels,
21
+ * e.g. ":" for an account named `Assets:Personal:Bank:Checking`. Setting it
22
+ * switches the tree to path mode: the branches between root and leaf are
23
+ * synthesised from the segments, `parentField` is neither read nor fetched,
24
+ * and the `is_tree` gate is skipped. A doctype whose hierarchy lives in the
25
+ * row's own key has no parent field and the server will never report
26
+ * `is_tree`.
27
+ */
28
+ pathSeparator?: string;
29
+ /**
30
+ * Row field holding the path. Path mode only; defaults to `name`. Path mode
31
+ * fetches `name`, this field, `valueField` and whatever `fields` adds, so a
32
+ * caller who needs more on the `onNodeClick` document asks for it there.
33
+ */
34
+ pathField?: string;
19
35
  titleField?: string;
20
36
  valueField?: string;
21
37
  fields?: readonly (keyof TDoc & string)[];
22
38
  filters?: Partial<TDoc> | Array<[keyof TDoc & string, string, any]>;
23
39
  onNodeClick?: (doc: Partial<TDoc>) => void;
40
+ /**
41
+ * Path-mode selection, fired for synthesised branches as well as documents.
42
+ * `doc` is present only when a document actually sits at that path.
43
+ */
44
+ onPathSelect?: (path: string, doc?: Partial<TDoc>) => void;
24
45
  /** Reserved for future DnD reparenting; not wired yet. */
25
46
  enableDragDrop?: boolean;
26
47
  /** Expand every branch on first paint (passed through to TreeView). */
@@ -88,20 +109,94 @@ export function buildTreeNodes<TDoc extends { name: string }>(
88
109
  return sortDocs(roots).map(buildNode);
89
110
  }
90
111
 
112
+ /** One level of the path forest, before it becomes TreeNodes. */
113
+ interface PathBucket<TDoc> {
114
+ path: string;
115
+ segment: string;
116
+ doc?: TDoc;
117
+ children: Map<string, PathBucket<TDoc>>;
118
+ }
119
+
120
+ /**
121
+ * Build a TreeNode forest from paths held on the rows themselves, e.g.
122
+ * `Assets:Personal:Bank:Checking` split on ":".
123
+ *
124
+ * The branches between root and leaf are not rows, so they are synthesised:
125
+ * a synthesised node carries no `data` and its `id` is the path prefix it
126
+ * stands for. A row whose path value is empty falls back to a single segment
127
+ * of its own name, so nothing is silently dropped.
128
+ */
129
+ export function buildPathTreeNodes<TDoc extends { name: string }>(
130
+ docs: TDoc[],
131
+ pathField: string,
132
+ separator: string,
133
+ ): TreeNode<Record<string, unknown>>[] {
134
+ const roots = new Map<string, PathBucket<TDoc>>();
135
+
136
+ const splitPath = (doc: TDoc): string[] => {
137
+ const raw = (doc as Record<string, unknown>)[pathField];
138
+ const value = raw == null ? "" : String(raw);
139
+ // An empty separator would split the value into single characters.
140
+ const segments = separator ? value.split(separator) : [value];
141
+ const cleaned = segments.map((segment) => segment.trim()).filter((segment) => segment !== "");
142
+ return cleaned.length > 0 ? cleaned : [doc.name];
143
+ };
144
+
145
+ for (const doc of docs) {
146
+ let level = roots;
147
+ let prefix = "";
148
+ let bucket: PathBucket<TDoc> | undefined;
149
+ for (const segment of splitPath(doc)) {
150
+ prefix = prefix ? `${prefix}${separator}${segment}` : segment;
151
+ bucket = level.get(segment);
152
+ if (!bucket) {
153
+ bucket = { path: prefix, segment, children: new Map() };
154
+ level.set(segment, bucket);
155
+ }
156
+ level = bucket.children;
157
+ }
158
+ // Two rows on one path: the first keeps the node rather than the last
159
+ // row the live query happened to deliver.
160
+ if (bucket && !bucket.doc) bucket.doc = doc;
161
+ }
162
+
163
+ const toNodes = (level: Map<string, PathBucket<TDoc>>): TreeNode<Record<string, unknown>>[] =>
164
+ Array.from(level.values())
165
+ .sort((a, b) => a.segment.localeCompare(b.segment))
166
+ .map((bucket) => {
167
+ const children = toNodes(bucket.children);
168
+ return {
169
+ id: bucket.path,
170
+ label: bucket.segment,
171
+ data: bucket.doc as unknown as Record<string, unknown> | undefined,
172
+ children: children.length > 0 ? children : undefined,
173
+ };
174
+ });
175
+
176
+ return toNodes(roots);
177
+ }
178
+
91
179
  /**
92
180
  * Thin Frappe wrapper around the generic TreeView component.
93
181
  *
94
182
  * Fetches the full DocType list via `useFrappeCollection` / `useLiveQuery`
95
183
  * (realtime-backed), then builds a `TreeNode` forest from the parent field.
184
+ *
185
+ * Pass `pathSeparator` instead when the hierarchy lives in the row's own key
186
+ * (`Assets:Personal:Bank:Checking`). The branches are synthesised client-side,
187
+ * so no parent field and no `is_tree` are needed.
96
188
  */
97
189
  export function FrappeTreeView<TDoc extends { name: string; doctype: string }>({
98
190
  doctype,
99
191
  parentField: parentFieldProp,
192
+ pathSeparator,
193
+ pathField,
100
194
  titleField: titleFieldProp,
101
195
  valueField,
102
196
  fields,
103
197
  filters,
104
198
  onNodeClick,
199
+ onPathSelect,
105
200
  enableDragDrop: _enableDragDrop = false,
106
201
  expandByDefault = false,
107
202
  display = "default",
@@ -116,6 +211,7 @@ export function FrappeTreeView<TDoc extends { name: string; doctype: string }>({
116
211
 
117
212
  const config = useFrappeConfig();
118
213
  const { knobProps } = useResolvedKnobs();
214
+ const emptyIconColor = useGlyphColor(formCommonColors.muted);
119
215
  const { t } = useTranslation();
120
216
  const [selectedId, setSelectedId] = useState<string | undefined>();
121
217
 
@@ -123,8 +219,10 @@ export function FrappeTreeView<TDoc extends { name: string; doctype: string }>({
123
219
  const finalAuth = auth ?? config?.auth;
124
220
  const finalSyncOptions = syncOptions ?? config?.syncOptions;
125
221
 
126
- if (!finalBaseURL) {
127
- throw new Error("FrappeTreeView requires baseURL prop or FrappeProvider with baseURL");
222
+ if (!finalBaseURL && !config?.fixtures) {
223
+ throw new Error(
224
+ "FrappeTreeView requires baseURL prop or FrappeProvider with baseURL or fixtures",
225
+ );
128
226
  }
129
227
 
130
228
  const appConfig = useMemo(
@@ -152,6 +250,8 @@ export function FrappeTreeView<TDoc extends { name: string; doctype: string }>({
152
250
 
153
251
  const { meta, isLoading: isMetaLoading, error: metaError } = useDocTypeMeta(appConfig, doctype);
154
252
 
253
+ const isPathMode = !!pathSeparator;
254
+ const effectivePathField = pathField ?? "name";
155
255
  const effectiveTitleField = titleFieldProp ?? meta?.title_field ?? "name";
156
256
  const effectiveParentField =
157
257
  parentFieldProp ?? `parent_${doctype.toLowerCase().replace(/ /g, "_")}`;
@@ -160,67 +260,75 @@ export function FrappeTreeView<TDoc extends { name: string; doctype: string }>({
160
260
  // lets callers continue (Guest peek / offline). Successful meta always
161
261
  // wins for the is_tree gate — including rejecting flat DocTypes that
162
262
  // happened to pass parentField.
163
- const isKnownNotTree = !!meta && !meta.is_tree;
164
- const canProceed = !!meta?.is_tree || (!!metaError && !meta && !!parentFieldProp);
263
+ //
264
+ // Path mode is the exception: the hierarchy comes from the row's own key,
265
+ // so there is nothing for is_tree to be right about.
266
+ const isKnownNotTree = !isPathMode && !!meta && !meta.is_tree;
267
+ const canProceed = isPathMode || !!meta?.is_tree || (!!metaError && !meta && !!parentFieldProp);
165
268
 
166
269
  const effectiveFields = useMemo(() => {
167
270
  const fieldSet = new Set<string>(fields as string[] | undefined);
168
271
  fieldSet.add("name");
169
- fieldSet.add(effectiveTitleField);
170
- fieldSet.add(effectiveParentField);
272
+ if (isPathMode) {
273
+ // The parent field does not exist on a path-shaped doctype; asking for
274
+ // it would fail the read on a live bench.
275
+ fieldSet.add(effectivePathField);
276
+ } else {
277
+ fieldSet.add(effectiveTitleField);
278
+ fieldSet.add(effectiveParentField);
279
+ }
171
280
  if (valueField) fieldSet.add(valueField);
172
281
  return Array.from(fieldSet) as (keyof TDoc & string)[];
173
- }, [fields, effectiveTitleField, effectiveParentField, valueField]);
174
-
175
- // Honest error surface: without onSyncError a 403/network failure renders
176
- // as an empty tree.
177
- const [syncError, setSyncError] = useState<Error | null>(null);
178
- const handleSyncError = useCallback(
179
- (err: unknown) => {
180
- setSyncError(
181
- toLiveLoadError(err, {
182
- permissionMessage: t("You do not have permission to view {{doctype}}", { doctype }),
183
- genericMessage: t("Failed to load {{doctype}} tree", { doctype }),
184
- }),
185
- );
186
- },
187
- [doctype, t],
188
- );
282
+ }, [
283
+ fields,
284
+ isPathMode,
285
+ effectivePathField,
286
+ effectiveTitleField,
287
+ effectiveParentField,
288
+ valueField,
289
+ ]);
189
290
 
190
- const collection = useFrappeCollection<TDoc>(
191
- canProceed && !isKnownNotTree ? appConfig : undefined,
192
- doctype,
193
- { fields: effectiveFields, filters, onSyncError: handleSyncError },
291
+ // The shared doctype read (MPO-251): collection + live query + the honest
292
+ // error surface a bare collection swallows — without it a 403 or a network
293
+ // failure renders as an empty tree. `limit: null` keeps the tree on the
294
+ // bench's own page length, as it was before the hook existed.
295
+ const rowsMessages = useMemo(
296
+ () => ({
297
+ permission: t("You do not have permission to view {{doctype}}", { doctype }),
298
+ generic: t("Failed to load {{doctype}} tree", { doctype }),
299
+ }),
300
+ [doctype, t],
194
301
  );
195
-
196
- // A new collection (doctype/filter change) starts with a clean slate.
197
- useEffect(() => {
198
- setSyncError(null);
199
- }, [collection]);
200
-
201
302
  const {
202
- data: rawData,
203
- isLoading: isDataLoading,
204
- isError,
205
- } = useLiveQuery((q) => (collection ? q.from({ _list: collection }) : null), [collection]);
206
-
207
- const docs = useMemo(() => (rawData as TDoc[] | undefined) ?? [], [rawData]);
303
+ rows: docs,
304
+ isQueryLoading: isDataLoading,
305
+ error: queryError,
306
+ } = useFrappeRows<TDoc>(appConfig, doctype, {
307
+ fields: effectiveFields,
308
+ filters,
309
+ limit: null,
310
+ enabled: canProceed && !isKnownNotTree,
311
+ messages: rowsMessages,
312
+ });
208
313
 
209
314
  const treeNodes = useMemo(
210
- () => buildTreeNodes(docs, effectiveParentField, effectiveTitleField),
211
- [docs, effectiveParentField, effectiveTitleField],
315
+ () =>
316
+ pathSeparator
317
+ ? buildPathTreeNodes(docs, effectivePathField, pathSeparator)
318
+ : buildTreeNodes(docs, effectiveParentField, effectiveTitleField),
319
+ [docs, pathSeparator, effectivePathField, effectiveParentField, effectiveTitleField],
212
320
  );
213
321
 
214
- const queryError =
215
- syncError ?? (isError ? new Error(t("Failed to load {{doctype}} tree", { doctype })) : null);
216
-
217
322
  const handleNodeSelect = useCallback(
218
323
  (node: TreeNode<Record<string, unknown>>) => {
219
324
  setSelectedId(node.id);
325
+ if (isPathMode) onPathSelect?.(node.id, node.data as Partial<TDoc> | undefined);
326
+ // A synthesised branch is not a document, so onNodeClick stays silent
327
+ // rather than handing a caller a name that would 404 on open.
220
328
  if (!node.data) return;
221
329
  onNodeClick?.(node.data as Partial<TDoc>);
222
330
  },
223
- [onNodeClick],
331
+ [isPathMode, onNodeClick, onPathSelect],
224
332
  );
225
333
 
226
334
  if (isMetaLoading) {
@@ -228,7 +336,7 @@ export function FrappeTreeView<TDoc extends { name: string; doctype: string }>({
228
336
  return <AsyncBoundary layout="list" loading {...stackProps} />;
229
337
  }
230
338
 
231
- if (metaError && !meta && !parentFieldProp) {
339
+ if (metaError && !meta && !parentFieldProp && !isPathMode) {
232
340
  if (error) return <>{error(metaError)}</>;
233
341
  return (
234
342
  <AsyncBoundary
@@ -243,12 +351,14 @@ export function FrappeTreeView<TDoc extends { name: string; doctype: string }>({
243
351
  if (isKnownNotTree) {
244
352
  const iconSize = Number(knobProps.controlIcon?.width) || 16;
245
353
  return (
246
- <YStack {...knobProps.panelPadding} alignItems="center" {...knobProps.gap} {...stackProps}>
247
- <TreeStructureIcon size={iconSize * 2} color={formCommonColors.muted} />
248
- <Text color={formCommonColors.muted} {...knobProps.body}>
249
- {t("This DocType does not support tree view")}
250
- </Text>
251
- </YStack>
354
+ <EmptyState
355
+ title={t("This DocType does not support tree view")}
356
+ description={t(
357
+ "Pass pathSeparator and pathField to build the hierarchy from a path-shaped value on each row. parentField alone does not, because a flat DocType has no parent field.",
358
+ )}
359
+ icon={<TreeStructureIcon size={iconSize * 2} color={emptyIconColor} />}
360
+ {...stackProps}
361
+ />
252
362
  );
253
363
  }
254
364
 
@@ -67,7 +67,7 @@ export function useFrappeCommentActions(
67
67
  ): FrappeCommentActions {
68
68
  return useMemo(() => {
69
69
  const app =
70
- config && !config.fixtures
70
+ config?.baseURL && !config.fixtures
71
71
  ? new FrappeApp(config.baseURL, (config.auth ?? {}) as Record<string, unknown>)
72
72
  : undefined;
73
73
 
@@ -208,7 +208,7 @@ export function Timeline({
208
208
 
209
209
  const resolveSessionUser = useCallback(async (): Promise<string> => {
210
210
  if (sessionUserRef.current) return sessionUserRef.current;
211
- if (config && !config.fixtures) {
211
+ if (config?.baseURL && !config.fixtures) {
212
212
  const app = new FrappeApp(config.baseURL, (config.auth ?? {}) as Record<string, unknown>);
213
213
  sessionUserRef.current = await app.auth().getLoggedInUser();
214
214
  } else {
@@ -209,6 +209,7 @@ export function DeskShell({
209
209
  {showSidebar && !isNarrow && (
210
210
  <YStack
211
211
  role="navigation"
212
+ aria-label={t("Desk navigation")}
212
213
  id="desk-sidebar"
213
214
  flexShrink={0}
214
215
  data-testid="desk-sidebar-rail"
@@ -234,6 +235,7 @@ export function DeskShell({
234
235
  />
235
236
  <YStack
236
237
  role="navigation"
238
+ aria-label={t("Desk navigation")}
237
239
  id="desk-sidebar"
238
240
  position="absolute"
239
241
  top={0}
@@ -101,7 +101,10 @@ async function addRecentSearch(query: string): Promise<void> {
101
101
  // Helpers
102
102
  // ---------------------------------------------------------------------------
103
103
 
104
- function createHttpClientFromConfig(config: FrappeConnectionConfig): HttpClient {
104
+ function createHttpClientFromConfig(config: {
105
+ baseURL: string;
106
+ auth?: FrappeConnectionConfig["auth"];
107
+ }): HttpClient {
105
108
  return new HttpClient(
106
109
  config.baseURL,
107
110
  config.auth as Parameters<typeof HttpClient.prototype.updateAuth>[0],
@@ -168,7 +171,7 @@ export function Awesomebar({ onNavigate, basePath = "/desk", ...stackProps }: Aw
168
171
  // HttpClient ref for document search
169
172
  const httpRef = useRef<HttpClient | undefined>(undefined);
170
173
  const configKeyRef = useRef("");
171
- if (config) {
174
+ if (config?.baseURL) {
172
175
  const key = `${config.baseURL}:${config.auth?.token ?? ""}`;
173
176
  if (key !== configKeyRef.current) {
174
177
  httpRef.current = createHttpClientFromConfig({
@@ -29,12 +29,10 @@ function readCodeEditorSource() {
29
29
  return readFileSync(join(codeEditorDir(), "index.tsx"), "utf8");
30
30
  }
31
31
 
32
- function readCodeEditorStories() {
33
- return readFileSync(join(codeEditorDir(), "CodeEditor.stories.tsx"), "utf8");
34
- }
35
-
36
32
  afterEach(cleanup);
37
33
 
34
+ vi.mock("@multiplatform.one/storybook", () => ({ action: () => vi.fn() }));
35
+
38
36
  describe("frappeCodeLanguage (stock ControlCode language_map)", () => {
39
37
  it("maps stock Frappe options case-insensitively", () => {
40
38
  expect(frappeCodeLanguage("Python")).toBe("python");
@@ -102,11 +100,14 @@ describe("CodeEditorField (standalone)", () => {
102
100
  expect(result.getTextArea()).toBeNull();
103
101
  });
104
102
 
105
- it("ships CSF3 story objects so native gallery keeps args (KS blank-pane)", () => {
106
- const stories = readCodeEditorStories();
107
- expect(stories).toMatch(/export const Main = \{/);
108
- expect(stories).not.toMatch(/Main\.args\s*=/);
109
- expect(stories).toContain("value: PYTHON_FIXTURE");
103
+ it("ships CSF3 story objects so native gallery keeps args (KS blank-pane)", async () => {
104
+ const { Main, Json, ReadOnly } = await import("./CodeEditor.stories");
105
+ for (const story of [Main, Json, ReadOnly]) {
106
+ expect(typeof story).toBe("object");
107
+ expect(story.render).toBeTypeOf("function");
108
+ expect(story.args.value?.trim().length).toBeGreaterThan(0);
109
+ }
110
+ expect(ReadOnly.args.readOnly).toBe(true);
110
111
  });
111
112
 
112
113
  it("forwards knob containerRadius onto the source textarea (F14)", () => {
@@ -168,6 +168,14 @@ async function pressText(root: ParentNode, text: string) {
168
168
  });
169
169
  }
170
170
 
171
+ async function selectPeriod(period: "AM" | "PM") {
172
+ const wheel = document.querySelector('[role="slider"][aria-label="AM/PM"]');
173
+ expect(wheel).toBeTruthy();
174
+ await act(async () => {
175
+ fireEvent.keyDown(wheel!, { key: period === "AM" ? "Home" : "End" });
176
+ });
177
+ }
178
+
171
179
  beforeEach(() => {
172
180
  // Both halves portal their surfaces, so a previous case's chrome would
173
181
  // otherwise still be in the document when the next one queries it.
@@ -194,7 +202,7 @@ describe("web Date field — type=date keeps the calendar", () => {
194
202
  await openPanel(triggerInput(result.container)!);
195
203
  expect(panelText()).toContain("Su");
196
204
  // A date-only control offers no clock.
197
- expect(panelText()).not.toMatch(/\bHour\b/);
205
+ expect(document.querySelector('[role="slider"][aria-label="Hour"]')).toBeNull();
198
206
 
199
207
  await waitFor(() => expect(dayCell(15)).toBeTruthy(), { timeout: 30_000 });
200
208
  await act(async () => {
@@ -226,8 +234,8 @@ describe("web Date field — type=time renders a time control", () => {
226
234
  );
227
235
  await openPanel(triggerInput(result.container)!);
228
236
  const text = panelText();
229
- expect(text).toContain("Hour");
230
- expect(text).toContain("Min");
237
+ expect(document.querySelector('[role="slider"][aria-label="Hour"]')).toBeTruthy();
238
+ expect(document.querySelector('[role="slider"][aria-label="Min"]')).toBeTruthy();
231
239
  expect(text).not.toContain("Su");
232
240
  });
233
241
 
@@ -239,11 +247,11 @@ describe("web Date field — type=time renders a time control", () => {
239
247
  );
240
248
  await openPanel(triggerInput(result.container)!);
241
249
  // 9:00 AM → PM is a deterministic edit with no wheel animation in it.
242
- await pressText(document.body, "PM");
250
+ await selectPeriod("PM");
243
251
 
244
252
  // The control speaks `HH:mm`; the field re-serializes through the shared
245
253
  // wire body, so what persists carries seconds like every other writer.
246
- expect(onChange).toHaveBeenCalledWith("21:00:00");
254
+ await waitFor(() => expect(onChange).toHaveBeenCalledWith("21:00:00"));
247
255
  });
248
256
 
249
257
  it("adopts a seconds-less HH:mm value too (what Frappe hands a fresh field)", async () => {
@@ -292,7 +300,9 @@ describe("web Date field — type=datetime renders date AND time", () => {
292
300
  await openPanel(triggerInput(result.container)!);
293
301
  expect(panelText()).toContain("Su");
294
302
  await pressText(document.body, "Time");
295
- await waitFor(() => expect(panelText()).toContain("Hour"), { timeout: 30_000 });
303
+ await waitFor(() =>
304
+ expect(document.querySelector('[role="slider"][aria-label="Hour"]')).toBeTruthy(),
305
+ );
296
306
  });
297
307
 
298
308
  it("a day change keeps the existing time — and the picker, not the serializer, keeps it", async () => {
@@ -330,8 +340,8 @@ describe("web Date field — type=datetime renders date AND time", () => {
330
340
  await pressText(document.body, "Time");
331
341
  // 2:30 PM → AM. Carrying `previousValue` here would have pinned the old
332
342
  // 14:30 back over the edit the user just made.
333
- await pressText(document.body, "AM");
334
- expect(onChange).toHaveBeenLastCalledWith("2026-03-05 02:30:00");
343
+ await selectPeriod("AM");
344
+ await waitFor(() => expect(onChange).toHaveBeenLastCalledWith("2026-03-05 02:30:00"));
335
345
  });
336
346
  });
337
347
 
@@ -412,7 +422,7 @@ describe("web and native persist byte-identical wire strings", () => {
412
422
 
413
423
  it("time — moving 9:00 AM to 9:00 PM writes the same string on both halves", async () => {
414
424
  const web = await webPick("time", "09:00:00", async () => {
415
- await pressText(document.body, "PM");
425
+ await selectPeriod("PM");
416
426
  });
417
427
  cleanup();
418
428
  mockState.picks.length = 0;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Duration adapter contract specs. The Frappe Duration field is seconds in,
3
+ * seconds out; this file only pins that the hop to forms Duration keeps
4
+ * value / onChange / disabled / label / error. The segment math lives in
5
+ * the forms spec.
6
+ */
7
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
8
+ import { cleanup, fireEvent } from "@testing-library/react";
9
+ import type React from "react";
10
+ import { afterEach, describe, expect, it, vi } from "vitest";
11
+ import { getFieldComponent, type FieldRenderProps } from "../../fieldRegistry";
12
+ import { Duration } from ".";
13
+
14
+ afterEach(cleanup);
15
+
16
+ const hours = (container: HTMLElement) =>
17
+ container.querySelector('input[aria-label="Hours"]') as HTMLInputElement | null;
18
+
19
+ describe("Duration composes the forms Duration", () => {
20
+ it("paints a controlled seconds value into the hours segment", () => {
21
+ const result = renderWithProviders(<Duration label="Time" value={7200} onChange={() => {}} />);
22
+ expect(hours(result.container)?.value ?? hours(result.container)?.getAttribute("value")).toBe(
23
+ "2",
24
+ );
25
+ });
26
+
27
+ it("emits seconds through canonical onChange", () => {
28
+ const onChange = vi.fn();
29
+ const result = renderWithProviders(<Duration label="Time" value={3600} onChange={onChange} />);
30
+ fireEvent.keyDown(hours(result.container) as HTMLInputElement, { key: "ArrowUp" });
31
+ expect(onChange).toHaveBeenCalledWith(7200);
32
+ });
33
+
34
+ it("forwards disabled onto the hours segment", () => {
35
+ const result = renderWithProviders(
36
+ <Duration label="Time" value={3600} disabled onChange={() => {}} />,
37
+ );
38
+ const el = hours(result.container);
39
+ expect(el?.getAttribute("aria-disabled") === "true" || el?.disabled).toBe(true);
40
+ });
41
+
42
+ it("forwards readOnly onto the hours segment", () => {
43
+ const result = renderWithProviders(
44
+ <Duration label="Time" value={3600} readOnly onChange={() => {}} />,
45
+ );
46
+ const el = result.container.querySelector("input");
47
+ expect(el?.getAttribute("aria-readonly") === "true" || el?.readOnly).toBe(true);
48
+ });
49
+
50
+ it("shows the label and error", () => {
51
+ const result = renderWithProviders(
52
+ <Duration label="Time" value={3600} error="not a duration" onChange={() => {}} />,
53
+ );
54
+ expect(result.container.textContent).toContain("Time");
55
+ expect(result.container.textContent).toContain("not a duration");
56
+ });
57
+ });
58
+
59
+ describe("registry resolution (Duration fieldtype)", () => {
60
+ function propsFor(overrides: Partial<FieldRenderProps> = {}): FieldRenderProps {
61
+ return { value: "", onChange: () => {}, ...overrides };
62
+ }
63
+
64
+ it("resolves 'Duration' to the Duration adapter and coerces a non-number to 0", () => {
65
+ const Component = getFieldComponent("Duration")!;
66
+ const element = (Component as Function)(
67
+ propsFor({ value: "nope", label: "Time" }),
68
+ ) as React.ReactElement<Record<string, unknown>>;
69
+ expect(element.type).toBe(Duration);
70
+ expect(element.props.value).toBe(0);
71
+ expect(element.props.label).toBe("Time");
72
+ });
73
+ });