@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
@@ -0,0 +1,79 @@
1
+ /**
2
+ * A list's favorites: the record names a reader starred, kept on the device
3
+ * (no bench write, so a guest can star too). One persistent store per
4
+ * storage key, following docs/standards/frontend/persistent-stores.md.
5
+ */
6
+ import {
7
+ Store,
8
+ createPersistStore,
9
+ storage,
10
+ useStore,
11
+ type PersistStore,
12
+ type Storage,
13
+ } from "@multiplatform.one/store";
14
+ import { useMemo } from "react";
15
+
16
+ export interface FavoritesState {
17
+ /** Starred record names, in the order they were starred. */
18
+ names: string[];
19
+ }
20
+
21
+ export function createFavoritesStore(
22
+ key: string,
23
+ storageImpl: Storage = storage,
24
+ ): PersistStore<FavoritesState> {
25
+ return createPersistStore<FavoritesState>({ names: [] }, { key, storage: storageImpl });
26
+ }
27
+
28
+ const stores = new Map<string, PersistStore<FavoritesState>>();
29
+
30
+ /** The shared store for a storage key. */
31
+ export function favoritesStore(key: string): PersistStore<FavoritesState> {
32
+ let store = stores.get(key);
33
+ if (!store) {
34
+ store = createFavoritesStore(key);
35
+ stores.set(key, store);
36
+ }
37
+ return store;
38
+ }
39
+
40
+ /** The storage key a doctype's favorites default to. */
41
+ export function favoritesKey(doctype: string): string {
42
+ return `frappe-favorites-${doctype}`;
43
+ }
44
+
45
+ export function toggleFavoriteIn(store: PersistStore<FavoritesState>, name: string): void {
46
+ store.setState((prev) =>
47
+ prev.names.includes(name)
48
+ ? { names: prev.names.filter((n) => n !== name) }
49
+ : { names: [...prev.names, name] },
50
+ );
51
+ }
52
+
53
+ export interface UseFavorites {
54
+ favorites: string[];
55
+ favoriteSet: ReadonlySet<string>;
56
+ isFavorite: (name: string) => boolean;
57
+ toggle: (name: string) => void;
58
+ count: number;
59
+ }
60
+
61
+ const noFavorites = new Store<FavoritesState>({ names: [] });
62
+
63
+ /** A key's favorites; without a key, none (and toggling does nothing). */
64
+ export function useFavorites(key: string | undefined): UseFavorites {
65
+ const store = key ? favoritesStore(key) : noFavorites;
66
+ const names = useStore(store, (state) => state.names);
67
+ return useMemo(() => {
68
+ const favoriteSet = new Set(names);
69
+ return {
70
+ favorites: names,
71
+ favoriteSet,
72
+ isFavorite: (name: string) => favoriteSet.has(name),
73
+ toggle: (name: string) => {
74
+ if (store !== noFavorites) toggleFavoriteIn(store as PersistStore<FavoritesState>, name);
75
+ },
76
+ count: names.length,
77
+ };
78
+ }, [names, store]);
79
+ }
@@ -0,0 +1,156 @@
1
+ import { Order } from "@multiplatform.one/frappe";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ isListNarrowed,
5
+ listStateFilters,
6
+ listStateFromParams,
7
+ listStateOrderBy,
8
+ listStateParams,
9
+ type FrappeListControls,
10
+ } from "./listState";
11
+
12
+ const controls: FrappeListControls = {
13
+ sorts: [
14
+ { value: "number", label: "Number", orderBy: [{ field: "poke_id", order: Order.Asc }] },
15
+ { value: "name", label: "Name", orderBy: [{ field: "pokemon_name", order: Order.Asc }] },
16
+ ],
17
+ facets: [
18
+ {
19
+ param: "type",
20
+ doctype: "Pokemon Poke Type",
21
+ field: "poke_type",
22
+ label: "Type",
23
+ options: ["grass", "fire"],
24
+ },
25
+ { param: "gen", field: "generation", label: "Generation", options: ["1", "2"] },
26
+ ],
27
+ ranges: [{ param: "xp", field: "base_experience", label: "Min XP" }],
28
+ defaultView: "grid",
29
+ views: ["grid", "list", "table"],
30
+ };
31
+
32
+ describe("listStateFromParams", () => {
33
+ it("reads nothing into the defaults", () => {
34
+ expect(listStateFromParams({}, controls)).toEqual({
35
+ search: "",
36
+ sort: "number",
37
+ facets: {},
38
+ ranges: {},
39
+ favoritesOnly: false,
40
+ view: "grid",
41
+ });
42
+ });
43
+
44
+ it("reads every control from its param", () => {
45
+ expect(
46
+ listStateFromParams(
47
+ { q: "bulba", sort: "name", type: "grass", xp: "60", fav: "1", view: "table" },
48
+ controls,
49
+ ),
50
+ ).toEqual({
51
+ search: "bulba",
52
+ sort: "name",
53
+ facets: { type: "grass" },
54
+ ranges: { xp: 60 },
55
+ favoritesOnly: true,
56
+ view: "table",
57
+ });
58
+ });
59
+
60
+ it("drops values the controls do not offer", () => {
61
+ expect(
62
+ listStateFromParams({ sort: "height", type: "water", xp: "-4", view: "kanban" }, controls),
63
+ ).toEqual(listStateFromParams({}, controls));
64
+ });
65
+ });
66
+
67
+ describe("listStateParams", () => {
68
+ it("writes defaults as absent params so a clean list has a clean URL", () => {
69
+ expect(
70
+ listStateParams(
71
+ { search: "", sort: "number", facets: { type: "" }, ranges: { xp: 0 }, view: "grid" },
72
+ controls,
73
+ ),
74
+ ).toEqual({ q: undefined, sort: undefined, type: undefined, xp: undefined, view: undefined });
75
+ });
76
+
77
+ it("writes a patch as only the params it touches", () => {
78
+ expect(listStateParams({ favoritesOnly: true }, controls)).toEqual({ fav: "1" });
79
+ expect(listStateParams({ sort: "name", facets: { type: "fire" } }, controls)).toEqual({
80
+ sort: "name",
81
+ type: "fire",
82
+ });
83
+ });
84
+
85
+ it("round-trips through listStateFromParams", () => {
86
+ const state = listStateFromParams(
87
+ { q: "25", sort: "name", gen: "2", xp: "100", fav: "1", view: "list" },
88
+ controls,
89
+ );
90
+ expect(listStateFromParams(listStateParams(state, controls), controls)).toEqual(state);
91
+ });
92
+ });
93
+
94
+ describe("listStateFilters", () => {
95
+ const empty = listStateFromParams({}, controls);
96
+
97
+ it("is undefined when nothing narrows, so the list reads its unfiltered query", () => {
98
+ expect(listStateFilters(empty, controls)).toBeUndefined();
99
+ expect(isListNarrowed(empty)).toBe(false);
100
+ });
101
+
102
+ it("turns each control into a bench filter", () => {
103
+ const state = {
104
+ ...empty,
105
+ search: " bulba ",
106
+ facets: { type: "grass", gen: "1" },
107
+ ranges: { xp: 60 },
108
+ favoritesOnly: true,
109
+ };
110
+ expect(
111
+ listStateFilters(state, controls, {
112
+ search: (query) => [["pokemon_name", "like", `%${query}%`]],
113
+ favorites: new Set(["ivysaur", "bulbasaur"]),
114
+ }),
115
+ ).toEqual([
116
+ ["pokemon_name", "like", "%bulba%"],
117
+ ["Pokemon Poke Type", "poke_type", "=", "grass"],
118
+ ["generation", "=", "1"],
119
+ ["base_experience", ">=", 60],
120
+ ["name", "in", ["bulbasaur", "ivysaur"]],
121
+ ]);
122
+ expect(isListNarrowed(state)).toBe(true);
123
+ });
124
+
125
+ it("keeps the given filters first", () => {
126
+ expect(
127
+ listStateFilters({ ...empty, ranges: { xp: 10 } }, controls, {
128
+ base: [["species", "=", "seed"]],
129
+ }),
130
+ ).toEqual([
131
+ ["species", "=", "seed"],
132
+ ["base_experience", ">=", 10],
133
+ ]);
134
+ });
135
+
136
+ it("filters favorites-only with no favorites to an empty match", () => {
137
+ expect(listStateFilters({ ...empty, favoritesOnly: true }, controls)).toEqual([
138
+ ["name", "in", []],
139
+ ]);
140
+ });
141
+ });
142
+
143
+ describe("listStateOrderBy", () => {
144
+ it("is the chosen sort's order, else the fallback", () => {
145
+ const empty = listStateFromParams({}, controls);
146
+ expect(listStateOrderBy({ ...empty, sort: "name" }, controls)).toEqual([
147
+ { field: "pokemon_name", order: Order.Asc },
148
+ ]);
149
+ expect(listStateOrderBy(empty, controls)).toEqual([{ field: "poke_id", order: Order.Asc }]);
150
+ expect(
151
+ listStateOrderBy(empty, { ...controls, sorts: [] }, [
152
+ { field: "modified", order: Order.Desc },
153
+ ]),
154
+ ).toEqual([{ field: "modified", order: Order.Desc }]);
155
+ });
156
+ });
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The state of a list page's controls (search, sort, facets, ranges,
3
+ * favorites, view) and its two codecs: URL params, so a refresh or a shared
4
+ * link reproduces the list, and bench filters, so the list reads exactly
5
+ * the rows the controls describe. Pure: no React, no IO.
6
+ *
7
+ * Params: `q` search, `sort` the sort's value (absent for the first sort),
8
+ * one param per facet and per range (absent when unset), `fav=1` favorites
9
+ * only, `view` (absent for the default view).
10
+ */
11
+ import type { Order } from "@multiplatform.one/frappe";
12
+
13
+ export type FrappeListViewMode = "list" | "table" | "grid";
14
+
15
+ export interface FrappeListSort {
16
+ /** URL value. */
17
+ value: string;
18
+ label: string;
19
+ orderBy: ReadonlyArray<{ field: string; order: Order }>;
20
+ }
21
+
22
+ /** Narrow the list to records carrying one value of a field. */
23
+ export interface FrappeListFacet {
24
+ /** URL param and state key. */
25
+ param: string;
26
+ /** Field whose value narrows. */
27
+ field: string;
28
+ /**
29
+ * A connection doctype whose rows carry `field` (Pokemon's types live on
30
+ * Pokemon Poke Type rows). Omitted, `field` is the list doctype's own.
31
+ */
32
+ doctype?: string;
33
+ /** Values offered, in display order. */
34
+ options: readonly string[];
35
+ /** Names the rail for assistive tech. */
36
+ label: string;
37
+ /** The chip that clears the facet. */
38
+ allLabel?: string;
39
+ optionLabel?: (value: string) => string;
40
+ /** A theme hue per value (`"grass"`), painting its chip from that ramp. */
41
+ tint?: (value: string) => string | undefined;
42
+ /** Connection field that orders one record's values (a `slot`). */
43
+ orderBy?: string;
44
+ }
45
+
46
+ /** Narrow the list to records whose numeric field is at least a value. */
47
+ export interface FrappeListRange {
48
+ param: string;
49
+ field: string;
50
+ label: string;
51
+ step?: number;
52
+ }
53
+
54
+ export interface FrappeListControls {
55
+ sorts?: readonly FrappeListSort[];
56
+ facets?: readonly FrappeListFacet[];
57
+ ranges?: readonly FrappeListRange[];
58
+ views?: readonly FrappeListViewMode[];
59
+ defaultView?: FrappeListViewMode;
60
+ }
61
+
62
+ export interface FrappeListState {
63
+ search: string;
64
+ /** The chosen sort's value; the first sort when none was chosen. */
65
+ sort: string | undefined;
66
+ /** Facet param to the value it narrows to. */
67
+ facets: Record<string, string>;
68
+ /** Range param to its minimum; absent or 0 is no minimum. */
69
+ ranges: Record<string, number>;
70
+ favoritesOnly: boolean;
71
+ view: FrappeListViewMode | undefined;
72
+ }
73
+
74
+ export type FrappeListParams = Record<string, string | undefined>;
75
+
76
+ export type FrappeListFilter = [string, string, unknown] | [string, string, string, unknown];
77
+
78
+ function positiveInt(value: string | undefined): number {
79
+ const parsed = Number.parseInt(value ?? "", 10);
80
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
81
+ }
82
+
83
+ export function listStateFromParams(
84
+ params: FrappeListParams,
85
+ controls: FrappeListControls,
86
+ ): FrappeListState {
87
+ const sorts = controls.sorts ?? [];
88
+ const sort = sorts.find((option) => option.value === params.sort)?.value ?? sorts[0]?.value;
89
+ const facets: Record<string, string> = {};
90
+ for (const facet of controls.facets ?? []) {
91
+ const value = params[facet.param];
92
+ if (value && facet.options.includes(value)) facets[facet.param] = value;
93
+ }
94
+ const ranges: Record<string, number> = {};
95
+ for (const range of controls.ranges ?? []) {
96
+ const value = positiveInt(params[range.param]);
97
+ if (value > 0) ranges[range.param] = value;
98
+ }
99
+ const views = controls.views ?? [];
100
+ const view = views.find((mode) => mode === params.view) ?? controls.defaultView;
101
+ return {
102
+ search: params.q ?? "",
103
+ sort,
104
+ facets,
105
+ ranges,
106
+ favoritesOnly: params.fav === "1",
107
+ view,
108
+ };
109
+ }
110
+
111
+ /** A state patch as the URL params it changes; a default value drops its param. */
112
+ export function listStateParams(
113
+ patch: Partial<FrappeListState>,
114
+ controls: FrappeListControls,
115
+ ): FrappeListParams {
116
+ const params: FrappeListParams = {};
117
+ if (patch.search !== undefined) params.q = patch.search.trim() || undefined;
118
+ if (patch.sort !== undefined) {
119
+ params.sort = patch.sort === controls.sorts?.[0]?.value ? undefined : patch.sort;
120
+ }
121
+ for (const [param, value] of Object.entries(patch.facets ?? {})) {
122
+ params[param] = value || undefined;
123
+ }
124
+ for (const [param, value] of Object.entries(patch.ranges ?? {})) {
125
+ params[param] = value > 0 ? String(value) : undefined;
126
+ }
127
+ if (patch.favoritesOnly !== undefined) params.fav = patch.favoritesOnly ? "1" : undefined;
128
+ if (patch.view !== undefined) {
129
+ params.view = patch.view === controls.defaultView ? undefined : patch.view;
130
+ }
131
+ return params;
132
+ }
133
+
134
+ /** Search, a facet, a range or favorites narrows the rows. */
135
+ export function isListNarrowed(state: FrappeListState): boolean {
136
+ return (
137
+ state.search.trim() !== "" ||
138
+ Object.values(state.facets).some(Boolean) ||
139
+ Object.values(state.ranges).some((value) => value > 0) ||
140
+ state.favoritesOnly
141
+ );
142
+ }
143
+
144
+ export interface ListStateFilterOptions {
145
+ /** Filters that always apply, ahead of the controls'. */
146
+ base?: readonly FrappeListFilter[];
147
+ /** The bench filters a search query becomes. */
148
+ search?: (query: string) => readonly FrappeListFilter[];
149
+ /** Starred names, for favorites only. */
150
+ favorites?: ReadonlySet<string>;
151
+ }
152
+
153
+ /** The bench filters the state describes, or undefined when nothing filters. */
154
+ export function listStateFilters(
155
+ state: FrappeListState,
156
+ controls: FrappeListControls,
157
+ options: ListStateFilterOptions = {},
158
+ ): FrappeListFilter[] | undefined {
159
+ const filters: FrappeListFilter[] = [...(options.base ?? [])];
160
+ const query = state.search.trim();
161
+ if (query && options.search) filters.push(...options.search(query));
162
+ for (const facet of controls.facets ?? []) {
163
+ const value = state.facets[facet.param];
164
+ if (!value) continue;
165
+ filters.push(
166
+ facet.doctype ? [facet.doctype, facet.field, "=", value] : [facet.field, "=", value],
167
+ );
168
+ }
169
+ for (const range of controls.ranges ?? []) {
170
+ const value = state.ranges[range.param] ?? 0;
171
+ if (value > 0) filters.push([range.field, ">=", value]);
172
+ }
173
+ if (state.favoritesOnly) filters.push(["name", "in", [...(options.favorites ?? [])].sort()]);
174
+ return filters.length > 0 ? filters : undefined;
175
+ }
176
+
177
+ export function listStateOrderBy(
178
+ state: FrappeListState,
179
+ controls: FrappeListControls,
180
+ fallback?: ReadonlyArray<{ field: string; order: Order }>,
181
+ ): ReadonlyArray<{ field: string; order: Order }> | undefined {
182
+ const sorts = controls.sorts ?? [];
183
+ return (sorts.find((option) => option.value === state.sort) ?? sorts[0])?.orderBy ?? fallback;
184
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * How a doctype's records present beyond their fields: a display title, a
3
+ * short code beside it, candidate images. Lists, insights and record pages
4
+ * draw a record the same way from one declaration.
5
+ */
6
+ export interface FrappeRecordLook<TDoc> {
7
+ /** The record's display name; defaults to the title field, then the name. */
8
+ title?: (doc: Partial<TDoc>) => string;
9
+ /** A short code shown beside the title ("#025"). */
10
+ code?: (doc: Partial<TDoc>) => string;
11
+ /** A muted line under the title on cards (a Pokemon's species). */
12
+ subtitle?: (doc: Partial<TDoc>) => string | undefined;
13
+ /** Image candidates in order; a failed load moves to the next. */
14
+ images?: (doc: Partial<TDoc>, baseURL: string | undefined) => string[];
15
+ /** Further images a record page shows after the first, one slide each. */
16
+ gallery?: (doc: Partial<TDoc>, baseURL: string | undefined) => FrappeRecordSlide[];
17
+ }
18
+
19
+ export interface FrappeRecordSlide {
20
+ src: string;
21
+ label: string;
22
+ /** Nearest-neighbor upscaling, for pixel art. */
23
+ pixelated?: boolean;
24
+ }
25
+
26
+ export function recordTitle<TDoc>(
27
+ doc: Partial<TDoc>,
28
+ look: FrappeRecordLook<TDoc> | undefined,
29
+ titleField?: string,
30
+ ): string {
31
+ const custom = look?.title?.(doc);
32
+ if (custom) return custom;
33
+ const row = doc as Record<string, unknown>;
34
+ const field = titleField ? row[titleField] : undefined;
35
+ if (typeof field === "string" && field) return field;
36
+ return typeof row.name === "string" ? row.name : "";
37
+ }
@@ -0,0 +1,223 @@
1
+ import type React from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import type { FrappeConnection } from "../frappeConnections";
4
+ import type { FrappeRouter } from "../frappeNavigation";
5
+ import { FrappeRowsReader, type FrappeRowsSnapshot } from "../FrappeRowsReader";
6
+ import type { FrappeRowsConnection } from "../useFrappeRows";
7
+ import { facetRead, readFacet, type FacetReading } from "./facetReading";
8
+ import { useFavorites, type UseFavorites } from "./favorites";
9
+ import {
10
+ listStateFromParams,
11
+ listStateParams,
12
+ type FrappeListControls,
13
+ type FrappeListParams,
14
+ type FrappeListState,
15
+ } from "./listState";
16
+
17
+ type Row = Record<string, unknown> & { name: string; doctype: string };
18
+ type Snapshot = FrappeRowsSnapshot<Row>;
19
+
20
+ export interface ListControlsOptions {
21
+ doctype: string;
22
+ controls: FrappeListControls;
23
+ connections: readonly FrappeConnection[] | undefined;
24
+ connection: FrappeRowsConnection | undefined;
25
+ /** The list's row ceiling; the base read (count, range ceilings) shares it. */
26
+ limit?: number;
27
+ /** Storage key of the favorites; undefined offers none. */
28
+ favoritesKey?: string;
29
+ /** Read and write the state through the router's query params. */
30
+ router?: FrappeRouter;
31
+ /** Read every record's name (and range fields) for counts and ceilings. */
32
+ readBase: boolean;
33
+ }
34
+
35
+ export interface ListControlsResult {
36
+ state: FrappeListState;
37
+ /** The search box's text, ahead of its debounced commit to `state.search`. */
38
+ searchText: string;
39
+ setSearchText: (text: string) => void;
40
+ update: (patch: Partial<FrappeListState>) => void;
41
+ /** Drops search, facets, ranges and favorites-only. */
42
+ clear: () => void;
43
+ favorites: UseFavorites;
44
+ facets: Readonly<Record<string, FacetReading>>;
45
+ /** Records the base read found, capped at the list's limit. */
46
+ total: number | undefined;
47
+ /** Highest value per range param in the base read. */
48
+ highest: Readonly<Record<string, number>>;
49
+ /** Mount these: the facet and base reads. */
50
+ readers: React.ReactNode;
51
+ }
52
+
53
+ function mergeState(prev: FrappeListState, patch: Partial<FrappeListState>): FrappeListState {
54
+ return {
55
+ ...prev,
56
+ ...patch,
57
+ facets: patch.facets ? { ...prev.facets, ...patch.facets } : prev.facets,
58
+ ranges: patch.ranges ? { ...prev.ranges, ...patch.ranges } : prev.ranges,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * The state behind a list page's controls, the favorites it filters by and
64
+ * the reads that label them (facet values and counts, the record count, the
65
+ * range ceilings). With a router that hands over query params the state
66
+ * seeds from them once and writes back after each commit: the write rides
67
+ * a post-commit effect, since a host router notifies its subscribers
68
+ * synchronously and a notify inside a render-phase update is a React error.
69
+ */
70
+ export function useListControls({
71
+ doctype,
72
+ controls,
73
+ connections,
74
+ connection,
75
+ limit,
76
+ favoritesKey,
77
+ router,
78
+ readBase,
79
+ }: ListControlsOptions): ListControlsResult {
80
+ const controlsRef = useRef(controls);
81
+ controlsRef.current = controls;
82
+ const setParams = router?.setSearchParams;
83
+ const setParamsRef = useRef(setParams);
84
+ setParamsRef.current = setParams;
85
+
86
+ const [state, setState] = useState<FrappeListState>(() =>
87
+ listStateFromParams(router?.searchParams ?? {}, controls),
88
+ );
89
+ const [searchText, setSearchText] = useState(state.search);
90
+ const pending = useRef<FrappeListParams>({});
91
+ const [flushTick, setFlushTick] = useState(0);
92
+
93
+ const update = useCallback((patch: Partial<FrappeListState>) => {
94
+ setState((prev) => mergeState(prev, patch));
95
+ if (!setParamsRef.current) return;
96
+ pending.current = { ...pending.current, ...listStateParams(patch, controlsRef.current) };
97
+ setFlushTick((tick) => tick + 1);
98
+ }, []);
99
+
100
+ useEffect(() => {
101
+ if (flushTick === 0) return;
102
+ const patch = pending.current;
103
+ pending.current = {};
104
+ if (Object.keys(patch).length === 0) return;
105
+ try {
106
+ setParamsRef.current?.(patch);
107
+ } catch {
108
+ // A host router that cannot take params right now; local state holds.
109
+ }
110
+ }, [flushTick]);
111
+
112
+ const clear = useCallback(() => {
113
+ setSearchText("");
114
+ const current = controlsRef.current;
115
+ update({
116
+ search: "",
117
+ favoritesOnly: false,
118
+ facets: Object.fromEntries((current.facets ?? []).map((facet) => [facet.param, ""])),
119
+ ranges: Object.fromEntries((current.ranges ?? []).map((range) => [range.param, 0])),
120
+ });
121
+ }, [update]);
122
+
123
+ const favorites = useFavorites(favoritesKey);
124
+
125
+ const [snapshots, setSnapshots] = useState<Record<string, Snapshot>>({});
126
+ const handlers = useRef(new Map<string, (snapshot: Snapshot) => void>());
127
+ const handlerFor = useCallback((key: string) => {
128
+ let handler = handlers.current.get(key);
129
+ if (!handler) {
130
+ handler = (snapshot: Snapshot) => setSnapshots((prev) => ({ ...prev, [key]: snapshot }));
131
+ handlers.current.set(key, handler);
132
+ }
133
+ return handler;
134
+ }, []);
135
+
136
+ const facetsKey = JSON.stringify(
137
+ (controls.facets ?? []).map(({ param, field, doctype: on, orderBy }) => [
138
+ param,
139
+ field,
140
+ on,
141
+ orderBy,
142
+ ]),
143
+ );
144
+ const rangeFieldsKey = JSON.stringify((controls.ranges ?? []).map((range) => range.field));
145
+ const connectionsKey = JSON.stringify(connections ?? null);
146
+
147
+ const reads = useMemo(() => {
148
+ const declared = JSON.parse(connectionsKey) as FrappeConnection[] | null;
149
+ const facetReads = (controlsRef.current.facets ?? []).map((facet) => ({
150
+ key: `facet:${facet.param}`,
151
+ facet,
152
+ ...facetRead(facet, doctype, declared ?? undefined, limit),
153
+ }));
154
+ const rangeFields = JSON.parse(rangeFieldsKey) as string[];
155
+ const base = readBase
156
+ ? {
157
+ key: "base",
158
+ doctype,
159
+ options: { fields: ["name", ...rangeFields], limit: limit ?? 0 },
160
+ }
161
+ : undefined;
162
+ return { facetReads: facetReads.filter((read) => read.ready), base };
163
+ // facetsKey stands for controls.facets, read through the ref.
164
+ }, [facetsKey, rangeFieldsKey, connectionsKey, doctype, limit, readBase]);
165
+
166
+ const facetReadings = useMemo(() => {
167
+ const readings: Record<string, FacetReading> = {};
168
+ for (const read of reads.facetReads) {
169
+ const rows = snapshots[read.key]?.rows ?? [];
170
+ readings[read.facet.param] = readFacet(read.facet, read.linkField, rows);
171
+ }
172
+ return readings;
173
+ }, [reads, snapshots]);
174
+
175
+ const baseRows = reads.base ? snapshots.base?.rows : undefined;
176
+ const { total, highest } = useMemo(() => {
177
+ if (!baseRows) return { total: undefined, highest: {} };
178
+ const peaks: Record<string, number> = {};
179
+ for (const range of controlsRef.current.ranges ?? []) {
180
+ peaks[range.param] = baseRows.reduce(
181
+ (acc, row) => Math.max(acc, Number(row[range.field]) || 0),
182
+ 0,
183
+ );
184
+ }
185
+ return { total: baseRows.length, highest: peaks };
186
+ }, [baseRows]);
187
+
188
+ const readers = (
189
+ <>
190
+ {reads.facetReads.map((read) => (
191
+ <FrappeRowsReader
192
+ key={read.key}
193
+ connection={connection}
194
+ doctype={read.doctype}
195
+ options={read.options as never}
196
+ onChange={handlerFor(read.key)}
197
+ />
198
+ ))}
199
+ {reads.base ? (
200
+ <FrappeRowsReader
201
+ key="base"
202
+ connection={connection}
203
+ doctype={reads.base.doctype}
204
+ options={reads.base.options as never}
205
+ onChange={handlerFor("base")}
206
+ />
207
+ ) : null}
208
+ </>
209
+ );
210
+
211
+ return {
212
+ state,
213
+ searchText,
214
+ setSearchText,
215
+ update,
216
+ clear,
217
+ favorites,
218
+ facets: facetReadings,
219
+ total,
220
+ highest,
221
+ readers,
222
+ };
223
+ }