@multiplatform.one/frappe-ui 7.23.2 → 7.24.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 (577) hide show
  1. package/dist/cjs/FrappeCalendar.cjs +1 -1
  2. package/dist/cjs/FrappeCalendar.native.js +1 -1
  3. package/dist/cjs/FrappeCalendar.native.js.map +1 -1
  4. package/dist/cjs/FrappeDashboard.cjs +1 -1
  5. package/dist/cjs/FrappeDashboard.native.js +1 -1
  6. package/dist/cjs/FrappeDashboard.native.js.map +1 -1
  7. package/dist/cjs/FrappeImageView.cjs +1 -1
  8. package/dist/cjs/FrappeImageView.native.js +1 -1
  9. package/dist/cjs/FrappeImageView.native.js.map +1 -1
  10. package/dist/cjs/FrappeInsights.cjs +380 -0
  11. package/dist/cjs/FrappeInsights.native.js +447 -0
  12. package/dist/cjs/FrappeInsights.native.js.map +1 -0
  13. package/dist/cjs/FrappeLikedBy.cjs +5 -2
  14. package/dist/cjs/FrappeLikedBy.native.js +5 -2
  15. package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
  16. package/dist/cjs/FrappeListView.cjs +392 -38
  17. package/dist/cjs/FrappeListView.native.js +454 -41
  18. package/dist/cjs/FrappeListView.native.js.map +1 -1
  19. package/dist/cjs/FrappeRecordView.cjs +199 -0
  20. package/dist/cjs/FrappeRecordView.native.js +218 -0
  21. package/dist/cjs/FrappeRecordView.native.js.map +1 -0
  22. package/dist/cjs/FrappeReportBuilder.cjs +2 -2
  23. package/dist/cjs/FrappeReportBuilder.native.js +2 -2
  24. package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
  25. package/dist/cjs/FrappeTreeView.cjs +1 -1
  26. package/dist/cjs/FrappeTreeView.native.js +1 -1
  27. package/dist/cjs/desk/awesomebar.cjs +1 -1
  28. package/dist/cjs/desk/awesomebar.native.js +3 -3
  29. package/dist/cjs/desk/awesomebar.native.js.map +1 -1
  30. package/dist/cjs/fields/Heading/index.cjs +18 -11
  31. package/dist/cjs/fields/Heading/index.native.js +21 -12
  32. package/dist/cjs/fields/Heading/index.native.js.map +1 -1
  33. package/dist/cjs/frappeNavigation.cjs +12 -2
  34. package/dist/cjs/frappeNavigation.native.js +13 -2
  35. package/dist/cjs/frappeNavigation.native.js.map +1 -1
  36. package/dist/cjs/index.cjs +34 -0
  37. package/dist/cjs/index.native.js +34 -0
  38. package/dist/cjs/index.native.js.map +1 -1
  39. package/dist/cjs/insights/InsightsFacetTags.cjs +147 -0
  40. package/dist/cjs/insights/InsightsFacetTags.native.js +165 -0
  41. package/dist/cjs/insights/InsightsFacetTags.native.js.map +1 -0
  42. package/dist/cjs/insights/InsightsKpiBand.cjs +124 -0
  43. package/dist/cjs/insights/InsightsKpiBand.native.js +133 -0
  44. package/dist/cjs/insights/InsightsKpiBand.native.js.map +1 -0
  45. package/dist/cjs/insights/InsightsLeaders.cjs +150 -0
  46. package/dist/cjs/insights/InsightsLeaders.native.js +178 -0
  47. package/dist/cjs/insights/InsightsLeaders.native.js.map +1 -0
  48. package/dist/cjs/insights/InsightsRecords.cjs +105 -0
  49. package/dist/cjs/insights/InsightsRecords.native.js +109 -0
  50. package/dist/cjs/insights/InsightsRecords.native.js.map +1 -0
  51. package/dist/cjs/insights/InsightsTable.cjs +157 -0
  52. package/dist/cjs/insights/InsightsTable.native.js +194 -0
  53. package/dist/cjs/insights/InsightsTable.native.js.map +1 -0
  54. package/dist/cjs/insights/insightsData.cjs +114 -0
  55. package/dist/cjs/insights/insightsData.native.js +220 -0
  56. package/dist/cjs/insights/insightsData.native.js.map +1 -0
  57. package/dist/cjs/insights/insightsRecord.cjs +35 -0
  58. package/dist/cjs/insights/insightsRecord.native.js +38 -0
  59. package/dist/cjs/insights/insightsRecord.native.js.map +1 -0
  60. package/dist/cjs/list/FacetBadge.cjs +62 -0
  61. package/dist/cjs/list/FacetBadge.native.js +66 -0
  62. package/dist/cjs/list/FacetBadge.native.js.map +1 -0
  63. package/dist/cjs/list/FacetRail.cjs +199 -0
  64. package/dist/cjs/list/FacetRail.native.js +223 -0
  65. package/dist/cjs/list/FacetRail.native.js.map +1 -0
  66. package/dist/cjs/list/ImageWell.cjs +111 -0
  67. package/dist/cjs/list/ImageWell.native.js +126 -0
  68. package/dist/cjs/list/ImageWell.native.js.map +1 -0
  69. package/dist/cjs/list/ListControls.cjs +226 -0
  70. package/dist/cjs/list/ListControls.native.js +252 -0
  71. package/dist/cjs/list/ListControls.native.js.map +1 -0
  72. package/dist/cjs/list/ListToastHost.cjs +54 -0
  73. package/dist/cjs/list/ListToastHost.native.js +59 -0
  74. package/dist/cjs/list/ListToastHost.native.js.map +1 -0
  75. package/dist/cjs/list/RangeFilter.cjs +122 -0
  76. package/dist/cjs/list/RangeFilter.native.js +135 -0
  77. package/dist/cjs/list/RangeFilter.native.js.map +1 -0
  78. package/dist/cjs/list/RecordCard.cjs +233 -0
  79. package/dist/cjs/list/RecordCard.native.js +173 -0
  80. package/dist/cjs/list/RecordCard.native.js.map +1 -0
  81. package/dist/cjs/list/ShufflePanel.cjs +112 -0
  82. package/dist/cjs/list/ShufflePanel.native.js +139 -0
  83. package/dist/cjs/list/ShufflePanel.native.js.map +1 -0
  84. package/dist/cjs/list/facetReading.cjs +71 -0
  85. package/dist/cjs/list/facetReading.native.js +163 -0
  86. package/dist/cjs/list/facetReading.native.js.map +1 -0
  87. package/dist/cjs/list/favorites.cjs +79 -0
  88. package/dist/cjs/list/favorites.native.js +91 -0
  89. package/dist/cjs/list/favorites.native.js.map +1 -0
  90. package/dist/cjs/list/listState.cjs +100 -0
  91. package/dist/cjs/list/listState.native.js +234 -0
  92. package/dist/cjs/list/listState.native.js.map +1 -0
  93. package/dist/cjs/list/recordLook.cjs +35 -0
  94. package/dist/cjs/list/recordLook.native.js +39 -0
  95. package/dist/cjs/list/recordLook.native.js.map +1 -0
  96. package/dist/cjs/list/useListControls.cjs +189 -0
  97. package/dist/cjs/list/useListControls.native.js +272 -0
  98. package/dist/cjs/list/useListControls.native.js.map +1 -0
  99. package/dist/cjs/record/RecordBars.cjs +200 -0
  100. package/dist/cjs/record/RecordBars.native.js +225 -0
  101. package/dist/cjs/record/RecordBars.native.js.map +1 -0
  102. package/dist/cjs/record/RecordGallery.cjs +67 -0
  103. package/dist/cjs/record/RecordGallery.native.js +75 -0
  104. package/dist/cjs/record/RecordGallery.native.js.map +1 -0
  105. package/dist/cjs/record/RecordHero.cjs +239 -0
  106. package/dist/cjs/record/RecordHero.native.js +250 -0
  107. package/dist/cjs/record/RecordHero.native.js.map +1 -0
  108. package/dist/cjs/record/RecordPage.cjs +330 -0
  109. package/dist/cjs/record/RecordPage.native.js +364 -0
  110. package/dist/cjs/record/RecordPage.native.js.map +1 -0
  111. package/dist/cjs/record/RecordPager.cjs +125 -0
  112. package/dist/cjs/record/RecordPager.native.js +130 -0
  113. package/dist/cjs/record/RecordPager.native.js.map +1 -0
  114. package/dist/cjs/record/RecordRows.cjs +126 -0
  115. package/dist/cjs/record/RecordRows.native.js +140 -0
  116. package/dist/cjs/record/RecordRows.native.js.map +1 -0
  117. package/dist/cjs/record/recordData.cjs +122 -0
  118. package/dist/cjs/record/recordData.native.js +189 -0
  119. package/dist/cjs/record/recordData.native.js.map +1 -0
  120. package/dist/cjs/record/recordSections.cjs +30 -0
  121. package/dist/cjs/record/recordSections.native.js +33 -0
  122. package/dist/cjs/record/recordSections.native.js.map +1 -0
  123. package/dist/esm/FrappeCalendar.mjs +1 -1
  124. package/dist/esm/FrappeCalendar.mjs.map +1 -1
  125. package/dist/esm/FrappeCalendar.native.js +1 -1
  126. package/dist/esm/FrappeCalendar.native.js.map +1 -1
  127. package/dist/esm/FrappeDashboard.mjs +1 -1
  128. package/dist/esm/FrappeDashboard.mjs.map +1 -1
  129. package/dist/esm/FrappeDashboard.native.js +1 -1
  130. package/dist/esm/FrappeDashboard.native.js.map +1 -1
  131. package/dist/esm/FrappeImageView.mjs +1 -1
  132. package/dist/esm/FrappeImageView.mjs.map +1 -1
  133. package/dist/esm/FrappeImageView.native.js +1 -1
  134. package/dist/esm/FrappeImageView.native.js.map +1 -1
  135. package/dist/esm/FrappeInsights.mjs +355 -0
  136. package/dist/esm/FrappeInsights.mjs.map +1 -0
  137. package/dist/esm/FrappeInsights.native.js +419 -0
  138. package/dist/esm/FrappeInsights.native.js.map +1 -0
  139. package/dist/esm/FrappeLikedBy.mjs +6 -3
  140. package/dist/esm/FrappeLikedBy.mjs.map +1 -1
  141. package/dist/esm/FrappeLikedBy.native.js +6 -3
  142. package/dist/esm/FrappeLikedBy.native.js.map +1 -1
  143. package/dist/esm/FrappeListView.mjs +396 -42
  144. package/dist/esm/FrappeListView.mjs.map +1 -1
  145. package/dist/esm/FrappeListView.native.js +458 -45
  146. package/dist/esm/FrappeListView.native.js.map +1 -1
  147. package/dist/esm/FrappeRecordView.mjs +174 -0
  148. package/dist/esm/FrappeRecordView.mjs.map +1 -0
  149. package/dist/esm/FrappeRecordView.native.js +190 -0
  150. package/dist/esm/FrappeRecordView.native.js.map +1 -0
  151. package/dist/esm/FrappeReportBuilder.mjs +2 -2
  152. package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
  153. package/dist/esm/FrappeReportBuilder.native.js +2 -2
  154. package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
  155. package/dist/esm/FrappeTreeView.mjs +1 -1
  156. package/dist/esm/FrappeTreeView.mjs.map +1 -1
  157. package/dist/esm/FrappeTreeView.native.js +1 -1
  158. package/dist/esm/desk/awesomebar.mjs +1 -1
  159. package/dist/esm/desk/awesomebar.mjs.map +1 -1
  160. package/dist/esm/desk/awesomebar.native.js +3 -3
  161. package/dist/esm/desk/awesomebar.native.js.map +1 -1
  162. package/dist/esm/fields/Heading/index.mjs +19 -12
  163. package/dist/esm/fields/Heading/index.mjs.map +1 -1
  164. package/dist/esm/fields/Heading/index.native.js +22 -13
  165. package/dist/esm/fields/Heading/index.native.js.map +1 -1
  166. package/dist/esm/frappeNavigation.mjs +11 -2
  167. package/dist/esm/frappeNavigation.mjs.map +1 -1
  168. package/dist/esm/frappeNavigation.native.js +12 -2
  169. package/dist/esm/frappeNavigation.native.js.map +1 -1
  170. package/dist/esm/index.mjs +14 -2
  171. package/dist/esm/index.mjs.map +1 -1
  172. package/dist/esm/index.native.js +14 -2
  173. package/dist/esm/index.native.js.map +1 -1
  174. package/dist/esm/insights/InsightsFacetTags.mjs +122 -0
  175. package/dist/esm/insights/InsightsFacetTags.mjs.map +1 -0
  176. package/dist/esm/insights/InsightsFacetTags.native.js +137 -0
  177. package/dist/esm/insights/InsightsFacetTags.native.js.map +1 -0
  178. package/dist/esm/insights/InsightsKpiBand.mjs +99 -0
  179. package/dist/esm/insights/InsightsKpiBand.mjs.map +1 -0
  180. package/dist/esm/insights/InsightsKpiBand.native.js +105 -0
  181. package/dist/esm/insights/InsightsKpiBand.native.js.map +1 -0
  182. package/dist/esm/insights/InsightsLeaders.mjs +125 -0
  183. package/dist/esm/insights/InsightsLeaders.mjs.map +1 -0
  184. package/dist/esm/insights/InsightsLeaders.native.js +150 -0
  185. package/dist/esm/insights/InsightsLeaders.native.js.map +1 -0
  186. package/dist/esm/insights/InsightsRecords.mjs +80 -0
  187. package/dist/esm/insights/InsightsRecords.mjs.map +1 -0
  188. package/dist/esm/insights/InsightsRecords.native.js +81 -0
  189. package/dist/esm/insights/InsightsRecords.native.js.map +1 -0
  190. package/dist/esm/insights/InsightsTable.mjs +132 -0
  191. package/dist/esm/insights/InsightsTable.mjs.map +1 -0
  192. package/dist/esm/insights/InsightsTable.native.js +166 -0
  193. package/dist/esm/insights/InsightsTable.native.js.map +1 -0
  194. package/dist/esm/insights/insightsData.mjs +82 -0
  195. package/dist/esm/insights/insightsData.mjs.map +1 -0
  196. package/dist/esm/insights/insightsData.native.js +185 -0
  197. package/dist/esm/insights/insightsData.native.js.map +1 -0
  198. package/dist/esm/insights/insightsRecord.mjs +9 -0
  199. package/dist/esm/insights/insightsRecord.mjs.map +1 -0
  200. package/dist/esm/insights/insightsRecord.native.js +9 -0
  201. package/dist/esm/insights/insightsRecord.native.js.map +1 -0
  202. package/dist/esm/list/FacetBadge.mjs +37 -0
  203. package/dist/esm/list/FacetBadge.mjs.map +1 -0
  204. package/dist/esm/list/FacetBadge.native.js +38 -0
  205. package/dist/esm/list/FacetBadge.native.js.map +1 -0
  206. package/dist/esm/list/FacetRail.mjs +174 -0
  207. package/dist/esm/list/FacetRail.mjs.map +1 -0
  208. package/dist/esm/list/FacetRail.native.js +195 -0
  209. package/dist/esm/list/FacetRail.native.js.map +1 -0
  210. package/dist/esm/list/ImageWell.mjs +86 -0
  211. package/dist/esm/list/ImageWell.mjs.map +1 -0
  212. package/dist/esm/list/ImageWell.native.js +98 -0
  213. package/dist/esm/list/ImageWell.native.js.map +1 -0
  214. package/dist/esm/list/ListControls.mjs +201 -0
  215. package/dist/esm/list/ListControls.mjs.map +1 -0
  216. package/dist/esm/list/ListControls.native.js +224 -0
  217. package/dist/esm/list/ListControls.native.js.map +1 -0
  218. package/dist/esm/list/ListToastHost.mjs +18 -0
  219. package/dist/esm/list/ListToastHost.mjs.map +1 -0
  220. package/dist/esm/list/ListToastHost.native.js +20 -0
  221. package/dist/esm/list/ListToastHost.native.js.map +1 -0
  222. package/dist/esm/list/RangeFilter.mjs +85 -0
  223. package/dist/esm/list/RangeFilter.mjs.map +1 -0
  224. package/dist/esm/list/RangeFilter.native.js +95 -0
  225. package/dist/esm/list/RangeFilter.native.js.map +1 -0
  226. package/dist/esm/list/RecordCard.mjs +208 -0
  227. package/dist/esm/list/RecordCard.mjs.map +1 -0
  228. package/dist/esm/list/RecordCard.native.js +145 -0
  229. package/dist/esm/list/RecordCard.native.js.map +1 -0
  230. package/dist/esm/list/ShufflePanel.mjs +87 -0
  231. package/dist/esm/list/ShufflePanel.mjs.map +1 -0
  232. package/dist/esm/list/ShufflePanel.native.js +111 -0
  233. package/dist/esm/list/ShufflePanel.native.js.map +1 -0
  234. package/dist/esm/list/facetReading.mjs +45 -0
  235. package/dist/esm/list/facetReading.mjs.map +1 -0
  236. package/dist/esm/list/facetReading.native.js +134 -0
  237. package/dist/esm/list/facetReading.native.js.map +1 -0
  238. package/dist/esm/list/favorites.mjs +50 -0
  239. package/dist/esm/list/favorites.mjs.map +1 -0
  240. package/dist/esm/list/favorites.native.js +59 -0
  241. package/dist/esm/list/favorites.native.js.map +1 -0
  242. package/dist/esm/list/listState.mjs +71 -0
  243. package/dist/esm/list/listState.mjs.map +1 -0
  244. package/dist/esm/list/listState.native.js +202 -0
  245. package/dist/esm/list/listState.native.js.map +1 -0
  246. package/dist/esm/list/recordLook.mjs +10 -0
  247. package/dist/esm/list/recordLook.mjs.map +1 -0
  248. package/dist/esm/list/recordLook.native.js +11 -0
  249. package/dist/esm/list/recordLook.native.js.map +1 -0
  250. package/dist/esm/list/useListControls.mjs +164 -0
  251. package/dist/esm/list/useListControls.mjs.map +1 -0
  252. package/dist/esm/list/useListControls.native.js +244 -0
  253. package/dist/esm/list/useListControls.native.js.map +1 -0
  254. package/dist/esm/record/RecordBars.mjs +175 -0
  255. package/dist/esm/record/RecordBars.mjs.map +1 -0
  256. package/dist/esm/record/RecordBars.native.js +197 -0
  257. package/dist/esm/record/RecordBars.native.js.map +1 -0
  258. package/dist/esm/record/RecordGallery.mjs +42 -0
  259. package/dist/esm/record/RecordGallery.mjs.map +1 -0
  260. package/dist/esm/record/RecordGallery.native.js +47 -0
  261. package/dist/esm/record/RecordGallery.native.js.map +1 -0
  262. package/dist/esm/record/RecordHero.mjs +205 -0
  263. package/dist/esm/record/RecordHero.mjs.map +1 -0
  264. package/dist/esm/record/RecordHero.native.js +213 -0
  265. package/dist/esm/record/RecordHero.native.js.map +1 -0
  266. package/dist/esm/record/RecordPage.mjs +305 -0
  267. package/dist/esm/record/RecordPage.mjs.map +1 -0
  268. package/dist/esm/record/RecordPage.native.js +336 -0
  269. package/dist/esm/record/RecordPage.native.js.map +1 -0
  270. package/dist/esm/record/RecordPager.mjs +100 -0
  271. package/dist/esm/record/RecordPager.mjs.map +1 -0
  272. package/dist/esm/record/RecordPager.native.js +102 -0
  273. package/dist/esm/record/RecordPager.native.js.map +1 -0
  274. package/dist/esm/record/RecordRows.mjs +101 -0
  275. package/dist/esm/record/RecordRows.mjs.map +1 -0
  276. package/dist/esm/record/RecordRows.native.js +112 -0
  277. package/dist/esm/record/RecordRows.native.js.map +1 -0
  278. package/dist/esm/record/recordData.mjs +92 -0
  279. package/dist/esm/record/recordData.mjs.map +1 -0
  280. package/dist/esm/record/recordData.native.js +156 -0
  281. package/dist/esm/record/recordData.native.js.map +1 -0
  282. package/dist/esm/record/recordSections.mjs +5 -0
  283. package/dist/esm/record/recordSections.mjs.map +1 -0
  284. package/dist/esm/record/recordSections.native.js +5 -0
  285. package/dist/esm/record/recordSections.native.js.map +1 -0
  286. package/dist/jsx/FrappeCalendar.mjs +1 -1
  287. package/dist/jsx/FrappeCalendar.mjs.map +1 -1
  288. package/dist/jsx/FrappeCalendar.native.js +1 -1
  289. package/dist/jsx/FrappeCalendar.native.js.map +1 -1
  290. package/dist/jsx/FrappeDashboard.mjs +1 -1
  291. package/dist/jsx/FrappeDashboard.mjs.map +1 -1
  292. package/dist/jsx/FrappeDashboard.native.js +1 -1
  293. package/dist/jsx/FrappeDashboard.native.js.map +1 -1
  294. package/dist/jsx/FrappeImageView.mjs +1 -1
  295. package/dist/jsx/FrappeImageView.mjs.map +1 -1
  296. package/dist/jsx/FrappeImageView.native.js +1 -1
  297. package/dist/jsx/FrappeImageView.native.js.map +1 -1
  298. package/dist/jsx/FrappeInsights.mjs +355 -0
  299. package/dist/jsx/FrappeInsights.mjs.map +1 -0
  300. package/dist/jsx/FrappeInsights.native.js +447 -0
  301. package/dist/jsx/FrappeInsights.native.js.map +1 -0
  302. package/dist/jsx/FrappeLikedBy.mjs +6 -3
  303. package/dist/jsx/FrappeLikedBy.mjs.map +1 -1
  304. package/dist/jsx/FrappeLikedBy.native.js +5 -2
  305. package/dist/jsx/FrappeLikedBy.native.js.map +1 -1
  306. package/dist/jsx/FrappeListView.mjs +396 -42
  307. package/dist/jsx/FrappeListView.mjs.map +1 -1
  308. package/dist/jsx/FrappeListView.native.js +454 -41
  309. package/dist/jsx/FrappeListView.native.js.map +1 -1
  310. package/dist/jsx/FrappeRecordView.mjs +174 -0
  311. package/dist/jsx/FrappeRecordView.mjs.map +1 -0
  312. package/dist/jsx/FrappeRecordView.native.js +218 -0
  313. package/dist/jsx/FrappeRecordView.native.js.map +1 -0
  314. package/dist/jsx/FrappeReportBuilder.mjs +2 -2
  315. package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
  316. package/dist/jsx/FrappeReportBuilder.native.js +2 -2
  317. package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
  318. package/dist/jsx/FrappeTreeView.mjs +1 -1
  319. package/dist/jsx/FrappeTreeView.mjs.map +1 -1
  320. package/dist/jsx/FrappeTreeView.native.js +1 -1
  321. package/dist/jsx/desk/awesomebar.mjs +1 -1
  322. package/dist/jsx/desk/awesomebar.mjs.map +1 -1
  323. package/dist/jsx/desk/awesomebar.native.js +3 -3
  324. package/dist/jsx/desk/awesomebar.native.js.map +1 -1
  325. package/dist/jsx/fields/Heading/index.mjs +19 -12
  326. package/dist/jsx/fields/Heading/index.mjs.map +1 -1
  327. package/dist/jsx/fields/Heading/index.native.js +21 -12
  328. package/dist/jsx/fields/Heading/index.native.js.map +1 -1
  329. package/dist/jsx/frappeNavigation.mjs +11 -2
  330. package/dist/jsx/frappeNavigation.mjs.map +1 -1
  331. package/dist/jsx/frappeNavigation.native.js +13 -2
  332. package/dist/jsx/frappeNavigation.native.js.map +1 -1
  333. package/dist/jsx/index.js +14 -2
  334. package/dist/jsx/index.js.map +1 -1
  335. package/dist/jsx/index.mjs +14 -2
  336. package/dist/jsx/index.mjs.map +1 -1
  337. package/dist/jsx/index.native.js +34 -0
  338. package/dist/jsx/index.native.js.map +1 -1
  339. package/dist/jsx/insights/InsightsFacetTags.mjs +122 -0
  340. package/dist/jsx/insights/InsightsFacetTags.mjs.map +1 -0
  341. package/dist/jsx/insights/InsightsFacetTags.native.js +165 -0
  342. package/dist/jsx/insights/InsightsFacetTags.native.js.map +1 -0
  343. package/dist/jsx/insights/InsightsKpiBand.mjs +99 -0
  344. package/dist/jsx/insights/InsightsKpiBand.mjs.map +1 -0
  345. package/dist/jsx/insights/InsightsKpiBand.native.js +133 -0
  346. package/dist/jsx/insights/InsightsKpiBand.native.js.map +1 -0
  347. package/dist/jsx/insights/InsightsLeaders.mjs +125 -0
  348. package/dist/jsx/insights/InsightsLeaders.mjs.map +1 -0
  349. package/dist/jsx/insights/InsightsLeaders.native.js +178 -0
  350. package/dist/jsx/insights/InsightsLeaders.native.js.map +1 -0
  351. package/dist/jsx/insights/InsightsRecords.mjs +80 -0
  352. package/dist/jsx/insights/InsightsRecords.mjs.map +1 -0
  353. package/dist/jsx/insights/InsightsRecords.native.js +109 -0
  354. package/dist/jsx/insights/InsightsRecords.native.js.map +1 -0
  355. package/dist/jsx/insights/InsightsTable.mjs +132 -0
  356. package/dist/jsx/insights/InsightsTable.mjs.map +1 -0
  357. package/dist/jsx/insights/InsightsTable.native.js +194 -0
  358. package/dist/jsx/insights/InsightsTable.native.js.map +1 -0
  359. package/dist/jsx/insights/insightsData.mjs +82 -0
  360. package/dist/jsx/insights/insightsData.mjs.map +1 -0
  361. package/dist/jsx/insights/insightsData.native.js +220 -0
  362. package/dist/jsx/insights/insightsData.native.js.map +1 -0
  363. package/dist/jsx/insights/insightsRecord.mjs +9 -0
  364. package/dist/jsx/insights/insightsRecord.mjs.map +1 -0
  365. package/dist/jsx/insights/insightsRecord.native.js +38 -0
  366. package/dist/jsx/insights/insightsRecord.native.js.map +1 -0
  367. package/dist/jsx/list/FacetBadge.mjs +37 -0
  368. package/dist/jsx/list/FacetBadge.mjs.map +1 -0
  369. package/dist/jsx/list/FacetBadge.native.js +66 -0
  370. package/dist/jsx/list/FacetBadge.native.js.map +1 -0
  371. package/dist/jsx/list/FacetRail.mjs +174 -0
  372. package/dist/jsx/list/FacetRail.mjs.map +1 -0
  373. package/dist/jsx/list/FacetRail.native.js +223 -0
  374. package/dist/jsx/list/FacetRail.native.js.map +1 -0
  375. package/dist/jsx/list/ImageWell.mjs +86 -0
  376. package/dist/jsx/list/ImageWell.mjs.map +1 -0
  377. package/dist/jsx/list/ImageWell.native.js +126 -0
  378. package/dist/jsx/list/ImageWell.native.js.map +1 -0
  379. package/dist/jsx/list/ListControls.mjs +201 -0
  380. package/dist/jsx/list/ListControls.mjs.map +1 -0
  381. package/dist/jsx/list/ListControls.native.js +252 -0
  382. package/dist/jsx/list/ListControls.native.js.map +1 -0
  383. package/dist/jsx/list/ListToastHost.mjs +18 -0
  384. package/dist/jsx/list/ListToastHost.mjs.map +1 -0
  385. package/dist/jsx/list/ListToastHost.native.js +59 -0
  386. package/dist/jsx/list/ListToastHost.native.js.map +1 -0
  387. package/dist/jsx/list/RangeFilter.mjs +85 -0
  388. package/dist/jsx/list/RangeFilter.mjs.map +1 -0
  389. package/dist/jsx/list/RangeFilter.native.js +135 -0
  390. package/dist/jsx/list/RangeFilter.native.js.map +1 -0
  391. package/dist/jsx/list/RecordCard.mjs +208 -0
  392. package/dist/jsx/list/RecordCard.mjs.map +1 -0
  393. package/dist/jsx/list/RecordCard.native.js +173 -0
  394. package/dist/jsx/list/RecordCard.native.js.map +1 -0
  395. package/dist/jsx/list/ShufflePanel.mjs +87 -0
  396. package/dist/jsx/list/ShufflePanel.mjs.map +1 -0
  397. package/dist/jsx/list/ShufflePanel.native.js +139 -0
  398. package/dist/jsx/list/ShufflePanel.native.js.map +1 -0
  399. package/dist/jsx/list/facetReading.mjs +45 -0
  400. package/dist/jsx/list/facetReading.mjs.map +1 -0
  401. package/dist/jsx/list/facetReading.native.js +163 -0
  402. package/dist/jsx/list/facetReading.native.js.map +1 -0
  403. package/dist/jsx/list/favorites.mjs +50 -0
  404. package/dist/jsx/list/favorites.mjs.map +1 -0
  405. package/dist/jsx/list/favorites.native.js +91 -0
  406. package/dist/jsx/list/favorites.native.js.map +1 -0
  407. package/dist/jsx/list/listState.mjs +71 -0
  408. package/dist/jsx/list/listState.mjs.map +1 -0
  409. package/dist/jsx/list/listState.native.js +234 -0
  410. package/dist/jsx/list/listState.native.js.map +1 -0
  411. package/dist/jsx/list/recordLook.mjs +10 -0
  412. package/dist/jsx/list/recordLook.mjs.map +1 -0
  413. package/dist/jsx/list/recordLook.native.js +39 -0
  414. package/dist/jsx/list/recordLook.native.js.map +1 -0
  415. package/dist/jsx/list/useListControls.mjs +164 -0
  416. package/dist/jsx/list/useListControls.mjs.map +1 -0
  417. package/dist/jsx/list/useListControls.native.js +272 -0
  418. package/dist/jsx/list/useListControls.native.js.map +1 -0
  419. package/dist/jsx/record/RecordBars.mjs +175 -0
  420. package/dist/jsx/record/RecordBars.mjs.map +1 -0
  421. package/dist/jsx/record/RecordBars.native.js +225 -0
  422. package/dist/jsx/record/RecordBars.native.js.map +1 -0
  423. package/dist/jsx/record/RecordGallery.mjs +42 -0
  424. package/dist/jsx/record/RecordGallery.mjs.map +1 -0
  425. package/dist/jsx/record/RecordGallery.native.js +75 -0
  426. package/dist/jsx/record/RecordGallery.native.js.map +1 -0
  427. package/dist/jsx/record/RecordHero.mjs +205 -0
  428. package/dist/jsx/record/RecordHero.mjs.map +1 -0
  429. package/dist/jsx/record/RecordHero.native.js +250 -0
  430. package/dist/jsx/record/RecordHero.native.js.map +1 -0
  431. package/dist/jsx/record/RecordPage.mjs +305 -0
  432. package/dist/jsx/record/RecordPage.mjs.map +1 -0
  433. package/dist/jsx/record/RecordPage.native.js +364 -0
  434. package/dist/jsx/record/RecordPage.native.js.map +1 -0
  435. package/dist/jsx/record/RecordPager.mjs +100 -0
  436. package/dist/jsx/record/RecordPager.mjs.map +1 -0
  437. package/dist/jsx/record/RecordPager.native.js +130 -0
  438. package/dist/jsx/record/RecordPager.native.js.map +1 -0
  439. package/dist/jsx/record/RecordRows.mjs +101 -0
  440. package/dist/jsx/record/RecordRows.mjs.map +1 -0
  441. package/dist/jsx/record/RecordRows.native.js +140 -0
  442. package/dist/jsx/record/RecordRows.native.js.map +1 -0
  443. package/dist/jsx/record/recordData.mjs +92 -0
  444. package/dist/jsx/record/recordData.mjs.map +1 -0
  445. package/dist/jsx/record/recordData.native.js +189 -0
  446. package/dist/jsx/record/recordData.native.js.map +1 -0
  447. package/dist/jsx/record/recordSections.mjs +5 -0
  448. package/dist/jsx/record/recordSections.mjs.map +1 -0
  449. package/dist/jsx/record/recordSections.native.js +33 -0
  450. package/dist/jsx/record/recordSections.native.js.map +1 -0
  451. package/package.json +16 -16
  452. package/src/FrappeCalendar.tsx +2 -2
  453. package/src/FrappeDashboard.tsx +2 -2
  454. package/src/FrappeImageView.tsx +2 -1
  455. package/src/FrappeInsights.spec.tsx +171 -0
  456. package/src/FrappeInsights.tsx +422 -0
  457. package/src/FrappeLikedBy.tsx +7 -5
  458. package/src/FrappeListView.card.spec.tsx +157 -0
  459. package/src/FrappeListView.controls.spec.tsx +225 -0
  460. package/src/FrappeListView.seed.spec.tsx +30 -0
  461. package/src/FrappeListView.tsx +575 -44
  462. package/src/FrappeRecordView.spec.tsx +276 -0
  463. package/src/FrappeRecordView.tsx +231 -0
  464. package/src/FrappeReportBuilder.tsx +4 -5
  465. package/src/FrappeTreeView.tsx +1 -1
  466. package/src/desk/awesomebar.auth.spec.tsx +77 -0
  467. package/src/desk/awesomebar.tsx +1 -1
  468. package/src/fields/Heading/index.tsx +33 -15
  469. package/src/frappeNavigation.loader.spec.tsx +45 -0
  470. package/src/frappeNavigation.tsx +32 -1
  471. package/src/index.ts +53 -1
  472. package/src/insights/InsightsFacetTags.tsx +131 -0
  473. package/src/insights/InsightsKpiBand.tsx +114 -0
  474. package/src/insights/InsightsLeaders.tsx +146 -0
  475. package/src/insights/InsightsRecords.tsx +71 -0
  476. package/src/insights/InsightsTable.tsx +169 -0
  477. package/src/insights/insightsData.spec.ts +143 -0
  478. package/src/insights/insightsData.ts +122 -0
  479. package/src/insights/insightsRecord.ts +36 -0
  480. package/src/list/FacetBadge.tsx +41 -0
  481. package/src/list/FacetRail.tsx +235 -0
  482. package/src/list/ImageWell.tsx +110 -0
  483. package/src/list/ListControls.tsx +208 -0
  484. package/src/list/ListToastHost.tsx +27 -0
  485. package/src/list/RangeFilter.tsx +83 -0
  486. package/src/list/RecordCard.native.tsx +126 -0
  487. package/src/list/RecordCard.tsx +260 -0
  488. package/src/list/ShufflePanel.tsx +107 -0
  489. package/src/list/facetReading.ts +79 -0
  490. package/src/list/favorites.spec.ts +80 -0
  491. package/src/list/favorites.ts +79 -0
  492. package/src/list/listState.spec.ts +156 -0
  493. package/src/list/listState.ts +184 -0
  494. package/src/list/recordLook.ts +37 -0
  495. package/src/list/useListControls.tsx +223 -0
  496. package/src/record/RecordBars.tsx +187 -0
  497. package/src/record/RecordGallery.tsx +47 -0
  498. package/src/record/RecordHero.tsx +186 -0
  499. package/src/record/RecordPage.tsx +358 -0
  500. package/src/record/RecordPager.tsx +97 -0
  501. package/src/record/RecordRows.tsx +90 -0
  502. package/src/record/recordData.spec.ts +148 -0
  503. package/src/record/recordData.ts +124 -0
  504. package/src/record/recordSections.ts +62 -0
  505. package/src/schemaForm/SchemaForm.stories.tsx +2 -2
  506. package/types/FrappeImageView.d.ts.map +1 -1
  507. package/types/FrappeInsights.d.ts +55 -0
  508. package/types/FrappeInsights.d.ts.map +1 -0
  509. package/types/FrappeLikedBy.d.ts.map +1 -1
  510. package/types/FrappeListView.d.ts +82 -2
  511. package/types/FrappeListView.d.ts.map +1 -1
  512. package/types/FrappeRecordView.d.ts +69 -0
  513. package/types/FrappeRecordView.d.ts.map +1 -0
  514. package/types/FrappeReportBuilder.d.ts.map +1 -1
  515. package/types/fields/Heading/index.d.ts.map +1 -1
  516. package/types/frappeNavigation.d.ts +21 -0
  517. package/types/frappeNavigation.d.ts.map +1 -1
  518. package/types/index.d.ts +14 -2
  519. package/types/index.d.ts.map +1 -1
  520. package/types/insights/InsightsFacetTags.d.ts +11 -0
  521. package/types/insights/InsightsFacetTags.d.ts.map +1 -0
  522. package/types/insights/InsightsKpiBand.d.ts +22 -0
  523. package/types/insights/InsightsKpiBand.d.ts.map +1 -0
  524. package/types/insights/InsightsLeaders.d.ts +16 -0
  525. package/types/insights/InsightsLeaders.d.ts.map +1 -0
  526. package/types/insights/InsightsRecords.d.ts +19 -0
  527. package/types/insights/InsightsRecords.d.ts.map +1 -0
  528. package/types/insights/InsightsTable.d.ts +17 -0
  529. package/types/insights/InsightsTable.d.ts.map +1 -0
  530. package/types/insights/insightsData.d.ts +45 -0
  531. package/types/insights/insightsData.d.ts.map +1 -0
  532. package/types/insights/insightsRecord.d.ts +29 -0
  533. package/types/insights/insightsRecord.d.ts.map +1 -0
  534. package/types/list/FacetBadge.d.ts +13 -0
  535. package/types/list/FacetBadge.d.ts.map +1 -0
  536. package/types/list/FacetRail.d.ts +26 -0
  537. package/types/list/FacetRail.d.ts.map +1 -0
  538. package/types/list/ImageWell.d.ts +24 -0
  539. package/types/list/ImageWell.d.ts.map +1 -0
  540. package/types/list/ListControls.d.ts +30 -0
  541. package/types/list/ListControls.d.ts.map +1 -0
  542. package/types/list/ListToastHost.d.ts +7 -0
  543. package/types/list/ListToastHost.d.ts.map +1 -0
  544. package/types/list/RangeFilter.d.ts +14 -0
  545. package/types/list/RangeFilter.d.ts.map +1 -0
  546. package/types/list/RecordCard.d.ts +38 -0
  547. package/types/list/RecordCard.d.ts.map +1 -0
  548. package/types/list/RecordCard.native.d.ts +9 -0
  549. package/types/list/RecordCard.native.d.ts.map +1 -0
  550. package/types/list/ShufflePanel.d.ts +24 -0
  551. package/types/list/ShufflePanel.d.ts.map +1 -0
  552. package/types/list/facetReading.d.ts +33 -0
  553. package/types/list/facetReading.d.ts.map +1 -0
  554. package/types/list/favorites.d.ts +26 -0
  555. package/types/list/favorites.d.ts.map +1 -0
  556. package/types/list/listState.d.ts +94 -0
  557. package/types/list/listState.d.ts.map +1 -0
  558. package/types/list/recordLook.d.ts +25 -0
  559. package/types/list/recordLook.d.ts.map +1 -0
  560. package/types/list/useListControls.d.ts +48 -0
  561. package/types/list/useListControls.d.ts.map +1 -0
  562. package/types/record/RecordBars.d.ts +11 -0
  563. package/types/record/RecordBars.d.ts.map +1 -0
  564. package/types/record/RecordGallery.d.ts +17 -0
  565. package/types/record/RecordGallery.d.ts.map +1 -0
  566. package/types/record/RecordHero.d.ts +42 -0
  567. package/types/record/RecordHero.d.ts.map +1 -0
  568. package/types/record/RecordPage.d.ts +54 -0
  569. package/types/record/RecordPage.d.ts.map +1 -0
  570. package/types/record/RecordPager.d.ts +13 -0
  571. package/types/record/RecordPager.d.ts.map +1 -0
  572. package/types/record/RecordRows.d.ts +13 -0
  573. package/types/record/RecordRows.d.ts.map +1 -0
  574. package/types/record/recordData.d.ts +46 -0
  575. package/types/record/recordData.d.ts.map +1 -0
  576. package/types/record/recordSections.d.ts +67 -0
  577. package/types/record/recordSections.d.ts.map +1 -0
@@ -1,6 +1,15 @@
1
1
  import { HttpClient, useDocTypeMeta } from "@multiplatform.one/frappe";
2
2
  import type { DataTableColumn } from "@multiplatform.one/table";
3
- import { ArrowsClockwiseIcon, PlusIcon } from "@phosphor-icons/react";
3
+ import {
4
+ ArrowsClockwiseIcon,
5
+ HeartIcon,
6
+ ImageIcon,
7
+ ListIcon,
8
+ MagnifyingGlassIcon,
9
+ PlusIcon,
10
+ SparkleIcon,
11
+ TableIcon,
12
+ } from "@phosphor-icons/react";
4
13
  import React, { useState, useMemo, useCallback, useEffect } from "react";
5
14
  import { useTranslation } from "react-i18next";
6
15
  import { Button, SearchInput, Spinner } from "@multiplatform.one/forms";
@@ -9,10 +18,14 @@ import { SavedFilterDropdown, useSavedFilters, type ParsedFilter } from "@multip
9
18
  import {
10
19
  AsyncBoundary,
11
20
  Avatar,
21
+ EmptyState,
12
22
  ListPageLayout,
13
23
  PageHeader,
24
+ SkipLink,
25
+ Tooltip,
14
26
  ViewSwitcher,
15
27
  componentColors,
28
+ showToast,
16
29
  type ImageGridProps,
17
30
  } from "@multiplatform.one/components";
18
31
  import { pressTargetStyle, useResolvedKnobs } from "@multiplatform.one/theme";
@@ -20,11 +33,29 @@ import { storage } from "@multiplatform.one/store";
20
33
  import { useFrappeConfig } from "./FrappeProvider";
21
34
  import { FrappeList, type FrappeListProps } from "./FrappeList";
22
35
  import { FrappeNotConfigured } from "./FrappeNotConfigured";
36
+ import { FrappeRowsReader, type FrappeRowsSnapshot } from "./FrappeRowsReader";
23
37
  import { FrappeTable } from "./FrappeTable";
24
38
  import { useDocRights } from "./formEngine/useDocRights";
25
- import { frappeRouteHref, useFrappeRouter } from "./frappeNavigation";
39
+ import { frappeRouteHref, useFrappeRouter, useRouteLoaderData } from "./frappeNavigation";
26
40
  import { connectionsFromMeta, type FrappeConnection } from "./frappeConnections";
27
41
  import { isPermissionError } from "./liveErrors";
42
+ import { favoritesKey } from "./list/favorites";
43
+ import { RecordCard } from "./list/RecordCard";
44
+ import { recordTitle, type FrappeRecordLook } from "./list/recordLook";
45
+ import { ListControls } from "./list/ListControls";
46
+ import {
47
+ isListNarrowed,
48
+ listStateFilters,
49
+ listStateOrderBy,
50
+ type FrappeListControls,
51
+ type FrappeListFacet,
52
+ type FrappeListFilter,
53
+ type FrappeListRange,
54
+ type FrappeListSort,
55
+ } from "./list/listState";
56
+ import { ListToastHost } from "./list/ListToastHost";
57
+ import { ShufflePanel } from "./list/ShufflePanel";
58
+ import { useListControls } from "./list/useListControls";
28
59
  import { useConnectionFilters, type FrappeDoctypeFilter } from "./useConnectionFilters";
29
60
 
30
61
  interface ListFieldConfig {
@@ -35,6 +66,30 @@ interface ListFieldConfig {
35
66
 
36
67
  export type ListViewMode = "list" | "table" | "grid";
37
68
 
69
+ /** What a card needs from the list beyond its row. */
70
+ export interface FrappeListCard {
71
+ /** The record's page, when the list is routed. */
72
+ href?: string;
73
+ /** Opens the record the way a row press does. */
74
+ open: () => void;
75
+ /** Facet param to the record's values (a Pokemon's types, in slot order). */
76
+ facets: Record<string, string[]>;
77
+ /** Present when the list offers favorites. */
78
+ favorite?: { active: boolean; toggle: () => void };
79
+ }
80
+
81
+ /** A page-header action of a routed list; icon-only under sm. */
82
+ export interface FrappeListAction {
83
+ label: string;
84
+ icon?: React.ComponentType<{ size?: number }>;
85
+ /** Opened through the FrappeProvider's navigation. */
86
+ href?: string;
87
+ onPress?: () => void;
88
+ /** Tooltip. */
89
+ hint?: string;
90
+ testID?: string;
91
+ }
92
+
38
93
  export interface FrappeListViewProps<TDoc extends { name: string; doctype: string }> extends Omit<
39
94
  YStackProps,
40
95
  "children"
@@ -71,14 +126,27 @@ export interface FrappeListViewProps<TDoc extends { name: string; doctype: strin
71
126
  orderBy?: FrappeListProps<TDoc>["orderBy"];
72
127
  limit?: number;
73
128
  initialRows?: readonly Partial<TDoc>[];
129
+ /**
130
+ * The route's loader (`frappeListLoader`): the rows it rendered on the
131
+ * server seed the list, read through the host's navigation.
132
+ */
133
+ loader?: (...args: never[]) => Promise<{ rows: readonly Partial<TDoc>[] }>;
74
134
  columns?: DataTableColumn<Partial<TDoc>>[];
75
135
  displayFields?: ListFieldConfig[];
76
136
  /**
77
137
  * Per-row card slot. Given, the view gains a card grid (`grid` mode, the
78
138
  * catalog ImageGrid) that draws each row with it; loading, empty and error
79
- * stay the view's own states.
139
+ * stay the view's own states. A host that draws the grid itself may leave
140
+ * `card` out.
80
141
  */
81
- renderCard?: (doc: Partial<TDoc>, index: number) => React.ReactNode;
142
+ renderCard?: (doc: Partial<TDoc>, index: number, card?: FrappeListCard) => React.ReactNode;
143
+ /**
144
+ * How the doctype's records present. With `images` and no card slot, the
145
+ * grid draws each record as a RecordCard: the look's title, code,
146
+ * subtitle and images, the well tinted and badged by the first facet that
147
+ * declares tints, the favorite heart when the list keeps favorites.
148
+ */
149
+ look?: FrappeRecordLook<TDoc>;
82
150
  /** Card grid: column count, tile rung and media aspect. */
83
151
  gridProps?: Pick<ImageGridProps<Partial<TDoc>>, "columns" | "tileSize" | "mediaAspect">;
84
152
  /** Replaces the default empty message in list and card modes. */
@@ -98,6 +166,40 @@ export interface FrappeListViewProps<TDoc extends { name: string; doctype: strin
98
166
  showFilterSidebar?: boolean;
99
167
  /** @deprecated The toolbar always renders the List/Table switcher now. */
100
168
  enableViewSwitcher?: boolean;
169
+ /** Routed page title; defaults to the doctype. */
170
+ title?: string;
171
+ /** Routed page subtitle from the number of records the list reads. */
172
+ subtitle?: (count: number) => string;
173
+ /**
174
+ * Routed page actions. With the toolbar off, the view switcher and Add
175
+ * join them in the page header.
176
+ */
177
+ actions?: readonly FrappeListAction[];
178
+ /** Header content ahead of the actions. */
179
+ headerAccessory?: React.ReactNode;
180
+ /** The field that names a record in toasts; defaults to meta.title_field. */
181
+ titleField?: string;
182
+ /** Sort choices, first is the default; given, a Sort select joins the controls. */
183
+ sorts?: readonly FrappeListSort[];
184
+ /** The bench filters a search becomes; defaults to a like on the title field. */
185
+ searchFilter?: (query: string) => readonly FrappeListFilter[];
186
+ searchPlaceholder?: string;
187
+ /** One chip rail per facet, with each value's record count. */
188
+ facets?: readonly FrappeListFacet[];
189
+ /** One minimum-value slider per numeric field. */
190
+ ranges?: readonly FrappeListRange[];
191
+ /**
192
+ * Star records on this device and filter to them: true keys the stars by
193
+ * doctype, a string names the storage key.
194
+ */
195
+ favorites?: boolean | string;
196
+ /** A random-record picker; `label` names a record on its wheel. */
197
+ shuffle?: { label: (doc: Partial<TDoc>) => string };
198
+ /**
199
+ * Keep the controls and the view in the route's query params, so a
200
+ * refresh or a shared link reproduces the list.
201
+ */
202
+ urlState?: boolean;
101
203
  baseURL?: string;
102
204
  auth?: { useToken?: boolean; token?: string; type?: string };
103
205
  syncOptions?: Record<string, unknown>;
@@ -105,6 +207,9 @@ export interface FrappeListViewProps<TDoc extends { name: string; doctype: strin
105
207
 
106
208
  const storageKeyPrefix = "frappe-listview-";
107
209
 
210
+ /** Cards whose images load eagerly; the rest wait until they near the viewport. */
211
+ const eagerCards = 6;
212
+
108
213
  // Frappe document bookkeeping keys -- never useful as display columns.
109
214
  const internalDocKeys = new Set([
110
215
  "name",
@@ -197,10 +302,12 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
197
302
  route,
198
303
  orderBy,
199
304
  limit,
200
- initialRows,
305
+ initialRows: initialRowsProp,
306
+ loader,
201
307
  columns,
202
308
  displayFields,
203
309
  renderCard,
310
+ look,
204
311
  gridProps,
205
312
  emptyState,
206
313
  onRetry,
@@ -211,13 +318,27 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
211
318
  showFilterSidebar = false,
212
319
  // deprecated: switcher always renders; destructured so it stays out of stackProps
213
320
  enableViewSwitcher: _enableViewSwitcher,
321
+ title,
322
+ subtitle,
323
+ actions,
324
+ headerAccessory,
325
+ titleField,
326
+ sorts,
327
+ searchFilter,
328
+ searchPlaceholder,
329
+ facets,
330
+ ranges,
331
+ favorites,
332
+ shuffle,
333
+ urlState = false,
214
334
  baseURL,
215
335
  auth,
216
336
  syncOptions,
217
337
  ...stackProps
218
338
  }: FrappeListViewProps<TDoc>) {
219
339
  const config = useFrappeConfig();
220
- const navigate = useFrappeRouter()?.navigate;
340
+ const router = useFrappeRouter();
341
+ const navigate = router?.navigate;
221
342
  const { knobProps } = useResolvedKnobs();
222
343
  const { t } = useTranslation();
223
344
  const theme = useTheme();
@@ -242,9 +363,6 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
242
363
  };
243
364
 
244
365
  const [storedViewMode, setViewModeState] = useState<ListViewMode>(defaultViewMode);
245
- const requestedViewMode = controlledViewMode ?? storedViewMode;
246
- const viewMode: ListViewMode =
247
- requestedViewMode === "grid" && !renderCard ? "list" : requestedViewMode;
248
366
 
249
367
  useEffect(() => {
250
368
  let cancelled = false;
@@ -260,16 +378,6 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
260
378
  };
261
379
  }, [doctype, defaultViewMode]);
262
380
 
263
- const setViewMode = useCallback(
264
- (mode: ListViewMode) => {
265
- onViewModeChange?.(mode);
266
- if (controlledViewMode !== undefined) return;
267
- setViewModeState(mode);
268
- void setStoredViewMode(doctype, mode);
269
- },
270
- [doctype, controlledViewMode, onViewModeChange],
271
- );
272
-
273
381
  const {
274
382
  meta,
275
383
  isLoading: isMetaLoading,
@@ -290,15 +398,6 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
290
398
  (route !== undefined && navigate
291
399
  ? () => navigate(frappeRouteHref(route, { mode: "create" }))
292
400
  : undefined));
293
- const asPage = (view: React.ReactElement) =>
294
- route === undefined ? (
295
- view
296
- ) : (
297
- <ListPageLayout scroll={false} header={<PageHeader title={doctype} divider={false} />}>
298
- {view}
299
- </ListPageLayout>
300
- );
301
-
302
401
  // Guest fallback: DocType meta is permission-gated (Guests get 403) while
303
402
  // the documents themselves are often guest-readable. Peek one doc with
304
403
  // fields=["*"] and derive display fields from its keys so the views render
@@ -472,10 +571,108 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
472
571
  () => connections ?? (isMetaLoading ? undefined : connectionsFromMeta(meta)),
473
572
  [connections, isMetaLoading, meta],
474
573
  );
574
+
575
+ const searchField = useMemo(() => {
576
+ if (meta?.title_field) return meta.title_field;
577
+ if (metaListFields.length > 0) return metaListFields[0].fieldname;
578
+ if (peekedFields && peekedFields.length > 0) return peekedFields[0];
579
+ return "name";
580
+ }, [meta, metaListFields, peekedFields]);
581
+
582
+ // The list's controls (sorts, facets, ranges, favorites, shuffle) own a
583
+ // state the filters and order derive from; a list without them keeps the
584
+ // plain search band below and its own view memory.
585
+ const hasCards = Boolean(renderCard || look?.images);
586
+ const listControls: FrappeListControls = {
587
+ sorts,
588
+ facets,
589
+ ranges,
590
+ views: hasCards ? ["grid", "list", "table"] : ["list", "table"],
591
+ defaultView: defaultViewMode,
592
+ };
593
+ const hasControls = Boolean(
594
+ sorts?.length || facets?.length || ranges?.length || favorites || shuffle,
595
+ );
596
+ const favoritesStorageKey = favorites
597
+ ? typeof favorites === "string"
598
+ ? favorites
599
+ : favoritesKey(doctype)
600
+ : undefined;
601
+ const list = useListControls({
602
+ doctype,
603
+ controls: listControls,
604
+ connections: effectiveConnections,
605
+ connection: connectionConfig,
606
+ limit,
607
+ favoritesKey: favoritesStorageKey,
608
+ router: urlState ? router : undefined,
609
+ readBase: Boolean(subtitle || facets?.length || ranges?.length),
610
+ });
611
+ const updateList = list.update;
612
+
613
+ const requestedViewMode =
614
+ controlledViewMode ?? (urlState ? list.state.view : undefined) ?? storedViewMode;
615
+ const viewMode: ListViewMode =
616
+ requestedViewMode === "grid" && !hasCards ? "list" : requestedViewMode;
617
+
618
+ const setViewMode = useCallback(
619
+ (mode: ListViewMode) => {
620
+ onViewModeChange?.(mode);
621
+ if (controlledViewMode !== undefined) return;
622
+ if (urlState) {
623
+ updateList({ view: mode });
624
+ return;
625
+ }
626
+ setViewModeState(mode);
627
+ void setStoredViewMode(doctype, mode);
628
+ },
629
+ [doctype, controlledViewMode, onViewModeChange, urlState, updateList],
630
+ );
631
+
632
+ const controlsKey = JSON.stringify({
633
+ facets: facets?.map((facet) => [facet.param, facet.field, facet.doctype]),
634
+ ranges: ranges?.map((range) => [range.param, range.field]),
635
+ sorts: sorts?.map((sort) => [sort.value, sort.orderBy]),
636
+ });
637
+ const controlFilters = useMemo(() => {
638
+ if (!hasControls) return activeFilters;
639
+ const base = !activeFilters
640
+ ? []
641
+ : Array.isArray(activeFilters)
642
+ ? activeFilters
643
+ : Object.entries(activeFilters).map(([field, value]) => [field, "=", value]);
644
+ return listStateFilters(list.state, listControls, {
645
+ base: base as FrappeListFilter[],
646
+ search: searchFilter ?? ((query) => [[searchField, "like", `%${query}%`]]),
647
+ favorites: list.favorites.favoriteSet,
648
+ });
649
+ // controlsKey stands for the facet and range definitions listControls carries.
650
+ }, [
651
+ hasControls,
652
+ activeFilters,
653
+ list.state,
654
+ list.favorites.favoriteSet,
655
+ searchFilter,
656
+ searchField,
657
+ controlsKey,
658
+ ]);
659
+ const effectiveOrderBy = hasControls
660
+ ? (listStateOrderBy(list.state, listControls, orderBy) as typeof orderBy)
661
+ : orderBy;
662
+ // The server page is the unfiltered list in its default order; any other
663
+ // query waits for its own read rather than flash rows it would drop.
664
+ const loaded = useRouteLoaderData(loader) as { rows?: readonly Partial<TDoc>[] } | undefined;
665
+ const initialRows = initialRowsProp ?? loaded?.rows;
666
+ const seedRows =
667
+ !hasControls ||
668
+ (!isListNarrowed(list.state) && (!sorts?.length || list.state.sort === sorts[0].value))
669
+ ? initialRows
670
+ : undefined;
671
+
475
672
  const linkFilters = useConnectionFilters(
476
673
  connectionConfig,
477
674
  doctype,
478
- activeFilters as Parameters<typeof useConnectionFilters>[2],
675
+ controlFilters as Parameters<typeof useConnectionFilters>[2],
479
676
  effectiveConnections,
480
677
  );
481
678
  const resolvedFilters = linkFilters.filters as typeof activeFilters;
@@ -487,13 +684,6 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
487
684
  const [searchText, setSearchText] = useState("");
488
685
  const [debouncedSearch, setDebouncedSearch] = useState("");
489
686
 
490
- const searchField = useMemo(() => {
491
- if (meta?.title_field) return meta.title_field;
492
- if (metaListFields.length > 0) return metaListFields[0].fieldname;
493
- if (peekedFields && peekedFields.length > 0) return peekedFields[0];
494
- return "name";
495
- }, [meta, metaListFields, peekedFields]);
496
-
497
687
  const listFilters = useMemo(() => {
498
688
  const query = debouncedSearch.trim();
499
689
  if (!query) return resolvedFilters;
@@ -551,6 +741,343 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
551
741
  [savedFiltersHook],
552
742
  );
553
743
 
744
+ const slug = doctype.toLowerCase().replace(/\s+/g, "-");
745
+ const searchId = `${slug}-list-search`;
746
+ const resultsId = `${slug}-results`;
747
+ const nameField = titleField ?? meta?.title_field;
748
+
749
+ const [shuffleOpen, setShuffleOpen] = useState(false);
750
+ const [shuffleRows, setShuffleRows] = useState<Partial<TDoc>[]>([]);
751
+ const handleShuffleRows = useCallback(
752
+ (snapshot: FrappeRowsSnapshot<TDoc>) => setShuffleRows(snapshot.rows),
753
+ [],
754
+ );
755
+ const shuffleOrderKey = JSON.stringify(sorts?.[0]?.orderBy ?? orderBy ?? null);
756
+ const shuffleFiltersKey = JSON.stringify(resolvedFilters ?? null);
757
+ const shuffleFieldsKey = JSON.stringify(effectiveFields);
758
+ const shuffleOptions = useMemo(
759
+ () => ({
760
+ fields: JSON.parse(shuffleFieldsKey),
761
+ filters: JSON.parse(shuffleFiltersKey) ?? undefined,
762
+ orderBy: JSON.parse(shuffleOrderKey) ?? undefined,
763
+ limit: limit ?? 0,
764
+ }),
765
+ [shuffleFieldsKey, shuffleFiltersKey, shuffleOrderKey, limit],
766
+ );
767
+
768
+ const { isFavorite, toggle: toggleStar } = list.favorites;
769
+ const toggleFavorite = useCallback(
770
+ (doc: Partial<TDoc>) => {
771
+ const name = doc.name;
772
+ if (!name) return;
773
+ const starred = isFavorite(name);
774
+ toggleStar(name);
775
+ const titleValue = nameField ? (doc as Record<string, unknown>)[nameField] : undefined;
776
+ const label = typeof titleValue === "string" && titleValue ? titleValue : name;
777
+ showToast(
778
+ starred
779
+ ? {
780
+ id: `${slug}-favorite-${name}`,
781
+ title: t("{{name}} removed from favorites", { name: label }),
782
+ intent: "accent",
783
+ action: { label: t("Undo"), onPress: () => toggleStar(name) },
784
+ }
785
+ : {
786
+ id: `${slug}-favorite-${name}`,
787
+ title: t("{{name}} added to favorites", { name: label }),
788
+ intent: "success",
789
+ },
790
+ );
791
+ },
792
+ [isFavorite, toggleStar, nameField, slug, t],
793
+ );
794
+
795
+ const cardFor = (doc: Partial<TDoc>): FrappeListCard => {
796
+ const name = doc.name ?? "";
797
+ const row = doc as Record<string, unknown>;
798
+ return {
799
+ href:
800
+ route !== undefined && name ? frappeRouteHref(route, { mode: "view", name }) : undefined,
801
+ open: () => onItemClick?.(doc),
802
+ facets: Object.fromEntries(
803
+ (facets ?? []).map((facet) => [
804
+ facet.param,
805
+ facet.doctype
806
+ ? (list.facets[facet.param]?.values.get(name) ?? [])
807
+ : row[facet.field] === undefined || row[facet.field] === null
808
+ ? []
809
+ : [String(row[facet.field])],
810
+ ]),
811
+ ),
812
+ favorite:
813
+ favoritesStorageKey && name
814
+ ? { active: isFavorite(name), toggle: () => toggleFavorite(doc) }
815
+ : undefined,
816
+ };
817
+ };
818
+ const tintFacet = facets?.find((facet) => facet.tint);
819
+ const drawRecordCard = (doc: Partial<TDoc>, index: number) => {
820
+ const card = cardFor(doc);
821
+ const name = doc.name ?? "";
822
+ const values = tintFacet ? (card.facets[tintFacet.param] ?? []) : [];
823
+ const title = recordTitle(doc, look, nameField);
824
+ return (
825
+ <RecordCard
826
+ title={title}
827
+ subtitle={look?.subtitle?.(doc)}
828
+ code={look?.code?.(doc)}
829
+ images={look?.images?.(doc, finalBaseURL) ?? []}
830
+ tints={values.slice(0, 2).map((value) => tintFacet?.tint?.(value))}
831
+ badges={values.map((value) => ({
832
+ value,
833
+ label: tintFacet?.optionLabel?.(value) ?? value,
834
+ tint: tintFacet?.tint?.(value),
835
+ }))}
836
+ href={card.href}
837
+ onPress={card.open}
838
+ favorite={card.favorite}
839
+ lazy={index >= eagerCards}
840
+ testID={`${slug}-card-${name}`}
841
+ favoriteTestID={`${slug}-favorite-${name}`}
842
+ />
843
+ );
844
+ };
845
+ const drawCard = renderCard
846
+ ? (doc: Partial<TDoc>, index: number) => renderCard(doc, index, cardFor(doc))
847
+ : look?.images
848
+ ? drawRecordCard
849
+ : undefined;
850
+
851
+ // "/" jumps to the search box on web, the common list-page affordance.
852
+ // Document scope adds no tab stop (LC-49); it stands down while the reader
853
+ // types in any editable control or works inside an open overlay.
854
+ useEffect(() => {
855
+ if (!isWeb || typeof document === "undefined" || !showSearch) return;
856
+ const handleKeyDown = (event: KeyboardEvent) => {
857
+ if (event.key !== "/" || event.metaKey || event.ctrlKey || event.altKey) return;
858
+ if (event.defaultPrevented) return;
859
+ const target = event.target as HTMLElement | null;
860
+ if (target) {
861
+ const tag = target.tagName;
862
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
863
+ if (target.isContentEditable) return;
864
+ if (target.closest?.('[role="dialog"], [role="menu"], [role="listbox"]')) return;
865
+ }
866
+ const input = document.getElementById(searchId)?.querySelector("input");
867
+ if (!input) return;
868
+ event.preventDefault();
869
+ input.focus();
870
+ input.select?.();
871
+ };
872
+ document.addEventListener("keydown", handleKeyDown);
873
+ return () => document.removeEventListener("keydown", handleKeyDown);
874
+ }, [searchId, showSearch]);
875
+
876
+ const headerOwnsTools = route !== undefined && !showToolbar;
877
+ const pressFor = (action: FrappeListAction) =>
878
+ action.onPress ?? (action.href && navigate ? () => navigate(action.href as string) : undefined);
879
+ const headerActions =
880
+ headerOwnsTools || headerAccessory || actions?.length ? (
881
+ <XStack {...knobProps.gap} alignItems="center" flexWrap="wrap" flexShrink={1} minWidth={0}>
882
+ {headerOwnsTools ? (
883
+ <Tooltip content={t("Switch views")}>
884
+ <XStack>
885
+ <ViewSwitcher
886
+ views={[
887
+ ...(hasCards ? [{ type: "grid", label: t("Cards"), icon: ImageIcon }] : []),
888
+ { type: "list", label: t("List"), icon: ListIcon },
889
+ { type: "table", label: t("Table"), icon: TableIcon },
890
+ ]}
891
+ currentView={viewMode}
892
+ onViewChange={(v) => setViewMode(v as ListViewMode)}
893
+ />
894
+ </XStack>
895
+ </Tooltip>
896
+ ) : null}
897
+ {headerAccessory}
898
+ {actions?.map((action) => {
899
+ const Icon = action.icon;
900
+ const onPress = pressFor(action);
901
+ return (
902
+ <React.Fragment key={action.label}>
903
+ <Tooltip content={action.hint ?? action.label}>
904
+ <Button
905
+ outlined
906
+ onPress={onPress}
907
+ icon={Icon ? <Icon size={16} /> : undefined}
908
+ $max-sm={{ display: "none" }}
909
+ testID={action.testID}
910
+ >
911
+ {action.label}
912
+ </Button>
913
+ </Tooltip>
914
+ <Button
915
+ outlined
916
+ circular
917
+ onPress={onPress}
918
+ icon={Icon ? <Icon size={16} /> : undefined}
919
+ display="none"
920
+ aria-label={action.label}
921
+ $max-sm={{ display: "flex" }}
922
+ />
923
+ </React.Fragment>
924
+ );
925
+ })}
926
+ {headerOwnsTools && onAdd ? (
927
+ <>
928
+ <Button
929
+ theme="accent"
930
+ onPress={onAdd}
931
+ icon={<PlusIcon size={16} />}
932
+ $max-sm={{ display: "none" }}
933
+ >
934
+ {t("Add {{doctype}}", { doctype })}
935
+ </Button>
936
+ <Button
937
+ theme="accent"
938
+ onPress={onAdd}
939
+ icon={<PlusIcon size={16} />}
940
+ display="none"
941
+ $max-sm={{ display: "flex" }}
942
+ >
943
+ {t("Add")}
944
+ </Button>
945
+ </>
946
+ ) : null}
947
+ </XStack>
948
+ ) : undefined;
949
+
950
+ const controlsBand = hasControls ? (
951
+ <ListControls
952
+ controls={listControls}
953
+ list={list}
954
+ searchId={searchId}
955
+ searchPlaceholder={searchPlaceholder}
956
+ showSearch={showSearch}
957
+ showFavorites={Boolean(favoritesStorageKey)}
958
+ shuffle={
959
+ shuffle
960
+ ? {
961
+ open: shuffleOpen,
962
+ toggle: () => setShuffleOpen((open) => !open),
963
+ panelId: `${slug}-shuffle-panel`,
964
+ }
965
+ : undefined
966
+ }
967
+ shufflePanel={
968
+ shuffle && shuffleOpen && shuffleRows.length > 0 && drawCard ? (
969
+ <ShufflePanel<Partial<TDoc>>
970
+ id={`${slug}-shuffle-panel`}
971
+ label={t("Surprise me")}
972
+ rows={shuffleRows}
973
+ itemLabel={shuffle.label}
974
+ renderPick={(doc) => drawCard(doc, 0)}
975
+ onGo={(doc) => {
976
+ setShuffleOpen(false);
977
+ onItemClick?.(doc);
978
+ }}
979
+ testID={`${slug}-shuffle-panel`}
980
+ />
981
+ ) : null
982
+ }
983
+ separator={!surface}
984
+ testID={slug}
985
+ />
986
+ ) : null;
987
+
988
+ const asPage = (view: React.ReactElement) => {
989
+ const extras = (
990
+ <>
991
+ {list.readers}
992
+ {shuffle && shuffleOpen && !linkFilters.pending ? (
993
+ <FrappeRowsReader<TDoc>
994
+ connection={connectionConfig}
995
+ doctype={doctype}
996
+ options={shuffleOptions as never}
997
+ onChange={handleShuffleRows}
998
+ />
999
+ ) : null}
1000
+ {favoritesStorageKey ? <ListToastHost /> : null}
1001
+ </>
1002
+ );
1003
+ if (route === undefined) {
1004
+ return controlsBand ? (
1005
+ <YStack {...knobProps.gap} flex={viewMode === "grid" ? undefined : 1}>
1006
+ {controlsBand}
1007
+ {view}
1008
+ {extras}
1009
+ </YStack>
1010
+ ) : (
1011
+ <>
1012
+ {view}
1013
+ {extras}
1014
+ </>
1015
+ );
1016
+ }
1017
+ return (
1018
+ <ListPageLayout
1019
+ scroll={!surface}
1020
+ header={
1021
+ <>
1022
+ {controlsBand ? <SkipLink targetId={resultsId}>{t("Skip to results")}</SkipLink> : null}
1023
+ <PageHeader
1024
+ title={title ?? doctype}
1025
+ subtitle={subtitle && list.total !== undefined ? subtitle(list.total) : undefined}
1026
+ actions={headerActions}
1027
+ divider={!surface}
1028
+ />
1029
+ </>
1030
+ }
1031
+ toolbar={controlsBand ?? undefined}
1032
+ >
1033
+ {view}
1034
+ {extras}
1035
+ </ListPageLayout>
1036
+ );
1037
+ };
1038
+
1039
+ const controlsEmptyState = hasControls ? (
1040
+ <EmptyState
1041
+ aria-live="polite"
1042
+ icon={
1043
+ list.state.favoritesOnly ? (
1044
+ <HeartIcon size={28} />
1045
+ ) : isListNarrowed(list.state) ? (
1046
+ <MagnifyingGlassIcon size={28} />
1047
+ ) : (
1048
+ <SparkleIcon size={28} />
1049
+ )
1050
+ }
1051
+ title={
1052
+ list.state.favoritesOnly
1053
+ ? t("No favorites yet")
1054
+ : list.state.search.trim()
1055
+ ? t('No match for "{{query}}"', { query: list.state.search.trim() })
1056
+ : isListNarrowed(list.state)
1057
+ ? t("No {{doctype}} match the active filters", { doctype })
1058
+ : t("No {{doctype}} yet", { doctype })
1059
+ }
1060
+ description={
1061
+ list.state.favoritesOnly
1062
+ ? t("Tap the heart on a record to favorite it.")
1063
+ : isListNarrowed(list.state)
1064
+ ? t("Try a different search or filter.")
1065
+ : undefined
1066
+ }
1067
+ action={
1068
+ isListNarrowed(list.state) ? (
1069
+ <Button outlined onPress={list.clear}>
1070
+ {t("Clear filters")}
1071
+ </Button>
1072
+ ) : onAdd ? (
1073
+ <Button theme="accent" onPress={onAdd}>
1074
+ {t("Add your first {{doctype}}", { doctype })}
1075
+ </Button>
1076
+ ) : undefined
1077
+ }
1078
+ />
1079
+ ) : undefined;
1080
+
554
1081
  // Named fields need no schema to read or draw: the view renders now (on
555
1082
  // the server too) and labels settle when meta lands.
556
1083
  if (isMetaLoading && !fields) {
@@ -559,10 +1086,11 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
559
1086
  {...knobProps.panelPadding}
560
1087
  alignItems="center"
561
1088
  justifyContent="center"
1089
+ gap="$2"
562
1090
  {...stackProps}
563
1091
  >
564
1092
  <Spinner size="large" />
565
- <Text {...knobProps.body} color={knobProps.textAccentColor} marginTop="$2">
1093
+ <Text {...knobProps.body} color={knobProps.textAccentColor}>
566
1094
  {t("Loading {{doctype}} schema...", { doctype })}
567
1095
  </Text>
568
1096
  </YStack>,
@@ -586,7 +1114,7 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
586
1114
  <XStack alignItems="center" {...knobProps.gap}>
587
1115
  <ViewSwitcher
588
1116
  views={[
589
- ...(renderCard ? [{ type: "grid", label: t("Cards") }] : []),
1117
+ ...(hasCards ? [{ type: "grid", label: t("Cards") }] : []),
590
1118
  { type: "list", label: t("List") },
591
1119
  { type: "table", label: t("Table") },
592
1120
  ]}
@@ -691,7 +1219,7 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
691
1219
  borderBottomWidth={1}
692
1220
  borderBottomColor="$borderColor"
693
1221
  >
694
- <View flex={1}>
1222
+ <View flex={1} id={searchId}>
695
1223
  <SearchInput
696
1224
  value={searchText}
697
1225
  onChange={setSearchText}
@@ -711,19 +1239,19 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
711
1239
  const isGrid = viewMode === "grid";
712
1240
  return (
713
1241
  <YStack flex={isGrid ? undefined : 1}>
714
- {showSearch ? renderListSearch() : null}
1242
+ {showSearch && !hasControls ? renderListSearch() : null}
715
1243
  <FrappeList
716
1244
  key={`${viewMode}-${refreshKey}`}
717
1245
  layout={isGrid ? "grid" : "list"}
718
1246
  gridProps={gridProps}
719
- emptyState={emptyState}
1247
+ emptyState={emptyState ?? controlsEmptyState}
720
1248
  pageSize={pageSize}
721
1249
  onRetry={onRetry ?? handleRefresh}
722
1250
  doctype={doctype}
723
1251
  fields={effectiveFields as readonly ("name" | "doctype")[]}
724
1252
  filters={listFilters as Partial<{ name: string; doctype: string }>}
725
1253
  renderItem={
726
- (isGrid && renderCard ? renderCard : renderListItem) as unknown as (
1254
+ (isGrid && drawCard ? drawCard : renderListItem) as unknown as (
727
1255
  item: Partial<{ name: string; doctype: string }>,
728
1256
  index: number,
729
1257
  ) => React.ReactNode
@@ -736,10 +1264,12 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
736
1264
  baseURL={finalBaseURL}
737
1265
  auth={finalAuth}
738
1266
  syncOptions={finalSyncOptions}
739
- orderBy={orderBy as FrappeListProps<{ name: string; doctype: string }>["orderBy"]}
1267
+ orderBy={
1268
+ effectiveOrderBy as FrappeListProps<{ name: string; doctype: string }>["orderBy"]
1269
+ }
740
1270
  limit={limit}
741
1271
  initialRows={
742
- initialRows as FrappeListProps<{ name: string; doctype: string }>["initialRows"]
1272
+ seedRows as FrappeListProps<{ name: string; doctype: string }>["initialRows"]
743
1273
  }
744
1274
  // Only override when narrowing is active: FrappeList's own
745
1275
  // "No {doctype} records found" stays the true-empty message.
@@ -792,6 +1322,7 @@ function FrappeListViewBody<TDoc extends { name: string; doctype: string }>({
792
1322
  {...(surface ? knobProps.containerRadius : {})}
793
1323
  overflow={surface ? "hidden" : undefined}
794
1324
  testID={`${doctype.toLowerCase()}-list`}
1325
+ {...(hasControls ? { id: resultsId } : {})}
795
1326
  {...stackProps}
796
1327
  >
797
1328
  {renderToolbar()}