@conduction/nextcloud-vue 2.59.0 → 2.61.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 (254) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +80 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +232 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
  13. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
  15. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
  22. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
  23. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
  24. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
  25. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
  31. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
  39. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
  41. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +10 -2
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +303 -144
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  53. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  54. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  55. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  56. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  57. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  61. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +171 -12
  62. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  64. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  66. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  67. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  68. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
  70. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
  72. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  73. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
  74. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
  75. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
  76. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
  77. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
  78. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
  79. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  80. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  81. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  82. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  83. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  84. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  85. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  86. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  87. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  88. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  89. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  90. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  91. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  92. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  95. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  97. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  98. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  99. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  100. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  101. package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
  102. package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
  103. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  104. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  105. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  106. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  107. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  108. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  109. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  111. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  112. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  113. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  114. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  115. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  116. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  117. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  118. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  119. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  120. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  121. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  122. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  123. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  124. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  125. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  126. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  127. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  128. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  129. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  130. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  131. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  132. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  133. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  134. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  135. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  136. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  137. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  139. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  140. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  141. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  142. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  143. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  144. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  145. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  146. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  147. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  148. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  149. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  150. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  151. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  152. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  153. package/dist/esm/index.js +16 -0
  154. package/dist/esm/index.js.map +1 -1
  155. package/dist/esm/l10n/en.json.js +55 -1
  156. package/dist/esm/l10n/en.json.js.map +1 -1
  157. package/dist/esm/l10n/nl.json.js +55 -1
  158. package/dist/esm/l10n/nl.json.js.map +1 -1
  159. package/dist/esm/nextcloud-vue.css +1036 -19
  160. package/dist/esm/utils/dateVariant.js +124 -0
  161. package/dist/esm/utils/dateVariant.js.map +1 -0
  162. package/dist/esm/utils/detailActionModel.js +260 -0
  163. package/dist/esm/utils/detailActionModel.js.map +1 -0
  164. package/dist/esm/utils/dueRule.js +84 -0
  165. package/dist/esm/utils/dueRule.js.map +1 -0
  166. package/dist/esm/utils/fetchAggregate.js +37 -1
  167. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  168. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  169. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  170. package/dist/esm/utils/headers.js +28 -2
  171. package/dist/esm/utils/headers.js.map +1 -1
  172. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  173. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  174. package/dist/esm/utils/readPath.js +30 -0
  175. package/dist/esm/utils/readPath.js.map +1 -0
  176. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  177. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  178. package/dist/nextcloud-vue.cjs.js +16019 -9528
  179. package/dist/nextcloud-vue.cjs.js.map +2 -2
  180. package/dist/nextcloud-vue.css +1036 -19
  181. package/l10n/en.json +55 -1
  182. package/l10n/nl.json +55 -1
  183. package/package.json +1 -1
  184. package/scripts/.jsdoc-baselines.json +4 -0
  185. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  186. package/src/components/CnAppNav/CnAppNav.md +26 -0
  187. package/src/components/CnAppNav/CnAppNav.vue +145 -2
  188. package/src/components/CnBannerWidget/CnBannerWidget.vue +373 -11
  189. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  190. package/src/components/CnBoardView/CnBoardView.vue +77 -0
  191. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  192. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  193. package/src/components/CnBrandStripe/index.js +4 -0
  194. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  195. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  196. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  197. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  198. package/src/components/CnConversationThread/index.js +4 -0
  199. package/src/components/CnDashboardPage/CnDashboardPage.vue +10 -2
  200. package/src/components/CnDetailPage/CnDetailPage.md +16 -0
  201. package/src/components/CnDetailPage/CnDetailPage.vue +687 -49
  202. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  203. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  204. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  205. package/src/components/CnDocumentReviewList/index.js +4 -0
  206. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +191 -14
  207. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  208. package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
  209. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +484 -0
  210. package/src/components/CnLinkCardsPage/index.js +4 -0
  211. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  212. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  213. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  214. package/src/components/CnNextStepCard/index.js +4 -0
  215. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  216. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  217. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  218. package/src/components/CnPageRenderer/pageTypes.js +1 -0
  219. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  220. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  221. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  222. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  223. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  224. package/src/components/CnSegmentedControl/index.js +4 -0
  225. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  226. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  227. package/src/components/CnStackedBarWidget/index.js +14 -0
  228. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  229. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  230. package/src/components/CnTabs/CnTab.vue +35 -0
  231. package/src/components/CnTabs/CnTabs.vue +244 -10
  232. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  233. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  234. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  235. package/src/components/CnWeekStripWidget/index.js +14 -0
  236. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  237. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  238. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  239. package/src/components/index.js +12 -0
  240. package/src/css/detail-page.css +57 -2
  241. package/src/index.js +16 -0
  242. package/src/schemas/app-manifest-v2.schema.json +433 -3
  243. package/src/schemas/app-manifest.schema.json +2 -1
  244. package/src/types/manifest.d.ts +1 -1
  245. package/src/utils/dateVariant.js +120 -0
  246. package/src/utils/detailActionModel.js +256 -0
  247. package/src/utils/dueRule.js +94 -0
  248. package/src/utils/fetchAggregate.js +36 -0
  249. package/src/utils/fetchFilterCounts.js +182 -0
  250. package/src/utils/headers.js +28 -2
  251. package/src/utils/index.js +1 -0
  252. package/src/utils/libraryWidgetKeys.js +5 -0
  253. package/src/utils/readPath.js +27 -0
  254. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnObjectDataWidgetForm.vue3.js","sources":["../../../../src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue?vue&type=template&id=10b5a40f&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-data-form\">\n\t\t<h4 class=\"cn-data-form__section\">\n\t\t\t{{ t('nextcloud-vue', 'Data widget') }}\n\t\t</h4>\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"title\"\n\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\tplaceholder=\"Data\"\n\t\t\t@update:modelValue=\"updateField('title', $event)\" />\n\n\t\t<div class=\"cn-data-form__row2\">\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"source.register\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Register')\"\n\t\t\t\t:placeholder=\"contextSource.register || 'pipelinq'\"\n\t\t\t\t@update:modelValue=\"updateSource('register', $event)\" />\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"source.schema\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Schema')\"\n\t\t\t\t:placeholder=\"contextSource.schema || 'lead'\"\n\t\t\t\t@update:modelValue=\"updateSource('schema', $event)\" />\n\t\t</div>\n\n\t\t<div class=\"cn-data-form__layout\">\n\t\t\t<span class=\"cn-data-form__layout-label\">{{ t('nextcloud-vue', 'Layout') }}</span>\n\t\t\t<div class=\"cn-data-form__presets\" role=\"group\" :aria-label=\"t('nextcloud-vue', 'Column layout')\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"preset in layoutPresets\"\n\t\t\t\t\t:key=\"preset.value\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-data-form__preset\"\n\t\t\t\t\t:class=\"{ 'cn-data-form__preset--active': columns === preset.value }\"\n\t\t\t\t\t:aria-pressed=\"columns === preset.value\"\n\t\t\t\t\t@click=\"setColumns(preset.value)\">\n\t\t\t\t\t{{ preset.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<NcTextField\n\t\t\t\ttype=\"number\"\n\t\t\t\t:modelValue=\"String(columns)\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Columns')\"\n\t\t\t\tclass=\"cn-data-form__columns\"\n\t\t\t\t@update:modelValue=\"updateField('columns', Number($event))\" />\n\t\t</div>\n\n\t\t<h4 class=\"cn-data-form__section\">\n\t\t\t{{ t('nextcloud-vue', 'Properties') }}\n\t\t</h4>\n\n\t\t<p v-if=\"!rows.length\" class=\"cn-data-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'Enter a register and schema to configure properties.') }}\n\t\t</p>\n\t\t<p v-else class=\"cn-data-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'Drag a property by its handle to reorder. The order applies to both the widget and the edit form.') }}\n\t\t</p>\n\n\t\t<div\n\t\t\tv-for=\"(row, index) in rows\"\n\t\t\t:key=\"row.key\"\n\t\t\tclass=\"cn-data-form__prop\"\n\t\t\t:class=\"{ 'cn-data-form__prop--hidden': row.hidden, 'cn-data-form__prop--dragging': dragIndex === index }\"\n\t\t\tdraggable=\"true\"\n\t\t\t@dragstart=\"onDragStart(index)\"\n\t\t\t@dragover.prevent\n\t\t\t@drop=\"onDrop(index)\"\n\t\t\t@dragend=\"dragIndex = null\">\n\t\t\t<div class=\"cn-data-form__prop-head\">\n\t\t\t\t<span class=\"cn-data-form__grip\" :title=\"t('nextcloud-vue', 'Drag to reorder')\" aria-hidden=\"true\">⋮⋮</span>\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t:modelValue=\"!row.hidden\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'hidden', !$event)\">\n\t\t\t\t\t<span class=\"cn-data-form__prop-name\">{{ row.key }}</span>\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"!row.hidden\" class=\"cn-data-form__prop-body\">\n\t\t\t\t<NcTextField\n\t\t\t\t\t:modelValue=\"row.label\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Label')\"\n\t\t\t\t\t:placeholder=\"row.key\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'label', $event)\" />\n\t\t\t\t<NcTextField\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:modelValue=\"String(row.gridColumn)\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Span')\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'gridColumn', Number($event))\" />\n\t\t\t\t<NcSelect\n\t\t\t\t\t:modelValue=\"row.widget\"\n\t\t\t\t\t:options=\"widgetOptions\"\n\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Editor')\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'widget', $event)\" />\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t:modelValue=\"row.editable\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'editable', $event)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Editable') }}\n\t\t\t\t</NcCheckboxRadioSwitch>\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 { NcCheckboxRadioSwitch, NcSelect, NcTextField } from '@nextcloud/vue'\nimport { fetchSchemaProperties } from '../../utils/fetchSchemaProperties.js'\n\nconst DEFAULT_CONTENT = Object.freeze({\n\ttitle: '',\n\tregister: '',\n\tschema: '',\n\tcolumns: 3,\n\toverrides: {},\n})\n\nconst WIDGET_OPTIONS = ['auto', 'text', 'textarea', 'number', 'select', 'multiselect', 'tags', 'checkbox', 'date', 'datetime', 'email', 'url']\n\n/**\n * CnObjectDataWidgetForm — the config sub-form for a `data` widget\n * (`CnObjectDataWidget`). Edits the widget title, grid column count, and a\n * per-property override map: visibility, display order, grid span, edit-widget\n * type, label, and editability — discovered from the register/schema. Emits\n * `update:content` with `{ title, register, schema, columns, overrides }`,\n * where `overrides` contains only the non-default entries. Used by\n * `CnAddWidgetModal` + the cog editor; the resolved overrides are forwarded to\n * `CnObjectDataWidget`'s `overrides` prop on detail pages.\n */\nexport default {\n\tname: 'CnObjectDataWidgetForm',\n\n\tcomponents: { NcTextField, NcSelect, NcCheckboxRadioSwitch },\n\n\t/**\n\t * Fallback object context (provided by CnDetailPage as a reactive\n\t * `{ objectId, object, register, schema }` ref). Used to resolve the\n\t * register/schema when the widget being edited is the page's default Data\n\t * widget, which inherits them from the page rather than storing them — so the\n\t * property list still loads even though the form's own Register/Schema fields\n\t * start empty.\n\t */\n\tinject: {\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`), or null.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: { type: Object, default: null },\n\t\t/**\n\t\t * Initial content values when not editing (registry defaults).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: { type: Object, default: () => ({ ...DEFAULT_CONTENT }) },\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = this.editingWidget?.content || this.value || {}\n\t\treturn {\n\t\t\ttitle: initial.title ?? '',\n\t\t\tcolumns: Number.isFinite(initial.columns) ? initial.columns : 3,\n\t\t\tsource: { register: initial.register ?? '', schema: initial.schema ?? '' },\n\t\t\toverrides: { ...(initial.overrides || {}) },\n\t\t\trows: [],\n\t\t\t/** Index of the property row currently being dragged, or null. */\n\t\t\tdragIndex: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Edit-widget type options ('auto' = derive from schema). */\n\t\twidgetOptions() {\n\t\t\treturn WIDGET_OPTIONS\n\t\t},\n\n\t\t/** The inherited register/schema (shown as placeholders when not overridden). */\n\t\tcontextSource() {\n\t\t\treturn this.unwrapContext()\n\t\t},\n\n\t\t/** Quick column-layout presets (1 = stacked beneath each other). */\n\t\tlayoutPresets() {\n\t\t\treturn [\n\t\t\t\t{ value: 1, label: t('nextcloud-vue', 'Stacked') },\n\t\t\t\t{ value: 2, label: t('nextcloud-vue', '2 columns') },\n\t\t\t\t{ value: 3, label: t('nextcloud-vue', '3 columns') },\n\t\t\t]\n\t\t},\n\n\t\t/** The assembled content blob from the current field values. */\n\t\tassembledContent() {\n\t\t\treturn {\n\t\t\t\ttitle: this.title,\n\t\t\t\tregister: this.source.register,\n\t\t\t\tschema: this.source.schema,\n\t\t\t\tcolumns: this.columns,\n\t\t\t\toverrides: this.buildOverrides(),\n\t\t\t}\n\t\t},\n\t},\n\n\twatch: {\n\t\t'source.register': 'loadFields',\n\t\t'source.schema': 'loadFields',\n\t},\n\n\tmounted() {\n\t\tthis.loadFields()\n\t},\n\n\tmethods: {\n\t\tt,\n\t\t/**\n\t\t * Unwrap the injected object context (a Vue ref or plain object) to a\n\t\t * plain `{ register, schema }`. Returns empties when no context is\n\t\t * provided (e.g. a dashboard surface).\n\t\t *\n\t\t * @return {{register: string, schema: string}} the resolved context.\n\t\t */\n\t\tunwrapContext() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tconst v = c && typeof c === 'object' && 'value' in c ? c.value : c\n\t\t\treturn { register: (v && v.register) || '', schema: (v && v.schema) || '' }\n\t\t},\n\n\t\t/**\n\t\t * Resolve the schema's property names and build editable rows, merging\n\t\t * any persisted overrides onto each.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadFields() {\n\t\t\tconst ctx = this.unwrapContext()\n\t\t\tconst register = this.source.register || ctx.register\n\t\t\tconst schema = this.source.schema || ctx.schema\n\t\t\t// includeId: the data widget renders the `id` column, so list it here\n\t\t\t// too — the user can then hide it like any other property.\n\t\t\tconst fields = await fetchSchemaProperties(register, schema, { includeId: true })\n\t\t\tthis.rows = fields.map((key) => {\n\t\t\t\tconst o = this.overrides[key] || {}\n\t\t\t\treturn {\n\t\t\t\t\tkey,\n\t\t\t\t\thidden: o.hidden === true,\n\t\t\t\t\tlabel: o.label ?? '',\n\t\t\t\t\torder: typeof o.order === 'number' ? o.order : null,\n\t\t\t\t\tgridColumn: Number.isFinite(o.gridColumn) ? o.gridColumn : 1,\n\t\t\t\t\twidget: o.widget || 'auto',\n\t\t\t\t\teditable: o.editable !== false,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Set a top-level field and emit.\n\t\t *\n\t\t * @param {'title'|'columns'} field The data key to write.\n\t\t * @param {string|number} value The widget title, or the column count.\n\t\t * @return {void}\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Apply a column-layout preset (1 = stacked) and emit.\n\t\t *\n\t\t * @param {number} value The column count to set.\n\t\t * @return {void}\n\t\t */\n\t\tsetColumns(value) {\n\t\t\tthis.columns = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Remember which row a drag started on.\n\t\t *\n\t\t * @param {number} index The row index being dragged.\n\t\t * @return {void}\n\t\t */\n\t\tonDragStart(index) {\n\t\t\tthis.dragIndex = index\n\t\t},\n\n\t\t/**\n\t\t * Reorder the property rows on drop: move the dragged row to the drop\n\t\t * target, then stamp every row with a sequential `order` so the new order\n\t\t * persists into the overrides map (applied to both the widget and the edit\n\t\t * form). No-op when dropping a row onto itself.\n\t\t *\n\t\t * @param {number} index The drop-target row index.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(index) {\n\t\t\tconst from = this.dragIndex\n\t\t\tthis.dragIndex = null\n\t\t\tif (from === null || from === index) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst moved = this.rows.splice(from, 1)[0]\n\t\t\tthis.rows.splice(index, 0, moved)\n\t\t\tthis.rows.forEach((row, i) => {\n\t\t\t\trow.order = i\n\t\t\t})\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Set a source sub-field and emit. Leaving either empty falls back to the\n\t\t * injected object context.\n\t\t *\n\t\t * @param {'register'|'schema'} field The `source` sub-key to write.\n\t\t * @param {string} value The chosen register or schema slug.\n\t\t * @return {void}\n\t\t */\n\t\tupdateSource(field, value) {\n\t\t\tthis.source[field] = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Mutate one property row and re-emit.\n\t\t *\n\t\t * @param {string} key The property name.\n\t\t * @param {string} field The row field to set.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetRow(key, field, value) {\n\t\t\tconst row = this.rows.find((r) => r.key === key)\n\t\t\tif (!row) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trow[field] = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Collapse the rows into a minimal overrides map — only non-default\n\t\t * fields are persisted so the stored config stays small.\n\t\t *\n\t\t * @return {object} The overrides map keyed by property name.\n\t\t */\n\t\tbuildOverrides() {\n\t\t\tconst out = {}\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst o = {}\n\t\t\t\tif (row.hidden) {\n\t\t\t\t\to.hidden = true\n\t\t\t\t}\n\t\t\t\tif (row.label && row.label.trim() !== '') {\n\t\t\t\t\to.label = row.label\n\t\t\t\t}\n\t\t\t\tif (typeof row.order === 'number') {\n\t\t\t\t\to.order = row.order\n\t\t\t\t}\n\t\t\t\tif (Number.isFinite(row.gridColumn) && row.gridColumn !== 1) {\n\t\t\t\t\to.gridColumn = row.gridColumn\n\t\t\t\t}\n\t\t\t\tif (row.widget && row.widget !== 'auto') {\n\t\t\t\t\to.widget = row.widget\n\t\t\t\t}\n\t\t\t\tif (row.editable === false) {\n\t\t\t\t\to.editable = false\n\t\t\t\t}\n\t\t\t\tif (Object.keys(o).length) {\n\t\t\t\t\tout[row.key] = o\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** Emit the assembled content. */\n\t\temitChange() {\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid.\n\t\t *\n\t\t * @return {string[]} the validation errors.\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = []\n\t\t\t// register/schema may be inherited from the page (injected\n\t\t\t// cnObjectContext) on a detail page — the widget renders against the\n\t\t\t// page's object regardless. Only require them when neither the form\n\t\t\t// fields NOR the context supply them (e.g. a dashboard data widget).\n\t\t\tconst ctx = this.unwrapContext()\n\t\t\tconst register = this.source.register || ctx.register\n\t\t\tconst schema = this.source.schema || ctx.schema\n\t\t\tif (!register || !schema) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'A register and schema are required'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-data-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-data-form__section {\n\tmargin: 8px 0 0;\n\tfont-size: 0.8em;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.03em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-data-form__hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-data-form__row2 {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-end;\n}\n\n.cn-data-form__row2 > * {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-data-form__layout {\n\tdisplay: flex;\n\talign-items: flex-end;\n\tgap: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-data-form__layout-label {\n\talign-self: center;\n\tfont-weight: 600;\n}\n\n.cn-data-form__presets {\n\tdisplay: inline-flex;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n.cn-data-form__preset {\n\tborder: none;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tpadding: 6px 12px;\n\tcursor: pointer;\n\tborder-inline-start: 1px solid var(--color-border);\n}\n\n.cn-data-form__preset:first-child {\n\tborder-inline-start: none;\n}\n\n.cn-data-form__preset--active {\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tfont-weight: 600;\n}\n\n.cn-data-form__columns {\n\twidth: 90px;\n\tflex: 0 0 auto;\n}\n\n.cn-data-form__prop {\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tpadding: 8px 10px;\n}\n\n.cn-data-form__prop--hidden {\n\topacity: 0.6;\n}\n\n.cn-data-form__prop--dragging {\n\topacity: 0.4;\n\tborder-style: dashed;\n}\n\n.cn-data-form__prop-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-data-form__grip {\n\tcursor: grab;\n\tcolor: var(--color-text-maxcontrast);\n\tletter-spacing: -2px;\n\tuser-select: none;\n}\n\n.cn-data-form__prop-name {\n\tfont-weight: 600;\n}\n\n.cn-data-form__prop-body {\n\tdisplay: grid;\n\tgrid-template-columns: 2fr 1fr 1fr 1.5fr;\n\tgap: 10px;\n\talign-items: center;\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["_createElementVNode","_toDisplayString","_createVNode","_createElementBlock","_Fragment","_renderList","_openBlock","_normalizeClass"],"mappings":"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"}
1
+ {"version":3,"file":"CnObjectDataWidgetForm.vue3.js","sources":["../../../../src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue?vue&type=template&id=10b5a40f&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-data-form\">\n\t\t<h4 class=\"cn-data-form__section\">\n\t\t\t{{ t('nextcloud-vue', 'Data widget') }}\n\t\t</h4>\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"title\"\n\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\tplaceholder=\"Data\"\n\t\t\t@update:modelValue=\"updateField('title', $event)\" />\n\n\t\t<div class=\"cn-data-form__row2\">\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"source.register\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Register')\"\n\t\t\t\t:placeholder=\"contextSource.register || 'pipelinq'\"\n\t\t\t\t@update:modelValue=\"updateSource('register', $event)\" />\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"source.schema\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Schema')\"\n\t\t\t\t:placeholder=\"contextSource.schema || 'lead'\"\n\t\t\t\t@update:modelValue=\"updateSource('schema', $event)\" />\n\t\t</div>\n\n\t\t<div class=\"cn-data-form__layout\">\n\t\t\t<span class=\"cn-data-form__layout-label\">{{ t('nextcloud-vue', 'Layout') }}</span>\n\t\t\t<div class=\"cn-data-form__presets\" role=\"group\" :aria-label=\"t('nextcloud-vue', 'Column layout')\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"preset in layoutPresets\"\n\t\t\t\t\t:key=\"preset.value\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-data-form__preset\"\n\t\t\t\t\t:class=\"{ 'cn-data-form__preset--active': columns === preset.value }\"\n\t\t\t\t\t:aria-pressed=\"columns === preset.value\"\n\t\t\t\t\t@click=\"setColumns(preset.value)\">\n\t\t\t\t\t{{ preset.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<NcTextField\n\t\t\t\ttype=\"number\"\n\t\t\t\t:modelValue=\"String(columns)\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Columns')\"\n\t\t\t\tclass=\"cn-data-form__columns\"\n\t\t\t\t@update:modelValue=\"updateField('columns', Number($event))\" />\n\t\t</div>\n\n\t\t<h4 class=\"cn-data-form__section\">\n\t\t\t{{ t('nextcloud-vue', 'Properties') }}\n\t\t</h4>\n\n\t\t<p v-if=\"!rows.length\" class=\"cn-data-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'Enter a register and schema to configure properties.') }}\n\t\t</p>\n\t\t<p v-else class=\"cn-data-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'Drag a property by its handle to reorder. The order applies to both the widget and the edit form.') }}\n\t\t</p>\n\n\t\t<div\n\t\t\tv-for=\"(row, index) in rows\"\n\t\t\t:key=\"row.key\"\n\t\t\tclass=\"cn-data-form__prop\"\n\t\t\t:class=\"{ 'cn-data-form__prop--hidden': row.hidden, 'cn-data-form__prop--dragging': dragIndex === index }\"\n\t\t\tdraggable=\"true\"\n\t\t\t@dragstart=\"onDragStart(index)\"\n\t\t\t@dragover.prevent\n\t\t\t@drop=\"onDrop(index)\"\n\t\t\t@dragend=\"dragIndex = null\">\n\t\t\t<div class=\"cn-data-form__prop-head\">\n\t\t\t\t<span class=\"cn-data-form__grip\" :title=\"t('nextcloud-vue', 'Drag to reorder')\" aria-hidden=\"true\">⋮⋮</span>\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t:modelValue=\"!row.hidden\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'hidden', !$event)\">\n\t\t\t\t\t<span class=\"cn-data-form__prop-name\">{{ row.key }}</span>\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"!row.hidden\" class=\"cn-data-form__prop-body\">\n\t\t\t\t<NcTextField\n\t\t\t\t\t:modelValue=\"row.label\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Label')\"\n\t\t\t\t\t:placeholder=\"row.key\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'label', $event)\" />\n\t\t\t\t<NcTextField\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:modelValue=\"String(row.gridColumn)\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Span')\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'gridColumn', Number($event))\" />\n\t\t\t\t<NcSelect\n\t\t\t\t\t:modelValue=\"row.widget\"\n\t\t\t\t\t:options=\"widgetOptions\"\n\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Editor')\"\n\t\t\t\t\t:clearable=\"false\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'widget', $event)\" />\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t:modelValue=\"row.editable\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setRow(row.key, 'editable', $event)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Editable') }}\n\t\t\t\t</NcCheckboxRadioSwitch>\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 { NcCheckboxRadioSwitch, NcSelect, NcTextField } from '@nextcloud/vue'\nimport { fetchSchemaProperties } from '../../utils/fetchSchemaProperties.js'\n\nconst DEFAULT_CONTENT = Object.freeze({\n\ttitle: '',\n\tregister: '',\n\tschema: '',\n\tcolumns: 3,\n\toverrides: {},\n})\n\nconst WIDGET_OPTIONS = ['auto', 'text', 'textarea', 'number', 'select', 'multiselect', 'tags', 'checkbox', 'date', 'datetime', 'email', 'url']\n\n/**\n * CnObjectDataWidgetForm — the config sub-form for a `data` widget\n * (`CnObjectDataWidget`). Edits the widget title, grid column count, and a\n * per-property override map: visibility, display order, grid span, edit-widget\n * type, label, and editability — discovered from the register/schema. Emits\n * `update:content` with `{ title, register, schema, columns, overrides }`,\n * where `overrides` contains only the non-default entries. Used by\n * `CnAddWidgetModal` + the cog editor; the resolved overrides are forwarded to\n * `CnObjectDataWidget`'s `overrides` prop on detail pages.\n */\nexport default {\n\tname: 'CnObjectDataWidgetForm',\n\n\tcomponents: { NcTextField, NcSelect, NcCheckboxRadioSwitch },\n\n\t/**\n\t * Fallback object context (provided by CnDetailPage as a reactive\n\t * `{ objectId, object, register, schema }` ref). Used to resolve the\n\t * register/schema when the widget being edited is the page's default Data\n\t * widget, which inherits them from the page rather than storing them — so the\n\t * property list still loads even though the form's own Register/Schema fields\n\t * start empty.\n\t */\n\tinject: {\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`), or null.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: { type: Object, default: null },\n\t\t/**\n\t\t * Initial content values when not editing (registry defaults).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: { type: Object, default: () => ({ ...DEFAULT_CONTENT }) },\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = this.editingWidget?.content || this.value || {}\n\t\treturn {\n\t\t\ttitle: initial.title ?? '',\n\t\t\tcolumns: Number.isFinite(initial.columns) ? initial.columns : 3,\n\t\t\tsource: { register: initial.register ?? '', schema: initial.schema ?? '' },\n\t\t\toverrides: { ...(initial.overrides || {}) },\n\t\t\trows: [],\n\t\t\t/** Index of the property row currently being dragged, or null. */\n\t\t\tdragIndex: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Edit-widget type options ('auto' = derive from schema). */\n\t\twidgetOptions() {\n\t\t\treturn WIDGET_OPTIONS\n\t\t},\n\n\t\t/** The inherited register/schema (shown as placeholders when not overridden). */\n\t\tcontextSource() {\n\t\t\treturn this.unwrapContext()\n\t\t},\n\n\t\t/** Quick column-layout presets (1 = stacked beneath each other). */\n\t\tlayoutPresets() {\n\t\t\treturn [\n\t\t\t\t{ value: 1, label: t('nextcloud-vue', 'Stacked') },\n\t\t\t\t{ value: 2, label: t('nextcloud-vue', '2 columns') },\n\t\t\t\t{ value: 3, label: t('nextcloud-vue', '3 columns') },\n\t\t\t]\n\t\t},\n\n\t\t/** The assembled content blob from the current field values. */\n\t\tassembledContent() {\n\t\t\treturn {\n\t\t\t\ttitle: this.title,\n\t\t\t\tregister: this.source.register,\n\t\t\t\tschema: this.source.schema,\n\t\t\t\tcolumns: this.columns,\n\t\t\t\toverrides: this.buildOverrides(),\n\t\t\t}\n\t\t},\n\t},\n\n\twatch: {\n\t\t'source.register': 'loadFields',\n\t\t'source.schema': 'loadFields',\n\t},\n\n\tmounted() {\n\t\tthis.loadFields()\n\t},\n\n\tmethods: {\n\t\tt,\n\t\t/**\n\t\t * Unwrap the injected object context (a Vue ref or plain object) to a\n\t\t * plain `{ register, schema }`. Returns empties when no context is\n\t\t * provided (e.g. a dashboard surface).\n\t\t *\n\t\t * @return {{register: string, schema: string}} the resolved context.\n\t\t */\n\t\tunwrapContext() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tconst v = c && typeof c === 'object' && 'value' in c ? c.value : c\n\t\t\treturn { register: (v && v.register) || '', schema: (v && v.schema) || '' }\n\t\t},\n\n\t\t/**\n\t\t * Resolve the schema's property names and build editable rows, merging\n\t\t * any persisted overrides onto each.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadFields() {\n\t\t\tconst ctx = this.unwrapContext()\n\t\t\tconst register = this.source.register || ctx.register\n\t\t\tconst schema = this.source.schema || ctx.schema\n\t\t\t// includeId: the data widget renders the `id` column, so list it here\n\t\t\t// too — the user can then hide it like any other property.\n\t\t\tconst fields = await fetchSchemaProperties(register, schema, { includeId: true })\n\t\t\tthis.rows = fields.map((key) => {\n\t\t\t\tconst o = this.overrides[key] || {}\n\t\t\t\treturn {\n\t\t\t\t\tkey,\n\t\t\t\t\thidden: o.hidden === true,\n\t\t\t\t\tlabel: o.label ?? '',\n\t\t\t\t\torder: typeof o.order === 'number' ? o.order : null,\n\t\t\t\t\tgridColumn: Number.isFinite(o.gridColumn) ? o.gridColumn : 1,\n\t\t\t\t\twidget: o.widget || 'auto',\n\t\t\t\t\teditable: o.editable !== false,\n\t\t\t\t\t// Not edited here, but carried through, so saving the\n\t\t\t\t\t// cog editor does not drop a manifest-authored formatter.\n\t\t\t\t\tformatter: typeof o.formatter === 'string' ? o.formatter : '',\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Set a top-level field and emit.\n\t\t *\n\t\t * @param {'title'|'columns'} field The data key to write.\n\t\t * @param {string|number} value The widget title, or the column count.\n\t\t * @return {void}\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Apply a column-layout preset (1 = stacked) and emit.\n\t\t *\n\t\t * @param {number} value The column count to set.\n\t\t * @return {void}\n\t\t */\n\t\tsetColumns(value) {\n\t\t\tthis.columns = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Remember which row a drag started on.\n\t\t *\n\t\t * @param {number} index The row index being dragged.\n\t\t * @return {void}\n\t\t */\n\t\tonDragStart(index) {\n\t\t\tthis.dragIndex = index\n\t\t},\n\n\t\t/**\n\t\t * Reorder the property rows on drop: move the dragged row to the drop\n\t\t * target, then stamp every row with a sequential `order` so the new order\n\t\t * persists into the overrides map (applied to both the widget and the edit\n\t\t * form). No-op when dropping a row onto itself.\n\t\t *\n\t\t * @param {number} index The drop-target row index.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(index) {\n\t\t\tconst from = this.dragIndex\n\t\t\tthis.dragIndex = null\n\t\t\tif (from === null || from === index) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst moved = this.rows.splice(from, 1)[0]\n\t\t\tthis.rows.splice(index, 0, moved)\n\t\t\tthis.rows.forEach((row, i) => {\n\t\t\t\trow.order = i\n\t\t\t})\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Set a source sub-field and emit. Leaving either empty falls back to the\n\t\t * injected object context.\n\t\t *\n\t\t * @param {'register'|'schema'} field The `source` sub-key to write.\n\t\t * @param {string} value The chosen register or schema slug.\n\t\t * @return {void}\n\t\t */\n\t\tupdateSource(field, value) {\n\t\t\tthis.source[field] = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Mutate one property row and re-emit.\n\t\t *\n\t\t * @param {string} key The property name.\n\t\t * @param {string} field The row field to set.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetRow(key, field, value) {\n\t\t\tconst row = this.rows.find((r) => r.key === key)\n\t\t\tif (!row) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trow[field] = value\n\t\t\tthis.emitChange()\n\t\t},\n\n\t\t/**\n\t\t * Collapse the rows into a minimal overrides map — only non-default\n\t\t * fields are persisted so the stored config stays small.\n\t\t *\n\t\t * @return {object} The overrides map keyed by property name.\n\t\t */\n\t\tbuildOverrides() {\n\t\t\tconst out = {}\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst o = {}\n\t\t\t\tif (row.hidden) {\n\t\t\t\t\to.hidden = true\n\t\t\t\t}\n\t\t\t\tif (row.label && row.label.trim() !== '') {\n\t\t\t\t\to.label = row.label\n\t\t\t\t}\n\t\t\t\tif (typeof row.order === 'number') {\n\t\t\t\t\to.order = row.order\n\t\t\t\t}\n\t\t\t\tif (Number.isFinite(row.gridColumn) && row.gridColumn !== 1) {\n\t\t\t\t\to.gridColumn = row.gridColumn\n\t\t\t\t}\n\t\t\t\tif (row.widget && row.widget !== 'auto') {\n\t\t\t\t\to.widget = row.widget\n\t\t\t\t}\n\t\t\t\tif (row.editable === false) {\n\t\t\t\t\to.editable = false\n\t\t\t\t}\n\t\t\t\tif (row.formatter) {\n\t\t\t\t\to.formatter = row.formatter\n\t\t\t\t}\n\t\t\t\tif (Object.keys(o).length) {\n\t\t\t\t\tout[row.key] = o\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/** Emit the assembled content. */\n\t\temitChange() {\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid.\n\t\t *\n\t\t * @return {string[]} the validation errors.\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = []\n\t\t\t// register/schema may be inherited from the page (injected\n\t\t\t// cnObjectContext) on a detail page — the widget renders against the\n\t\t\t// page's object regardless. Only require them when neither the form\n\t\t\t// fields NOR the context supply them (e.g. a dashboard data widget).\n\t\t\tconst ctx = this.unwrapContext()\n\t\t\tconst register = this.source.register || ctx.register\n\t\t\tconst schema = this.source.schema || ctx.schema\n\t\t\tif (!register || !schema) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'A register and schema are required'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-data-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-data-form__section {\n\tmargin: 8px 0 0;\n\tfont-size: 0.8em;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.03em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-data-form__hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-data-form__row2 {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-end;\n}\n\n.cn-data-form__row2 > * {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-data-form__layout {\n\tdisplay: flex;\n\talign-items: flex-end;\n\tgap: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-data-form__layout-label {\n\talign-self: center;\n\tfont-weight: 600;\n}\n\n.cn-data-form__presets {\n\tdisplay: inline-flex;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n.cn-data-form__preset {\n\tborder: none;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tpadding: 6px 12px;\n\tcursor: pointer;\n\tborder-inline-start: 1px solid var(--color-border);\n}\n\n.cn-data-form__preset:first-child {\n\tborder-inline-start: none;\n}\n\n.cn-data-form__preset--active {\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tfont-weight: 600;\n}\n\n.cn-data-form__columns {\n\twidth: 90px;\n\tflex: 0 0 auto;\n}\n\n.cn-data-form__prop {\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tpadding: 8px 10px;\n}\n\n.cn-data-form__prop--hidden {\n\topacity: 0.6;\n}\n\n.cn-data-form__prop--dragging {\n\topacity: 0.4;\n\tborder-style: dashed;\n}\n\n.cn-data-form__prop-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-data-form__grip {\n\tcursor: grab;\n\tcolor: var(--color-text-maxcontrast);\n\tletter-spacing: -2px;\n\tuser-select: none;\n}\n\n.cn-data-form__prop-name {\n\tfont-weight: 600;\n}\n\n.cn-data-form__prop-body {\n\tdisplay: grid;\n\tgrid-template-columns: 2fr 1fr 1fr 1.5fr;\n\tgap: 10px;\n\talign-items: center;\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["_createElementVNode","_toDisplayString","_createVNode","_createElementBlock","_Fragment","_renderList","_openBlock","_normalizeClass"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { translate } from '@nextcloud/l10n';
2
2
  import { NcButton, NcLoadingIcon, NcEmptyContent } from '@nextcloud/vue';
3
3
  import draggable from '../../node_modules/vuedraggable/dist/vuedraggable.umd.js';
4
4
  import ViewColumn from 'vue-material-design-icons/ViewColumn.vue';
5
+ import { dueStateForRow } from '../../utils/dueRule.js';
5
6
  import __sfc_main from '../CnCellRenderer/CnCellRenderer.vue.js';
6
7
 
7
8
  /**
@@ -135,6 +136,22 @@ var script = {
135
136
  type: String,
136
137
  default: 'id',
137
138
  },
139
+
140
+ /**
141
+ * Marks late cards: `{ field, soonDays? }`. `field` is the dot-path to
142
+ * a card's due date. A date before today gets an error edge and the
143
+ * label "Overdue"; a date within `soonDays` (default 3) gets "Due
144
+ * soon" in the warning colour. `null` (the default) marks nothing.
145
+ *
146
+ * The same rule shape the table's date cell uses, so a list and its
147
+ * board agree on what late means.
148
+ *
149
+ * @type {{field: string, soonDays?: number}|null}
150
+ */
151
+ dueRule: {
152
+ type: Object,
153
+ default: null,
154
+ },
138
155
  },
139
156
 
140
157
  emits: ['card-click', 'load-more', 'move', 'move-rejected'],
@@ -150,6 +167,8 @@ var script = {
150
167
  // objectId -> { fromValue, toValue, card, originIndex }
151
168
  pendingMoves: {},
152
169
  dragOriginValue: null,
170
+ // The day late marking measures from.
171
+ today: new Date(),
153
172
  }
154
173
  },
155
174
 
@@ -204,6 +223,33 @@ var script = {
204
223
  methods: {
205
224
  t: translate,
206
225
 
226
+ /**
227
+ * A card's due state under `dueRule`.
228
+ *
229
+ * @param {object} card The card's row.
230
+ * @return {'overdue'|'soon'|'ok'|null} The state, or null when no rule applies.
231
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
232
+ */
233
+ dueState(card) {
234
+ return dueStateForRow(card, this.dueRule, this.today)
235
+ },
236
+
237
+ /**
238
+ * The words a late card carries. Colour alone would not reach a reader
239
+ * who cannot see it, or cannot tell red from amber.
240
+ *
241
+ * @param {object} card The card's row.
242
+ * @return {string} "Overdue", "Due soon", or '' for a card that is fine.
243
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
244
+ */
245
+ dueLabel(card) {
246
+ const state = this.dueState(card);
247
+ if (state === 'overdue') {
248
+ return translate('nextcloud-vue', 'Overdue')
249
+ }
250
+ return state === 'soon' ? translate('nextcloud-vue', 'Due soon') : ''
251
+ },
252
+
207
253
  /**
208
254
  * Rebuild `localColumns` from either the `columns` prop (backend-paginated
209
255
  * mode) or `objects` + `groupByField` (locally-derived mode).
@@ -1 +1 @@
1
- {"version":3,"file":"CnObjectKanban.vue2.js","sources":["../../../../src/components/CnObjectKanban/CnObjectKanban.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-object-kanban\">\n\t\t<div v-if=\"loading\" class=\"cn-object-kanban__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<div v-else-if=\"localColumns.length === 0\" class=\"cn-object-kanban__empty\">\n\t\t\t<!-- @slot empty Custom empty state shown when there are no columns to render. -->\n\t\t\t<slot name=\"empty\">\n\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ViewColumn :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-object-kanban__board\">\n\t\t\t<div\n\t\t\t\tv-for=\"column in localColumns\"\n\t\t\t\t:key=\"columnKey(column.value)\"\n\t\t\t\tclass=\"cn-object-kanban__column\">\n\t\t\t\t<div class=\"cn-object-kanban__column-header\">\n\t\t\t\t\t<!-- @slot column-header Override a column's header. -->\n\t\t\t\t\t<!-- @binding {object} column The column ({ value, cards, total, limit, offset }). -->\n\t\t\t\t\t<slot name=\"column-header\" :column=\"column\">\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-title\">{{ columnLabel(column) }}</span>\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-count\">{{ column.total }}</span>\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\n\t\t\t\t<!-- `#item` + `item-key`: vuedraggable@4 (the Vue 3 build)\n\t\t\t\t renders the cards itself from this slot. A `v-for` in the\n\t\t\t\t default slot throws \"draggable element must have an item\n\t\t\t\t slot\" the moment the column paints. -->\n\t\t\t\t<Draggable\n\t\t\t\t\t:list=\"column.cards\"\n\t\t\t\t\ttag=\"div\"\n\t\t\t\t\tclass=\"cn-object-kanban__column-cards\"\n\t\t\t\t\t:itemKey=\"cardKey\"\n\t\t\t\t\tgroup=\"cn-object-kanban-cards\"\n\t\t\t\t\t:data-column-value=\"columnKey(column.value)\"\n\t\t\t\t\t@start=\"onDragStart(column)\"\n\t\t\t\t\t@change=\"onColumnChange($event, column)\">\n\t\t\t\t\t<template #item=\"{ element: card }\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"cn-object-kanban__card\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-object-kanban__card--pending': isPending(card) }\">\n\t\t\t\t\t\t\t<!-- @slot card Fully replace the default card rendering. -->\n\t\t\t\t\t\t\t<!-- @binding {object} object The card's object. -->\n\t\t\t\t\t\t\t<!-- @binding {object} column The column the card currently sits in. -->\n\t\t\t\t\t\t\t<slot name=\"card\" :object=\"card\" :column=\"column\">\n\t\t\t\t\t\t\t\t<div class=\"cn-object-kanban__card-title\" @click=\"onCardClick(card)\">\n\t\t\t\t\t\t\t\t\t{{ cardTitle(card) }}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div v-if=\"cardFields.length\" class=\"cn-object-kanban__card-fields\">\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tv-for=\"field in cardFields\"\n\t\t\t\t\t\t\t\t\t\t:key=\"field\"\n\t\t\t\t\t\t\t\t\t\tclass=\"cn-object-kanban__card-field\">\n\t\t\t\t\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t\t\t\t\t:value=\"card[field]\"\n\t\t\t\t\t\t\t\t\t\t\t:property=\"schemaProperty(field)\"\n\t\t\t\t\t\t\t\t\t\t\t:truncate=\"40\" />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</slot>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</Draggable>\n\n\t\t\t\t<div v-if=\"hasMore(column)\" class=\"cn-object-kanban__load-more\">\n\t\t\t\t\t<NcButton :disabled=\"isColumnLoading(column)\" @click=\"onLoadMore(column)\">\n\t\t\t\t\t\t<template v-if=\"isColumnLoading(column)\" #icon>\n\t\t\t\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport draggable from 'vuedraggable'\nimport ViewColumn from 'vue-material-design-icons/ViewColumn.vue'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\n\n/**\n * CnObjectKanban — Kanban board over a schema property's distinct values.\n *\n * Renders one column per distinct value of `groupByField` and paginates each\n * column's cards independently (\"load more\", never a whole column at once).\n * Column values/order come from (in precedence order) an explicit\n * `columnOrder`, the `groupByField` schema property's `enum`, or values\n * discovered from `objects` — mirroring OpenRegister's\n * `ViewPresentationService::deriveColumnValues()`.\n *\n * Two rendering modes:\n * - **Pre-built columns** — pass `columns` (the shape returned by\n * `GET /api/views/{id}/kanban`: `[{ value, cards, total, limit, offset }]`);\n * the host owns pagination and re-fetches on `load-more`.\n * - **Flat objects** — pass `objects` + `groupByField` (and optionally\n * `columnOrder`/`schema`); the component derives and paginates columns\n * locally.\n *\n * Dragging a card to another column does NOT write anything itself — there is\n * deliberately no bespoke \"move card\" endpoint (REQ-VIEW-KANBAN-03). The move\n * is applied optimistically to the local board and the component emits\n * `move`; the host performs the actual object PATCH/PUT through the existing\n * guarded write path and reports back:\n * - On success, nothing further is required (the optimistic move already\n * reflects it) — optionally call `resolveMove(id)` to clear the pending\n * visual state sooner.\n * - On a rejected (e.g. illegal lifecycle transition) write, call\n * `rejectMove(id, reason)` — the card snaps back to its origin column and\n * `move-rejected` fires with the reason for the host to surface.\n *\n * ```vue\n * <CnObjectKanban\n * :objects=\"objects\"\n * group-by-field=\"status\"\n * :column-order=\"['todo', 'doing', 'done']\"\n * :card-fields=\"['title', 'assignee']\"\n * :schema=\"schema\"\n * ref=\"kanban\"\n * @move=\"onMove\"\n * @load-more=\"fetchMoreCards\" />\n * ```\n * ```js\n * async onMove({ object, groupByField, fromValue, toValue }) {\n * try {\n * await patchObject(object.id, { [groupByField]: toValue })\n * } catch (e) {\n * this.$refs.kanban.rejectMove(object.id, e.message)\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnObjectKanban',\n\n\tcomponents: {\n\t\tDraggable: draggable,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tNcEmptyContent,\n\t\tViewColumn,\n\t\tCnCellRenderer,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Flat objects to derive columns from (ignored when `columns` is set).\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tobjects: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Pre-built columns, e.g. the response of `GET /api/views/{id}/kanban`.\n\t\t *\n\t\t * @type {Array<{value: string|number, cards: Array<object>, total: number, limit: number, offset: number}>|null}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The schema property whose distinct values become columns. */\n\t\tgroupByField: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Explicit column order. Takes precedence over the schema's enum order. */\n\t\tcolumnOrder: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Object fields rendered on each card (in order). */\n\t\tcardFields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Schema definition, used to resolve enum column order and card field types. */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Overall loading state (initial board fetch). */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Column values currently loading more cards (drives the per-column spinner). */\n\t\tloadingColumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Cards shown per column before \"load more\", in local (`objects`-driven) mode. */\n\t\tpageSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 20,\n\t\t},\n\n\t\t/** Object property used as each card's identity. */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\t},\n\n\temits: ['card-click', 'load-more', 'move', 'move-rejected'],\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalColumns: [],\n\t\t\t// column.value -> full unpaginated card list, local mode only.\n\t\t\tfullCardsByColumn: {},\n\t\t\t// Suppressed while a move is pending so the prop-driven rebuild\n\t\t\t// doesn't clobber the optimistic UI before the host's data catches up.\n\t\t\tsuppressRebuild: false,\n\t\t\t// objectId -> { fromValue, toValue, card, originIndex }\n\t\t\tpendingMoves: {},\n\t\t\tdragOriginValue: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\temptyText() {\n\t\t\treturn t('nextcloud-vue', 'No columns to show')\n\t\t},\n\n\t\tloadMoreLabel() {\n\t\t\treturn t('nextcloud-vue', 'Load more')\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjects: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tcolumns: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tgroupByField() {\n\t\t\tthis.rebuild()\n\t\t},\n\n\t\tcolumnOrder: {\n\t\t\thandler() {\n\t\t\t\tthis.rebuild()\n\t\t\t},\n\n\t\t\tdeep: true,\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.rebuild()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Rebuild `localColumns` from either the `columns` prop (backend-paginated\n\t\t * mode) or `objects` + `groupByField` (locally-derived mode).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trebuild() {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\tthis.localColumns = this.columns.map((column) => ({\n\t\t\t\t\tvalue: column.value,\n\t\t\t\t\tcards: Array.isArray(column.cards) ? [...column.cards] : [],\n\t\t\t\t\ttotal: typeof column.total === 'number' ? column.total : (column.cards || []).length,\n\t\t\t\t\tlimit: column.limit,\n\t\t\t\t\toffset: column.offset,\n\t\t\t\t}))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst values = this.deriveColumnValues()\n\t\t\tconst fullByColumn = {}\n\t\t\tthis.localColumns = values.map((value) => {\n\t\t\t\tconst fullCards = this.objects.filter((object) => object && object[this.groupByField] === value)\n\t\t\t\tfullByColumn[this.columnKey(value)] = fullCards\n\t\t\t\treturn {\n\t\t\t\t\tvalue,\n\t\t\t\t\tcards: fullCards.slice(0, this.pageSize),\n\t\t\t\t\ttotal: fullCards.length,\n\t\t\t\t\tlimit: this.pageSize,\n\t\t\t\t\toffset: 0,\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis.fullCardsByColumn = fullByColumn\n\t\t},\n\n\t\t/**\n\t\t * Derive the ordered column values: `columnOrder` > schema enum order >\n\t\t * distinct values observed in `objects`. Mirrors\n\t\t * `ViewPresentationService::deriveColumnValues()`.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tderiveColumnValues() {\n\t\t\tif (Array.isArray(this.columnOrder) && this.columnOrder.length > 0) {\n\t\t\t\treturn [...this.columnOrder]\n\t\t\t}\n\n\t\t\tconst enumValues = this.schema?.properties?.[this.groupByField]?.enum\n\t\t\tif (Array.isArray(enumValues) && enumValues.length > 0) {\n\t\t\t\treturn [...enumValues]\n\t\t\t}\n\n\t\t\treturn this.discoverDistinctValues()\n\t\t},\n\n\t\t/**\n\t\t * Distinct `groupByField` values observed in `objects`, in first-seen order.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tdiscoverDistinctValues() {\n\t\t\tconst seen = new Set()\n\t\t\tconst values = []\n\t\t\tfor (const object of this.objects) {\n\t\t\t\tconst value = object?.[this.groupByField]\n\t\t\t\tif (value !== undefined && value !== null && !seen.has(value)) {\n\t\t\t\t\tseen.add(value)\n\t\t\t\t\tvalues.push(value)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn values\n\t\t},\n\n\t\t/**\n\t\t * Stable string key for a column value (used as a `:key` and as the\n\t\t * `fullCardsByColumn` index — object/array values are unlikely but guarded).\n\t\t *\n\t\t * @param {unknown} value The column value.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnKey(value) {\n\t\t\treturn typeof value === 'string' || typeof value === 'number' ? String(value) : JSON.stringify(value)\n\t\t},\n\n\t\t/**\n\t\t * The card's identity, per `rowKey`.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {unknown}\n\t\t */\n\t\tcardKey(card) {\n\t\t\treturn card?.[this.rowKey]\n\t\t},\n\n\t\t/**\n\t\t * Display label for a column: its schema `oneOf[].title` when the\n\t\t * property declares per-value titles, otherwise the raw value.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnLabel(column) {\n\t\t\tconst oneOf = this.schema?.properties?.[this.groupByField]?.oneOf\n\t\t\tif (Array.isArray(oneOf)) {\n\t\t\t\tconst match = oneOf.find((entry) => entry?.const === column.value)\n\t\t\t\tif (match?.title) {\n\t\t\t\t\treturn match.title\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Best-effort card title: the schema's configured name field, else\n\t\t * `title`/`name`/the row key.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {string}\n\t\t */\n\t\tcardTitle(card) {\n\t\t\tconst nameField = this.schema?.configuration?.objectNameField\n\t\t\tif (nameField && card[nameField]) {\n\t\t\t\treturn String(card[nameField])\n\t\t\t}\n\t\t\treturn String(card.title || card.name || card[this.rowKey] || '—')\n\t\t},\n\n\t\t/**\n\t\t * The schema property definition for a card field, if known.\n\t\t *\n\t\t * @param {string} field The field name.\n\t\t * @return {object|undefined}\n\t\t */\n\t\tschemaProperty(field) {\n\t\t\treturn this.schema?.properties?.[field]\n\t\t},\n\n\t\t/**\n\t\t * Whether a column has more cards to load: backend-paginated mode compares\n\t\t * `cards.length` to `total`; local mode compares to the full filtered set.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\thasMore(column) {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\treturn column.cards.length < column.total\n\t\t\t}\n\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\treturn column.cards.length < full.length\n\t\t},\n\n\t\t/**\n\t\t * Whether a column's \"load more\" is in flight.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\tisColumnLoading(column) {\n\t\t\treturn this.loadingColumns.includes(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Whether a card has an unresolved optimistic move.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {boolean}\n\t\t */\n\t\tisPending(card) {\n\t\t\treturn Object.hasOwn(this.pendingMoves, this.cardKey(card))\n\t\t},\n\n\t\t/**\n\t\t * Handle a column's \"load more\" click: local mode reveals the next page\n\t\t * from the already-fetched full set; backend-paginated mode only emits —\n\t\t * the host fetches the next page and passes updated `columns`.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {void}\n\t\t */\n\t\tonLoadMore(column) {\n\t\t\tif (!Array.isArray(this.columns)) {\n\t\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\t\tcolumn.cards = full.slice(0, column.cards.length + this.pageSize)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event load-more Emitted when a column's \"load more\" is clicked.\n\t\t\t * @type {{ value: unknown, offset: number }}\n\t\t\t */\n\t\t\tthis.$emit('load-more', { value: column.value, offset: column.cards.length })\n\t\t},\n\n\t\t/**\n\t\t * Card click (ignored while the card is mid-drag).\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {void}\n\t\t */\n\t\tonCardClick(card) {\n\t\t\t/**\n\t\t\t * @event card-click Emitted when a card is clicked (not dragged).\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('card-click', card)\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@start` — remember which column a drag began in, since\n\t\t * `@change` on the destination column doesn't know the source.\n\t\t *\n\t\t * @param {object} column The column the drag started in.\n\t\t * @return {void}\n\t\t */\n\t\tonDragStart(column) {\n\t\t\tthis.dragOriginValue = column.value\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@change` on a column's card list. Only the destination\n\t\t * column's `added` event carries the dropped object; a same-column\n\t\t * reorder (`moved`) or the source column's `removed` are no-ops here.\n\t\t *\n\t\t * @param {object} evt vuedraggable's change event.\n\t\t * @param {object} column The column the event fired on.\n\t\t * @return {void}\n\t\t */\n\t\tonColumnChange(evt, column) {\n\t\t\tif (!evt.added) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst card = evt.added.element\n\t\t\tconst fromValue = this.dragOriginValue\n\t\t\tconst toValue = column.value\n\t\t\tthis.dragOriginValue = null\n\n\t\t\tif (fromValue === toValue) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.commitMove(card, fromValue, toValue, evt.added.newIndex)\n\t\t},\n\n\t\t/**\n\t\t * Record the pending move and emit it for the host to persist. The card\n\t\t * has already been moved optimistically by vuedraggable's v-model splice.\n\t\t *\n\t\t * @param {object} card The moved object.\n\t\t * @param {unknown} fromValue The origin column's value.\n\t\t * @param {unknown} toValue The destination column's value.\n\t\t * @param {number} originIndex The index the card is currently at in the destination column (used only for logging/debugging).\n\t\t * @return {void}\n\t\t */\n\t\tcommitMove(card, fromValue, toValue, originIndex) {\n\t\t\tconst id = this.cardKey(card)\n\t\t\tthis.suppressRebuild = true\n\t\t\tthis.pendingMoves = {\n\t\t\t\t...this.pendingMoves,\n\t\t\t\t[id]: { fromValue, toValue, card, originIndex },\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event move Emitted after a card is optimistically moved to another\n\t\t\t * column. The host performs the actual object write through the\n\t\t\t * existing guarded PATCH/PUT endpoint and calls `resolveMove`/\n\t\t\t * `rejectMove` on this component to confirm or roll back.\n\t\t\t * @type {{ object: object, groupByField: string, fromValue: unknown, toValue: unknown }}\n\t\t\t */\n\t\t\tthis.$emit('move', {\n\t\t\t\tobject: card,\n\t\t\t\tgroupByField: this.groupByField,\n\t\t\t\tfromValue,\n\t\t\t\ttoValue,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Confirm a pending move succeeded — clears its pending visual state.\n\t\t * The card already sits in the destination column (optimistic UI); no\n\t\t * further mutation is needed.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\tresolveMove(objectId) {\n\t\t\tthis.clearPending(objectId)\n\t\t},\n\n\t\t/**\n\t\t * Roll back a rejected move: the card returns to its origin column and\n\t\t * `move-rejected` fires with the server's reason.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @param {string} [reason] The rejection reason (e.g. an illegal lifecycle\n\t\t * transition message) to surface to the user.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\trejectMove(objectId, reason) {\n\t\t\tconst pending = this.pendingMoves[objectId]\n\t\t\tif (!pending) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst toColumn = this.localColumns.find((c) => c.value === pending.toValue)\n\t\t\tconst fromColumn = this.localColumns.find((c) => c.value === pending.fromValue)\n\n\t\t\tif (toColumn) {\n\t\t\t\tconst idx = toColumn.cards.findIndex((c) => this.cardKey(c) === objectId)\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\ttoColumn.cards.splice(idx, 1)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (fromColumn) {\n\t\t\t\tfromColumn.cards.push(pending.card)\n\t\t\t}\n\n\t\t\tthis.clearPending(objectId)\n\n\t\t\t/**\n\t\t\t * @event move-rejected Emitted after `rejectMove()` rolls a card back\n\t\t\t * to its origin column.\n\t\t\t * @type {{ object: object, fromValue: unknown, toValue: unknown, reason: (string|undefined) }}\n\t\t\t */\n\t\t\tthis.$emit('move-rejected', {\n\t\t\t\tobject: pending.card,\n\t\t\t\tfromValue: pending.fromValue,\n\t\t\t\ttoValue: pending.toValue,\n\t\t\t\treason,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Remove a move from the pending map and, once none remain, allow\n\t\t * prop-driven rebuilds again.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t */\n\t\tclearPending(objectId) {\n\t\t\tconst { [objectId]: _removed, ...rest } = this.pendingMoves\n\t\t\tthis.pendingMoves = rest\n\t\t\tif (Object.keys(rest).length === 0) {\n\t\t\t\tthis.suppressRebuild = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-kanban__loading,\n.cn-object-kanban__empty {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 40px;\n}\n\n.cn-object-kanban__board {\n\tdisplay: flex;\n\tgap: 16px;\n\toverflow-x: auto;\n\tpadding-bottom: 8px;\n}\n\n.cn-object-kanban__column {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 0 0 280px;\n\tmin-width: 280px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: var(--border-radius-large, 10px);\n\tpadding: 8px;\n}\n\n.cn-object-kanban__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px;\n\tfont-weight: 600;\n}\n\n.cn-object-kanban__column-count {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-object-kanban__column-cards {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-height: 40px;\n}\n\n.cn-object-kanban__card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius, 8px);\n\tpadding: 10px;\n\tcursor: grab;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-object-kanban__card--pending {\n\topacity: 0.6;\n}\n\n.cn-object-kanban__card-title {\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-object-kanban__card-fields {\n\tmargin-top: 6px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-object-kanban__load-more {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding-top: 8px;\n}\n</style>\n"],"names":["CnCellRenderer","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnObjectKanban.vue2.js","sources":["../../../../src/components/CnObjectKanban/CnObjectKanban.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-object-kanban\">\n\t\t<div v-if=\"loading\" class=\"cn-object-kanban__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<div v-else-if=\"localColumns.length === 0\" class=\"cn-object-kanban__empty\">\n\t\t\t<!-- @slot empty Custom empty state shown when there are no columns to render. -->\n\t\t\t<slot name=\"empty\">\n\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ViewColumn :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-object-kanban__board\">\n\t\t\t<div\n\t\t\t\tv-for=\"column in localColumns\"\n\t\t\t\t:key=\"columnKey(column.value)\"\n\t\t\t\tclass=\"cn-object-kanban__column\">\n\t\t\t\t<div class=\"cn-object-kanban__column-header\">\n\t\t\t\t\t<!-- @slot column-header Override a column's header. -->\n\t\t\t\t\t<!-- @binding {object} column The column ({ value, cards, total, limit, offset }). -->\n\t\t\t\t\t<slot name=\"column-header\" :column=\"column\">\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-title\">{{ columnLabel(column) }}</span>\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-count\">{{ column.total }}</span>\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\n\t\t\t\t<!-- `#item` + `item-key`: vuedraggable@4 (the Vue 3 build)\n\t\t\t\t renders the cards itself from this slot. A `v-for` in the\n\t\t\t\t default slot throws \"draggable element must have an item\n\t\t\t\t slot\" the moment the column paints. -->\n\t\t\t\t<Draggable\n\t\t\t\t\t:list=\"column.cards\"\n\t\t\t\t\ttag=\"div\"\n\t\t\t\t\tclass=\"cn-object-kanban__column-cards\"\n\t\t\t\t\t:itemKey=\"cardKey\"\n\t\t\t\t\tgroup=\"cn-object-kanban-cards\"\n\t\t\t\t\t:data-column-value=\"columnKey(column.value)\"\n\t\t\t\t\t@start=\"onDragStart(column)\"\n\t\t\t\t\t@change=\"onColumnChange($event, column)\">\n\t\t\t\t\t<template #item=\"{ element: card }\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"cn-object-kanban__card\"\n\t\t\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t\t\t'cn-object-kanban__card--pending': isPending(card),\n\t\t\t\t\t\t\t\t'cn-object-kanban__card--overdue': dueState(card) === 'overdue',\n\t\t\t\t\t\t\t\t'cn-object-kanban__card--soon': dueState(card) === 'soon',\n\t\t\t\t\t\t\t}\">\n\t\t\t\t\t\t\t<!-- @slot card Fully replace the default card rendering. -->\n\t\t\t\t\t\t\t<!-- @binding {object} object The card's object. -->\n\t\t\t\t\t\t\t<!-- @binding {object} column The column the card currently sits in. -->\n\t\t\t\t\t\t\t<!-- @binding {string|null} dueState The card's due state under `dueRule`: `overdue`, `soon`, `ok`, or null without a rule. -->\n\t\t\t\t\t\t\t<slot\n\t\t\t\t\t\t\t\tname=\"card\"\n\t\t\t\t\t\t\t\t:object=\"card\"\n\t\t\t\t\t\t\t\t:column=\"column\"\n\t\t\t\t\t\t\t\t:dueState=\"dueState(card)\">\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"dueLabel(card)\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-object-kanban__due\"\n\t\t\t\t\t\t\t\t\t:class=\"`cn-object-kanban__due--${dueState(card)}`\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-kanban-due\">\n\t\t\t\t\t\t\t\t\t{{ dueLabel(card) }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<div class=\"cn-object-kanban__card-title\" @click=\"onCardClick(card)\">\n\t\t\t\t\t\t\t\t\t{{ cardTitle(card) }}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div v-if=\"cardFields.length\" class=\"cn-object-kanban__card-fields\">\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tv-for=\"field in cardFields\"\n\t\t\t\t\t\t\t\t\t\t:key=\"field\"\n\t\t\t\t\t\t\t\t\t\tclass=\"cn-object-kanban__card-field\">\n\t\t\t\t\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t\t\t\t\t:value=\"card[field]\"\n\t\t\t\t\t\t\t\t\t\t\t:property=\"schemaProperty(field)\"\n\t\t\t\t\t\t\t\t\t\t\t:truncate=\"40\" />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</slot>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</Draggable>\n\n\t\t\t\t<div v-if=\"hasMore(column)\" class=\"cn-object-kanban__load-more\">\n\t\t\t\t\t<NcButton :disabled=\"isColumnLoading(column)\" @click=\"onLoadMore(column)\">\n\t\t\t\t\t\t<template v-if=\"isColumnLoading(column)\" #icon>\n\t\t\t\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport draggable from 'vuedraggable'\nimport ViewColumn from 'vue-material-design-icons/ViewColumn.vue'\nimport { dueStateForRow } from '../../utils/dueRule.js'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\n\n/**\n * CnObjectKanban — Kanban board over a schema property's distinct values.\n *\n * Renders one column per distinct value of `groupByField` and paginates each\n * column's cards independently (\"load more\", never a whole column at once).\n * Column values/order come from (in precedence order) an explicit\n * `columnOrder`, the `groupByField` schema property's `enum`, or values\n * discovered from `objects` — mirroring OpenRegister's\n * `ViewPresentationService::deriveColumnValues()`.\n *\n * Two rendering modes:\n * - **Pre-built columns** — pass `columns` (the shape returned by\n * `GET /api/views/{id}/kanban`: `[{ value, cards, total, limit, offset }]`);\n * the host owns pagination and re-fetches on `load-more`.\n * - **Flat objects** — pass `objects` + `groupByField` (and optionally\n * `columnOrder`/`schema`); the component derives and paginates columns\n * locally.\n *\n * Dragging a card to another column does NOT write anything itself — there is\n * deliberately no bespoke \"move card\" endpoint (REQ-VIEW-KANBAN-03). The move\n * is applied optimistically to the local board and the component emits\n * `move`; the host performs the actual object PATCH/PUT through the existing\n * guarded write path and reports back:\n * - On success, nothing further is required (the optimistic move already\n * reflects it) — optionally call `resolveMove(id)` to clear the pending\n * visual state sooner.\n * - On a rejected (e.g. illegal lifecycle transition) write, call\n * `rejectMove(id, reason)` — the card snaps back to its origin column and\n * `move-rejected` fires with the reason for the host to surface.\n *\n * ```vue\n * <CnObjectKanban\n * :objects=\"objects\"\n * group-by-field=\"status\"\n * :column-order=\"['todo', 'doing', 'done']\"\n * :card-fields=\"['title', 'assignee']\"\n * :schema=\"schema\"\n * ref=\"kanban\"\n * @move=\"onMove\"\n * @load-more=\"fetchMoreCards\" />\n * ```\n * ```js\n * async onMove({ object, groupByField, fromValue, toValue }) {\n * try {\n * await patchObject(object.id, { [groupByField]: toValue })\n * } catch (e) {\n * this.$refs.kanban.rejectMove(object.id, e.message)\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnObjectKanban',\n\n\tcomponents: {\n\t\tDraggable: draggable,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tNcEmptyContent,\n\t\tViewColumn,\n\t\tCnCellRenderer,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Flat objects to derive columns from (ignored when `columns` is set).\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tobjects: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Pre-built columns, e.g. the response of `GET /api/views/{id}/kanban`.\n\t\t *\n\t\t * @type {Array<{value: string|number, cards: Array<object>, total: number, limit: number, offset: number}>|null}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The schema property whose distinct values become columns. */\n\t\tgroupByField: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Explicit column order. Takes precedence over the schema's enum order. */\n\t\tcolumnOrder: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Object fields rendered on each card (in order). */\n\t\tcardFields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Schema definition, used to resolve enum column order and card field types. */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Overall loading state (initial board fetch). */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Column values currently loading more cards (drives the per-column spinner). */\n\t\tloadingColumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Cards shown per column before \"load more\", in local (`objects`-driven) mode. */\n\t\tpageSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 20,\n\t\t},\n\n\t\t/** Object property used as each card's identity. */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Marks late cards: `{ field, soonDays? }`. `field` is the dot-path to\n\t\t * a card's due date. A date before today gets an error edge and the\n\t\t * label \"Overdue\"; a date within `soonDays` (default 3) gets \"Due\n\t\t * soon\" in the warning colour. `null` (the default) marks nothing.\n\t\t *\n\t\t * The same rule shape the table's date cell uses, so a list and its\n\t\t * board agree on what late means.\n\t\t *\n\t\t * @type {{field: string, soonDays?: number}|null}\n\t\t */\n\t\tdueRule: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['card-click', 'load-more', 'move', 'move-rejected'],\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalColumns: [],\n\t\t\t// column.value -> full unpaginated card list, local mode only.\n\t\t\tfullCardsByColumn: {},\n\t\t\t// Suppressed while a move is pending so the prop-driven rebuild\n\t\t\t// doesn't clobber the optimistic UI before the host's data catches up.\n\t\t\tsuppressRebuild: false,\n\t\t\t// objectId -> { fromValue, toValue, card, originIndex }\n\t\t\tpendingMoves: {},\n\t\t\tdragOriginValue: null,\n\t\t\t// The day late marking measures from.\n\t\t\ttoday: new Date(),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\temptyText() {\n\t\t\treturn t('nextcloud-vue', 'No columns to show')\n\t\t},\n\n\t\tloadMoreLabel() {\n\t\t\treturn t('nextcloud-vue', 'Load more')\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjects: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tcolumns: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tgroupByField() {\n\t\t\tthis.rebuild()\n\t\t},\n\n\t\tcolumnOrder: {\n\t\t\thandler() {\n\t\t\t\tthis.rebuild()\n\t\t\t},\n\n\t\t\tdeep: true,\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.rebuild()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A card's due state under `dueRule`.\n\t\t *\n\t\t * @param {object} card The card's row.\n\t\t * @return {'overdue'|'soon'|'ok'|null} The state, or null when no rule applies.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n\t\t */\n\t\tdueState(card) {\n\t\t\treturn dueStateForRow(card, this.dueRule, this.today)\n\t\t},\n\n\t\t/**\n\t\t * The words a late card carries. Colour alone would not reach a reader\n\t\t * who cannot see it, or cannot tell red from amber.\n\t\t *\n\t\t * @param {object} card The card's row.\n\t\t * @return {string} \"Overdue\", \"Due soon\", or '' for a card that is fine.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n\t\t */\n\t\tdueLabel(card) {\n\t\t\tconst state = this.dueState(card)\n\t\t\tif (state === 'overdue') {\n\t\t\t\treturn t('nextcloud-vue', 'Overdue')\n\t\t\t}\n\t\t\treturn state === 'soon' ? t('nextcloud-vue', 'Due soon') : ''\n\t\t},\n\n\t\t/**\n\t\t * Rebuild `localColumns` from either the `columns` prop (backend-paginated\n\t\t * mode) or `objects` + `groupByField` (locally-derived mode).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trebuild() {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\tthis.localColumns = this.columns.map((column) => ({\n\t\t\t\t\tvalue: column.value,\n\t\t\t\t\tcards: Array.isArray(column.cards) ? [...column.cards] : [],\n\t\t\t\t\ttotal: typeof column.total === 'number' ? column.total : (column.cards || []).length,\n\t\t\t\t\tlimit: column.limit,\n\t\t\t\t\toffset: column.offset,\n\t\t\t\t}))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst values = this.deriveColumnValues()\n\t\t\tconst fullByColumn = {}\n\t\t\tthis.localColumns = values.map((value) => {\n\t\t\t\tconst fullCards = this.objects.filter((object) => object && object[this.groupByField] === value)\n\t\t\t\tfullByColumn[this.columnKey(value)] = fullCards\n\t\t\t\treturn {\n\t\t\t\t\tvalue,\n\t\t\t\t\tcards: fullCards.slice(0, this.pageSize),\n\t\t\t\t\ttotal: fullCards.length,\n\t\t\t\t\tlimit: this.pageSize,\n\t\t\t\t\toffset: 0,\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis.fullCardsByColumn = fullByColumn\n\t\t},\n\n\t\t/**\n\t\t * Derive the ordered column values: `columnOrder` > schema enum order >\n\t\t * distinct values observed in `objects`. Mirrors\n\t\t * `ViewPresentationService::deriveColumnValues()`.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tderiveColumnValues() {\n\t\t\tif (Array.isArray(this.columnOrder) && this.columnOrder.length > 0) {\n\t\t\t\treturn [...this.columnOrder]\n\t\t\t}\n\n\t\t\tconst enumValues = this.schema?.properties?.[this.groupByField]?.enum\n\t\t\tif (Array.isArray(enumValues) && enumValues.length > 0) {\n\t\t\t\treturn [...enumValues]\n\t\t\t}\n\n\t\t\treturn this.discoverDistinctValues()\n\t\t},\n\n\t\t/**\n\t\t * Distinct `groupByField` values observed in `objects`, in first-seen order.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tdiscoverDistinctValues() {\n\t\t\tconst seen = new Set()\n\t\t\tconst values = []\n\t\t\tfor (const object of this.objects) {\n\t\t\t\tconst value = object?.[this.groupByField]\n\t\t\t\tif (value !== undefined && value !== null && !seen.has(value)) {\n\t\t\t\t\tseen.add(value)\n\t\t\t\t\tvalues.push(value)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn values\n\t\t},\n\n\t\t/**\n\t\t * Stable string key for a column value (used as a `:key` and as the\n\t\t * `fullCardsByColumn` index — object/array values are unlikely but guarded).\n\t\t *\n\t\t * @param {unknown} value The column value.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnKey(value) {\n\t\t\treturn typeof value === 'string' || typeof value === 'number' ? String(value) : JSON.stringify(value)\n\t\t},\n\n\t\t/**\n\t\t * The card's identity, per `rowKey`.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {unknown}\n\t\t */\n\t\tcardKey(card) {\n\t\t\treturn card?.[this.rowKey]\n\t\t},\n\n\t\t/**\n\t\t * Display label for a column: its schema `oneOf[].title` when the\n\t\t * property declares per-value titles, otherwise the raw value.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnLabel(column) {\n\t\t\tconst oneOf = this.schema?.properties?.[this.groupByField]?.oneOf\n\t\t\tif (Array.isArray(oneOf)) {\n\t\t\t\tconst match = oneOf.find((entry) => entry?.const === column.value)\n\t\t\t\tif (match?.title) {\n\t\t\t\t\treturn match.title\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Best-effort card title: the schema's configured name field, else\n\t\t * `title`/`name`/the row key.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {string}\n\t\t */\n\t\tcardTitle(card) {\n\t\t\tconst nameField = this.schema?.configuration?.objectNameField\n\t\t\tif (nameField && card[nameField]) {\n\t\t\t\treturn String(card[nameField])\n\t\t\t}\n\t\t\treturn String(card.title || card.name || card[this.rowKey] || '—')\n\t\t},\n\n\t\t/**\n\t\t * The schema property definition for a card field, if known.\n\t\t *\n\t\t * @param {string} field The field name.\n\t\t * @return {object|undefined}\n\t\t */\n\t\tschemaProperty(field) {\n\t\t\treturn this.schema?.properties?.[field]\n\t\t},\n\n\t\t/**\n\t\t * Whether a column has more cards to load: backend-paginated mode compares\n\t\t * `cards.length` to `total`; local mode compares to the full filtered set.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\thasMore(column) {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\treturn column.cards.length < column.total\n\t\t\t}\n\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\treturn column.cards.length < full.length\n\t\t},\n\n\t\t/**\n\t\t * Whether a column's \"load more\" is in flight.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\tisColumnLoading(column) {\n\t\t\treturn this.loadingColumns.includes(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Whether a card has an unresolved optimistic move.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {boolean}\n\t\t */\n\t\tisPending(card) {\n\t\t\treturn Object.hasOwn(this.pendingMoves, this.cardKey(card))\n\t\t},\n\n\t\t/**\n\t\t * Handle a column's \"load more\" click: local mode reveals the next page\n\t\t * from the already-fetched full set; backend-paginated mode only emits —\n\t\t * the host fetches the next page and passes updated `columns`.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {void}\n\t\t */\n\t\tonLoadMore(column) {\n\t\t\tif (!Array.isArray(this.columns)) {\n\t\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\t\tcolumn.cards = full.slice(0, column.cards.length + this.pageSize)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event load-more Emitted when a column's \"load more\" is clicked.\n\t\t\t * @type {{ value: unknown, offset: number }}\n\t\t\t */\n\t\t\tthis.$emit('load-more', { value: column.value, offset: column.cards.length })\n\t\t},\n\n\t\t/**\n\t\t * Card click (ignored while the card is mid-drag).\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {void}\n\t\t */\n\t\tonCardClick(card) {\n\t\t\t/**\n\t\t\t * @event card-click Emitted when a card is clicked (not dragged).\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('card-click', card)\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@start` — remember which column a drag began in, since\n\t\t * `@change` on the destination column doesn't know the source.\n\t\t *\n\t\t * @param {object} column The column the drag started in.\n\t\t * @return {void}\n\t\t */\n\t\tonDragStart(column) {\n\t\t\tthis.dragOriginValue = column.value\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@change` on a column's card list. Only the destination\n\t\t * column's `added` event carries the dropped object; a same-column\n\t\t * reorder (`moved`) or the source column's `removed` are no-ops here.\n\t\t *\n\t\t * @param {object} evt vuedraggable's change event.\n\t\t * @param {object} column The column the event fired on.\n\t\t * @return {void}\n\t\t */\n\t\tonColumnChange(evt, column) {\n\t\t\tif (!evt.added) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst card = evt.added.element\n\t\t\tconst fromValue = this.dragOriginValue\n\t\t\tconst toValue = column.value\n\t\t\tthis.dragOriginValue = null\n\n\t\t\tif (fromValue === toValue) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.commitMove(card, fromValue, toValue, evt.added.newIndex)\n\t\t},\n\n\t\t/**\n\t\t * Record the pending move and emit it for the host to persist. The card\n\t\t * has already been moved optimistically by vuedraggable's v-model splice.\n\t\t *\n\t\t * @param {object} card The moved object.\n\t\t * @param {unknown} fromValue The origin column's value.\n\t\t * @param {unknown} toValue The destination column's value.\n\t\t * @param {number} originIndex The index the card is currently at in the destination column (used only for logging/debugging).\n\t\t * @return {void}\n\t\t */\n\t\tcommitMove(card, fromValue, toValue, originIndex) {\n\t\t\tconst id = this.cardKey(card)\n\t\t\tthis.suppressRebuild = true\n\t\t\tthis.pendingMoves = {\n\t\t\t\t...this.pendingMoves,\n\t\t\t\t[id]: { fromValue, toValue, card, originIndex },\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event move Emitted after a card is optimistically moved to another\n\t\t\t * column. The host performs the actual object write through the\n\t\t\t * existing guarded PATCH/PUT endpoint and calls `resolveMove`/\n\t\t\t * `rejectMove` on this component to confirm or roll back.\n\t\t\t * @type {{ object: object, groupByField: string, fromValue: unknown, toValue: unknown }}\n\t\t\t */\n\t\t\tthis.$emit('move', {\n\t\t\t\tobject: card,\n\t\t\t\tgroupByField: this.groupByField,\n\t\t\t\tfromValue,\n\t\t\t\ttoValue,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Confirm a pending move succeeded — clears its pending visual state.\n\t\t * The card already sits in the destination column (optimistic UI); no\n\t\t * further mutation is needed.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\tresolveMove(objectId) {\n\t\t\tthis.clearPending(objectId)\n\t\t},\n\n\t\t/**\n\t\t * Roll back a rejected move: the card returns to its origin column and\n\t\t * `move-rejected` fires with the server's reason.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @param {string} [reason] The rejection reason (e.g. an illegal lifecycle\n\t\t * transition message) to surface to the user.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\trejectMove(objectId, reason) {\n\t\t\tconst pending = this.pendingMoves[objectId]\n\t\t\tif (!pending) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst toColumn = this.localColumns.find((c) => c.value === pending.toValue)\n\t\t\tconst fromColumn = this.localColumns.find((c) => c.value === pending.fromValue)\n\n\t\t\tif (toColumn) {\n\t\t\t\tconst idx = toColumn.cards.findIndex((c) => this.cardKey(c) === objectId)\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\ttoColumn.cards.splice(idx, 1)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (fromColumn) {\n\t\t\t\tfromColumn.cards.push(pending.card)\n\t\t\t}\n\n\t\t\tthis.clearPending(objectId)\n\n\t\t\t/**\n\t\t\t * @event move-rejected Emitted after `rejectMove()` rolls a card back\n\t\t\t * to its origin column.\n\t\t\t * @type {{ object: object, fromValue: unknown, toValue: unknown, reason: (string|undefined) }}\n\t\t\t */\n\t\t\tthis.$emit('move-rejected', {\n\t\t\t\tobject: pending.card,\n\t\t\t\tfromValue: pending.fromValue,\n\t\t\t\ttoValue: pending.toValue,\n\t\t\t\treason,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Remove a move from the pending map and, once none remain, allow\n\t\t * prop-driven rebuilds again.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t */\n\t\tclearPending(objectId) {\n\t\t\tconst { [objectId]: _removed, ...rest } = this.pendingMoves\n\t\t\tthis.pendingMoves = rest\n\t\t\tif (Object.keys(rest).length === 0) {\n\t\t\t\tthis.suppressRebuild = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-kanban__loading,\n.cn-object-kanban__empty {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 40px;\n}\n\n.cn-object-kanban__board {\n\tdisplay: flex;\n\tgap: 16px;\n\toverflow-x: auto;\n\tpadding-bottom: 8px;\n}\n\n.cn-object-kanban__column {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 0 0 280px;\n\tmin-width: 280px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: var(--border-radius-large, 10px);\n\tpadding: 8px;\n}\n\n.cn-object-kanban__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px;\n\tfont-weight: 600;\n}\n\n.cn-object-kanban__column-count {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-object-kanban__column-cards {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-height: 40px;\n}\n\n.cn-object-kanban__card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius, 8px);\n\tpadding: 10px;\n\tcursor: grab;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-object-kanban__card--pending {\n\topacity: 0.6;\n}\n\n/* A late card. The label carries the meaning; the edge helps a sighted\n reader find it. An inset shadow, so the card keeps its size. */\n.cn-object-kanban__card--overdue {\n\tborder-color: var(--color-error);\n\tbox-shadow: inset 3px 0 0 var(--color-error);\n}\n\n.cn-object-kanban__due {\n\tdisplay: block;\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n}\n\n.cn-object-kanban__due--overdue {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-object-kanban__due--soon {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n\tfont-weight: 600;\n}\n\n.cn-object-kanban__card-title {\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-object-kanban__card-fields {\n\tmargin-top: 6px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-object-kanban__load-more {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding-top: 8px;\n}\n</style>\n"],"names":["CnCellRenderer","t"],"mappings":"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"}
@@ -18,7 +18,7 @@ const _hoisted_6 = { class: "cn-object-kanban__column-title" };
18
18
  const _hoisted_7 = { class: "cn-object-kanban__column-count" };
19
19
  const _hoisted_8 = ["onClick"];
20
20
  const _hoisted_9 = {
21
- key: 0,
21
+ key: 1,
22
22
  class: "cn-object-kanban__card-fields"
23
23
  };
24
24
  const _hoisted_10 = {
@@ -74,12 +74,24 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
74
74
  }, {
75
75
  item: withCtx(({ element: card }) => [
76
76
  createElementVNode("div", {
77
- class: normalizeClass(["cn-object-kanban__card", { 'cn-object-kanban__card--pending': $options.isPending(card) }])
77
+ class: normalizeClass(["cn-object-kanban__card", {
78
+ 'cn-object-kanban__card--pending': $options.isPending(card),
79
+ 'cn-object-kanban__card--overdue': $options.dueState(card) === 'overdue',
80
+ 'cn-object-kanban__card--soon': $options.dueState(card) === 'soon',
81
+ }])
78
82
  }, [
79
83
  renderSlot(_ctx.$slots, "card", {
80
84
  object: card,
81
- column: column
85
+ column: column,
86
+ dueState: $options.dueState(card)
82
87
  }, () => [
88
+ ($options.dueLabel(card))
89
+ ? (openBlock(), createElementBlock("span", {
90
+ key: 0,
91
+ class: normalizeClass(["cn-object-kanban__due", `cn-object-kanban__due--${$options.dueState(card)}`]),
92
+ "data-testid": "cn-kanban-due"
93
+ }, toDisplayString($options.dueLabel(card)), 3 /* TEXT, CLASS */))
94
+ : createCommentVNode("v-if", true),
83
95
  createElementVNode("div", {
84
96
  class: "cn-object-kanban__card-title",
85
97
  onClick: $event => ($options.onCardClick(card))
@@ -1 +1 @@
1
- {"version":3,"file":"CnObjectKanban.vue3.js","sources":["../../../../src/components/CnObjectKanban/CnObjectKanban.vue?vue&type=template&id=3523a46f&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-object-kanban\">\n\t\t<div v-if=\"loading\" class=\"cn-object-kanban__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<div v-else-if=\"localColumns.length === 0\" class=\"cn-object-kanban__empty\">\n\t\t\t<!-- @slot empty Custom empty state shown when there are no columns to render. -->\n\t\t\t<slot name=\"empty\">\n\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ViewColumn :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-object-kanban__board\">\n\t\t\t<div\n\t\t\t\tv-for=\"column in localColumns\"\n\t\t\t\t:key=\"columnKey(column.value)\"\n\t\t\t\tclass=\"cn-object-kanban__column\">\n\t\t\t\t<div class=\"cn-object-kanban__column-header\">\n\t\t\t\t\t<!-- @slot column-header Override a column's header. -->\n\t\t\t\t\t<!-- @binding {object} column The column ({ value, cards, total, limit, offset }). -->\n\t\t\t\t\t<slot name=\"column-header\" :column=\"column\">\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-title\">{{ columnLabel(column) }}</span>\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-count\">{{ column.total }}</span>\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\n\t\t\t\t<!-- `#item` + `item-key`: vuedraggable@4 (the Vue 3 build)\n\t\t\t\t renders the cards itself from this slot. A `v-for` in the\n\t\t\t\t default slot throws \"draggable element must have an item\n\t\t\t\t slot\" the moment the column paints. -->\n\t\t\t\t<Draggable\n\t\t\t\t\t:list=\"column.cards\"\n\t\t\t\t\ttag=\"div\"\n\t\t\t\t\tclass=\"cn-object-kanban__column-cards\"\n\t\t\t\t\t:itemKey=\"cardKey\"\n\t\t\t\t\tgroup=\"cn-object-kanban-cards\"\n\t\t\t\t\t:data-column-value=\"columnKey(column.value)\"\n\t\t\t\t\t@start=\"onDragStart(column)\"\n\t\t\t\t\t@change=\"onColumnChange($event, column)\">\n\t\t\t\t\t<template #item=\"{ element: card }\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"cn-object-kanban__card\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-object-kanban__card--pending': isPending(card) }\">\n\t\t\t\t\t\t\t<!-- @slot card Fully replace the default card rendering. -->\n\t\t\t\t\t\t\t<!-- @binding {object} object The card's object. -->\n\t\t\t\t\t\t\t<!-- @binding {object} column The column the card currently sits in. -->\n\t\t\t\t\t\t\t<slot name=\"card\" :object=\"card\" :column=\"column\">\n\t\t\t\t\t\t\t\t<div class=\"cn-object-kanban__card-title\" @click=\"onCardClick(card)\">\n\t\t\t\t\t\t\t\t\t{{ cardTitle(card) }}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div v-if=\"cardFields.length\" class=\"cn-object-kanban__card-fields\">\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tv-for=\"field in cardFields\"\n\t\t\t\t\t\t\t\t\t\t:key=\"field\"\n\t\t\t\t\t\t\t\t\t\tclass=\"cn-object-kanban__card-field\">\n\t\t\t\t\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t\t\t\t\t:value=\"card[field]\"\n\t\t\t\t\t\t\t\t\t\t\t:property=\"schemaProperty(field)\"\n\t\t\t\t\t\t\t\t\t\t\t:truncate=\"40\" />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</slot>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</Draggable>\n\n\t\t\t\t<div v-if=\"hasMore(column)\" class=\"cn-object-kanban__load-more\">\n\t\t\t\t\t<NcButton :disabled=\"isColumnLoading(column)\" @click=\"onLoadMore(column)\">\n\t\t\t\t\t\t<template v-if=\"isColumnLoading(column)\" #icon>\n\t\t\t\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport draggable from 'vuedraggable'\nimport ViewColumn from 'vue-material-design-icons/ViewColumn.vue'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\n\n/**\n * CnObjectKanban — Kanban board over a schema property's distinct values.\n *\n * Renders one column per distinct value of `groupByField` and paginates each\n * column's cards independently (\"load more\", never a whole column at once).\n * Column values/order come from (in precedence order) an explicit\n * `columnOrder`, the `groupByField` schema property's `enum`, or values\n * discovered from `objects` — mirroring OpenRegister's\n * `ViewPresentationService::deriveColumnValues()`.\n *\n * Two rendering modes:\n * - **Pre-built columns** — pass `columns` (the shape returned by\n * `GET /api/views/{id}/kanban`: `[{ value, cards, total, limit, offset }]`);\n * the host owns pagination and re-fetches on `load-more`.\n * - **Flat objects** — pass `objects` + `groupByField` (and optionally\n * `columnOrder`/`schema`); the component derives and paginates columns\n * locally.\n *\n * Dragging a card to another column does NOT write anything itself — there is\n * deliberately no bespoke \"move card\" endpoint (REQ-VIEW-KANBAN-03). The move\n * is applied optimistically to the local board and the component emits\n * `move`; the host performs the actual object PATCH/PUT through the existing\n * guarded write path and reports back:\n * - On success, nothing further is required (the optimistic move already\n * reflects it) — optionally call `resolveMove(id)` to clear the pending\n * visual state sooner.\n * - On a rejected (e.g. illegal lifecycle transition) write, call\n * `rejectMove(id, reason)` — the card snaps back to its origin column and\n * `move-rejected` fires with the reason for the host to surface.\n *\n * ```vue\n * <CnObjectKanban\n * :objects=\"objects\"\n * group-by-field=\"status\"\n * :column-order=\"['todo', 'doing', 'done']\"\n * :card-fields=\"['title', 'assignee']\"\n * :schema=\"schema\"\n * ref=\"kanban\"\n * @move=\"onMove\"\n * @load-more=\"fetchMoreCards\" />\n * ```\n * ```js\n * async onMove({ object, groupByField, fromValue, toValue }) {\n * try {\n * await patchObject(object.id, { [groupByField]: toValue })\n * } catch (e) {\n * this.$refs.kanban.rejectMove(object.id, e.message)\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnObjectKanban',\n\n\tcomponents: {\n\t\tDraggable: draggable,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tNcEmptyContent,\n\t\tViewColumn,\n\t\tCnCellRenderer,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Flat objects to derive columns from (ignored when `columns` is set).\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tobjects: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Pre-built columns, e.g. the response of `GET /api/views/{id}/kanban`.\n\t\t *\n\t\t * @type {Array<{value: string|number, cards: Array<object>, total: number, limit: number, offset: number}>|null}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The schema property whose distinct values become columns. */\n\t\tgroupByField: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Explicit column order. Takes precedence over the schema's enum order. */\n\t\tcolumnOrder: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Object fields rendered on each card (in order). */\n\t\tcardFields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Schema definition, used to resolve enum column order and card field types. */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Overall loading state (initial board fetch). */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Column values currently loading more cards (drives the per-column spinner). */\n\t\tloadingColumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Cards shown per column before \"load more\", in local (`objects`-driven) mode. */\n\t\tpageSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 20,\n\t\t},\n\n\t\t/** Object property used as each card's identity. */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\t},\n\n\temits: ['card-click', 'load-more', 'move', 'move-rejected'],\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalColumns: [],\n\t\t\t// column.value -> full unpaginated card list, local mode only.\n\t\t\tfullCardsByColumn: {},\n\t\t\t// Suppressed while a move is pending so the prop-driven rebuild\n\t\t\t// doesn't clobber the optimistic UI before the host's data catches up.\n\t\t\tsuppressRebuild: false,\n\t\t\t// objectId -> { fromValue, toValue, card, originIndex }\n\t\t\tpendingMoves: {},\n\t\t\tdragOriginValue: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\temptyText() {\n\t\t\treturn t('nextcloud-vue', 'No columns to show')\n\t\t},\n\n\t\tloadMoreLabel() {\n\t\t\treturn t('nextcloud-vue', 'Load more')\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjects: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tcolumns: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tgroupByField() {\n\t\t\tthis.rebuild()\n\t\t},\n\n\t\tcolumnOrder: {\n\t\t\thandler() {\n\t\t\t\tthis.rebuild()\n\t\t\t},\n\n\t\t\tdeep: true,\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.rebuild()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Rebuild `localColumns` from either the `columns` prop (backend-paginated\n\t\t * mode) or `objects` + `groupByField` (locally-derived mode).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trebuild() {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\tthis.localColumns = this.columns.map((column) => ({\n\t\t\t\t\tvalue: column.value,\n\t\t\t\t\tcards: Array.isArray(column.cards) ? [...column.cards] : [],\n\t\t\t\t\ttotal: typeof column.total === 'number' ? column.total : (column.cards || []).length,\n\t\t\t\t\tlimit: column.limit,\n\t\t\t\t\toffset: column.offset,\n\t\t\t\t}))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst values = this.deriveColumnValues()\n\t\t\tconst fullByColumn = {}\n\t\t\tthis.localColumns = values.map((value) => {\n\t\t\t\tconst fullCards = this.objects.filter((object) => object && object[this.groupByField] === value)\n\t\t\t\tfullByColumn[this.columnKey(value)] = fullCards\n\t\t\t\treturn {\n\t\t\t\t\tvalue,\n\t\t\t\t\tcards: fullCards.slice(0, this.pageSize),\n\t\t\t\t\ttotal: fullCards.length,\n\t\t\t\t\tlimit: this.pageSize,\n\t\t\t\t\toffset: 0,\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis.fullCardsByColumn = fullByColumn\n\t\t},\n\n\t\t/**\n\t\t * Derive the ordered column values: `columnOrder` > schema enum order >\n\t\t * distinct values observed in `objects`. Mirrors\n\t\t * `ViewPresentationService::deriveColumnValues()`.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tderiveColumnValues() {\n\t\t\tif (Array.isArray(this.columnOrder) && this.columnOrder.length > 0) {\n\t\t\t\treturn [...this.columnOrder]\n\t\t\t}\n\n\t\t\tconst enumValues = this.schema?.properties?.[this.groupByField]?.enum\n\t\t\tif (Array.isArray(enumValues) && enumValues.length > 0) {\n\t\t\t\treturn [...enumValues]\n\t\t\t}\n\n\t\t\treturn this.discoverDistinctValues()\n\t\t},\n\n\t\t/**\n\t\t * Distinct `groupByField` values observed in `objects`, in first-seen order.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tdiscoverDistinctValues() {\n\t\t\tconst seen = new Set()\n\t\t\tconst values = []\n\t\t\tfor (const object of this.objects) {\n\t\t\t\tconst value = object?.[this.groupByField]\n\t\t\t\tif (value !== undefined && value !== null && !seen.has(value)) {\n\t\t\t\t\tseen.add(value)\n\t\t\t\t\tvalues.push(value)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn values\n\t\t},\n\n\t\t/**\n\t\t * Stable string key for a column value (used as a `:key` and as the\n\t\t * `fullCardsByColumn` index — object/array values are unlikely but guarded).\n\t\t *\n\t\t * @param {unknown} value The column value.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnKey(value) {\n\t\t\treturn typeof value === 'string' || typeof value === 'number' ? String(value) : JSON.stringify(value)\n\t\t},\n\n\t\t/**\n\t\t * The card's identity, per `rowKey`.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {unknown}\n\t\t */\n\t\tcardKey(card) {\n\t\t\treturn card?.[this.rowKey]\n\t\t},\n\n\t\t/**\n\t\t * Display label for a column: its schema `oneOf[].title` when the\n\t\t * property declares per-value titles, otherwise the raw value.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnLabel(column) {\n\t\t\tconst oneOf = this.schema?.properties?.[this.groupByField]?.oneOf\n\t\t\tif (Array.isArray(oneOf)) {\n\t\t\t\tconst match = oneOf.find((entry) => entry?.const === column.value)\n\t\t\t\tif (match?.title) {\n\t\t\t\t\treturn match.title\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Best-effort card title: the schema's configured name field, else\n\t\t * `title`/`name`/the row key.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {string}\n\t\t */\n\t\tcardTitle(card) {\n\t\t\tconst nameField = this.schema?.configuration?.objectNameField\n\t\t\tif (nameField && card[nameField]) {\n\t\t\t\treturn String(card[nameField])\n\t\t\t}\n\t\t\treturn String(card.title || card.name || card[this.rowKey] || '—')\n\t\t},\n\n\t\t/**\n\t\t * The schema property definition for a card field, if known.\n\t\t *\n\t\t * @param {string} field The field name.\n\t\t * @return {object|undefined}\n\t\t */\n\t\tschemaProperty(field) {\n\t\t\treturn this.schema?.properties?.[field]\n\t\t},\n\n\t\t/**\n\t\t * Whether a column has more cards to load: backend-paginated mode compares\n\t\t * `cards.length` to `total`; local mode compares to the full filtered set.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\thasMore(column) {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\treturn column.cards.length < column.total\n\t\t\t}\n\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\treturn column.cards.length < full.length\n\t\t},\n\n\t\t/**\n\t\t * Whether a column's \"load more\" is in flight.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\tisColumnLoading(column) {\n\t\t\treturn this.loadingColumns.includes(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Whether a card has an unresolved optimistic move.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {boolean}\n\t\t */\n\t\tisPending(card) {\n\t\t\treturn Object.hasOwn(this.pendingMoves, this.cardKey(card))\n\t\t},\n\n\t\t/**\n\t\t * Handle a column's \"load more\" click: local mode reveals the next page\n\t\t * from the already-fetched full set; backend-paginated mode only emits —\n\t\t * the host fetches the next page and passes updated `columns`.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {void}\n\t\t */\n\t\tonLoadMore(column) {\n\t\t\tif (!Array.isArray(this.columns)) {\n\t\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\t\tcolumn.cards = full.slice(0, column.cards.length + this.pageSize)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event load-more Emitted when a column's \"load more\" is clicked.\n\t\t\t * @type {{ value: unknown, offset: number }}\n\t\t\t */\n\t\t\tthis.$emit('load-more', { value: column.value, offset: column.cards.length })\n\t\t},\n\n\t\t/**\n\t\t * Card click (ignored while the card is mid-drag).\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {void}\n\t\t */\n\t\tonCardClick(card) {\n\t\t\t/**\n\t\t\t * @event card-click Emitted when a card is clicked (not dragged).\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('card-click', card)\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@start` — remember which column a drag began in, since\n\t\t * `@change` on the destination column doesn't know the source.\n\t\t *\n\t\t * @param {object} column The column the drag started in.\n\t\t * @return {void}\n\t\t */\n\t\tonDragStart(column) {\n\t\t\tthis.dragOriginValue = column.value\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@change` on a column's card list. Only the destination\n\t\t * column's `added` event carries the dropped object; a same-column\n\t\t * reorder (`moved`) or the source column's `removed` are no-ops here.\n\t\t *\n\t\t * @param {object} evt vuedraggable's change event.\n\t\t * @param {object} column The column the event fired on.\n\t\t * @return {void}\n\t\t */\n\t\tonColumnChange(evt, column) {\n\t\t\tif (!evt.added) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst card = evt.added.element\n\t\t\tconst fromValue = this.dragOriginValue\n\t\t\tconst toValue = column.value\n\t\t\tthis.dragOriginValue = null\n\n\t\t\tif (fromValue === toValue) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.commitMove(card, fromValue, toValue, evt.added.newIndex)\n\t\t},\n\n\t\t/**\n\t\t * Record the pending move and emit it for the host to persist. The card\n\t\t * has already been moved optimistically by vuedraggable's v-model splice.\n\t\t *\n\t\t * @param {object} card The moved object.\n\t\t * @param {unknown} fromValue The origin column's value.\n\t\t * @param {unknown} toValue The destination column's value.\n\t\t * @param {number} originIndex The index the card is currently at in the destination column (used only for logging/debugging).\n\t\t * @return {void}\n\t\t */\n\t\tcommitMove(card, fromValue, toValue, originIndex) {\n\t\t\tconst id = this.cardKey(card)\n\t\t\tthis.suppressRebuild = true\n\t\t\tthis.pendingMoves = {\n\t\t\t\t...this.pendingMoves,\n\t\t\t\t[id]: { fromValue, toValue, card, originIndex },\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event move Emitted after a card is optimistically moved to another\n\t\t\t * column. The host performs the actual object write through the\n\t\t\t * existing guarded PATCH/PUT endpoint and calls `resolveMove`/\n\t\t\t * `rejectMove` on this component to confirm or roll back.\n\t\t\t * @type {{ object: object, groupByField: string, fromValue: unknown, toValue: unknown }}\n\t\t\t */\n\t\t\tthis.$emit('move', {\n\t\t\t\tobject: card,\n\t\t\t\tgroupByField: this.groupByField,\n\t\t\t\tfromValue,\n\t\t\t\ttoValue,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Confirm a pending move succeeded — clears its pending visual state.\n\t\t * The card already sits in the destination column (optimistic UI); no\n\t\t * further mutation is needed.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\tresolveMove(objectId) {\n\t\t\tthis.clearPending(objectId)\n\t\t},\n\n\t\t/**\n\t\t * Roll back a rejected move: the card returns to its origin column and\n\t\t * `move-rejected` fires with the server's reason.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @param {string} [reason] The rejection reason (e.g. an illegal lifecycle\n\t\t * transition message) to surface to the user.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\trejectMove(objectId, reason) {\n\t\t\tconst pending = this.pendingMoves[objectId]\n\t\t\tif (!pending) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst toColumn = this.localColumns.find((c) => c.value === pending.toValue)\n\t\t\tconst fromColumn = this.localColumns.find((c) => c.value === pending.fromValue)\n\n\t\t\tif (toColumn) {\n\t\t\t\tconst idx = toColumn.cards.findIndex((c) => this.cardKey(c) === objectId)\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\ttoColumn.cards.splice(idx, 1)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (fromColumn) {\n\t\t\t\tfromColumn.cards.push(pending.card)\n\t\t\t}\n\n\t\t\tthis.clearPending(objectId)\n\n\t\t\t/**\n\t\t\t * @event move-rejected Emitted after `rejectMove()` rolls a card back\n\t\t\t * to its origin column.\n\t\t\t * @type {{ object: object, fromValue: unknown, toValue: unknown, reason: (string|undefined) }}\n\t\t\t */\n\t\t\tthis.$emit('move-rejected', {\n\t\t\t\tobject: pending.card,\n\t\t\t\tfromValue: pending.fromValue,\n\t\t\t\ttoValue: pending.toValue,\n\t\t\t\treason,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Remove a move from the pending map and, once none remain, allow\n\t\t * prop-driven rebuilds again.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t */\n\t\tclearPending(objectId) {\n\t\t\tconst { [objectId]: _removed, ...rest } = this.pendingMoves\n\t\t\tthis.pendingMoves = rest\n\t\t\tif (Object.keys(rest).length === 0) {\n\t\t\t\tthis.suppressRebuild = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-kanban__loading,\n.cn-object-kanban__empty {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 40px;\n}\n\n.cn-object-kanban__board {\n\tdisplay: flex;\n\tgap: 16px;\n\toverflow-x: auto;\n\tpadding-bottom: 8px;\n}\n\n.cn-object-kanban__column {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 0 0 280px;\n\tmin-width: 280px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: var(--border-radius-large, 10px);\n\tpadding: 8px;\n}\n\n.cn-object-kanban__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px;\n\tfont-weight: 600;\n}\n\n.cn-object-kanban__column-count {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-object-kanban__column-cards {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-height: 40px;\n}\n\n.cn-object-kanban__card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius, 8px);\n\tpadding: 10px;\n\tcursor: grab;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-object-kanban__card--pending {\n\topacity: 0.6;\n}\n\n.cn-object-kanban__card-title {\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-object-kanban__card-fields {\n\tmargin-top: 6px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-object-kanban__load-more {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding-top: 8px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_renderSlot","_createElementVNode","_toDisplayString","_withCtx","_normalizeClass","_Fragment","_renderList","_createTextVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnObjectKanban.vue3.js","sources":["../../../../src/components/CnObjectKanban/CnObjectKanban.vue?vue&type=template&id=3523a46f&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-object-kanban\">\n\t\t<div v-if=\"loading\" class=\"cn-object-kanban__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<div v-else-if=\"localColumns.length === 0\" class=\"cn-object-kanban__empty\">\n\t\t\t<!-- @slot empty Custom empty state shown when there are no columns to render. -->\n\t\t\t<slot name=\"empty\">\n\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ViewColumn :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-object-kanban__board\">\n\t\t\t<div\n\t\t\t\tv-for=\"column in localColumns\"\n\t\t\t\t:key=\"columnKey(column.value)\"\n\t\t\t\tclass=\"cn-object-kanban__column\">\n\t\t\t\t<div class=\"cn-object-kanban__column-header\">\n\t\t\t\t\t<!-- @slot column-header Override a column's header. -->\n\t\t\t\t\t<!-- @binding {object} column The column ({ value, cards, total, limit, offset }). -->\n\t\t\t\t\t<slot name=\"column-header\" :column=\"column\">\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-title\">{{ columnLabel(column) }}</span>\n\t\t\t\t\t\t<span class=\"cn-object-kanban__column-count\">{{ column.total }}</span>\n\t\t\t\t\t</slot>\n\t\t\t\t</div>\n\n\t\t\t\t<!-- `#item` + `item-key`: vuedraggable@4 (the Vue 3 build)\n\t\t\t\t renders the cards itself from this slot. A `v-for` in the\n\t\t\t\t default slot throws \"draggable element must have an item\n\t\t\t\t slot\" the moment the column paints. -->\n\t\t\t\t<Draggable\n\t\t\t\t\t:list=\"column.cards\"\n\t\t\t\t\ttag=\"div\"\n\t\t\t\t\tclass=\"cn-object-kanban__column-cards\"\n\t\t\t\t\t:itemKey=\"cardKey\"\n\t\t\t\t\tgroup=\"cn-object-kanban-cards\"\n\t\t\t\t\t:data-column-value=\"columnKey(column.value)\"\n\t\t\t\t\t@start=\"onDragStart(column)\"\n\t\t\t\t\t@change=\"onColumnChange($event, column)\">\n\t\t\t\t\t<template #item=\"{ element: card }\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"cn-object-kanban__card\"\n\t\t\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t\t\t'cn-object-kanban__card--pending': isPending(card),\n\t\t\t\t\t\t\t\t'cn-object-kanban__card--overdue': dueState(card) === 'overdue',\n\t\t\t\t\t\t\t\t'cn-object-kanban__card--soon': dueState(card) === 'soon',\n\t\t\t\t\t\t\t}\">\n\t\t\t\t\t\t\t<!-- @slot card Fully replace the default card rendering. -->\n\t\t\t\t\t\t\t<!-- @binding {object} object The card's object. -->\n\t\t\t\t\t\t\t<!-- @binding {object} column The column the card currently sits in. -->\n\t\t\t\t\t\t\t<!-- @binding {string|null} dueState The card's due state under `dueRule`: `overdue`, `soon`, `ok`, or null without a rule. -->\n\t\t\t\t\t\t\t<slot\n\t\t\t\t\t\t\t\tname=\"card\"\n\t\t\t\t\t\t\t\t:object=\"card\"\n\t\t\t\t\t\t\t\t:column=\"column\"\n\t\t\t\t\t\t\t\t:dueState=\"dueState(card)\">\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tv-if=\"dueLabel(card)\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-object-kanban__due\"\n\t\t\t\t\t\t\t\t\t:class=\"`cn-object-kanban__due--${dueState(card)}`\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-kanban-due\">\n\t\t\t\t\t\t\t\t\t{{ dueLabel(card) }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<div class=\"cn-object-kanban__card-title\" @click=\"onCardClick(card)\">\n\t\t\t\t\t\t\t\t\t{{ cardTitle(card) }}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div v-if=\"cardFields.length\" class=\"cn-object-kanban__card-fields\">\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tv-for=\"field in cardFields\"\n\t\t\t\t\t\t\t\t\t\t:key=\"field\"\n\t\t\t\t\t\t\t\t\t\tclass=\"cn-object-kanban__card-field\">\n\t\t\t\t\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t\t\t\t\t:value=\"card[field]\"\n\t\t\t\t\t\t\t\t\t\t\t:property=\"schemaProperty(field)\"\n\t\t\t\t\t\t\t\t\t\t\t:truncate=\"40\" />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</slot>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</Draggable>\n\n\t\t\t\t<div v-if=\"hasMore(column)\" class=\"cn-object-kanban__load-more\">\n\t\t\t\t\t<NcButton :disabled=\"isColumnLoading(column)\" @click=\"onLoadMore(column)\">\n\t\t\t\t\t\t<template v-if=\"isColumnLoading(column)\" #icon>\n\t\t\t\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\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, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport draggable from 'vuedraggable'\nimport ViewColumn from 'vue-material-design-icons/ViewColumn.vue'\nimport { dueStateForRow } from '../../utils/dueRule.js'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\n\n/**\n * CnObjectKanban — Kanban board over a schema property's distinct values.\n *\n * Renders one column per distinct value of `groupByField` and paginates each\n * column's cards independently (\"load more\", never a whole column at once).\n * Column values/order come from (in precedence order) an explicit\n * `columnOrder`, the `groupByField` schema property's `enum`, or values\n * discovered from `objects` — mirroring OpenRegister's\n * `ViewPresentationService::deriveColumnValues()`.\n *\n * Two rendering modes:\n * - **Pre-built columns** — pass `columns` (the shape returned by\n * `GET /api/views/{id}/kanban`: `[{ value, cards, total, limit, offset }]`);\n * the host owns pagination and re-fetches on `load-more`.\n * - **Flat objects** — pass `objects` + `groupByField` (and optionally\n * `columnOrder`/`schema`); the component derives and paginates columns\n * locally.\n *\n * Dragging a card to another column does NOT write anything itself — there is\n * deliberately no bespoke \"move card\" endpoint (REQ-VIEW-KANBAN-03). The move\n * is applied optimistically to the local board and the component emits\n * `move`; the host performs the actual object PATCH/PUT through the existing\n * guarded write path and reports back:\n * - On success, nothing further is required (the optimistic move already\n * reflects it) — optionally call `resolveMove(id)` to clear the pending\n * visual state sooner.\n * - On a rejected (e.g. illegal lifecycle transition) write, call\n * `rejectMove(id, reason)` — the card snaps back to its origin column and\n * `move-rejected` fires with the reason for the host to surface.\n *\n * ```vue\n * <CnObjectKanban\n * :objects=\"objects\"\n * group-by-field=\"status\"\n * :column-order=\"['todo', 'doing', 'done']\"\n * :card-fields=\"['title', 'assignee']\"\n * :schema=\"schema\"\n * ref=\"kanban\"\n * @move=\"onMove\"\n * @load-more=\"fetchMoreCards\" />\n * ```\n * ```js\n * async onMove({ object, groupByField, fromValue, toValue }) {\n * try {\n * await patchObject(object.id, { [groupByField]: toValue })\n * } catch (e) {\n * this.$refs.kanban.rejectMove(object.id, e.message)\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnObjectKanban',\n\n\tcomponents: {\n\t\tDraggable: draggable,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tNcEmptyContent,\n\t\tViewColumn,\n\t\tCnCellRenderer,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Flat objects to derive columns from (ignored when `columns` is set).\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tobjects: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Pre-built columns, e.g. the response of `GET /api/views/{id}/kanban`.\n\t\t *\n\t\t * @type {Array<{value: string|number, cards: Array<object>, total: number, limit: number, offset: number}>|null}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The schema property whose distinct values become columns. */\n\t\tgroupByField: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Explicit column order. Takes precedence over the schema's enum order. */\n\t\tcolumnOrder: {\n\t\t\ttype: Array,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Object fields rendered on each card (in order). */\n\t\tcardFields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Schema definition, used to resolve enum column order and card field types. */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Overall loading state (initial board fetch). */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Column values currently loading more cards (drives the per-column spinner). */\n\t\tloadingColumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Cards shown per column before \"load more\", in local (`objects`-driven) mode. */\n\t\tpageSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 20,\n\t\t},\n\n\t\t/** Object property used as each card's identity. */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Marks late cards: `{ field, soonDays? }`. `field` is the dot-path to\n\t\t * a card's due date. A date before today gets an error edge and the\n\t\t * label \"Overdue\"; a date within `soonDays` (default 3) gets \"Due\n\t\t * soon\" in the warning colour. `null` (the default) marks nothing.\n\t\t *\n\t\t * The same rule shape the table's date cell uses, so a list and its\n\t\t * board agree on what late means.\n\t\t *\n\t\t * @type {{field: string, soonDays?: number}|null}\n\t\t */\n\t\tdueRule: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['card-click', 'load-more', 'move', 'move-rejected'],\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalColumns: [],\n\t\t\t// column.value -> full unpaginated card list, local mode only.\n\t\t\tfullCardsByColumn: {},\n\t\t\t// Suppressed while a move is pending so the prop-driven rebuild\n\t\t\t// doesn't clobber the optimistic UI before the host's data catches up.\n\t\t\tsuppressRebuild: false,\n\t\t\t// objectId -> { fromValue, toValue, card, originIndex }\n\t\t\tpendingMoves: {},\n\t\t\tdragOriginValue: null,\n\t\t\t// The day late marking measures from.\n\t\t\ttoday: new Date(),\n\t\t}\n\t},\n\n\tcomputed: {\n\t\temptyText() {\n\t\t\treturn t('nextcloud-vue', 'No columns to show')\n\t\t},\n\n\t\tloadMoreLabel() {\n\t\t\treturn t('nextcloud-vue', 'Load more')\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjects: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tcolumns: {\n\t\t\thandler() {\n\t\t\t\tif (!this.suppressRebuild) {\n\t\t\t\t\tthis.rebuild()\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tdeep: false,\n\t\t},\n\n\t\tgroupByField() {\n\t\t\tthis.rebuild()\n\t\t},\n\n\t\tcolumnOrder: {\n\t\t\thandler() {\n\t\t\t\tthis.rebuild()\n\t\t\t},\n\n\t\t\tdeep: true,\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.rebuild()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A card's due state under `dueRule`.\n\t\t *\n\t\t * @param {object} card The card's row.\n\t\t * @return {'overdue'|'soon'|'ok'|null} The state, or null when no rule applies.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n\t\t */\n\t\tdueState(card) {\n\t\t\treturn dueStateForRow(card, this.dueRule, this.today)\n\t\t},\n\n\t\t/**\n\t\t * The words a late card carries. Colour alone would not reach a reader\n\t\t * who cannot see it, or cannot tell red from amber.\n\t\t *\n\t\t * @param {object} card The card's row.\n\t\t * @return {string} \"Overdue\", \"Due soon\", or '' for a card that is fine.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n\t\t */\n\t\tdueLabel(card) {\n\t\t\tconst state = this.dueState(card)\n\t\t\tif (state === 'overdue') {\n\t\t\t\treturn t('nextcloud-vue', 'Overdue')\n\t\t\t}\n\t\t\treturn state === 'soon' ? t('nextcloud-vue', 'Due soon') : ''\n\t\t},\n\n\t\t/**\n\t\t * Rebuild `localColumns` from either the `columns` prop (backend-paginated\n\t\t * mode) or `objects` + `groupByField` (locally-derived mode).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trebuild() {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\tthis.localColumns = this.columns.map((column) => ({\n\t\t\t\t\tvalue: column.value,\n\t\t\t\t\tcards: Array.isArray(column.cards) ? [...column.cards] : [],\n\t\t\t\t\ttotal: typeof column.total === 'number' ? column.total : (column.cards || []).length,\n\t\t\t\t\tlimit: column.limit,\n\t\t\t\t\toffset: column.offset,\n\t\t\t\t}))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst values = this.deriveColumnValues()\n\t\t\tconst fullByColumn = {}\n\t\t\tthis.localColumns = values.map((value) => {\n\t\t\t\tconst fullCards = this.objects.filter((object) => object && object[this.groupByField] === value)\n\t\t\t\tfullByColumn[this.columnKey(value)] = fullCards\n\t\t\t\treturn {\n\t\t\t\t\tvalue,\n\t\t\t\t\tcards: fullCards.slice(0, this.pageSize),\n\t\t\t\t\ttotal: fullCards.length,\n\t\t\t\t\tlimit: this.pageSize,\n\t\t\t\t\toffset: 0,\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis.fullCardsByColumn = fullByColumn\n\t\t},\n\n\t\t/**\n\t\t * Derive the ordered column values: `columnOrder` > schema enum order >\n\t\t * distinct values observed in `objects`. Mirrors\n\t\t * `ViewPresentationService::deriveColumnValues()`.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tderiveColumnValues() {\n\t\t\tif (Array.isArray(this.columnOrder) && this.columnOrder.length > 0) {\n\t\t\t\treturn [...this.columnOrder]\n\t\t\t}\n\n\t\t\tconst enumValues = this.schema?.properties?.[this.groupByField]?.enum\n\t\t\tif (Array.isArray(enumValues) && enumValues.length > 0) {\n\t\t\t\treturn [...enumValues]\n\t\t\t}\n\n\t\t\treturn this.discoverDistinctValues()\n\t\t},\n\n\t\t/**\n\t\t * Distinct `groupByField` values observed in `objects`, in first-seen order.\n\t\t *\n\t\t * @return {Array<unknown>}\n\t\t */\n\t\tdiscoverDistinctValues() {\n\t\t\tconst seen = new Set()\n\t\t\tconst values = []\n\t\t\tfor (const object of this.objects) {\n\t\t\t\tconst value = object?.[this.groupByField]\n\t\t\t\tif (value !== undefined && value !== null && !seen.has(value)) {\n\t\t\t\t\tseen.add(value)\n\t\t\t\t\tvalues.push(value)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn values\n\t\t},\n\n\t\t/**\n\t\t * Stable string key for a column value (used as a `:key` and as the\n\t\t * `fullCardsByColumn` index — object/array values are unlikely but guarded).\n\t\t *\n\t\t * @param {unknown} value The column value.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnKey(value) {\n\t\t\treturn typeof value === 'string' || typeof value === 'number' ? String(value) : JSON.stringify(value)\n\t\t},\n\n\t\t/**\n\t\t * The card's identity, per `rowKey`.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {unknown}\n\t\t */\n\t\tcardKey(card) {\n\t\t\treturn card?.[this.rowKey]\n\t\t},\n\n\t\t/**\n\t\t * Display label for a column: its schema `oneOf[].title` when the\n\t\t * property declares per-value titles, otherwise the raw value.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {string}\n\t\t */\n\t\tcolumnLabel(column) {\n\t\t\tconst oneOf = this.schema?.properties?.[this.groupByField]?.oneOf\n\t\t\tif (Array.isArray(oneOf)) {\n\t\t\t\tconst match = oneOf.find((entry) => entry?.const === column.value)\n\t\t\t\tif (match?.title) {\n\t\t\t\t\treturn match.title\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Best-effort card title: the schema's configured name field, else\n\t\t * `title`/`name`/the row key.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {string}\n\t\t */\n\t\tcardTitle(card) {\n\t\t\tconst nameField = this.schema?.configuration?.objectNameField\n\t\t\tif (nameField && card[nameField]) {\n\t\t\t\treturn String(card[nameField])\n\t\t\t}\n\t\t\treturn String(card.title || card.name || card[this.rowKey] || '—')\n\t\t},\n\n\t\t/**\n\t\t * The schema property definition for a card field, if known.\n\t\t *\n\t\t * @param {string} field The field name.\n\t\t * @return {object|undefined}\n\t\t */\n\t\tschemaProperty(field) {\n\t\t\treturn this.schema?.properties?.[field]\n\t\t},\n\n\t\t/**\n\t\t * Whether a column has more cards to load: backend-paginated mode compares\n\t\t * `cards.length` to `total`; local mode compares to the full filtered set.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\thasMore(column) {\n\t\t\tif (Array.isArray(this.columns)) {\n\t\t\t\treturn column.cards.length < column.total\n\t\t\t}\n\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\treturn column.cards.length < full.length\n\t\t},\n\n\t\t/**\n\t\t * Whether a column's \"load more\" is in flight.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {boolean}\n\t\t */\n\t\tisColumnLoading(column) {\n\t\t\treturn this.loadingColumns.includes(column.value)\n\t\t},\n\n\t\t/**\n\t\t * Whether a card has an unresolved optimistic move.\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {boolean}\n\t\t */\n\t\tisPending(card) {\n\t\t\treturn Object.hasOwn(this.pendingMoves, this.cardKey(card))\n\t\t},\n\n\t\t/**\n\t\t * Handle a column's \"load more\" click: local mode reveals the next page\n\t\t * from the already-fetched full set; backend-paginated mode only emits —\n\t\t * the host fetches the next page and passes updated `columns`.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {void}\n\t\t */\n\t\tonLoadMore(column) {\n\t\t\tif (!Array.isArray(this.columns)) {\n\t\t\t\tconst full = this.fullCardsByColumn[this.columnKey(column.value)] || []\n\t\t\t\tcolumn.cards = full.slice(0, column.cards.length + this.pageSize)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event load-more Emitted when a column's \"load more\" is clicked.\n\t\t\t * @type {{ value: unknown, offset: number }}\n\t\t\t */\n\t\t\tthis.$emit('load-more', { value: column.value, offset: column.cards.length })\n\t\t},\n\n\t\t/**\n\t\t * Card click (ignored while the card is mid-drag).\n\t\t *\n\t\t * @param {object} card The card object.\n\t\t * @return {void}\n\t\t */\n\t\tonCardClick(card) {\n\t\t\t/**\n\t\t\t * @event card-click Emitted when a card is clicked (not dragged).\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('card-click', card)\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@start` — remember which column a drag began in, since\n\t\t * `@change` on the destination column doesn't know the source.\n\t\t *\n\t\t * @param {object} column The column the drag started in.\n\t\t * @return {void}\n\t\t */\n\t\tonDragStart(column) {\n\t\t\tthis.dragOriginValue = column.value\n\t\t},\n\n\t\t/**\n\t\t * vuedraggable `@change` on a column's card list. Only the destination\n\t\t * column's `added` event carries the dropped object; a same-column\n\t\t * reorder (`moved`) or the source column's `removed` are no-ops here.\n\t\t *\n\t\t * @param {object} evt vuedraggable's change event.\n\t\t * @param {object} column The column the event fired on.\n\t\t * @return {void}\n\t\t */\n\t\tonColumnChange(evt, column) {\n\t\t\tif (!evt.added) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst card = evt.added.element\n\t\t\tconst fromValue = this.dragOriginValue\n\t\t\tconst toValue = column.value\n\t\t\tthis.dragOriginValue = null\n\n\t\t\tif (fromValue === toValue) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.commitMove(card, fromValue, toValue, evt.added.newIndex)\n\t\t},\n\n\t\t/**\n\t\t * Record the pending move and emit it for the host to persist. The card\n\t\t * has already been moved optimistically by vuedraggable's v-model splice.\n\t\t *\n\t\t * @param {object} card The moved object.\n\t\t * @param {unknown} fromValue The origin column's value.\n\t\t * @param {unknown} toValue The destination column's value.\n\t\t * @param {number} originIndex The index the card is currently at in the destination column (used only for logging/debugging).\n\t\t * @return {void}\n\t\t */\n\t\tcommitMove(card, fromValue, toValue, originIndex) {\n\t\t\tconst id = this.cardKey(card)\n\t\t\tthis.suppressRebuild = true\n\t\t\tthis.pendingMoves = {\n\t\t\t\t...this.pendingMoves,\n\t\t\t\t[id]: { fromValue, toValue, card, originIndex },\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event move Emitted after a card is optimistically moved to another\n\t\t\t * column. The host performs the actual object write through the\n\t\t\t * existing guarded PATCH/PUT endpoint and calls `resolveMove`/\n\t\t\t * `rejectMove` on this component to confirm or roll back.\n\t\t\t * @type {{ object: object, groupByField: string, fromValue: unknown, toValue: unknown }}\n\t\t\t */\n\t\t\tthis.$emit('move', {\n\t\t\t\tobject: card,\n\t\t\t\tgroupByField: this.groupByField,\n\t\t\t\tfromValue,\n\t\t\t\ttoValue,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Confirm a pending move succeeded — clears its pending visual state.\n\t\t * The card already sits in the destination column (optimistic UI); no\n\t\t * further mutation is needed.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\tresolveMove(objectId) {\n\t\t\tthis.clearPending(objectId)\n\t\t},\n\n\t\t/**\n\t\t * Roll back a rejected move: the card returns to its origin column and\n\t\t * `move-rejected` fires with the server's reason.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @param {string} [reason] The rejection reason (e.g. an illegal lifecycle\n\t\t * transition message) to surface to the user.\n\t\t * @return {void}\n\t\t * @public\n\t\t */\n\t\trejectMove(objectId, reason) {\n\t\t\tconst pending = this.pendingMoves[objectId]\n\t\t\tif (!pending) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst toColumn = this.localColumns.find((c) => c.value === pending.toValue)\n\t\t\tconst fromColumn = this.localColumns.find((c) => c.value === pending.fromValue)\n\n\t\t\tif (toColumn) {\n\t\t\t\tconst idx = toColumn.cards.findIndex((c) => this.cardKey(c) === objectId)\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\ttoColumn.cards.splice(idx, 1)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (fromColumn) {\n\t\t\t\tfromColumn.cards.push(pending.card)\n\t\t\t}\n\n\t\t\tthis.clearPending(objectId)\n\n\t\t\t/**\n\t\t\t * @event move-rejected Emitted after `rejectMove()` rolls a card back\n\t\t\t * to its origin column.\n\t\t\t * @type {{ object: object, fromValue: unknown, toValue: unknown, reason: (string|undefined) }}\n\t\t\t */\n\t\t\tthis.$emit('move-rejected', {\n\t\t\t\tobject: pending.card,\n\t\t\t\tfromValue: pending.fromValue,\n\t\t\t\ttoValue: pending.toValue,\n\t\t\t\treason,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Remove a move from the pending map and, once none remain, allow\n\t\t * prop-driven rebuilds again.\n\t\t *\n\t\t * @param {unknown} objectId The moved object's `rowKey` value.\n\t\t * @return {void}\n\t\t */\n\t\tclearPending(objectId) {\n\t\t\tconst { [objectId]: _removed, ...rest } = this.pendingMoves\n\t\t\tthis.pendingMoves = rest\n\t\t\tif (Object.keys(rest).length === 0) {\n\t\t\t\tthis.suppressRebuild = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-kanban__loading,\n.cn-object-kanban__empty {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 40px;\n}\n\n.cn-object-kanban__board {\n\tdisplay: flex;\n\tgap: 16px;\n\toverflow-x: auto;\n\tpadding-bottom: 8px;\n}\n\n.cn-object-kanban__column {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 0 0 280px;\n\tmin-width: 280px;\n\tbackground: var(--color-background-dark);\n\tborder-radius: var(--border-radius-large, 10px);\n\tpadding: 8px;\n}\n\n.cn-object-kanban__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px;\n\tfont-weight: 600;\n}\n\n.cn-object-kanban__column-count {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-object-kanban__column-cards {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-height: 40px;\n}\n\n.cn-object-kanban__card {\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius, 8px);\n\tpadding: 10px;\n\tcursor: grab;\n\ttransition: opacity 0.15s ease;\n}\n\n.cn-object-kanban__card--pending {\n\topacity: 0.6;\n}\n\n/* A late card. The label carries the meaning; the edge helps a sighted\n reader find it. An inset shadow, so the card keeps its size. */\n.cn-object-kanban__card--overdue {\n\tborder-color: var(--color-error);\n\tbox-shadow: inset 3px 0 0 var(--color-error);\n}\n\n.cn-object-kanban__due {\n\tdisplay: block;\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n}\n\n.cn-object-kanban__due--overdue {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-object-kanban__due--soon {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n\tfont-weight: 600;\n}\n\n.cn-object-kanban__card-title {\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-object-kanban__card-fields {\n\tmargin-top: 6px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-object-kanban__load-more {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding-top: 8px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_renderSlot","_createElementVNode","_toDisplayString","_withCtx","_normalizeClass","_Fragment","_renderList","_createTextVNode"],"mappings":";;AAWO,MAAA,UAAA,GAAA,EAAA,KAAI,EAAC,kBAAkB,GAAA;;;AACR,EAAA,KAAK,EAAC,2BAA4B;;;;AAI1B,EAAA,KAAA,EAAA,yBAAA;;;;AAWhB,EAAA,KAAK,EAAC,yBAAyB;;AAKpC,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,iCAAiC,GAAA;AAIpC,MAAA,UAAA,GAAA,EAAA,KAAK,EAAA,gCAAgC,GAAA;AACrC,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,gCAAgC,GAAA;;;;AA4C1B,EAAA,KAAA,EAAA,+BAAA;;;;AAgBA,EAAA,KAAA,EAAA,6BAAA;;;;;;;;;;;AAtFtB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;KACU,MAAQ,CAAA,OAAA;AAAjB,SAAAD,SAAA,EAAA,EAAAC,kBAAA,CAEK,OAFL,UAEK,EAAA;UADLC,WAAkB,CAAA,wBAAA,EAAA,EAAF,IAAE,EAAA,EAAA,EAAA,CAAA;;AAGH,QAAA,CAAA,KAAA,CAAA,YAAY,CAAA,MAAA,KAAA,CAAA;2CAStB,iBAAA,EAAA;AAPJ,YAAAC,UAAA,CAMM,0BANN,MAMM;cALND,WAIE,CAAA,yBAAA,EAAA,EAJG,IAAA,EAAA,QAAA,CAAA,SAAA,EAAA,EAAA;AACF,gBAAA,IAAA;;;;;;;AAOH,WAAAF,SAAA,EAAA,EAAAC,kBAAA,QAAA;4EAEiB,KAAY,CAAA,YAAA,EAAA,CAAtB,MAAM,KAAA;;AACZ,gBAAA,GAAG,EAAG,QAAA,CAAA,SAAQ,CAAC,MAAM,CAAC,KAAK,CAAA;AAC1B,gBAAA,KAAG,EAAC,0BAA0B;;AAC9B,gBAAAG,kBAAA,CAOI,OAPJ,UAOI,EAAA;6BADE,wDAAA;AAFN,oBAAAA,kBAAA,CAAwD,MAAxD,EAAA,UAAwD,EAATC,eAAA,CAAA,QAAA,CAAA,WAAS,CAAA,MAAA,CAAA,CAAA,EAAA,CAAA,YAAA;uCACG,kBAAA,EAAXA,eAAA,CAAA,MAAM,CAAC,KAAI,CAAA,EAAA,CAAA,YAAA;;;2BA0DjD,CAAA,oBAAA,EAAA;kBAjDV,IAAI,EAAE,MAAM,CAAC,KAAK;AACnB,kBAAA,GAAG,EAAC,KAAK;AACT,kBAAA,KAAK,EAAC,gCAAgC;AACrC,kBAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,kBAAA,KAAG,EAAA,wBAAA;qCACc,EAAE,QAAA,CAAA,SAAS,CAAC,MAAC,CAAA,KAAA,CAAA;kBAC7B,OAAI,EAAA,MAAA,KAAE,QAAE,CAAA,WAAA,CAAA,MAAA,CAAA,CAAA;0BACH,EAAE,MAAA,KAAA,QAAA,CAAA,cAAc,CAAC,MAAM,EAAE,MAAM,CAAA,CAAA;;kBAC3B,IAAI,EAAAC,OAAA,EAuCR,EAAA,OAAA,EAvCqB,IAAI,EAAA,KAAA;sCAuCzB,CAAA,KAAA,EAAA;AArCL,sBAAA,KAAE,kBAAA,wBAAA,EAAA;AACmD,QAAA,iCAAA,EAAA,QAAA,CAAA,SAAO,CAAA,IAAA,CAAA;AAAA,QAAA,iCAAA,EAAA,QAAA,CAAA,QAAA,CAAA,IAAA,CAAA,KAAA,SAAA;AAAA,QAAA,8BAAA,EAAA,QAAA,CAAA,QAAA,CAAA,IAAA,CAAA,KAAA,MAAA;;;gCAmCrD,CAAA,IAAA,CAAA,MAAA,EAAA,MAAA,EAAA;AAxBL,wBAAA,MAAM,EAAE,IAAI;AACZ,wBAAA,MAAM,EAAE,MAAM;wBACd,QAAQ,EAAE,QAAQ,CAAA,QAAA,CAAC,IAAI,CAAA;+BAsBlB;AApBA,wBAAA,CAAA,QAAA,CAAA,QAAA,CAAA,IAAA,CAAA;0CADLL,kBAMG,CAAA,MAAA,EAAA;;AAJH,8BAAA,KAAK,EAACM,cAAA,CAAA,CAAA,uBAAuB,EACT,CAAA,uBAAA,EAAA,QAAA,CAAA,QAAA,CAAA,IAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AACpB,8BAAA,aAAI,EAAA,eAAA;AACA,6BAAA,EAAAF,eAAA,CAAA,QAAA,CAAA,QAAO,CAAC,IAAI,CAAA,CAAA,EAAA,CAAA,mBAAA;;0CAIX,CAAA,KAAA,EAAA;AAFD,0BAAA,KAAK,EAAC,8BAAiB;0BAAA,OAAA,EAAA,MAAA,KAAA,QAAA,CAAA,WAAA,CAAA,IAAA,CAAA,CAAA;AACxB,yBAAA,EAAAA,eAAA,CAAA,QAAA,CAAA,SAAS,CAAA,IAAA,CAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;AAEF,wBAAA,CAAA,MAAA,CAAA,UAAK,CAAA,MAAA;AAAd,6BAAAL,SAAA,EAAA,EAAAC,kBAAA,QAAA;gDACCA,kBAQI,CAAAO,QAAA,EAAA,IAAA,EAAAC,UAAA,CAPW,MAAU,CAAA,UAAA,EAAA,CAAnB,KAAK,KAAA;qDADXR,kBAQI,CAAA,KAAA,EAAA;AANJ,kCAAA,GAAG,EAAE,KAAK;AACX,kCAAA,KAAI,EAAA,8BAAA;;kCACJC,WAGK,CAAA,yBAAA,EAAA;oCAFH,KAAC,EAAA,IAAA,CAAA,KAAA,CAAA;oCACF,QAAA,EAAA,QAAA,CAAA,cAAA,CAAA,KAAA,CAAA;AACC,oCAAA,QAAG,EAAA,EAAA;;;;;;;;;;;AAQA,gBAAA,CAAA,QAAA,CAAA,QAAQ,MAAC,CAAA;AAAlB,qBAAAF,SAAA,EAAA,EAAAC,kBAAA,QAAA;sBACDC;wBAAU,QAAA,EAAA,QAAA,CAAA,eAAA,CAAA,MAAA,CAAA;wBAAA,OAAA,EAAA,MAAA,KAAA,QAAA,CAAA,UAAA,CAAA,MAAA,CAAA,CAAA;;yCAGR,MACD;AADC,0BAAAQ,eAAA,CAAA,GACD,mBAAA,QAAA,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;;;AAHG,wBAAA,CAAA,QAAA,CAAA,eAAA,CAAA,MAAA,CAAA;;oCAAA,MAAA;0CACF,MAAC;gCAADR,WAAC,CAAA,wBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;;;;;;;;;;;;;;;"}
@@ -57,6 +57,7 @@ const defaultPageTypes = {
57
57
  map: defineAsyncComponent(() => import('../CnMapPage/CnMapPage.vue.js').then((m) => m.default)),
58
58
  roadmap: defineAsyncComponent(() => import('../CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue.js').then((m) => m.default)),
59
59
  reports: defineAsyncComponent(() => import('../CnReportsPage/CnReportsPage.vue.js').then((m) => m.default)),
60
+ links: defineAsyncComponent(() => import('../CnLinkCardsPage/CnLinkCardsPage.vue.js').then((m) => m.default)),
60
61
  store: defineAsyncComponent(() => import('../CnStorePage/CnStorePage.vue.js').then((m) => m.default)),
61
62
  search: defineAsyncComponent(() => import('../CnSearchPage/CnSearchPage.vue.js').then((m) => m.default)),
62
63
  wiki: defineAsyncComponent(() => import('../CnWikiPage/CnWikiPage.vue.js').then((m) => m.default)),
@@ -1 +1 @@
1
- {"version":3,"file":"pageTypes.js","sources":["../../../../src/components/CnPageRenderer/pageTypes.js"],"sourcesContent":["import { defineAsyncComponent } from 'vue'\n\n/**\n * Default mapping from manifest `pages[].type` value to the Vue\n * component the renderer mounts.\n *\n * The library ships built-in types here; consumers and downstream\n * library extensions can add their own by passing a merged map to\n * `CnAppRoot` (or `CnPageRenderer`) via the `pageTypes` prop.\n *\n * Each entry uses `defineAsyncComponent` with an explicit\n * `.then(m => m.default)` unwrap. The unwrap is load-bearing: rollup's\n * `inlineDynamicImports: true` flattens every `import()` call into a\n * `Promise.resolve().then(() => namespace)` where `namespace` is a\n * frozen `Object.freeze({__proto__: null, default: <component>})`.\n * Vue 2's async-component resolution against that frozen wrapper trips\n * `Cannot add property _Ctor, object is not extensible` when downstream\n * `Vue.extend()` (e.g. via Vue Router or `<component :is>`) tries to\n * attach its internal `_Ctor` cache. Pre-unwrapping the `default`\n * property yields the raw, extensible component options object —\n * which Vue can then mutate freely.\n *\n * Keeping `defineAsyncComponent` (rather than collapsing to static\n * imports) preserves the test-environment hack of NOT loading\n * `CnDashboardPage` → `CnDashboardGrid` → `gridstack` at module-load\n * time; gridstack ships ESM that Jest's default transform refuses\n * unless explicitly allowlisted. Async imports keep that load\n * deferred to actual render.\n *\n * The special `custom` type is NOT registered here — CnPageRenderer\n * handles it inline, resolving `page.component` against the\n * customComponents registry rather than this map.\n *\n * @example Extending with an app-specific page type\n *\n * import { defaultPageTypes } from '@conduction/nextcloud-vue'\n * import MyReportPage from './views/MyReportPage.vue'\n *\n * const pageTypes = { ...defaultPageTypes, report: MyReportPage }\n *\n * <CnAppRoot :manifest=\"manifest\" app-id=\"myapp\" :page-types=\"pageTypes\" />\n *\n * @example Adding a built-in page type to the library\n *\n * Add a new entry to this map and export the component from the\n * `src/components/index.js` barrel. No change to CnPageRenderer.vue.\n */\nexport const defaultPageTypes = {\n\tindex: defineAsyncComponent(() => import('../CnIndexPage/CnIndexPage.vue').then((m) => m.default)),\n\tdetail: defineAsyncComponent(() => import('../CnDetailPage/CnDetailPage.vue').then((m) => m.default)),\n\tdashboard: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),\n\tlogs: defineAsyncComponent(() => import('../CnLogsPage/CnLogsPage.vue').then((m) => m.default)),\n\tsettings: defineAsyncComponent(() => import('../CnSettingsPage/CnSettingsPage.vue').then((m) => m.default)),\n\tchat: defineAsyncComponent(() => import('../CnChatPage/CnChatPage.vue').then((m) => m.default)),\n\tfiles: defineAsyncComponent(() => import('../CnFilesPage/CnFilesPage.vue').then((m) => m.default)),\n\tform: defineAsyncComponent(() => import('../CnFormPage/CnFormPage.vue').then((m) => m.default)),\n\tmap: defineAsyncComponent(() => import('../CnMapPage/CnMapPage.vue').then((m) => m.default)),\n\troadmap: defineAsyncComponent(() => import('../CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue').then((m) => m.default)),\n\treports: defineAsyncComponent(() => import('../CnReportsPage/CnReportsPage.vue').then((m) => m.default)),\n\tstore: defineAsyncComponent(() => import('../CnStorePage/CnStorePage.vue').then((m) => m.default)),\n\tsearch: defineAsyncComponent(() => import('../CnSearchPage/CnSearchPage.vue').then((m) => m.default)),\n\twiki: defineAsyncComponent(() => import('../CnWikiPage/CnWikiPage.vue').then((m) => m.default)),\n\t// A flow list USED TO need its own page type. A flow lives in\n\t// OpenRegister's native flow store, not a register/schema pair, so the\n\t// object-backed index had nothing to bind to — and every app adopting flows\n\t// copied ~270 lines of identical wrapper differing only in an app-id\n\t// string, which is how three apps ended up with the same dead `@rowClick`\n\t// listener (CnIndexPage emits `row-click`).\n\t//\n\t// Named index sources removed that constraint: `type: \"index\"` with\n\t// `config.entitySource: \"flows\"` lists them, so only the EDITOR still needs a\n\t// page type of its own.\n\t//\n\t// The flow EDITOR. `flow` opens one flow; the LIST is now an ordinary\n\t// `type: \"index\"` with `config.entitySource: \"flows\"`, so a flow surface needs no\n\t// custom page and no page type of its own for the list half.\n\tflow: defineAsyncComponent(() => import('../CnFlowsPage/CnFlowEditorPage.vue').then((m) => m.default)),\n\n\t// DEPRECATED, kept so the fleet can migrate without a flag day. `flows`\n\t// predates named index sources and `flow-detail` is the old name for\n\t// `flow`. Both still resolve; remove them once no manifest names them.\n\tflows: defineAsyncComponent(() => import('../CnFlowsPage/CnFlowsPage.vue').then((m) => m.default)),\n\t'flow-detail': defineAsyncComponent(() => import('../CnFlowsPage/CnFlowEditorPage.vue').then((m) => m.default)),\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pageTypes.js","sources":["../../../../src/components/CnPageRenderer/pageTypes.js"],"sourcesContent":["import { defineAsyncComponent } from 'vue'\n\n/**\n * Default mapping from manifest `pages[].type` value to the Vue\n * component the renderer mounts.\n *\n * The library ships built-in types here; consumers and downstream\n * library extensions can add their own by passing a merged map to\n * `CnAppRoot` (or `CnPageRenderer`) via the `pageTypes` prop.\n *\n * Each entry uses `defineAsyncComponent` with an explicit\n * `.then(m => m.default)` unwrap. The unwrap is load-bearing: rollup's\n * `inlineDynamicImports: true` flattens every `import()` call into a\n * `Promise.resolve().then(() => namespace)` where `namespace` is a\n * frozen `Object.freeze({__proto__: null, default: <component>})`.\n * Vue 2's async-component resolution against that frozen wrapper trips\n * `Cannot add property _Ctor, object is not extensible` when downstream\n * `Vue.extend()` (e.g. via Vue Router or `<component :is>`) tries to\n * attach its internal `_Ctor` cache. Pre-unwrapping the `default`\n * property yields the raw, extensible component options object —\n * which Vue can then mutate freely.\n *\n * Keeping `defineAsyncComponent` (rather than collapsing to static\n * imports) preserves the test-environment hack of NOT loading\n * `CnDashboardPage` → `CnDashboardGrid` → `gridstack` at module-load\n * time; gridstack ships ESM that Jest's default transform refuses\n * unless explicitly allowlisted. Async imports keep that load\n * deferred to actual render.\n *\n * The special `custom` type is NOT registered here — CnPageRenderer\n * handles it inline, resolving `page.component` against the\n * customComponents registry rather than this map.\n *\n * @example Extending with an app-specific page type\n *\n * import { defaultPageTypes } from '@conduction/nextcloud-vue'\n * import MyReportPage from './views/MyReportPage.vue'\n *\n * const pageTypes = { ...defaultPageTypes, report: MyReportPage }\n *\n * <CnAppRoot :manifest=\"manifest\" app-id=\"myapp\" :page-types=\"pageTypes\" />\n *\n * @example Adding a built-in page type to the library\n *\n * Add a new entry to this map and export the component from the\n * `src/components/index.js` barrel. No change to CnPageRenderer.vue.\n */\nexport const defaultPageTypes = {\n\tindex: defineAsyncComponent(() => import('../CnIndexPage/CnIndexPage.vue').then((m) => m.default)),\n\tdetail: defineAsyncComponent(() => import('../CnDetailPage/CnDetailPage.vue').then((m) => m.default)),\n\tdashboard: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),\n\tlogs: defineAsyncComponent(() => import('../CnLogsPage/CnLogsPage.vue').then((m) => m.default)),\n\tsettings: defineAsyncComponent(() => import('../CnSettingsPage/CnSettingsPage.vue').then((m) => m.default)),\n\tchat: defineAsyncComponent(() => import('../CnChatPage/CnChatPage.vue').then((m) => m.default)),\n\tfiles: defineAsyncComponent(() => import('../CnFilesPage/CnFilesPage.vue').then((m) => m.default)),\n\tform: defineAsyncComponent(() => import('../CnFormPage/CnFormPage.vue').then((m) => m.default)),\n\tmap: defineAsyncComponent(() => import('../CnMapPage/CnMapPage.vue').then((m) => m.default)),\n\troadmap: defineAsyncComponent(() => import('../CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue').then((m) => m.default)),\n\treports: defineAsyncComponent(() => import('../CnReportsPage/CnReportsPage.vue').then((m) => m.default)),\n\tlinks: defineAsyncComponent(() => import('../CnLinkCardsPage/CnLinkCardsPage.vue').then((m) => m.default)),\n\tstore: defineAsyncComponent(() => import('../CnStorePage/CnStorePage.vue').then((m) => m.default)),\n\tsearch: defineAsyncComponent(() => import('../CnSearchPage/CnSearchPage.vue').then((m) => m.default)),\n\twiki: defineAsyncComponent(() => import('../CnWikiPage/CnWikiPage.vue').then((m) => m.default)),\n\t// A flow list USED TO need its own page type. A flow lives in\n\t// OpenRegister's native flow store, not a register/schema pair, so the\n\t// object-backed index had nothing to bind to — and every app adopting flows\n\t// copied ~270 lines of identical wrapper differing only in an app-id\n\t// string, which is how three apps ended up with the same dead `@rowClick`\n\t// listener (CnIndexPage emits `row-click`).\n\t//\n\t// Named index sources removed that constraint: `type: \"index\"` with\n\t// `config.entitySource: \"flows\"` lists them, so only the EDITOR still needs a\n\t// page type of its own.\n\t//\n\t// The flow EDITOR. `flow` opens one flow; the LIST is now an ordinary\n\t// `type: \"index\"` with `config.entitySource: \"flows\"`, so a flow surface needs no\n\t// custom page and no page type of its own for the list half.\n\tflow: defineAsyncComponent(() => import('../CnFlowsPage/CnFlowEditorPage.vue').then((m) => m.default)),\n\n\t// DEPRECATED, kept so the fleet can migrate without a flag day. `flows`\n\t// predates named index sources and `flow-detail` is the old name for\n\t// `flow`. Both still resolve; remove them once no manifest names them.\n\tflows: defineAsyncComponent(() => import('../CnFlowsPage/CnFlowsPage.vue').then((m) => m.default)),\n\t'flow-detail': defineAsyncComponent(() => import('../CnFlowsPage/CnFlowEditorPage.vue').then((m) => m.default)),\n}\n"],"names":[],"mappings":"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"}