@conduction/nextcloud-vue 2.80.0 → 2.81.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 (290) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +140 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -3
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
  15. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  16. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  17. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  18. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  19. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  20. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  21. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  22. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  23. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  24. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  25. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  26. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  27. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  28. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  29. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  30. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  31. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
  33. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  35. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
  36. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  38. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
  39. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  41. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
  43. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
  45. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +72 -0
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
  49. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
  53. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
  54. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
  55. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
  56. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +131 -0
  57. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +80 -5
  60. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  61. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
  62. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
  64. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  65. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
  66. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  67. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
  68. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  69. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
  70. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  71. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
  72. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  73. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
  74. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  75. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -2
  76. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  77. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
  78. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  79. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
  80. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  81. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  82. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
  83. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
  84. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  85. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  86. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  87. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  90. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
  91. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  92. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
  93. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  94. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
  95. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  96. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  97. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  98. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  99. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  100. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  101. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  102. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  103. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  104. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +30 -6
  105. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  106. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
  107. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  109. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  110. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  111. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  112. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  113. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  114. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  115. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  116. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  117. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  118. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  119. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  120. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  121. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  122. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  123. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  124. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  125. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  126. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  127. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  128. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  129. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
  130. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  131. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
  132. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  133. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  134. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  135. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  136. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  137. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  138. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  139. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  140. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  141. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
  142. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  143. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
  144. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  145. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  146. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  147. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  148. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  149. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  150. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  151. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  152. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  153. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  154. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  155. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  156. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  157. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  158. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  159. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
  160. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  161. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  162. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  163. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
  164. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
  165. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  166. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +96 -50
  167. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  168. package/dist/esm/composables/useListView.js +4 -4
  169. package/dist/esm/composables/useListView.js.map +1 -1
  170. package/dist/esm/index.js +17 -1
  171. package/dist/esm/index.js.map +1 -1
  172. package/dist/esm/l10n/en.json.js +9 -1
  173. package/dist/esm/l10n/en.json.js.map +1 -1
  174. package/dist/esm/l10n/nl.json.js +55 -1
  175. package/dist/esm/l10n/nl.json.js.map +1 -1
  176. package/dist/esm/nextcloud-vue.css +1041 -12
  177. package/dist/esm/utils/contentHeaderLink.js +51 -0
  178. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  179. package/dist/esm/utils/facets.js +75 -3
  180. package/dist/esm/utils/facets.js.map +1 -1
  181. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  182. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  183. package/dist/esm/utils/fillTemplate.js +7 -1
  184. package/dist/esm/utils/fillTemplate.js.map +1 -1
  185. package/dist/esm/utils/initials.js +32 -0
  186. package/dist/esm/utils/initials.js.map +1 -0
  187. package/dist/esm/utils/libraryWidgetKeys.js +7 -0
  188. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  189. package/dist/esm/utils/objectWrite.js +59 -0
  190. package/dist/esm/utils/objectWrite.js.map +1 -0
  191. package/dist/esm/utils/pageControls.js +81 -0
  192. package/dist/esm/utils/pageControls.js.map +1 -0
  193. package/dist/esm/utils/personalLenses.js +40 -3
  194. package/dist/esm/utils/personalLenses.js.map +1 -1
  195. package/dist/esm/utils/resolveFilterTokens.js +1 -1
  196. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  197. package/dist/esm/utils/resolveManifestTokens.js +2 -0
  198. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  199. package/dist/esm/utils/sentinelTokens.js +19 -1
  200. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  201. package/dist/esm/utils/userGroupsPeek.js +91 -0
  202. package/dist/esm/utils/userGroupsPeek.js.map +1 -0
  203. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  204. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  205. package/dist/esm/utils/visibleWhen.js +57 -5
  206. package/dist/esm/utils/visibleWhen.js.map +1 -1
  207. package/dist/esm/utils/widgetDispatch.js +1 -1
  208. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  209. package/dist/esm/utils/widgetVisibility.js +4 -57
  210. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  211. package/dist/nextcloud-vue.cjs.js +13589 -7833
  212. package/dist/nextcloud-vue.cjs.js.map +2 -2
  213. package/dist/nextcloud-vue.css +1041 -12
  214. package/l10n/en.json +9 -1
  215. package/l10n/nl.json +55 -1
  216. package/package.json +2 -1
  217. package/scripts/check-docs-mdx.mjs +108 -0
  218. package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
  219. package/src/components/CnAppRoot/CnAppRoot.md +4 -2
  220. package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
  221. package/src/components/CnBannerWidget/CnBannerWidget.vue +205 -20
  222. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  223. package/src/components/CnCellRenderer/CnCellRenderer.vue +47 -4
  224. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  225. package/src/components/CnChipsFieldWidget/index.js +25 -0
  226. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  227. package/src/components/CnChoicePickerWidget/index.js +26 -0
  228. package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
  229. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
  230. package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
  231. package/src/components/CnDataTable/CnDataTable.vue +21 -12
  232. package/src/components/CnDetailPage/CnDetailPage.vue +89 -3
  233. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
  234. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
  235. package/src/components/CnFactsWidget/CnFactsWidget.vue +142 -2
  236. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +268 -6
  237. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
  238. package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
  239. package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
  240. package/src/components/CnFormPage/CnFormPage.vue +81 -0
  241. package/src/components/CnIndexPage/CnIndexPage.vue +56 -4
  242. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
  243. package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
  244. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  245. package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
  246. package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
  247. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  248. package/src/components/CnRadioCardsWidget/index.js +27 -0
  249. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +55 -6
  250. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  251. package/src/components/CnSavedViewsWidget/index.js +29 -0
  252. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  253. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  254. package/src/components/CnSettingsFormWidget/index.js +26 -0
  255. package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
  256. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
  257. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  258. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  259. package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
  260. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  261. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  262. package/src/components/CnToggleTableWidget/index.js +26 -0
  263. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  264. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
  265. package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  266. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +309 -11
  267. package/src/components/index.js +15 -0
  268. package/src/composables/useListView.js +4 -4
  269. package/src/css/look-board-detail.css +36 -2
  270. package/src/css/look-board-index.css +39 -8
  271. package/src/css/look-board.css +72 -0
  272. package/src/index.js +15 -0
  273. package/src/schemas/app-manifest-v2.schema.json +93 -9
  274. package/src/utils/contentHeaderLink.js +48 -0
  275. package/src/utils/facets.js +74 -2
  276. package/src/utils/fetchFilterCounts.js +39 -4
  277. package/src/utils/fillTemplate.js +7 -1
  278. package/src/utils/initials.js +29 -0
  279. package/src/utils/libraryWidgetKeys.js +7 -0
  280. package/src/utils/objectWrite.js +55 -0
  281. package/src/utils/pageControls.js +78 -0
  282. package/src/utils/personalLenses.js +39 -2
  283. package/src/utils/resolveFilterTokens.js +3 -1
  284. package/src/utils/resolveManifestTokens.js +2 -0
  285. package/src/utils/sentinelTokens.js +19 -1
  286. package/src/utils/userGroupsPeek.js +87 -0
  287. package/src/utils/validateManifestV2.compiled.js +3 -3
  288. package/src/utils/visibleWhen.js +56 -4
  289. package/src/utils/widgetDispatch.js +1 -1
  290. package/src/utils/widgetVisibility.js +7 -56
@@ -7,7 +7,7 @@ import { readPath } from '../../utils/readPath.js';
7
7
  * Renders CnDocumentReviewList from a list on the bound record:
8
8
  * `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.
9
9
  * A row action with a `route` opens that route with the row's key as `id`.
10
- * Resolved by its registry key, not exported.
10
+ * Resolved by its registry key, and exported for an app that renders it itself.
11
11
  */
12
12
  var script = {
13
13
  name: 'CnDocumentReviewWidget',
@@ -1 +1 @@
1
- {"version":3,"file":"CnDocumentReviewWidget.vue2.js","sources":["../../../../src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnDocumentReviewList\n\t\t\t:documents=\"documents\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:nameField=\"content.nameField || 'name'\"\n\t\t\t:metaField=\"content.metaField || 'meta'\"\n\t\t\t:statusField=\"content.statusField || 'reviewStatus'\"\n\t\t\t:hrefField=\"content.hrefField || 'href'\"\n\t\t\t:rowKey=\"content.rowKey || 'id'\"\n\t\t\t:statuses=\"content.statuses || []\"\n\t\t\t:defaultStatus=\"content.defaultStatus || ''\"\n\t\t\t:rowActionLabel=\"rowActionLabel\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@rowAction=\"onRowAction\" />\n\t</div>\n</template>\n\n<script>\nimport CnDocumentReviewList from './CnDocumentReviewList.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnDocumentReviewWidget: the `document-review` detail widget type.\n *\n * Renders CnDocumentReviewList from a list on the bound record:\n * `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.\n * A row action with a `route` opens that route with the row's key as `id`.\n * Resolved by its registry key, not exported.\n */\nexport default {\n\tname: 'CnDocumentReviewWidget',\n\n\tcomponents: {\n\t\tCnDocumentReviewList,\n\t},\n\n\tprops: {\n\t\t/** The widget's content. `field` is the dot-path to the documents on the record. */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The documents read off the record.\n\t\t *\n\t\t * @return {Array<object>} The documents, or an empty list.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tdocuments() {\n\t\t\tconst value = readPath(this.objectData || {}, this.content.field || 'documents')\n\t\t\treturn Array.isArray(value) ? value : []\n\t\t},\n\n\t\t/**\n\t\t * The row action's label. Empty, and so no button, unless the content declares one.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\trowActionLabel() {\n\t\t\tconst label = this.content.rowAction?.label\n\t\t\treturn typeof label === 'string' ? label : ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Open the route the row action names, with the row's key as `id`.\n\t\t *\n\t\t * @param {object} document The row's document.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tonRowAction(document) {\n\t\t\tconst route = this.content.rowAction?.route\n\t\t\tconst id = readPath(document, this.content.rowKey || 'id')\n\t\t\tif (typeof route !== 'string' || route === '' || !this.$router || id === undefined || id === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push({ name: route, params: { id: String(id) } })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-case-widget-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tpadding: calc(5 * var(--default-grid-baseline));\n}\n</style>\n"],"names":["CnDocumentReviewList"],"mappings":";;;AA2BA;;AAEC;;AAEA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,wBAAwB;;CAE9B,UAAU,EAAE;AACX,wBAAAA,UAAoB;EACpB;;AAED,CAAA,KAAK,EAAE;AACN;AACA,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,CAAC;GACnB;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;EACD;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;;AAEA;;EAED,SAAS,GAAG;AACX,GAAA,MAAM,KAAI,GAAI,QAAQ,CAAC,IAAI,CAAC,UAAS,IAAK,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,KAAM,IAAG,WAAW,EAAA;AAC/E,GAAA,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAA,GAAI,KAAI,GAAI,EAAC;GACvC;;AAED;AACC;AACA;;AAEA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAI;AAC1C,GAAA,OAAO,OAAO,KAAM,KAAI,QAAS,GAAE,KAAI,GAAI,EAAC;GAC5C;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;AAEC;AACA;;AAEA;;AAED,EAAA,WAAW,CAAC,QAAQ,EAAE;AACrB,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAI;GAC1C,MAAM,EAAG,GAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,MAAK,IAAK,IAAI,EAAA;AACzD,GAAA,IAAI,OAAO,KAAI,KAAM,QAAS,IAAG,UAAU,EAAC,IAAK,CAAC,IAAI,CAAC,OAAM,IAAK,EAAG,KAAI,aAAa,EAAC,KAAM,IAAI,EAAE;IAClG,MAAK;AACN,IAAA;GACA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAE,EAAA,EAAG,EAAA;GAC7D;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnDocumentReviewWidget.vue2.js","sources":["../../../../src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnDocumentReviewList\n\t\t\t:documents=\"documents\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:nameField=\"content.nameField || 'name'\"\n\t\t\t:metaField=\"content.metaField || 'meta'\"\n\t\t\t:statusField=\"content.statusField || 'reviewStatus'\"\n\t\t\t:hrefField=\"content.hrefField || 'href'\"\n\t\t\t:rowKey=\"content.rowKey || 'id'\"\n\t\t\t:statuses=\"content.statuses || []\"\n\t\t\t:defaultStatus=\"content.defaultStatus || ''\"\n\t\t\t:rowActionLabel=\"rowActionLabel\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@rowAction=\"onRowAction\" />\n\t</div>\n</template>\n\n<script>\nimport CnDocumentReviewList from './CnDocumentReviewList.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnDocumentReviewWidget: the `document-review` detail widget type.\n *\n * Renders CnDocumentReviewList from a list on the bound record:\n * `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.\n * A row action with a `route` opens that route with the row's key as `id`.\n * Resolved by its registry key, and exported for an app that renders it itself.\n */\nexport default {\n\tname: 'CnDocumentReviewWidget',\n\n\tcomponents: {\n\t\tCnDocumentReviewList,\n\t},\n\n\tprops: {\n\t\t/** The widget's content. `field` is the dot-path to the documents on the record. */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The documents read off the record.\n\t\t *\n\t\t * @return {Array<object>} The documents, or an empty list.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tdocuments() {\n\t\t\tconst value = readPath(this.objectData || {}, this.content.field || 'documents')\n\t\t\treturn Array.isArray(value) ? value : []\n\t\t},\n\n\t\t/**\n\t\t * The row action's label. Empty, and so no button, unless the content declares one.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\trowActionLabel() {\n\t\t\tconst label = this.content.rowAction?.label\n\t\t\treturn typeof label === 'string' ? label : ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Open the route the row action names, with the row's key as `id`.\n\t\t *\n\t\t * @param {object} document The row's document.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tonRowAction(document) {\n\t\t\tconst route = this.content.rowAction?.route\n\t\t\tconst id = readPath(document, this.content.rowKey || 'id')\n\t\t\tif (typeof route !== 'string' || route === '' || !this.$router || id === undefined || id === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push({ name: route, params: { id: String(id) } })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-case-widget-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tpadding: calc(5 * var(--default-grid-baseline));\n}\n</style>\n"],"names":["CnDocumentReviewList"],"mappings":";;;AA2BA;;AAEC;;AAEA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,wBAAwB;;CAE9B,UAAU,EAAE;AACX,wBAAAA,UAAoB;EACpB;;AAED,CAAA,KAAK,EAAE;AACN;AACA,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,CAAC;GACnB;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;EACD;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;;AAEA;;EAED,SAAS,GAAG;AACX,GAAA,MAAM,KAAI,GAAI,QAAQ,CAAC,IAAI,CAAC,UAAS,IAAK,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,KAAM,IAAG,WAAW,EAAA;AAC/E,GAAA,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAA,GAAI,KAAI,GAAI,EAAC;GACvC;;AAED;AACC;AACA;;AAEA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAI;AAC1C,GAAA,OAAO,OAAO,KAAM,KAAI,QAAS,GAAE,KAAI,GAAI,EAAC;GAC5C;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;AAEC;AACA;;AAEA;;AAED,EAAA,WAAW,CAAC,QAAQ,EAAE;AACrB,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAI;GAC1C,MAAM,EAAG,GAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,MAAK,IAAK,IAAI,EAAA;AACzD,GAAA,IAAI,OAAO,KAAI,KAAM,QAAS,IAAG,UAAU,EAAC,IAAK,CAAC,IAAI,CAAC,OAAM,IAAK,EAAG,KAAI,aAAa,EAAC,KAAM,IAAI,EAAE;IAClG,MAAK;AACN,IAAA;GACA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAE,EAAA,EAAG,EAAA;GAC7D;EACD;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnDocumentReviewWidget.vue3.js","sources":["../../../../src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue?vue&type=template&id=22291596&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnDocumentReviewList\n\t\t\t:documents=\"documents\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:nameField=\"content.nameField || 'name'\"\n\t\t\t:metaField=\"content.metaField || 'meta'\"\n\t\t\t:statusField=\"content.statusField || 'reviewStatus'\"\n\t\t\t:hrefField=\"content.hrefField || 'href'\"\n\t\t\t:rowKey=\"content.rowKey || 'id'\"\n\t\t\t:statuses=\"content.statuses || []\"\n\t\t\t:defaultStatus=\"content.defaultStatus || ''\"\n\t\t\t:rowActionLabel=\"rowActionLabel\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@rowAction=\"onRowAction\" />\n\t</div>\n</template>\n\n<script>\nimport CnDocumentReviewList from './CnDocumentReviewList.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnDocumentReviewWidget: the `document-review` detail widget type.\n *\n * Renders CnDocumentReviewList from a list on the bound record:\n * `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.\n * A row action with a `route` opens that route with the row's key as `id`.\n * Resolved by its registry key, not exported.\n */\nexport default {\n\tname: 'CnDocumentReviewWidget',\n\n\tcomponents: {\n\t\tCnDocumentReviewList,\n\t},\n\n\tprops: {\n\t\t/** The widget's content. `field` is the dot-path to the documents on the record. */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The documents read off the record.\n\t\t *\n\t\t * @return {Array<object>} The documents, or an empty list.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tdocuments() {\n\t\t\tconst value = readPath(this.objectData || {}, this.content.field || 'documents')\n\t\t\treturn Array.isArray(value) ? value : []\n\t\t},\n\n\t\t/**\n\t\t * The row action's label. Empty, and so no button, unless the content declares one.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\trowActionLabel() {\n\t\t\tconst label = this.content.rowAction?.label\n\t\t\treturn typeof label === 'string' ? label : ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Open the route the row action names, with the row's key as `id`.\n\t\t *\n\t\t * @param {object} document The row's document.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tonRowAction(document) {\n\t\t\tconst route = this.content.rowAction?.route\n\t\t\tconst id = readPath(document, this.content.rowKey || 'id')\n\t\t\tif (typeof route !== 'string' || route === '' || !this.$router || id === undefined || id === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push({ name: route, params: { id: String(id) } })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-case-widget-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tpadding: calc(5 * var(--default-grid-baseline));\n}\n</style>\n"],"names":["_openBlock","_createElementBlock"],"mappings":";;AAWM,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;;;AAA9B,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;;AAEC,MAAA,SAAS,EAAE,QAAS,CAAA,SAAA;MACpB,KAAK,EAAE,MAAO,CAAA,OAAA,CAAC,KAAK,IAAA,EAAA;MACpB,SAAS,EAAE,MAAO,CAAA,OAAA,CAAC,SAAS,IAAA,MAAA;MAC5B,SAAS,EAAE,MAAO,CAAA,OAAA,CAAC,SAAS,IAAA,MAAA;MAC5B,WAAW,EAAE,MAAO,CAAA,OAAA,CAAC,WAAS,IAAA,cAAA;MAC9B,SAAS,EAAE,MAAO,CAAA,OAAA,CAAC,SAAU,IAAA,MAAA;MAC7B,MAAM,EAAE,MAAO,CAAA,OAAA,CAAC,MAAM,IAAA,IAAA;MACtB,QAAE,EAAA,MAAA,CAAA,OAAA,CAAA,QAAA,IAAA,EAAA;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnDocumentReviewWidget.vue3.js","sources":["../../../../src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue?vue&type=template&id=22291596&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-case-widget-card\">\n\t\t<CnDocumentReviewList\n\t\t\t:documents=\"documents\"\n\t\t\t:title=\"content.title || ''\"\n\t\t\t:nameField=\"content.nameField || 'name'\"\n\t\t\t:metaField=\"content.metaField || 'meta'\"\n\t\t\t:statusField=\"content.statusField || 'reviewStatus'\"\n\t\t\t:hrefField=\"content.hrefField || 'href'\"\n\t\t\t:rowKey=\"content.rowKey || 'id'\"\n\t\t\t:statuses=\"content.statuses || []\"\n\t\t\t:defaultStatus=\"content.defaultStatus || ''\"\n\t\t\t:rowActionLabel=\"rowActionLabel\"\n\t\t\t:emptyText=\"content.emptyText || undefined\"\n\t\t\t@rowAction=\"onRowAction\" />\n\t</div>\n</template>\n\n<script>\nimport CnDocumentReviewList from './CnDocumentReviewList.vue'\nimport { readPath } from '../../utils/readPath.js'\n\n/**\n * CnDocumentReviewWidget: the `document-review` detail widget type.\n *\n * Renders CnDocumentReviewList from a list on the bound record:\n * `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.\n * A row action with a `route` opens that route with the row's key as `id`.\n * Resolved by its registry key, and exported for an app that renders it itself.\n */\nexport default {\n\tname: 'CnDocumentReviewWidget',\n\n\tcomponents: {\n\t\tCnDocumentReviewList,\n\t},\n\n\tprops: {\n\t\t/** The widget's content. `field` is the dot-path to the documents on the record. */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The documents read off the record.\n\t\t *\n\t\t * @return {Array<object>} The documents, or an empty list.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tdocuments() {\n\t\t\tconst value = readPath(this.objectData || {}, this.content.field || 'documents')\n\t\t\treturn Array.isArray(value) ? value : []\n\t\t},\n\n\t\t/**\n\t\t * The row action's label. Empty, and so no button, unless the content declares one.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\trowActionLabel() {\n\t\t\tconst label = this.content.rowAction?.label\n\t\t\treturn typeof label === 'string' ? label : ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Open the route the row action names, with the row's key as `id`.\n\t\t *\n\t\t * @param {object} document The row's document.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-document-review-list\n\t\t */\n\t\tonRowAction(document) {\n\t\t\tconst route = this.content.rowAction?.route\n\t\t\tconst id = readPath(document, this.content.rowKey || 'id')\n\t\t\tif (typeof route !== 'string' || route === '' || !this.$router || id === undefined || id === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push({ name: route, params: { id: String(id) } })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-case-widget-card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tpadding: calc(5 * var(--default-grid-baseline));\n}\n</style>\n"],"names":["_openBlock","_createElementBlock"],"mappings":";;AAWM,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;;;AAA9B,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;;AAEC,MAAA,SAAS,EAAE,QAAS,CAAA,SAAA;MACpB,KAAK,EAAE,MAAO,CAAA,OAAA,CAAC,KAAK,IAAA,EAAA;MACpB,SAAS,EAAE,MAAO,CAAA,OAAA,CAAC,SAAS,IAAA,MAAA;MAC5B,SAAS,EAAE,MAAO,CAAA,OAAA,CAAC,SAAS,IAAA,MAAA;MAC5B,WAAW,EAAE,MAAO,CAAA,OAAA,CAAC,WAAS,IAAA,cAAA;MAC9B,SAAS,EAAE,MAAO,CAAA,OAAA,CAAC,SAAU,IAAA,MAAA;MAC7B,MAAM,EAAE,MAAO,CAAA,OAAA,CAAC,MAAM,IAAA,IAAA;MACtB,QAAE,EAAA,MAAA,CAAA,OAAA,CAAA,QAAA,IAAA,EAAA;;;;;;;;;;;"}
@@ -1,6 +1,6 @@
1
1
  import { translate } from '@nextcloud/l10n';
2
2
  import { NcButton, NcSelect, NcTextField, NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue';
3
- import { facetOptionLabel } from '../../utils/facets.js';
3
+ import { facetOptions } from '../../utils/facets.js';
4
4
  import { filtersFromSchema } from '../../utils/schema.js';
5
5
 
6
6
  /**
@@ -113,12 +113,12 @@ var script = {
113
113
 
114
114
  getFilterOptions(filter) {
115
115
  // Use facet data if available (live values with counts)
116
- const facet = this.facetData[filter.key];
117
- if (facet?.values?.length > 0) {
118
- return facet.values.map((v) => ({
119
- id: v.value,
120
- label: facetOptionLabel(v),
121
- }))
116
+ const live = facetOptions(
117
+ this.facetData[filter.key],
118
+ (count) => translate('nextcloud-vue', 'No value ({count})', { count }),
119
+ );
120
+ if (live !== null) {
121
+ return live
122
122
  }
123
123
  // Fall back to static enum options from schema
124
124
  return filter.options || []
@@ -1 +1 @@
1
- {"version":3,"file":"CnFacetSidebar.vue2.js","sources":["../../../../src/components/CnFacetSidebar/CnFacetSidebar.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-facet-sidebar\">\n\t\t<div class=\"cn-facet-sidebar__header\">\n\t\t\t<h3 class=\"cn-facet-sidebar__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"hasActiveFilters\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tclass=\"cn-facet-sidebar__clear\"\n\t\t\t\t@click=\"$emit('clear-all')\">\n\t\t\t\t{{ clearLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading state -->\n\t\t<div v-if=\"loading\" class=\"cn-facet-sidebar__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t</div>\n\n\t\t<!-- Filter groups -->\n\t\t<div v-else class=\"cn-facet-sidebar__filters\">\n\t\t\t<div\n\t\t\t\tv-for=\"filter in effectiveFilters\"\n\t\t\t\t:key=\"filter.key\"\n\t\t\t\tclass=\"cn-facet-sidebar__group\">\n\t\t\t\t<label class=\"cn-facet-sidebar__label\">{{ filter.label }}</label>\n\n\t\t\t\t<!-- Checkbox filter (boolean) -->\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"filter.type === 'checkbox'\"\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) === true\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\">\n\t\t\t\t\t{{ filter.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<!-- Select filter (enum / facet values) -->\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"filter.type === 'select'\"\n\t\t\t\t\tclass=\"cn-facet-sidebar__select\"\n\t\t\t\t\t:modelValue=\"getSelectedOptions(filter)\"\n\t\t\t\t\t:options=\"getFilterOptions(filter)\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:inputLabel=\"filter.label\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t:keepOpen=\"true\"\n\t\t\t\t\t:clearable=\"true\"\n\t\t\t\t\t@update:modelValue=\"onSelectChange(filter.key, $event)\" />\n\n\t\t\t\t<!-- Text filter (fallback) -->\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) || ''\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:label=\"filter.label\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\" />\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextField } from '@nextcloud/vue'\nimport { facetOptionLabel } from '../../utils/facets.js'\nimport { filtersFromSchema } from '../../utils/schema.js'\n\n/**\n * CnFacetSidebar — Auto-generated faceted search sidebar from schema properties.\n *\n * Reads properties marked `facetable: true` in the schema and generates\n * appropriate filter widgets. Accepts live facet data from the API for\n * dynamic option values with counts.\n *\n * ```vue\n * <CnFacetSidebar\n * :schema=\"schema\"\n * :facet-data=\"facetData\"\n * :active-filters=\"filters\"\n * @filter-change=\"onFilterChange\"\n * @clear-all=\"clearFilters\" />\n * ```\n */\nexport default {\n\tname: 'CnFacetSidebar',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Filter\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Schema definition — reads facetable properties */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Live facet data from the API: `{ fieldName: { values: [{ value, count?, label? }] } }` */\n\t\tfacetData: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Current active filters: { fieldName: value | [values] } */\n\t\tactiveFilters: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether facet data is loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Sidebar title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Filters'),\n\t\t},\n\n\t\t/** Clear all button label */\n\t\tclearLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Clear all'),\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is an admin.\n\t\t * When false, schema properties with `adminOnly: true` are hidden from filters.\n\t\t */\n\t\tuserIsAdmin: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: ['clear-all', 'filter-change'],\n\n\tcomputed: {\n\t\teffectiveFilters() {\n\t\t\treturn filtersFromSchema(this.schema, { isAdmin: this.userIsAdmin, translate: this.cnTranslate })\n\t\t},\n\n\t\thasActiveFilters() {\n\t\t\treturn Object.values(this.activeFilters).some((v) => {\n\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\treturn v.length > 0\n\t\t\t\t}\n\t\t\t\treturn v !== null && v !== undefined && v !== '' && v !== false\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\tgetFilterValue(key) {\n\t\t\treturn this.activeFilters[key] ?? null\n\t\t},\n\n\t\tgetFilterOptions(filter) {\n\t\t\t// Use facet data if available (live values with counts)\n\t\t\tconst facet = this.facetData[filter.key]\n\t\t\tif (facet?.values?.length > 0) {\n\t\t\t\treturn facet.values.map((v) => ({\n\t\t\t\t\tid: v.value,\n\t\t\t\t\tlabel: facetOptionLabel(v),\n\t\t\t\t}))\n\t\t\t}\n\t\t\t// Fall back to static enum options from schema\n\t\t\treturn filter.options || []\n\t\t},\n\n\t\tgetSelectedOptions(filter) {\n\t\t\tconst value = this.getFilterValue(filter.key)\n\t\t\tif (!value) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst values = Array.isArray(value) ? value : [value]\n\t\t\tconst options = this.getFilterOptions(filter)\n\t\t\treturn values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })\n\t\t},\n\n\t\tonSelectChange(key, selected) {\n\t\t\tconst values = selected ? selected.map((o) => o.id) : []\n\t\t\tthis.$emit('filter-change', { key, values })\n\t\t},\n\n\t\tonFilterChange(key, value) {\n\t\t\tthis.$emit('filter-change', { key, values: value })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-facet-sidebar {\n\tpadding: 16px;\n\tborder-right: 1px solid var(--color-border);\n\tmin-width: 240px;\n\tmax-width: 300px;\n}\n\n.cn-facet-sidebar__header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tmargin-bottom: 16px;\n}\n\n.cn-facet-sidebar__title {\n\tmargin: 0;\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n.cn-facet-sidebar__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 20px;\n}\n\n.cn-facet-sidebar__filters {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-facet-sidebar__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-facet-sidebar__label {\n\tfont-size: 12px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-facet-sidebar__select {\n\twidth: 100%;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnFacetSidebar.vue2.js","sources":["../../../../src/components/CnFacetSidebar/CnFacetSidebar.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-facet-sidebar\">\n\t\t<div class=\"cn-facet-sidebar__header\">\n\t\t\t<h3 class=\"cn-facet-sidebar__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"hasActiveFilters\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tclass=\"cn-facet-sidebar__clear\"\n\t\t\t\t@click=\"$emit('clear-all')\">\n\t\t\t\t{{ clearLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading state -->\n\t\t<div v-if=\"loading\" class=\"cn-facet-sidebar__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t</div>\n\n\t\t<!-- Filter groups -->\n\t\t<div v-else class=\"cn-facet-sidebar__filters\">\n\t\t\t<div\n\t\t\t\tv-for=\"filter in effectiveFilters\"\n\t\t\t\t:key=\"filter.key\"\n\t\t\t\tclass=\"cn-facet-sidebar__group\">\n\t\t\t\t<label class=\"cn-facet-sidebar__label\">{{ filter.label }}</label>\n\n\t\t\t\t<!-- Checkbox filter (boolean) -->\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"filter.type === 'checkbox'\"\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) === true\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\">\n\t\t\t\t\t{{ filter.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<!-- Select filter (enum / facet values) -->\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"filter.type === 'select'\"\n\t\t\t\t\tclass=\"cn-facet-sidebar__select\"\n\t\t\t\t\t:modelValue=\"getSelectedOptions(filter)\"\n\t\t\t\t\t:options=\"getFilterOptions(filter)\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:inputLabel=\"filter.label\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t:keepOpen=\"true\"\n\t\t\t\t\t:clearable=\"true\"\n\t\t\t\t\t@update:modelValue=\"onSelectChange(filter.key, $event)\" />\n\n\t\t\t\t<!-- Text filter (fallback) -->\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) || ''\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:label=\"filter.label\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\" />\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextField } from '@nextcloud/vue'\nimport { facetOptions } from '../../utils/facets.js'\nimport { filtersFromSchema } from '../../utils/schema.js'\n\n/**\n * CnFacetSidebar — Auto-generated faceted search sidebar from schema properties.\n *\n * Reads properties marked `facetable: true` in the schema and generates\n * appropriate filter widgets. Accepts live facet data from the API for\n * dynamic option values with counts.\n *\n * ```vue\n * <CnFacetSidebar\n * :schema=\"schema\"\n * :facet-data=\"facetData\"\n * :active-filters=\"filters\"\n * @filter-change=\"onFilterChange\"\n * @clear-all=\"clearFilters\" />\n * ```\n */\nexport default {\n\tname: 'CnFacetSidebar',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Filter\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Schema definition — reads facetable properties */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Live facet data from the API: `{ fieldName: { values: [{ value, count?, label? }] } }` */\n\t\tfacetData: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Current active filters: { fieldName: value | [values] } */\n\t\tactiveFilters: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether facet data is loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Sidebar title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Filters'),\n\t\t},\n\n\t\t/** Clear all button label */\n\t\tclearLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Clear all'),\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is an admin.\n\t\t * When false, schema properties with `adminOnly: true` are hidden from filters.\n\t\t */\n\t\tuserIsAdmin: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: ['clear-all', 'filter-change'],\n\n\tcomputed: {\n\t\teffectiveFilters() {\n\t\t\treturn filtersFromSchema(this.schema, { isAdmin: this.userIsAdmin, translate: this.cnTranslate })\n\t\t},\n\n\t\thasActiveFilters() {\n\t\t\treturn Object.values(this.activeFilters).some((v) => {\n\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\treturn v.length > 0\n\t\t\t\t}\n\t\t\t\treturn v !== null && v !== undefined && v !== '' && v !== false\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\tgetFilterValue(key) {\n\t\t\treturn this.activeFilters[key] ?? null\n\t\t},\n\n\t\tgetFilterOptions(filter) {\n\t\t\t// Use facet data if available (live values with counts)\n\t\t\tconst live = facetOptions(\n\t\t\t\tthis.facetData[filter.key],\n\t\t\t\t(count) => t('nextcloud-vue', 'No value ({count})', { count }),\n\t\t\t)\n\t\t\tif (live !== null) {\n\t\t\t\treturn live\n\t\t\t}\n\t\t\t// Fall back to static enum options from schema\n\t\t\treturn filter.options || []\n\t\t},\n\n\t\tgetSelectedOptions(filter) {\n\t\t\tconst value = this.getFilterValue(filter.key)\n\t\t\tif (!value) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst values = Array.isArray(value) ? value : [value]\n\t\t\tconst options = this.getFilterOptions(filter)\n\t\t\treturn values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })\n\t\t},\n\n\t\tonSelectChange(key, selected) {\n\t\t\tconst values = selected ? selected.map((o) => o.id) : []\n\t\t\tthis.$emit('filter-change', { key, values })\n\t\t},\n\n\t\tonFilterChange(key, value) {\n\t\t\tthis.$emit('filter-change', { key, values: value })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-facet-sidebar {\n\tpadding: 16px;\n\tborder-right: 1px solid var(--color-border);\n\tmin-width: 240px;\n\tmax-width: 300px;\n}\n\n.cn-facet-sidebar__header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tmargin-bottom: 16px;\n}\n\n.cn-facet-sidebar__title {\n\tmargin: 0;\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n.cn-facet-sidebar__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 20px;\n}\n\n.cn-facet-sidebar__filters {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-facet-sidebar__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-facet-sidebar__label {\n\tfont-size: 12px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-facet-sidebar__select {\n\twidth: 100%;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnFacetSidebar.vue3.js","sources":["../../../../src/components/CnFacetSidebar/CnFacetSidebar.vue?vue&type=template&id=202f66e2&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-facet-sidebar\">\n\t\t<div class=\"cn-facet-sidebar__header\">\n\t\t\t<h3 class=\"cn-facet-sidebar__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"hasActiveFilters\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tclass=\"cn-facet-sidebar__clear\"\n\t\t\t\t@click=\"$emit('clear-all')\">\n\t\t\t\t{{ clearLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading state -->\n\t\t<div v-if=\"loading\" class=\"cn-facet-sidebar__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t</div>\n\n\t\t<!-- Filter groups -->\n\t\t<div v-else class=\"cn-facet-sidebar__filters\">\n\t\t\t<div\n\t\t\t\tv-for=\"filter in effectiveFilters\"\n\t\t\t\t:key=\"filter.key\"\n\t\t\t\tclass=\"cn-facet-sidebar__group\">\n\t\t\t\t<label class=\"cn-facet-sidebar__label\">{{ filter.label }}</label>\n\n\t\t\t\t<!-- Checkbox filter (boolean) -->\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"filter.type === 'checkbox'\"\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) === true\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\">\n\t\t\t\t\t{{ filter.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<!-- Select filter (enum / facet values) -->\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"filter.type === 'select'\"\n\t\t\t\t\tclass=\"cn-facet-sidebar__select\"\n\t\t\t\t\t:modelValue=\"getSelectedOptions(filter)\"\n\t\t\t\t\t:options=\"getFilterOptions(filter)\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:inputLabel=\"filter.label\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t:keepOpen=\"true\"\n\t\t\t\t\t:clearable=\"true\"\n\t\t\t\t\t@update:modelValue=\"onSelectChange(filter.key, $event)\" />\n\n\t\t\t\t<!-- Text filter (fallback) -->\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) || ''\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:label=\"filter.label\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\" />\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextField } from '@nextcloud/vue'\nimport { facetOptionLabel } from '../../utils/facets.js'\nimport { filtersFromSchema } from '../../utils/schema.js'\n\n/**\n * CnFacetSidebar — Auto-generated faceted search sidebar from schema properties.\n *\n * Reads properties marked `facetable: true` in the schema and generates\n * appropriate filter widgets. Accepts live facet data from the API for\n * dynamic option values with counts.\n *\n * ```vue\n * <CnFacetSidebar\n * :schema=\"schema\"\n * :facet-data=\"facetData\"\n * :active-filters=\"filters\"\n * @filter-change=\"onFilterChange\"\n * @clear-all=\"clearFilters\" />\n * ```\n */\nexport default {\n\tname: 'CnFacetSidebar',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Filter\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Schema definition — reads facetable properties */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Live facet data from the API: `{ fieldName: { values: [{ value, count?, label? }] } }` */\n\t\tfacetData: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Current active filters: { fieldName: value | [values] } */\n\t\tactiveFilters: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether facet data is loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Sidebar title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Filters'),\n\t\t},\n\n\t\t/** Clear all button label */\n\t\tclearLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Clear all'),\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is an admin.\n\t\t * When false, schema properties with `adminOnly: true` are hidden from filters.\n\t\t */\n\t\tuserIsAdmin: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: ['clear-all', 'filter-change'],\n\n\tcomputed: {\n\t\teffectiveFilters() {\n\t\t\treturn filtersFromSchema(this.schema, { isAdmin: this.userIsAdmin, translate: this.cnTranslate })\n\t\t},\n\n\t\thasActiveFilters() {\n\t\t\treturn Object.values(this.activeFilters).some((v) => {\n\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\treturn v.length > 0\n\t\t\t\t}\n\t\t\t\treturn v !== null && v !== undefined && v !== '' && v !== false\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\tgetFilterValue(key) {\n\t\t\treturn this.activeFilters[key] ?? null\n\t\t},\n\n\t\tgetFilterOptions(filter) {\n\t\t\t// Use facet data if available (live values with counts)\n\t\t\tconst facet = this.facetData[filter.key]\n\t\t\tif (facet?.values?.length > 0) {\n\t\t\t\treturn facet.values.map((v) => ({\n\t\t\t\t\tid: v.value,\n\t\t\t\t\tlabel: facetOptionLabel(v),\n\t\t\t\t}))\n\t\t\t}\n\t\t\t// Fall back to static enum options from schema\n\t\t\treturn filter.options || []\n\t\t},\n\n\t\tgetSelectedOptions(filter) {\n\t\t\tconst value = this.getFilterValue(filter.key)\n\t\t\tif (!value) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst values = Array.isArray(value) ? value : [value]\n\t\t\tconst options = this.getFilterOptions(filter)\n\t\t\treturn values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })\n\t\t},\n\n\t\tonSelectChange(key, selected) {\n\t\t\tconst values = selected ? selected.map((o) => o.id) : []\n\t\t\tthis.$emit('filter-change', { key, values })\n\t\t},\n\n\t\tonFilterChange(key, value) {\n\t\t\tthis.$emit('filter-change', { key, values: value })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-facet-sidebar {\n\tpadding: 16px;\n\tborder-right: 1px solid var(--color-border);\n\tmin-width: 240px;\n\tmax-width: 300px;\n}\n\n.cn-facet-sidebar__header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tmargin-bottom: 16px;\n}\n\n.cn-facet-sidebar__title {\n\tmargin: 0;\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n.cn-facet-sidebar__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 20px;\n}\n\n.cn-facet-sidebar__filters {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-facet-sidebar__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-facet-sidebar__label {\n\tfont-size: 12px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-facet-sidebar__select {\n\twidth: 100%;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_createBlock","_createVNode","_Fragment","_renderList","_createTextVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnFacetSidebar.vue3.js","sources":["../../../../src/components/CnFacetSidebar/CnFacetSidebar.vue?vue&type=template&id=202f66e2&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-facet-sidebar\">\n\t\t<div class=\"cn-facet-sidebar__header\">\n\t\t\t<h3 class=\"cn-facet-sidebar__title\">\n\t\t\t\t{{ title }}\n\t\t\t</h3>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"hasActiveFilters\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\tclass=\"cn-facet-sidebar__clear\"\n\t\t\t\t@click=\"$emit('clear-all')\">\n\t\t\t\t{{ clearLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading state -->\n\t\t<div v-if=\"loading\" class=\"cn-facet-sidebar__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t</div>\n\n\t\t<!-- Filter groups -->\n\t\t<div v-else class=\"cn-facet-sidebar__filters\">\n\t\t\t<div\n\t\t\t\tv-for=\"filter in effectiveFilters\"\n\t\t\t\t:key=\"filter.key\"\n\t\t\t\tclass=\"cn-facet-sidebar__group\">\n\t\t\t\t<label class=\"cn-facet-sidebar__label\">{{ filter.label }}</label>\n\n\t\t\t\t<!-- Checkbox filter (boolean) -->\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"filter.type === 'checkbox'\"\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) === true\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\">\n\t\t\t\t\t{{ filter.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<!-- Select filter (enum / facet values) -->\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"filter.type === 'select'\"\n\t\t\t\t\tclass=\"cn-facet-sidebar__select\"\n\t\t\t\t\t:modelValue=\"getSelectedOptions(filter)\"\n\t\t\t\t\t:options=\"getFilterOptions(filter)\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:inputLabel=\"filter.label\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t:keepOpen=\"true\"\n\t\t\t\t\t:clearable=\"true\"\n\t\t\t\t\t@update:modelValue=\"onSelectChange(filter.key, $event)\" />\n\n\t\t\t\t<!-- Text filter (fallback) -->\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"getFilterValue(filter.key) || ''\"\n\t\t\t\t\t:placeholder=\"filter.label\"\n\t\t\t\t\t:label=\"filter.label\"\n\t\t\t\t\t@update:modelValue=\"onFilterChange(filter.key, $event)\" />\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextField } from '@nextcloud/vue'\nimport { facetOptions } from '../../utils/facets.js'\nimport { filtersFromSchema } from '../../utils/schema.js'\n\n/**\n * CnFacetSidebar — Auto-generated faceted search sidebar from schema properties.\n *\n * Reads properties marked `facetable: true` in the schema and generates\n * appropriate filter widgets. Accepts live facet data from the API for\n * dynamic option values with counts.\n *\n * ```vue\n * <CnFacetSidebar\n * :schema=\"schema\"\n * :facet-data=\"facetData\"\n * :active-filters=\"filters\"\n * @filter-change=\"onFilterChange\"\n * @clear-all=\"clearFilters\" />\n * ```\n */\nexport default {\n\tname: 'CnFacetSidebar',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Filter\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Schema definition — reads facetable properties */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Live facet data from the API: `{ fieldName: { values: [{ value, count?, label? }] } }` */\n\t\tfacetData: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Current active filters: { fieldName: value | [values] } */\n\t\tactiveFilters: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether facet data is loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Sidebar title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Filters'),\n\t\t},\n\n\t\t/** Clear all button label */\n\t\tclearLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Clear all'),\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is an admin.\n\t\t * When false, schema properties with `adminOnly: true` are hidden from filters.\n\t\t */\n\t\tuserIsAdmin: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: ['clear-all', 'filter-change'],\n\n\tcomputed: {\n\t\teffectiveFilters() {\n\t\t\treturn filtersFromSchema(this.schema, { isAdmin: this.userIsAdmin, translate: this.cnTranslate })\n\t\t},\n\n\t\thasActiveFilters() {\n\t\t\treturn Object.values(this.activeFilters).some((v) => {\n\t\t\t\tif (Array.isArray(v)) {\n\t\t\t\t\treturn v.length > 0\n\t\t\t\t}\n\t\t\t\treturn v !== null && v !== undefined && v !== '' && v !== false\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\tgetFilterValue(key) {\n\t\t\treturn this.activeFilters[key] ?? null\n\t\t},\n\n\t\tgetFilterOptions(filter) {\n\t\t\t// Use facet data if available (live values with counts)\n\t\t\tconst live = facetOptions(\n\t\t\t\tthis.facetData[filter.key],\n\t\t\t\t(count) => t('nextcloud-vue', 'No value ({count})', { count }),\n\t\t\t)\n\t\t\tif (live !== null) {\n\t\t\t\treturn live\n\t\t\t}\n\t\t\t// Fall back to static enum options from schema\n\t\t\treturn filter.options || []\n\t\t},\n\n\t\tgetSelectedOptions(filter) {\n\t\t\tconst value = this.getFilterValue(filter.key)\n\t\t\tif (!value) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst values = Array.isArray(value) ? value : [value]\n\t\t\tconst options = this.getFilterOptions(filter)\n\t\t\treturn values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })\n\t\t},\n\n\t\tonSelectChange(key, selected) {\n\t\t\tconst values = selected ? selected.map((o) => o.id) : []\n\t\t\tthis.$emit('filter-change', { key, values })\n\t\t},\n\n\t\tonFilterChange(key, value) {\n\t\t\tthis.$emit('filter-change', { key, values: value })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-facet-sidebar {\n\tpadding: 16px;\n\tborder-right: 1px solid var(--color-border);\n\tmin-width: 240px;\n\tmax-width: 300px;\n}\n\n.cn-facet-sidebar__header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tmargin-bottom: 16px;\n}\n\n.cn-facet-sidebar__title {\n\tmargin: 0;\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n.cn-facet-sidebar__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 20px;\n}\n\n.cn-facet-sidebar__filters {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-facet-sidebar__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-facet-sidebar__label {\n\tfont-size: 12px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-facet-sidebar__select {\n\twidth: 100%;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_createBlock","_createVNode","_Fragment","_renderList","_createTextVNode"],"mappings":"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"}
@@ -7,6 +7,7 @@ import EyeOutline from 'vue-material-design-icons/EyeOutline.vue';
7
7
  import __sfc_main from '../CnIcon/CnIcon.vue.js';
8
8
  import __sfc_main$1 from '../CnStatusBadge/CnStatusBadge.vue.js';
9
9
  import { resolveEndpointRequest } from '../../composables/useEndpointSource.js';
10
+ import { useRefLabels } from '../../composables/useRefLabels.js';
10
11
  import { dispatchAction } from '../../utils/actionsDispatcher.js';
11
12
  import { isBadgeVariant } from '../../utils/badgeVariants.js';
12
13
  import { formatBoardDate } from '../../utils/boardDate.js';
@@ -17,6 +18,35 @@ import { resolveDeepTokens, hasUnresolvedDeepTokens } from '../../utils/resolveF
17
18
  /** Kinds of entry a `fields[]` item can be. A labelled item without `as` is a row. */
18
19
  const KINDS = Object.freeze(['row', 'title', 'line', 'pill', 'lead', 'text']);
19
20
 
21
+ /**
22
+ * The label resolver every facts widget on the page shares, made on first use
23
+ * (the object store needs an active pinia). One batched request per register and
24
+ * schema for the ids it has not seen; a label it has read stays until the page
25
+ * reloads, and an object the store already holds is not fetched at all.
26
+ *
27
+ * @type {{resolve: Function}|null}
28
+ */
29
+ let refLabels = null;
30
+
31
+ /** The page-wide reference label resolver. */
32
+ function sharedRefLabels() {
33
+ if (!refLabels) {
34
+ refLabels = useRefLabels();
35
+ }
36
+ return refLabels
37
+ }
38
+
39
+ /**
40
+ * Key of a resolved reference in the widget's `labels` data.
41
+ *
42
+ * @param {{register: string, schema: string, labelField?: string}} resolve The entry's `resolve` config.
43
+ * @param {string} id The referenced object's id.
44
+ * @return {string} The key.
45
+ */
46
+ function refKey(resolve, id) {
47
+ return `${resolve.register}/${resolve.schema}/${resolve.labelField || ''}/${id}`
48
+ }
49
+
20
50
  /** Event-bus channel a reveal of a masked value is announced on, so an app can log it. */
21
51
  const FACTS_REVEAL_CHANNEL = 'cn:facts:reveal';
22
52
 
@@ -80,6 +110,7 @@ var script = {
80
110
  * `as` is `row` (default when `label` or `icon` is set), `title`, `line`, `text` (a line in the normal ink), `lead` (an icon tile or initials beside `title` and `subtitle`) or `pill`.
81
111
  * A row's `format` may also be `email`, `phone` or `url` (a link) or `masked` (the last four characters; `reveal: true` adds Show). `copy: true` adds a copy button; `icon` replaces the label.
82
112
  * A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
113
+ * `resolve: { register, schema, labelField? }` on an entry or a part turns a value that is a reference (an id, or a list of ids) into the label of the referenced object (`labelField`, else `title`, then `name`): the case type or status of a task shows its name, not its uuid. The ids are fetched once per register and schema through the object store and shared by every facts widget on the page; the entry stays empty until its label is known, and when no label can be read.
83
114
  * - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
84
115
  * `format` is `text` (default), `date` (board short date), `date-long` (always with the year) or `list` (an array joined by ", ", each item read at `itemPath`).
85
116
  * `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
@@ -122,6 +153,8 @@ var script = {
122
153
  revealed: {},
123
154
  /** Key of the row whose value was just copied. */
124
155
  copied: '',
156
+ /** Labels of the referenced objects read so far, by `refKey`. */
157
+ labels: {},
125
158
  }
126
159
  },
127
160
 
@@ -297,6 +330,13 @@ var script = {
297
330
  },
298
331
  },
299
332
 
333
+ record: {
334
+ immediate: true,
335
+ handler() {
336
+ this.loadLabels();
337
+ },
338
+ },
339
+
300
340
  headerAction: {
301
341
  immediate: true,
302
342
  handler() {
@@ -535,6 +575,94 @@ var script = {
535
575
  });
536
576
  },
537
577
 
578
+ /**
579
+ * The text of a reference: the labels of the objects its id(s) name, joined
580
+ * by ", ". An id whose label is not known yet (or cannot be read) adds nothing.
581
+ *
582
+ * @param {unknown} raw The id, or a list of ids (or of `{ id }` objects).
583
+ * @param {object} spec `{ resolve: { register, schema, labelField? }, itemPath? }`.
584
+ * @return {string}
585
+ */
586
+ refText(raw, spec) {
587
+ return this.refIds(raw, spec)
588
+ .map((id) => this.labels[refKey(spec.resolve, id)])
589
+ .filter((label) => typeof label === 'string' && label !== '')
590
+ .join(', ')
591
+ },
592
+
593
+ /**
594
+ * The ids a reference value holds.
595
+ *
596
+ * @param {unknown} raw The value.
597
+ * @param {object} spec `{ itemPath? }`.
598
+ * @return {string[]}
599
+ */
600
+ refIds(raw, spec) {
601
+ return (Array.isArray(raw) ? raw : [raw])
602
+ .map((item) => (item && typeof item === 'object' ? (spec.itemPath ? readPath(item, spec.itemPath) : (item.id ?? item.uuid)) : item))
603
+ .filter((id) => id !== null && id !== undefined && id !== '' && typeof id !== 'object')
604
+ .map(String)
605
+ },
606
+
607
+ /**
608
+ * Every entry or part that carries `resolve`, with the record's value at its path.
609
+ *
610
+ * @param {object} record The object.
611
+ * @return {Array<{spec: object, raw: unknown}>}
612
+ */
613
+ refSpecs(record) {
614
+ const fields = Array.isArray(this.content?.fields) ? this.content.fields : [];
615
+ const specs = [];
616
+ const visit = (spec) => {
617
+ if (!spec || typeof spec !== 'object') {
618
+ return
619
+ }
620
+ if (spec.resolve && typeof spec.resolve === 'object' && spec.path) {
621
+ specs.push({ spec, raw: readPath(record, spec.path) });
622
+ }
623
+ const nested = [...(Array.isArray(spec.parts) ? spec.parts : []), ...(Array.isArray(spec.title) ? spec.title : [spec.title]), ...(Array.isArray(spec.subtitle) ? spec.subtitle : [spec.subtitle])];
624
+ nested.forEach(visit);
625
+ };
626
+ fields.forEach(visit);
627
+ return specs
628
+ },
629
+
630
+ /**
631
+ * Read the labels of the referenced objects, one batched request per
632
+ * register and schema, and keep them in `labels`.
633
+ *
634
+ * @param {object|null} record The object shown (defaults to the current record).
635
+ * @return {Promise<void>}
636
+ */
637
+ async loadLabels(record = this.record) {
638
+ if (!record) {
639
+ return
640
+ }
641
+ const groups = new Map();
642
+ for (const { spec, raw } of this.refSpecs(record)) {
643
+ const { register, schema, labelField } = spec.resolve;
644
+ if (!register || !schema) {
645
+ continue
646
+ }
647
+ const groupKey = `${register}/${schema}/${labelField || ''}`;
648
+ const group = groups.get(groupKey) || { resolve: spec.resolve, ids: new Set() };
649
+ this.refIds(raw, spec).forEach((id) => group.ids.add(id));
650
+ groups.set(groupKey, group);
651
+ }
652
+ if (groups.size === 0) {
653
+ return
654
+ }
655
+ const resolver = sharedRefLabels();
656
+ const next = { ...this.labels };
657
+ await Promise.all([...groups.values()].map(async ({ resolve, ids }) => {
658
+ const found = await resolver.resolve(resolve.register, resolve.schema, [...ids], resolve.labelField);
659
+ Object.entries(found).forEach(([id, label]) => {
660
+ next[refKey(resolve, id)] = label;
661
+ });
662
+ }));
663
+ this.labels = next;
664
+ },
665
+
538
666
  /**
539
667
  * Read the source object.
540
668
  *
@@ -641,6 +769,9 @@ var script = {
641
769
  if (raw === null || raw === undefined || raw === '') {
642
770
  return ''
643
771
  }
772
+ if (spec.resolve && typeof spec.resolve === 'object') {
773
+ return this.refText(raw, spec)
774
+ }
644
775
  const format = spec.format || 'text';
645
776
  if (format === 'list' || Array.isArray(raw)) {
646
777
  const items = (Array.isArray(raw) ? raw : [raw])