@conduction/nextcloud-vue 2.79.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 (390) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
  5. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +13 -11
  7. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
  9. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +179 -13
  13. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +96 -4
  17. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  18. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +158 -110
  19. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  20. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  21. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  22. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  23. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  24. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  25. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  26. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  27. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  28. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  29. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  30. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  31. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  32. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  33. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  34. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  35. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
  40. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  41. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  45. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
  47. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
  49. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +134 -4
  51. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
  53. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +24 -0
  55. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  56. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
  57. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
  62. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
  63. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
  64. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
  65. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
  66. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +867 -0
  67. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
  68. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
  69. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
  70. package/dist/esm/components/CnFactsWidget/index.js +30 -0
  71. package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
  72. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
  73. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
  74. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +626 -0
  75. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
  76. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +396 -0
  77. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
  78. package/dist/esm/components/CnFieldListWidget/index.js +29 -0
  79. package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
  80. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
  81. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  82. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
  83. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  84. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
  85. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  86. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
  87. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  88. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +65 -17
  89. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  90. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
  91. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  92. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +78 -3
  93. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  94. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
  95. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  96. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +333 -73
  97. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  98. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +79 -17
  99. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  100. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
  101. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
  102. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
  103. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
  104. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
  105. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
  106. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
  107. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  108. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  109. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
  110. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
  111. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  112. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  113. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  114. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  115. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
  116. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  117. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
  118. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  119. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
  120. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  121. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
  122. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  123. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
  124. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  125. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  126. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  127. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  129. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  131. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  133. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  134. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
  135. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
  136. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +411 -0
  137. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
  138. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +178 -0
  139. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
  140. package/dist/esm/components/CnRecordListWidget/index.js +31 -0
  141. package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
  142. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  143. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  144. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  145. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  146. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  147. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  148. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  149. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  150. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
  151. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
  152. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
  153. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
  154. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
  155. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
  156. package/dist/esm/components/CnSectionsWidget/index.js +35 -0
  157. package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
  158. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  159. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  160. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  161. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  162. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  163. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  164. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  165. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  166. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  167. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  168. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
  169. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  170. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  171. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  172. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  173. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
  174. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  175. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
  176. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  177. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
  178. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  179. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  180. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  181. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  182. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  183. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  184. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  185. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  186. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  187. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +168 -7
  188. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  189. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +86 -23
  190. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  191. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  192. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  193. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  194. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  195. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  196. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  197. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  198. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  199. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  200. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  201. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  202. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  203. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  204. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  205. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
  206. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  207. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  208. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  209. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
  210. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +301 -6
  211. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  212. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +153 -55
  213. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  214. package/dist/esm/composables/useFormDraft.js +140 -9
  215. package/dist/esm/composables/useFormDraft.js.map +1 -1
  216. package/dist/esm/composables/useListView.js +4 -4
  217. package/dist/esm/composables/useListView.js.map +1 -1
  218. package/dist/esm/index.js +21 -1
  219. package/dist/esm/index.js.map +1 -1
  220. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
  221. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  222. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
  223. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  224. package/dist/esm/integrations/builtin/field-inspection.js +21 -0
  225. package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
  226. package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
  227. package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
  228. package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
  229. package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
  230. package/dist/esm/integrations/offline/index.js +2 -1
  231. package/dist/esm/integrations/offline/index.js.map +1 -1
  232. package/dist/esm/integrations/offline/offlineDb.js +6 -1
  233. package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
  234. package/dist/esm/integrations/offline/syncReplayService.js +8 -2
  235. package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
  236. package/dist/esm/l10n/en.json.js +20 -1
  237. package/dist/esm/l10n/en.json.js.map +1 -1
  238. package/dist/esm/l10n/nl.json.js +70 -2
  239. package/dist/esm/l10n/nl.json.js.map +1 -1
  240. package/dist/esm/nextcloud-vue.css +2192 -222
  241. package/dist/esm/utils/contentHeaderLink.js +51 -0
  242. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  243. package/dist/esm/utils/detailActionModel.js +60 -3
  244. package/dist/esm/utils/detailActionModel.js.map +1 -1
  245. package/dist/esm/utils/facets.js +75 -3
  246. package/dist/esm/utils/facets.js.map +1 -1
  247. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  248. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  249. package/dist/esm/utils/fillTemplate.js +120 -0
  250. package/dist/esm/utils/fillTemplate.js.map +1 -0
  251. package/dist/esm/utils/initials.js +32 -0
  252. package/dist/esm/utils/initials.js.map +1 -0
  253. package/dist/esm/utils/libraryWidgetKeys.js +11 -0
  254. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  255. package/dist/esm/utils/objectWrite.js +59 -0
  256. package/dist/esm/utils/objectWrite.js.map +1 -0
  257. package/dist/esm/utils/pageControls.js +81 -0
  258. package/dist/esm/utils/pageControls.js.map +1 -0
  259. package/dist/esm/utils/personalLenses.js +40 -3
  260. package/dist/esm/utils/personalLenses.js.map +1 -1
  261. package/dist/esm/utils/queryPresets.js +96 -0
  262. package/dist/esm/utils/queryPresets.js.map +1 -0
  263. package/dist/esm/utils/resolveFilterTokens.js +12 -1
  264. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  265. package/dist/esm/utils/resolveManifestTokens.js +2 -0
  266. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  267. package/dist/esm/utils/schema.js +21 -0
  268. package/dist/esm/utils/schema.js.map +1 -1
  269. package/dist/esm/utils/sentinelTokens.js +22 -4
  270. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  271. package/dist/esm/utils/userGroupsPeek.js +91 -0
  272. package/dist/esm/utils/userGroupsPeek.js.map +1 -0
  273. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  274. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  275. package/dist/esm/utils/visibleWhen.js +57 -5
  276. package/dist/esm/utils/visibleWhen.js.map +1 -1
  277. package/dist/esm/utils/widgetDispatch.js +7 -1
  278. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  279. package/dist/esm/utils/widgetVisibility.js +4 -57
  280. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  281. package/dist/nextcloud-vue.cjs.js +19708 -9301
  282. package/dist/nextcloud-vue.cjs.js.map +2 -2
  283. package/dist/nextcloud-vue.css +2192 -222
  284. package/l10n/en.json +20 -1
  285. package/l10n/nl.json +70 -2
  286. package/package.json +2 -1
  287. package/scripts/check-docs-mdx.mjs +108 -0
  288. package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
  289. package/src/components/CnActionsBar/CnActionsBar.md +4 -0
  290. package/src/components/CnActionsBar/CnActionsBar.vue +48 -11
  291. package/src/components/CnAppRoot/CnAppRoot.md +4 -2
  292. package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
  293. package/src/components/CnBannerWidget/CnBannerWidget.vue +244 -23
  294. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  295. package/src/components/CnCellRenderer/CnCellRenderer.vue +235 -5
  296. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  297. package/src/components/CnChipsFieldWidget/index.js +25 -0
  298. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  299. package/src/components/CnChoicePickerWidget/index.js +26 -0
  300. package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
  301. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
  302. package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
  303. package/src/components/CnDataTable/CnDataTable.vue +22 -12
  304. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  305. package/src/components/CnDetailPage/CnDetailPage.vue +152 -6
  306. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
  307. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
  308. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
  309. package/src/components/CnFactsWidget/CnFactsWidget.vue +1190 -0
  310. package/src/components/CnFactsWidget/index.js +29 -0
  311. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +1008 -0
  312. package/src/components/CnFieldListWidget/index.js +28 -0
  313. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
  314. package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
  315. package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
  316. package/src/components/CnFormDialog/CnFormDialog.md +9 -3
  317. package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
  318. package/src/components/CnFormPage/CnFormPage.vue +99 -3
  319. package/src/components/CnIndexPage/CnIndexPage.vue +309 -9
  320. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
  321. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
  322. package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
  323. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  324. package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
  325. package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
  326. package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
  327. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  328. package/src/components/CnRadioCardsWidget/index.js +27 -0
  329. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +652 -0
  330. package/src/components/CnRecordListWidget/index.js +30 -0
  331. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  332. package/src/components/CnSavedViewsWidget/index.js +29 -0
  333. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
  334. package/src/components/CnSectionsWidget/index.js +34 -0
  335. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  336. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  337. package/src/components/CnSettingsFormWidget/index.js +26 -0
  338. package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
  339. package/src/components/CnStatWidget/CnStatWidget.vue +88 -7
  340. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
  341. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  342. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  343. package/src/components/CnTabsWidget/CnTabsWidget.vue +263 -8
  344. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  345. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  346. package/src/components/CnToggleTableWidget/index.js +26 -0
  347. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  348. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
  349. package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  350. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +493 -11
  351. package/src/components/index.js +19 -0
  352. package/src/composables/__tests__/useFormDraft.spec.js +69 -0
  353. package/src/composables/useFormDraft.js +139 -8
  354. package/src/composables/useListView.js +4 -4
  355. package/src/css/form-field.css +14 -0
  356. package/src/css/index-page.css +71 -0
  357. package/src/css/kpi-card.css +32 -0
  358. package/src/css/look-board-detail.css +113 -2
  359. package/src/css/look-board-index.css +90 -8
  360. package/src/css/look-board.css +109 -2
  361. package/src/css/table.css +19 -0
  362. package/src/index.js +19 -0
  363. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
  364. package/src/integrations/builtin/field-inspection.js +21 -0
  365. package/src/integrations/offline/checklistSubmission.js +137 -0
  366. package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
  367. package/src/integrations/offline/index.js +3 -0
  368. package/src/integrations/offline/offlineDb.js +6 -1
  369. package/src/integrations/offline/syncReplayService.js +8 -2
  370. package/src/schemas/app-manifest-v2.schema.json +196 -13
  371. package/src/utils/contentHeaderLink.js +48 -0
  372. package/src/utils/detailActionModel.js +59 -2
  373. package/src/utils/facets.js +74 -2
  374. package/src/utils/fetchFilterCounts.js +39 -4
  375. package/src/utils/fillTemplate.js +116 -0
  376. package/src/utils/initials.js +29 -0
  377. package/src/utils/libraryWidgetKeys.js +11 -0
  378. package/src/utils/objectWrite.js +55 -0
  379. package/src/utils/pageControls.js +78 -0
  380. package/src/utils/personalLenses.js +39 -2
  381. package/src/utils/queryPresets.js +93 -0
  382. package/src/utils/resolveFilterTokens.js +14 -1
  383. package/src/utils/resolveManifestTokens.js +2 -0
  384. package/src/utils/schema.js +21 -0
  385. package/src/utils/sentinelTokens.js +22 -4
  386. package/src/utils/userGroupsPeek.js +87 -0
  387. package/src/utils/validateManifestV2.compiled.js +3 -3
  388. package/src/utils/visibleWhen.js +56 -4
  389. package/src/utils/widgetDispatch.js +7 -1
  390. package/src/utils/widgetVisibility.js +7 -56
@@ -178,8 +178,10 @@ import {
178
178
  readAvailableActions,
179
179
  transitionError,
180
180
  } from '../../composables/useLifecycleTransitions.js'
181
+ import { normalizeLook } from '../../composables/useLook.js'
181
182
  import { useObjectStore } from '../../store/useObjectStore.js'
182
183
  import { resolveObjectOpType } from '../../utils/actionsDispatcher.js'
184
+ import { formatBoardDate } from '../../utils/boardDate.js'
183
185
  import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
184
186
  import {
185
187
  dropOptionalUnresolved,
@@ -264,6 +266,14 @@ function userError(message) {
264
266
  * labelField?, descriptionField?, finalField?, limit? }`, an OpenRegister
265
267
  * query whose filter takes the same tokens.
266
268
  *
269
+ * `datesSource`: `{ url, method?, params?, path?, idField?, dateField? }`, an
270
+ * endpoint (same token grammar) answering the date the record reached each
271
+ * stage, when the stage list does not carry it: the case type's statuses list
272
+ * the steps, `/blueprint?case=@objectId` says when this case reached them.
273
+ * Read once, merged into the steps by `idField` (default `id`) with the value
274
+ * at `dateField` (default `reachedAt`); a date it does not give falls back to
275
+ * the stage list's own `dateField`. The bars variant draws it.
276
+ *
267
277
  * `finalField` marks the stages that close the record, which the timeline
268
278
  * draws differently. It grants nothing either.
269
279
  *
@@ -356,7 +366,7 @@ export default {
356
366
  * `label`, the placement's own title, so a titled card does not have to
357
367
  * repeat itself, and then to "Stages".
358
368
  *
359
- * @type {{currentField?: string, stagesEndpoint?: object, stagesSource?: object, transition?: object, unreachableReason?: string, orientation?: ('horizontal'|'vertical'), size?: ('medium'|'small'), ariaLabel?: string, label?: string, variant?: ('dots'|'bars')}}
369
+ * @type {{currentField?: string, stagesEndpoint?: object, stagesSource?: object, transition?: object, unreachableReason?: string, orientation?: ('horizontal'|'vertical'), size?: ('medium'|'small'), ariaLabel?: string, label?: string, variant?: ('dots'|'bars'), datesSource?: {url: string, method?: string, params?: object, path?: string, idField?: string, dateField?: string}}}
360
370
  */
361
371
  content: {
362
372
  type: Object,
@@ -437,6 +447,8 @@ export default {
437
447
  const workspaceRaw = inject('cnWorkspaceContext', ref({}))
438
448
  const appConfigRaw = inject('cnAppConfig', ref({}))
439
449
  const cnTranslate = inject('cnTranslate', (key) => key)
450
+ // The app's look (`nextcloud` or `board`), provided by CnAppRoot or CnPageRenderer.
451
+ const cnLook = inject('cnLook', 'nextcloud')
440
452
 
441
453
  /**
442
454
  * The token context: the bound record, its id, and the page context.
@@ -462,15 +474,24 @@ export default {
462
474
  return (cfg && cfg.url) ? { url: cfg.url, method: cfg.method, params: cfg.params } : null
463
475
  }, { ctx: tokenCtx })
464
476
 
477
+ // The dates the record reached each stage, when they live apart from
478
+ // the stage list (`datesSource`): one read, merged into the steps by id.
479
+ const datesRead = useEndpointSource(() => {
480
+ const cfg = props.content?.datesSource
481
+ return (cfg && cfg.url) ? { url: cfg.url, method: cfg.method, params: cfg.params } : null
482
+ }, { ctx: tokenCtx })
483
+
465
484
  return {
466
485
  detailCtxRaw,
467
486
  objectCtxRaw,
468
487
  cnTranslate,
488
+ cnLook,
469
489
  tokenCtx,
470
490
  stagesBody: stagesRead.data,
471
491
  stagesBodyLoading: stagesRead.loading,
472
492
  stagesBodyError: stagesRead.error,
473
493
  stagesBodyBlocked: stagesRead.blocked,
494
+ datesBody: datesRead.data,
474
495
  }
475
496
  },
476
497
 
@@ -614,6 +635,42 @@ export default {
614
635
  return normalizeStages(rows, { ...cfg, orderField })
615
636
  },
616
637
 
638
+ /**
639
+ * The reached dates by stage id, from `datesSource`: the rows at its
640
+ * `path` (or the response itself when it is a list), keyed by
641
+ * `idField` (default `id`), the value at `dateField` (default
642
+ * `reachedAt`). Empty without the option, while it loads, or when it
643
+ * failed: the steps then show the stage list's own dates.
644
+ *
645
+ * @return {{[id: string]: string}} Date by stage id.
646
+ * @spec openspec/changes/screens-stage-dates-source/specs/stage-dates-source/spec.md#requirement-the-reached-dates-can-come-from-their-own-endpoint
647
+ */
648
+ stageDates() {
649
+ const cfg = this.content.datesSource
650
+ const body = this.datesBody
651
+ if (!cfg || !cfg.url || body === null || body === undefined) {
652
+ return {}
653
+ }
654
+ const rows = Array.isArray(body) ? body : getByPath(body, cfg.path || '')
655
+ if (!Array.isArray(rows)) {
656
+ return {}
657
+ }
658
+ const idField = cfg.idField || 'id'
659
+ const dateField = cfg.dateField || 'reachedAt'
660
+ const out = {}
661
+ for (const row of rows) {
662
+ if (!row || typeof row !== 'object') {
663
+ continue
664
+ }
665
+ const id = getByPath(row, idField)
666
+ const date = getByPath(row, dateField)
667
+ if (id !== null && id !== undefined && id !== '' && typeof date === 'string' && date !== '') {
668
+ out[String(id)] = date
669
+ }
670
+ }
671
+ return out
672
+ },
673
+
617
674
  /**
618
675
  * Whether the stage list is still on its way.
619
676
  *
@@ -795,7 +852,7 @@ export default {
795
852
  }
796
853
  return {
797
854
  ...stage,
798
- date: this.stages[index]?.date || '',
855
+ date: this.barDate(this.stageDates[stage.id] || this.stages[index]?.date || '', state),
799
856
  state,
800
857
  clickable: this.interactive && state !== 'current',
801
858
  }
@@ -891,6 +948,28 @@ export default {
891
948
  },
892
949
 
893
950
  methods: {
951
+ /**
952
+ * A bar's date line. Under the board look a date value reads in the
953
+ * board's short form ("3 okt"), and the current step's reads "sinds
954
+ * 4 okt", as DqZaak draws it. Any other value, and every value outside
955
+ * the board look, shows as written.
956
+ *
957
+ * @param {string} value The `dateField` value of the stage row.
958
+ * @param {string} state The bar state (`done`, `current`, `todo`).
959
+ * @return {string} The date line, or ''.
960
+ * @spec openspec/changes/screens-stage-bar-dates-parity/specs/stage-bar-dates/spec.md#requirement-a-bar-date-reads-as-the-board-draws-it
961
+ */
962
+ barDate(value, state) {
963
+ if (!value || normalizeLook(this.cnLook) !== 'board') {
964
+ return value
965
+ }
966
+ const short = formatBoardDate(value)
967
+ if (short === '') {
968
+ return value
969
+ }
970
+ return state === 'current' ? t('nextcloud-vue', 'since {date}', { date: short }) : short
971
+ },
972
+
894
973
  /**
895
974
  * Translate a library string.
896
975
  *
@@ -46,7 +46,7 @@
46
46
  aria-hidden="true">
47
47
  <CnWidgetIcon :name="resolvedIcon" :size="18" />
48
48
  </span>
49
- <span v-if="content.label" :title="resolvedLabel">{{ resolvedLabel }}</span>
49
+ <span v-if="content.label" class="cn-kpi-card__title-text" :title="resolvedLabel">{{ resolvedLabel }}</span>
50
50
 
51
51
  <!-- Per-tile range override. Rendered ONLY when the tile declares its
52
52
  own `content.dateRange.presets`; a tile that merely follows the
@@ -163,6 +163,7 @@ import widgetLink from '../../mixins/widgetLink.js'
163
163
  import { useObjectStore } from '../../store/useObjectStore.js'
164
164
  import { resolveObjectOpType } from '../../utils/actionsDispatcher.js'
165
165
  import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
166
+ import { fetchListTotal, needsListEndpoint } from '../../utils/fetchFilterCounts.js'
166
167
  import { formatMetricValue, unwrapAppConfig } from '../../utils/formatMetric.js'
167
168
  import { dropOptionalUnresolved, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
168
169
  import { STATUS_TEXT_COLORS } from '../../utils/statusColors.js'
@@ -1746,8 +1747,7 @@ export default {
1746
1747
  // drop any optional `@workspace.<key>?` / `@config.<key>?` that stayed
1747
1748
  // unresolved so an unset value omits the filter (show all) instead of
1748
1749
  // sending a literal token.
1749
- const ctx = { ...(this.objectCtx || {}), workspace: this.pageCtx, config: this.configCtx }
1750
- filter = dropOptionalUnresolved(resolveFilterTokens(filter, ctx))
1750
+ filter = this.resolveSourceFilter(filter)
1751
1751
  for (const [k, v] of Object.entries(filter)) {
1752
1752
  if (v && typeof v === 'object') {
1753
1753
  for (const [op, ov] of Object.entries(v)) {
@@ -1759,6 +1759,21 @@ export default {
1759
1759
  }
1760
1760
  },
1761
1761
 
1762
+ /**
1763
+ * A source filter with its @-tokens resolved against the object, page
1764
+ * and config contexts, and unresolved optional clauses dropped.
1765
+ *
1766
+ * @param {object} filter The raw filter map.
1767
+ * @return {object} The resolved filter.
1768
+ */
1769
+ resolveSourceFilter(filter) {
1770
+ if (!filter || typeof filter !== 'object') {
1771
+ return {}
1772
+ }
1773
+ const ctx = { ...(this.objectCtx || {}), workspace: this.pageCtx, config: this.configCtx }
1774
+ return dropOptionalUnresolved(resolveFilterTokens(filter, ctx))
1775
+ },
1776
+
1762
1777
  /**
1763
1778
  * Fetch one scalar from the OpenRegister `/value` aggregation endpoint.
1764
1779
  *
@@ -1768,9 +1783,19 @@ export default {
1768
1783
  * @param {string} metric The aggregation metric.
1769
1784
  * @param {?string} field The numeric field (non-count metrics).
1770
1785
  * @param {object} filter The filter map.
1786
+ * @param {string} [via] `"list"` counts through the list endpoint's total.
1771
1787
  * @return {Promise<number|null>} The aggregated value.
1772
1788
  */
1773
- async fetchAggregate(axios, generateUrl, s, metric, field, filter) {
1789
+ async fetchAggregate(axios, generateUrl, s, metric, field, filter, via) {
1790
+ // A count the aggregation endpoint cannot express (a filter holding a
1791
+ // list of values, a test for no value (`null` or `{ isnull: true }`),
1792
+ // or `via: "list"`) reads the list endpoint's total instead, because
1793
+ // the aggregation endpoint has no null operator.
1794
+ const resolved = this.resolveSourceFilter(filter)
1795
+ const listed = Object.values(resolved).some((v) => Array.isArray(v) || (v && typeof v === 'object' && Object.values(v).some(Array.isArray)))
1796
+ if ((!metric || metric === 'count') && (via === 'list' || listed || needsListEndpoint(resolved))) {
1797
+ return fetchListTotal(s.register, s.schema, resolved, {})
1798
+ }
1774
1799
  const url = generateUrl(
1775
1800
  '/apps/openregister/api/objects/aggregations/{register}/{schema}/value',
1776
1801
  { register: s.register, schema: s.schema },
@@ -1827,9 +1852,15 @@ export default {
1827
1852
  if (s.kind === 'endpoint') {
1828
1853
  this.value = await this.fetchEndpoint(s, refresh)
1829
1854
  } else if (s.kind === 'ratio') {
1830
- const num = await this.fetchAggregate(axios, generateUrl, s, s.metric, s.field, (s.numerator && s.numerator.filter) || {})
1831
- const den = await this.fetchAggregate(axios, generateUrl, s, s.metric, s.field, (s.denominator && s.denominator.filter) || {})
1855
+ // Each part may name its own metric, field and `via`, so "answered
1856
+ // in time" over "answered" is two counts, and a share of a sum is
1857
+ // two sums.
1858
+ const part = (p) => this.fetchAggregate(axios, generateUrl, s, (p && p.metric) || s.metric, (p && p.field) || s.field, (p && p.filter) || {}, (p && p.via) || s.via)
1859
+ const num = await part(s.numerator)
1860
+ const den = await part(s.denominator)
1832
1861
  this.value = (den && Number(den) !== 0) ? (Number(num) / Number(den)) * 100 : null
1862
+ } else if (s.kind === 'elapsed') {
1863
+ this.value = await this.fetchElapsed(axios, generateUrl, s)
1833
1864
  } else if (s.kind === 'computed') {
1834
1865
  // Fetch each named part, then evaluate the formula over them.
1835
1866
  const { evalFormula } = await import('../../utils/evalFormula.js')
@@ -1842,7 +1873,7 @@ export default {
1842
1873
  } else if (s.kind === 'weighted') {
1843
1874
  this.value = await this.fetchWeighted(axios, generateUrl, s)
1844
1875
  } else {
1845
- this.value = await this.fetchAggregate(axios, generateUrl, s, s.metric, s.field, s.filter || {})
1876
+ this.value = await this.fetchAggregate(axios, generateUrl, s, s.metric, s.field, s.filter || {}, s.via)
1846
1877
  }
1847
1878
  } catch (e) {
1848
1879
  this.error = (e && e.message) || 'error'
@@ -1852,6 +1883,56 @@ export default {
1852
1883
  }
1853
1884
  },
1854
1885
 
1886
+ /**
1887
+ * The average time between two date fields of the matching objects
1888
+ * (`{ from, to, unit?, filter?, limit? }`): an average answer time is
1889
+ * `occurredAt` to `resolvedAt`. Computed client-side over the fetched
1890
+ * objects (capped at `limit`, default 1000), because the aggregation
1891
+ * endpoint averages one numeric field, not a difference. Objects missing
1892
+ * either date, or with `to` before `from`, are left out; null when none
1893
+ * is left. `unit` is `days` (default), `hours` or `minutes`.
1894
+ *
1895
+ * @param {object} axios The axios instance.
1896
+ * @param {(url: string, params?: object) => string} generateUrl The router helper.
1897
+ * @param {object} s The elapsed source.
1898
+ * @return {Promise<number|null>} The average, in `unit`.
1899
+ * @spec openspec/changes/screens-ratio-stat/specs/ratio-stat/spec.md#requirement-a-stat-can-average-the-time-between-two-dates
1900
+ */
1901
+ async fetchElapsed(axios, generateUrl, s) {
1902
+ if (!s.from || !s.to) {
1903
+ return null
1904
+ }
1905
+ const url = generateUrl(
1906
+ '/apps/openregister/api/objects/{register}/{schema}',
1907
+ { register: s.register, schema: s.schema },
1908
+ )
1909
+ const params = { _limit: s.limit || 1000 }
1910
+ const filter = this.resolveSourceFilter(s.filter || {})
1911
+ for (const [k, v] of Object.entries(filter)) {
1912
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
1913
+ for (const [op, operand] of Object.entries(v)) {
1914
+ params[`${k}[${op}]`] = operand
1915
+ }
1916
+ } else if (v !== '' && v !== null && v !== undefined) {
1917
+ params[k] = v
1918
+ }
1919
+ }
1920
+ const res = await axios.get(url, { params })
1921
+ const rows = (res && res.data && res.data.results) || []
1922
+ const per = { minutes: 60000, hours: 3600000, days: 86400000 }[s.unit] || 86400000
1923
+ let sum = 0
1924
+ let n = 0
1925
+ for (const r of rows) {
1926
+ const from = Date.parse(r && r[s.from])
1927
+ const to = Date.parse(r && r[s.to])
1928
+ if (Number.isFinite(from) && Number.isFinite(to) && to >= from) {
1929
+ sum += (to - from) / per
1930
+ n += 1
1931
+ }
1932
+ }
1933
+ return n > 0 ? sum / n : null
1934
+ },
1935
+
1855
1936
  /**
1856
1937
  * Compute a weighted sum `Σ (field × weightField) ÷ divisor` client-side
1857
1938
  * (no OpenRegister expression-aggregation primitive yet). Pulls the
@@ -17,10 +17,42 @@
17
17
  <template>
18
18
  <div
19
19
  class="cn-stats-block-widget"
20
- :class="[iconClass, { 'cn-stats-block-widget--multi': hasEntries }]">
20
+ :class="[iconClass, { 'cn-stats-block-widget--multi': hasEntries && !isRows, 'cn-stats-block-widget--rows': isRows }]">
21
+ <!-- Rows layout: a title and one label-left, number-right row per entry
22
+ (the board's "Mijn Zuiddrecht nu" card). -->
23
+ <template v-if="isRows">
24
+ <h2 v-if="title" class="cn-stats-block-widget__rows-title" data-testid="cn-stats-block-rows-title">
25
+ {{ tl(title) }}
26
+ </h2>
27
+ <dl class="cn-stats-block-widget__rows" data-testid="cn-stats-block-rows">
28
+ <div
29
+ v-for="view in rowViews"
30
+ :key="view.key"
31
+ class="cn-stats-block-widget__row"
32
+ data-testid="cn-stats-block-row">
33
+ <dt class="cn-stats-block-widget__row-label">
34
+ <a
35
+ v-if="view.href"
36
+ :href="view.href"
37
+ data-testid="cn-stats-block-row-link"
38
+ @click="onRowClick($event, view.route)">{{ view.label }}</a>
39
+ <template v-else>
40
+ {{ view.label }}
41
+ </template>
42
+ </dt>
43
+ <dd
44
+ class="cn-stats-block-widget__row-value"
45
+ :class="'cn-stats-block-widget__row-value--' + view.variant"
46
+ data-testid="cn-stats-block-row-value">
47
+ {{ view.display }}
48
+ </dd>
49
+ </div>
50
+ </dl>
51
+ </template>
52
+
21
53
  <!-- Multi-entry mode: one KPI per entry, all inside this widget card.
22
54
  An entry with hideWhenZero whose resolved count is 0 is omitted. -->
23
- <template v-if="hasEntries">
55
+ <template v-else-if="hasEntries">
24
56
  <CnStatsBlock
25
57
  v-for="view in entryViews"
26
58
  :key="view.key"
@@ -71,6 +103,7 @@ import { subscribe, unsubscribe } from '@nextcloud/event-bus'
71
103
  import CnStatsBlock from '../CnStatsBlock/CnStatsBlock.vue'
72
104
  import { useDataSource } from '../../composables/useDataSource.js'
73
105
  import { formatMetricValue, normalizeMetricFormat, unwrapAppConfig } from '../../utils/formatMetric.js'
106
+ import { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'
74
107
  import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
75
108
 
76
109
  // The canonical KPI scale (`--cn-kpi-*`) lives in one stylesheet. Imported
@@ -167,6 +200,8 @@ export default {
167
200
  * currency (`currency`) and `@config.<key>` tokens in `format`.
168
201
  */
169
202
  cnAppConfig: { default: () => ({}) },
203
+ /** The host's label lookup (CnAppRoot provides it); identity without one. */
204
+ cnTranslate: { default: () => (key) => key },
170
205
  },
171
206
 
172
207
  props: {
@@ -264,7 +299,10 @@ export default {
264
299
 
265
300
  /**
266
301
  * `stacked` gives every block the board look (see CnStatsBlock
267
- * `layout`). Empty (the default) keeps the KPI card.
302
+ * `layout`). `rows` draws the title and then one row per entry (or the
303
+ * single source): the label on the left, the number on the right, the
304
+ * label a link when the entry has a `route`. Empty (the default) keeps
305
+ * the KPI card.
268
306
  *
269
307
  * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look
270
308
  */
@@ -353,6 +391,52 @@ export default {
353
391
  return Array.isArray(this.entries) && this.entries.length > 0
354
392
  },
355
393
 
394
+ /**
395
+ * Whether the widget draws label/number rows (`layout: "rows"`).
396
+ *
397
+ * @return {boolean}
398
+ */
399
+ isRows() {
400
+ return this.layout === 'rows'
401
+ },
402
+
403
+ /**
404
+ * The rows of the rows layout: one per entry, or a single row from the
405
+ * `dataSource` (labelled by `countLabel`). A row is `{ key, label,
406
+ * display, variant, route, href }`; `display` is the formatted value,
407
+ * a dash while it loads or when the count could not be read.
408
+ *
409
+ * @return {Array<object>}
410
+ */
411
+ rowViews() {
412
+ if (!this.hasEntries) {
413
+ const formatted = this.formattedSingle
414
+ return [{
415
+ key: 'single',
416
+ label: this.tl(this.countLabel),
417
+ display: this.loading ? '\u2014' : (formatted !== null ? formatted : this.formatCount(this.resolvedCount)),
418
+ variant: this.variant || 'default',
419
+ route: this.route,
420
+ href: this.route ? resolveHref(this.route, this.$router) : '',
421
+ }]
422
+ }
423
+ return this.entryViews.map((view) => {
424
+ const resolved = view.resolved === true
425
+ let display = '\u2014'
426
+ if (resolved) {
427
+ display = view.formatted !== null ? view.formatted : this.formatCount(view.count)
428
+ }
429
+ return {
430
+ key: view.key,
431
+ label: this.tl(view.entry.title || ''),
432
+ display,
433
+ variant: view.entry.variant || 'default',
434
+ route: view.route,
435
+ href: view.route ? resolveHref(view.route, this.$router) : '',
436
+ }
437
+ })
438
+ },
439
+
356
440
  /**
357
441
  * The unwrapped detail-page object context for token resolution, or
358
442
  * null on surfaces (dashboards) that don't provide one.
@@ -566,6 +650,37 @@ export default {
566
650
  },
567
651
 
568
652
  methods: {
653
+ /**
654
+ * A manifest string through the host's label lookup.
655
+ *
656
+ * @param {string} text The text as written in the manifest.
657
+ * @return {string} The translated text.
658
+ */
659
+ tl(text) {
660
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
661
+ },
662
+
663
+ /**
664
+ * A count in the user's locale.
665
+ *
666
+ * @param {number} count The count.
667
+ * @return {string} The localized count.
668
+ */
669
+ formatCount(count) {
670
+ return new Intl.NumberFormat().format(count)
671
+ },
672
+
673
+ /**
674
+ * Follow a row label's link in the router, leaving a modified click to the browser.
675
+ *
676
+ * @param {MouseEvent} event The click.
677
+ * @param {object} route The route location.
678
+ * @return {void}
679
+ */
680
+ onRowClick(event, route) {
681
+ followLinkClick(event, route, this.$router)
682
+ },
683
+
569
684
  /**
570
685
  * Re-query the tile's counts — the REST `/value` aggregation, the
571
686
  * per-entry counts of multi-entry mode, and the `dataSource` GraphQL
@@ -758,4 +873,66 @@ export default {
758
873
  flex-direction: column;
759
874
  gap: var(--cn-kpi-stack-gap, 12px);
760
875
  }
876
+
877
+ /* Rows layout: the board's "label left, number right" card body. */
878
+ .cn-stats-block-widget--rows {
879
+ display: flex;
880
+ flex-direction: column;
881
+ gap: 12px;
882
+ box-sizing: border-box;
883
+ padding: 20px 22px;
884
+ }
885
+
886
+ .cn-stats-block-widget__rows-title {
887
+ margin: 0;
888
+ font-size: 15px;
889
+ font-weight: 600;
890
+ color: var(--color-text-maxcontrast);
891
+ }
892
+
893
+ .cn-stats-block-widget__rows {
894
+ display: flex;
895
+ flex-direction: column;
896
+ gap: 8px;
897
+ margin: 0;
898
+ font-size: 14px;
899
+ }
900
+
901
+ .cn-stats-block-widget__row {
902
+ display: flex;
903
+ align-items: baseline;
904
+ justify-content: space-between;
905
+ gap: 16px;
906
+ }
907
+
908
+ .cn-stats-block-widget__row-label {
909
+ min-width: 0;
910
+ margin: 0;
911
+ overflow: hidden;
912
+ text-overflow: ellipsis;
913
+ }
914
+
915
+ .cn-stats-block-widget__row-value {
916
+ flex: none;
917
+ margin: 0;
918
+ font-size: 17px;
919
+ font-weight: 700;
920
+ text-align: right;
921
+ }
922
+
923
+ .cn-stats-block-widget__row-value--success {
924
+ color: var(--color-text-success, var(--color-success-text));
925
+ }
926
+
927
+ .cn-stats-block-widget__row-value--warning {
928
+ color: var(--color-text-warning, var(--color-warning-text));
929
+ }
930
+
931
+ .cn-stats-block-widget__row-value--error {
932
+ color: var(--color-text-error, var(--color-error-text));
933
+ }
934
+
935
+ .cn-stats-block-widget__row-value--primary {
936
+ color: var(--color-primary-element);
937
+ }
761
938
  </style>