@conduction/nextcloud-vue 2.56.0-beta.6 → 2.56.0-beta.7

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 (235) 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 +39 -0
  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 +224 -9
  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/CnDetailPage/CnDetailPage.vue2.js +503 -28
  43. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +300 -142
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  47. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  48. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  49. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  53. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  54. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  55. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  56. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  57. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  58. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +169 -12
  59. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  61. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  62. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  63. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  64. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  65. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
  67. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  68. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  70. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  71. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  72. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  73. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  74. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  75. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  76. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  77. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  78. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  79. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  80. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  81. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  82. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  83. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  84. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  85. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  86. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  87. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  88. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  89. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  90. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  91. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  92. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  93. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  94. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  95. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  96. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  97. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  98. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  99. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  100. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  101. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  102. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  103. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  104. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  105. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  106. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  107. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  108. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  109. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  110. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  111. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  112. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  113. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  114. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  115. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  116. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  117. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  118. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  119. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  120. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  121. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  122. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  123. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  124. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  125. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  126. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  127. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  128. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  129. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  130. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  131. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  132. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  133. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  134. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  135. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  136. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  137. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  138. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  139. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  140. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  141. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  142. package/dist/esm/index.js +10 -0
  143. package/dist/esm/index.js.map +1 -1
  144. package/dist/esm/l10n/en.json.js +54 -1
  145. package/dist/esm/l10n/en.json.js.map +1 -1
  146. package/dist/esm/l10n/nl.json.js +54 -1
  147. package/dist/esm/l10n/nl.json.js.map +1 -1
  148. package/dist/esm/nextcloud-vue.css +931 -4
  149. package/dist/esm/utils/dateVariant.js +124 -0
  150. package/dist/esm/utils/dateVariant.js.map +1 -0
  151. package/dist/esm/utils/detailActionModel.js +260 -0
  152. package/dist/esm/utils/detailActionModel.js.map +1 -0
  153. package/dist/esm/utils/dueRule.js +84 -0
  154. package/dist/esm/utils/dueRule.js.map +1 -0
  155. package/dist/esm/utils/fetchAggregate.js +37 -1
  156. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  157. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  158. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  159. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  160. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  161. package/dist/esm/utils/readPath.js +30 -0
  162. package/dist/esm/utils/readPath.js.map +1 -0
  163. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  164. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  165. package/dist/nextcloud-vue.cjs.js +14970 -8991
  166. package/dist/nextcloud-vue.cjs.js.map +2 -2
  167. package/dist/nextcloud-vue.css +931 -4
  168. package/l10n/en.json +54 -1
  169. package/l10n/nl.json +54 -1
  170. package/package.json +1 -1
  171. package/scripts/.jsdoc-baselines.json +3 -0
  172. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  173. package/src/components/CnAppNav/CnAppNav.md +26 -0
  174. package/src/components/CnAppNav/CnAppNav.vue +104 -1
  175. package/src/components/CnBannerWidget/CnBannerWidget.vue +365 -10
  176. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  177. package/src/components/CnBoardView/CnBoardView.vue +77 -0
  178. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  179. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  180. package/src/components/CnBrandStripe/index.js +4 -0
  181. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  182. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  183. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  184. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  185. package/src/components/CnConversationThread/index.js +4 -0
  186. package/src/components/CnDetailPage/CnDetailPage.md +16 -0
  187. package/src/components/CnDetailPage/CnDetailPage.vue +683 -48
  188. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  189. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  190. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  191. package/src/components/CnDocumentReviewList/index.js +4 -0
  192. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +189 -14
  193. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  194. package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
  195. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  196. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  197. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  198. package/src/components/CnNextStepCard/index.js +4 -0
  199. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  200. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  201. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  202. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  203. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  204. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  205. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  206. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  207. package/src/components/CnSegmentedControl/index.js +4 -0
  208. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  209. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  210. package/src/components/CnStackedBarWidget/index.js +14 -0
  211. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  212. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  213. package/src/components/CnTabs/CnTab.vue +35 -0
  214. package/src/components/CnTabs/CnTabs.vue +244 -10
  215. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  216. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  217. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  218. package/src/components/CnWeekStripWidget/index.js +14 -0
  219. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  220. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  221. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  222. package/src/components/index.js +11 -0
  223. package/src/css/detail-page.css +47 -0
  224. package/src/index.js +9 -0
  225. package/src/schemas/app-manifest-v2.schema.json +389 -1
  226. package/src/schemas/app-manifest.schema.json +2 -1
  227. package/src/utils/dateVariant.js +120 -0
  228. package/src/utils/detailActionModel.js +256 -0
  229. package/src/utils/dueRule.js +94 -0
  230. package/src/utils/fetchAggregate.js +36 -0
  231. package/src/utils/fetchFilterCounts.js +182 -0
  232. package/src/utils/index.js +1 -0
  233. package/src/utils/libraryWidgetKeys.js +5 -0
  234. package/src/utils/readPath.js +27 -0
  235. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -6,7 +6,9 @@ import { NcTextField, NcSelect } from '@nextcloud/vue';
6
6
  *
7
7
  * Edits the banner text, the severity variant, and the optional
8
8
  * click-through route (a page id). The `visibleWhen` condition is a
9
- * manifest-authored construct and is round-tripped untouched. Emits
9
+ * manifest-authored construct and is round-tripped untouched. With the
10
+ * `attention` layout it also edits the kicker, the title and the reason; the
11
+ * card's `actions` are manifest-authored and round-tripped too. Emits
10
12
  * `update:content` with the assembled blob on every change; `validate()`
11
13
  * requires a non-empty text. Used by both `CnAddWidgetModal` and the cog
12
14
  * `CnWidgetStyleEditorModal`.
@@ -57,6 +59,12 @@ var script = {
57
59
  route: typeof initial.route === 'string' ? initial.route : '',
58
60
  // Round-tripped untouched — the form doesn't edit conditions.
59
61
  visibleWhen: initial.visibleWhen ?? null,
62
+ layout: initial.layout === 'attention' ? 'attention' : 'banner',
63
+ kicker: initial.kicker ?? '',
64
+ title: initial.title ?? '',
65
+ reason: initial.reason ?? '',
66
+ // Round-tripped untouched: actions are written in the manifest.
67
+ actions: Array.isArray(initial.actions) ? initial.actions : null,
60
68
  }
61
69
  },
62
70
 
@@ -66,14 +74,40 @@ var script = {
66
74
  return ['info', 'warning', 'error']
67
75
  },
68
76
 
69
- /** The assembled content blob from the current field values. */
77
+ /**
78
+ * Selectable layouts: the note card or the attention card.
79
+ *
80
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card
81
+ */
82
+ layoutOptions() {
83
+ return ['banner', 'attention']
84
+ },
85
+
86
+ /**
87
+ * The assembled content blob from the current field values. The
88
+ * attention keys are only written for an attention card, so a plain
89
+ * banner's stored content stays what it always was.
90
+ *
91
+ * @return {object}
92
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card
93
+ */
70
94
  assembledContent() {
71
- return {
95
+ const content = {
72
96
  text: this.text,
73
97
  variant: this.variant,
74
98
  route: this.route || null,
75
99
  visibleWhen: this.visibleWhen,
100
+ };
101
+ if (this.layout === 'attention') {
102
+ content.layout = 'attention';
103
+ content.kicker = this.kicker;
104
+ content.title = this.title;
105
+ content.reason = this.reason;
106
+ if (this.actions !== null) {
107
+ content.actions = this.actions;
108
+ }
76
109
  }
110
+ return content
77
111
  },
78
112
  },
79
113
 
@@ -95,8 +129,13 @@ var script = {
95
129
  * Validate the form; an empty array means valid.
96
130
  *
97
131
  * @return {string[]} the validation errors.
132
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card
98
133
  */
99
134
  validate() {
135
+ if (this.layout === 'attention') {
136
+ const hasHeadline = (this.title && this.title.trim() !== '') || (this.text && this.text.trim() !== '');
137
+ return hasHeadline ? [] : [translate('nextcloud-vue', 'An attention card needs a title')]
138
+ }
100
139
  if (!this.text || this.text.trim() === '') {
101
140
  return [translate('nextcloud-vue', 'A banner text is required')]
102
141
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CnBannerWidgetForm.vue2.js","sources":["../../../../src/components/CnBannerWidgetForm/CnBannerWidgetForm.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-banner-form\">\n\t\t<NcTextField\n\t\t\t:modelValue=\"text\"\n\t\t\t:label=\"t('nextcloud-vue', 'Banner text')\"\n\t\t\t@update:modelValue=\"update('text', $event)\" />\n\t\t<NcSelect\n\t\t\t:modelValue=\"variant\"\n\t\t\t:options=\"variantOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Variant')\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"update('variant', $event)\" />\n\t\t<NcTextField\n\t\t\t:modelValue=\"route\"\n\t\t\t:label=\"t('nextcloud-vue', 'Route (page id, optional)')\"\n\t\t\t@update:modelValue=\"update('route', $event)\" />\n\t\t<p class=\"cn-banner-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'A conditional banner (visibleWhen) is declared in the manifest.') }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcSelect, NcTextField } from '@nextcloud/vue'\n\n/**\n * CnBannerWidgetForm — the config sub-form for a `banner` widget.\n *\n * Edits the banner text, the severity variant, and the optional\n * click-through route (a page id). The `visibleWhen` condition is a\n * manifest-authored construct and is round-tripped untouched. Emits\n * `update:content` with the assembled blob on every change; `validate()`\n * requires a non-empty text. Used by both `CnAddWidgetModal` and the cog\n * `CnWidgetStyleEditorModal`.\n */\nexport default {\n\tname: 'CnBannerWidgetForm',\n\n\tcomponents: { NcTextField, NcSelect },\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`),\n\t\t * or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\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: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\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\ttext: initial.text ?? '',\n\t\t\tvariant: initial.variant ?? 'info',\n\t\t\troute: typeof initial.route === 'string' ? initial.route : '',\n\t\t\t// Round-tripped untouched — the form doesn't edit conditions.\n\t\t\tvisibleWhen: initial.visibleWhen ?? null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Selectable severity variants. */\n\t\tvariantOptions() {\n\t\t\treturn ['info', 'warning', 'error']\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\ttext: this.text,\n\t\t\t\tvariant: this.variant,\n\t\t\t\troute: this.route || null,\n\t\t\t\tvisibleWhen: this.visibleWhen,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set one field and emit the assembled content.\n\t\t *\n\t\t * @param {string} field The data field name.\n\t\t * @param {unknown} value The new value.\n\t\t */\n\t\tupdate(field, value) {\n\t\t\tthis[field] = value\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\tif (!this.text || this.text.trim() === '') {\n\t\t\t\treturn [t('nextcloud-vue', 'A banner text is required')]\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-banner-form__hint {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":";;;AA+BA;;AAEC;;;;AAIA;;;;AAID,aAAe;CACd,IAAI,EAAE,oBAAoB;;AAE1B,CAAA,UAAU,EAAE,EAAE,WAAW,EAAE,UAAU;;AAErC,CAAA,KAAK,EAAE;AACN;AACC;AACA;AACA;;;AAGD,EAAA,aAAa,EAAE;GACd,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;AAEC;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,CAAC;GACnB;EACD;;AAED,CAAA,KAAK,EAAE;AACN;;AAEC;AACA;;;AAGD,EAAA,gBAAgB;EAChB;;AAED,CAAA,IAAI,GAAG;EACN,MAAM,OAAQ,GAAE,IAAI,CAAC,aAAa,EAAE,OAAQ,IAAG,IAAI,CAAC,KAAI,IAAK,GAAC;AAC9D,EAAA,OAAO;AACN,GAAA,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE;AACxB,GAAA,OAAO,EAAE,OAAO,CAAC,OAAM,IAAK,MAAM;GAClC,KAAK,EAAE,OAAO,OAAO,CAAC,KAAM,KAAI,QAAO,GAAI,OAAO,CAAC,KAAI,GAAI,EAAE;;AAE7D,GAAA,WAAW,EAAE,OAAO,CAAC,eAAe,IAAI;AACzC,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;AACA,EAAA,cAAc,GAAG;AAChB,GAAA,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAA;GAClC;;;AAGD,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAO;AACN,IAAA,IAAI,EAAE,IAAI,CAAC,IAAI;AACf,IAAA,OAAO,EAAE,IAAI,CAAC,OAAO;IACrB,KAAK,EAAE,IAAI,CAAC,KAAM,IAAG,IAAI;IACzB,WAAW,EAAE,IAAI,CAAC,WAAW;AAC9B,IAAA;GACA;EACD;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED;AACC;AACA;AACA;AACA;;AAED,EAAA,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE;AACpB,GAAA,IAAI,CAAC,KAAK,CAAE,GAAE,MAAI;GAClB,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAA;GAClD;;AAED;AACC;AACA;AACA;;EAED,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,IAAI,CAAC,IAAG,IAAK,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,EAAE,EAAE;IAC1C,OAAO,CAACA,SAAC,CAAC,eAAe,EAAE,2BAA2B,CAAC,CAAA;AACxD,IAAA;AACA,GAAA,OAAO,EAAC;GACR;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnBannerWidgetForm.vue2.js","sources":["../../../../src/components/CnBannerWidgetForm/CnBannerWidgetForm.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-banner-form\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"layout\"\n\t\t\t:options=\"layoutOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Layout')\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"update('layout', $event)\" />\n\t\t<template v-if=\"layout === 'attention'\">\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"kicker\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Label above the title')\"\n\t\t\t\t@update:modelValue=\"update('kicker', $event)\" />\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"title\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\t\t@update:modelValue=\"update('title', $event)\" />\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"reason\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Reason (why this needs attention)')\"\n\t\t\t\t@update:modelValue=\"update('reason', $event)\" />\n\t\t</template>\n\t\t<NcTextField\n\t\t\t:modelValue=\"text\"\n\t\t\t:label=\"t('nextcloud-vue', 'Banner text')\"\n\t\t\t@update:modelValue=\"update('text', $event)\" />\n\t\t<NcSelect\n\t\t\t:modelValue=\"variant\"\n\t\t\t:options=\"variantOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Variant')\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"update('variant', $event)\" />\n\t\t<NcTextField\n\t\t\t:modelValue=\"route\"\n\t\t\t:label=\"t('nextcloud-vue', 'Route (page id, optional)')\"\n\t\t\t@update:modelValue=\"update('route', $event)\" />\n\t\t<p class=\"cn-banner-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'A conditional banner (visibleWhen) is declared in the manifest.') }}\n\t\t</p>\n\t\t<p v-if=\"layout === 'attention'\" class=\"cn-banner-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'The actions of an attention card are declared in the manifest.') }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcSelect, NcTextField } from '@nextcloud/vue'\n\n/**\n * CnBannerWidgetForm — the config sub-form for a `banner` widget.\n *\n * Edits the banner text, the severity variant, and the optional\n * click-through route (a page id). The `visibleWhen` condition is a\n * manifest-authored construct and is round-tripped untouched. With the\n * `attention` layout it also edits the kicker, the title and the reason; the\n * card's `actions` are manifest-authored and round-tripped too. Emits\n * `update:content` with the assembled blob on every change; `validate()`\n * requires a non-empty text. Used by both `CnAddWidgetModal` and the cog\n * `CnWidgetStyleEditorModal`.\n */\nexport default {\n\tname: 'CnBannerWidgetForm',\n\n\tcomponents: { NcTextField, NcSelect },\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`),\n\t\t * or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\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: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\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\ttext: initial.text ?? '',\n\t\t\tvariant: initial.variant ?? 'info',\n\t\t\troute: typeof initial.route === 'string' ? initial.route : '',\n\t\t\t// Round-tripped untouched — the form doesn't edit conditions.\n\t\t\tvisibleWhen: initial.visibleWhen ?? null,\n\t\t\tlayout: initial.layout === 'attention' ? 'attention' : 'banner',\n\t\t\tkicker: initial.kicker ?? '',\n\t\t\ttitle: initial.title ?? '',\n\t\t\treason: initial.reason ?? '',\n\t\t\t// Round-tripped untouched: actions are written in the manifest.\n\t\t\tactions: Array.isArray(initial.actions) ? initial.actions : null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Selectable severity variants. */\n\t\tvariantOptions() {\n\t\t\treturn ['info', 'warning', 'error']\n\t\t},\n\n\t\t/**\n\t\t * Selectable layouts: the note card or the attention card.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tlayoutOptions() {\n\t\t\treturn ['banner', 'attention']\n\t\t},\n\n\t\t/**\n\t\t * The assembled content blob from the current field values. The\n\t\t * attention keys are only written for an attention card, so a plain\n\t\t * banner's stored content stays what it always was.\n\t\t *\n\t\t * @return {object}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tassembledContent() {\n\t\t\tconst content = {\n\t\t\t\ttext: this.text,\n\t\t\t\tvariant: this.variant,\n\t\t\t\troute: this.route || null,\n\t\t\t\tvisibleWhen: this.visibleWhen,\n\t\t\t}\n\t\t\tif (this.layout === 'attention') {\n\t\t\t\tcontent.layout = 'attention'\n\t\t\t\tcontent.kicker = this.kicker\n\t\t\t\tcontent.title = this.title\n\t\t\t\tcontent.reason = this.reason\n\t\t\t\tif (this.actions !== null) {\n\t\t\t\t\tcontent.actions = this.actions\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn content\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set one field and emit the assembled content.\n\t\t *\n\t\t * @param {string} field The data field name.\n\t\t * @param {unknown} value The new value.\n\t\t */\n\t\tupdate(field, value) {\n\t\t\tthis[field] = value\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 * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tvalidate() {\n\t\t\tif (this.layout === 'attention') {\n\t\t\t\tconst hasHeadline = (this.title && this.title.trim() !== '') || (this.text && this.text.trim() !== '')\n\t\t\t\treturn hasHeadline ? [] : [t('nextcloud-vue', 'An attention card needs a title')]\n\t\t\t}\n\t\t\tif (!this.text || this.text.trim() === '') {\n\t\t\t\treturn [t('nextcloud-vue', 'A banner text is required')]\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-banner-form__hint {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1,31 +1,64 @@
1
- import { resolveComponent, openBlock, createElementBlock, createVNode, createElementVNode, toDisplayString } from 'vue';
1
+ import { resolveComponent, openBlock, createElementBlock, createVNode, Fragment, createCommentVNode, createElementVNode, toDisplayString } from 'vue';
2
2
 
3
3
  const _hoisted_1 = { class: "cn-banner-form" };
4
4
  const _hoisted_2 = { class: "cn-banner-form__hint" };
5
+ const _hoisted_3 = {
6
+ key: 1,
7
+ class: "cn-banner-form__hint"
8
+ };
5
9
 
6
10
  function render(_ctx, _cache, $props, $setup, $data, $options) {
7
- const _component_NcTextField = resolveComponent("NcTextField");
8
11
  const _component_NcSelect = resolveComponent("NcSelect");
12
+ const _component_NcTextField = resolveComponent("NcTextField");
9
13
 
10
14
  return (openBlock(), createElementBlock("div", _hoisted_1, [
15
+ createVNode(_component_NcSelect, {
16
+ modelValue: $data.layout,
17
+ options: $options.layoutOptions,
18
+ inputLabel: $options.t('nextcloud-vue', 'Layout'),
19
+ clearable: false,
20
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ($options.update('layout', $event)))
21
+ }, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel"]),
22
+ ($data.layout === 'attention')
23
+ ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
24
+ createVNode(_component_NcTextField, {
25
+ modelValue: $data.kicker,
26
+ label: $options.t('nextcloud-vue', 'Label above the title'),
27
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ($options.update('kicker', $event)))
28
+ }, null, 8 /* PROPS */, ["modelValue", "label"]),
29
+ createVNode(_component_NcTextField, {
30
+ modelValue: $data.title,
31
+ label: $options.t('nextcloud-vue', 'Title'),
32
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ($options.update('title', $event)))
33
+ }, null, 8 /* PROPS */, ["modelValue", "label"]),
34
+ createVNode(_component_NcTextField, {
35
+ modelValue: $data.reason,
36
+ label: $options.t('nextcloud-vue', 'Reason (why this needs attention)'),
37
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ($options.update('reason', $event)))
38
+ }, null, 8 /* PROPS */, ["modelValue", "label"])
39
+ ], 64 /* STABLE_FRAGMENT */))
40
+ : createCommentVNode("v-if", true),
11
41
  createVNode(_component_NcTextField, {
12
42
  modelValue: $data.text,
13
43
  label: $options.t('nextcloud-vue', 'Banner text'),
14
- "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ($options.update('text', $event)))
44
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ($options.update('text', $event)))
15
45
  }, null, 8 /* PROPS */, ["modelValue", "label"]),
16
46
  createVNode(_component_NcSelect, {
17
47
  modelValue: $data.variant,
18
48
  options: $options.variantOptions,
19
49
  inputLabel: $options.t('nextcloud-vue', 'Variant'),
20
50
  clearable: false,
21
- "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ($options.update('variant', $event)))
51
+ "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ($options.update('variant', $event)))
22
52
  }, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel"]),
23
53
  createVNode(_component_NcTextField, {
24
54
  modelValue: $data.route,
25
55
  label: $options.t('nextcloud-vue', 'Route (page id, optional)'),
26
- "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ($options.update('route', $event)))
56
+ "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ($options.update('route', $event)))
27
57
  }, null, 8 /* PROPS */, ["modelValue", "label"]),
28
- createElementVNode("p", _hoisted_2, toDisplayString($options.t('nextcloud-vue', 'A conditional banner (visibleWhen) is declared in the manifest.')), 1 /* TEXT */)
58
+ createElementVNode("p", _hoisted_2, toDisplayString($options.t('nextcloud-vue', 'A conditional banner (visibleWhen) is declared in the manifest.')), 1 /* TEXT */),
59
+ ($data.layout === 'attention')
60
+ ? (openBlock(), createElementBlock("p", _hoisted_3, toDisplayString($options.t('nextcloud-vue', 'The actions of an attention card are declared in the manifest.')), 1 /* TEXT */))
61
+ : createCommentVNode("v-if", true)
29
62
  ]))
30
63
  }
31
64
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnBannerWidgetForm.vue3.js","sources":["../../../../src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue?vue&type=template&id=038574af&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-banner-form\">\n\t\t<NcTextField\n\t\t\t:modelValue=\"text\"\n\t\t\t:label=\"t('nextcloud-vue', 'Banner text')\"\n\t\t\t@update:modelValue=\"update('text', $event)\" />\n\t\t<NcSelect\n\t\t\t:modelValue=\"variant\"\n\t\t\t:options=\"variantOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Variant')\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"update('variant', $event)\" />\n\t\t<NcTextField\n\t\t\t:modelValue=\"route\"\n\t\t\t:label=\"t('nextcloud-vue', 'Route (page id, optional)')\"\n\t\t\t@update:modelValue=\"update('route', $event)\" />\n\t\t<p class=\"cn-banner-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'A conditional banner (visibleWhen) is declared in the manifest.') }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcSelect, NcTextField } from '@nextcloud/vue'\n\n/**\n * CnBannerWidgetForm — the config sub-form for a `banner` widget.\n *\n * Edits the banner text, the severity variant, and the optional\n * click-through route (a page id). The `visibleWhen` condition is a\n * manifest-authored construct and is round-tripped untouched. Emits\n * `update:content` with the assembled blob on every change; `validate()`\n * requires a non-empty text. Used by both `CnAddWidgetModal` and the cog\n * `CnWidgetStyleEditorModal`.\n */\nexport default {\n\tname: 'CnBannerWidgetForm',\n\n\tcomponents: { NcTextField, NcSelect },\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`),\n\t\t * or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\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: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\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\ttext: initial.text ?? '',\n\t\t\tvariant: initial.variant ?? 'info',\n\t\t\troute: typeof initial.route === 'string' ? initial.route : '',\n\t\t\t// Round-tripped untouched — the form doesn't edit conditions.\n\t\t\tvisibleWhen: initial.visibleWhen ?? null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Selectable severity variants. */\n\t\tvariantOptions() {\n\t\t\treturn ['info', 'warning', 'error']\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\ttext: this.text,\n\t\t\t\tvariant: this.variant,\n\t\t\t\troute: this.route || null,\n\t\t\t\tvisibleWhen: this.visibleWhen,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set one field and emit the assembled content.\n\t\t *\n\t\t * @param {string} field The data field name.\n\t\t * @param {unknown} value The new value.\n\t\t */\n\t\tupdate(field, value) {\n\t\t\tthis[field] = value\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\tif (!this.text || this.text.trim() === '') {\n\t\t\t\treturn [t('nextcloud-vue', 'A banner text is required')]\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-banner-form__hint {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode"],"mappings":";;AAWM,MAAA,UAAA,GAAA,EAAA,KAAI,EAAA,gBAAA,GAAA;;;;;;;AAAR,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;IACCC,WAGiB,CAAA,sBAAA,EAAA;AAFhB,MAAA,UAAU,EAAE,KAAI,CAAA,IAAA;AAChB,MAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,aAAA,CAAA;MACR,qBAAgB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAA,QAAA,CAAA,MAAA,CAAA,MAAA,EAAA,MAAA,CAAA,CAAA,CAAA;;eAMa,CAAA,mBAAA,EAAA;AAJ7B,MAAA,UAAS,EAAA,KAAA,CAAA,OAAA;AACT,MAAA,OAAO,EAAE,QAAQ,CAAA,cAAA;AACjB,MAAA,UAAU,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,SAAA,CAAA;AACb,MAAA,SAAS,EAAE,KAAK;MACf,qBAAgB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAM,CAAA,MAAA,CAAA,SAAA,EAAI,MAAA,CAAA,CAAA,CAAA;;IAC9BA;AACC,MAAA,UAAC,EAAA,KAAA,CAAA,KAAA;AACD,MAAA,KAAE,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,2BAAA,CAAA;;;;;;;;;"}
1
+ {"version":3,"file":"CnBannerWidgetForm.vue3.js","sources":["../../../../src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue?vue&type=template&id=038574af&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-banner-form\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"layout\"\n\t\t\t:options=\"layoutOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Layout')\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"update('layout', $event)\" />\n\t\t<template v-if=\"layout === 'attention'\">\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"kicker\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Label above the title')\"\n\t\t\t\t@update:modelValue=\"update('kicker', $event)\" />\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"title\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\t\t@update:modelValue=\"update('title', $event)\" />\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"reason\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Reason (why this needs attention)')\"\n\t\t\t\t@update:modelValue=\"update('reason', $event)\" />\n\t\t</template>\n\t\t<NcTextField\n\t\t\t:modelValue=\"text\"\n\t\t\t:label=\"t('nextcloud-vue', 'Banner text')\"\n\t\t\t@update:modelValue=\"update('text', $event)\" />\n\t\t<NcSelect\n\t\t\t:modelValue=\"variant\"\n\t\t\t:options=\"variantOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Variant')\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"update('variant', $event)\" />\n\t\t<NcTextField\n\t\t\t:modelValue=\"route\"\n\t\t\t:label=\"t('nextcloud-vue', 'Route (page id, optional)')\"\n\t\t\t@update:modelValue=\"update('route', $event)\" />\n\t\t<p class=\"cn-banner-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'A conditional banner (visibleWhen) is declared in the manifest.') }}\n\t\t</p>\n\t\t<p v-if=\"layout === 'attention'\" class=\"cn-banner-form__hint\">\n\t\t\t{{ t('nextcloud-vue', 'The actions of an attention card are declared in the manifest.') }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcSelect, NcTextField } from '@nextcloud/vue'\n\n/**\n * CnBannerWidgetForm — the config sub-form for a `banner` widget.\n *\n * Edits the banner text, the severity variant, and the optional\n * click-through route (a page id). The `visibleWhen` condition is a\n * manifest-authored construct and is round-tripped untouched. With the\n * `attention` layout it also edits the kicker, the title and the reason; the\n * card's `actions` are manifest-authored and round-tripped too. Emits\n * `update:content` with the assembled blob on every change; `validate()`\n * requires a non-empty text. Used by both `CnAddWidgetModal` and the cog\n * `CnWidgetStyleEditorModal`.\n */\nexport default {\n\tname: 'CnBannerWidgetForm',\n\n\tcomponents: { NcTextField, NcSelect },\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`),\n\t\t * or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\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: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\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\ttext: initial.text ?? '',\n\t\t\tvariant: initial.variant ?? 'info',\n\t\t\troute: typeof initial.route === 'string' ? initial.route : '',\n\t\t\t// Round-tripped untouched — the form doesn't edit conditions.\n\t\t\tvisibleWhen: initial.visibleWhen ?? null,\n\t\t\tlayout: initial.layout === 'attention' ? 'attention' : 'banner',\n\t\t\tkicker: initial.kicker ?? '',\n\t\t\ttitle: initial.title ?? '',\n\t\t\treason: initial.reason ?? '',\n\t\t\t// Round-tripped untouched: actions are written in the manifest.\n\t\t\tactions: Array.isArray(initial.actions) ? initial.actions : null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Selectable severity variants. */\n\t\tvariantOptions() {\n\t\t\treturn ['info', 'warning', 'error']\n\t\t},\n\n\t\t/**\n\t\t * Selectable layouts: the note card or the attention card.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tlayoutOptions() {\n\t\t\treturn ['banner', 'attention']\n\t\t},\n\n\t\t/**\n\t\t * The assembled content blob from the current field values. The\n\t\t * attention keys are only written for an attention card, so a plain\n\t\t * banner's stored content stays what it always was.\n\t\t *\n\t\t * @return {object}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tassembledContent() {\n\t\t\tconst content = {\n\t\t\t\ttext: this.text,\n\t\t\t\tvariant: this.variant,\n\t\t\t\troute: this.route || null,\n\t\t\t\tvisibleWhen: this.visibleWhen,\n\t\t\t}\n\t\t\tif (this.layout === 'attention') {\n\t\t\t\tcontent.layout = 'attention'\n\t\t\t\tcontent.kicker = this.kicker\n\t\t\t\tcontent.title = this.title\n\t\t\t\tcontent.reason = this.reason\n\t\t\t\tif (this.actions !== null) {\n\t\t\t\t\tcontent.actions = this.actions\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn content\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set one field and emit the assembled content.\n\t\t *\n\t\t * @param {string} field The data field name.\n\t\t * @param {unknown} value The new value.\n\t\t */\n\t\tupdate(field, value) {\n\t\t\tthis[field] = value\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 * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card\n\t\t */\n\t\tvalidate() {\n\t\t\tif (this.layout === 'attention') {\n\t\t\t\tconst hasHeadline = (this.title && this.title.trim() !== '') || (this.text && this.text.trim() !== '')\n\t\t\t\treturn hasHeadline ? [] : [t('nextcloud-vue', 'An attention card needs a title')]\n\t\t\t}\n\t\t\tif (!this.text || this.text.trim() === '') {\n\t\t\t\treturn [t('nextcloud-vue', 'A banner text is required')]\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-banner-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-banner-form__hint {\n\tmargin: 0;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_Fragment","_createElementVNode"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { translate } from '@nextcloud/l10n';
2
2
  import { buildBoardColumns } from '../../utils/boardColumns.js';
3
3
  import { buildSwimlanes } from '../../utils/boardSwimlanes.js';
4
4
  import { runBoardDrop, DROP_OUTCOMES } from '../../utils/boardTransition.js';
5
+ import { dueStateForRow } from '../../utils/dueRule.js';
5
6
  import { preventMiddleClickAutoscroll, isRowMiddleClick } from '../../utils/rowAuxClick.js';
6
7
 
7
8
  /**
@@ -92,6 +93,22 @@ var script = {
92
93
  type: Boolean,
93
94
  default: false,
94
95
  },
96
+
97
+ /**
98
+ * Marks late cards: `{ field, soonDays? }`. `field` is the dot-path to
99
+ * a card's due date. A date before today gets an error edge and the
100
+ * label "Overdue"; a date within `soonDays` (default 3) gets "Due
101
+ * soon" in the warning colour. `null` (the default) marks nothing.
102
+ *
103
+ * The same rule shape the table's date cell uses, so a list and its
104
+ * board agree on what late means.
105
+ *
106
+ * @type {{field: string, soonDays?: number}|null}
107
+ */
108
+ dueRule: {
109
+ type: Object,
110
+ default: null,
111
+ },
95
112
  },
96
113
 
97
114
  emits: ['card-click', 'card-aux-click', 'moved', 'refused', 'stale'],
@@ -104,6 +121,8 @@ var script = {
104
121
  refusal: '',
105
122
  /** The card being dragged, and where from. */
106
123
  dragging: null,
124
+ /** The day late marking measures from. */
125
+ today: new Date(),
107
126
  }
108
127
  },
109
128
 
@@ -176,6 +195,33 @@ var script = {
176
195
 
177
196
  preventMiddleClickAutoscroll,
178
197
 
198
+ /**
199
+ * A card's due state under `dueRule`.
200
+ *
201
+ * @param {object} card The card's row.
202
+ * @return {'overdue'|'soon'|'ok'|null} The state, or null when no rule applies.
203
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
204
+ */
205
+ dueState(card) {
206
+ return dueStateForRow(card, this.dueRule, this.today)
207
+ },
208
+
209
+ /**
210
+ * The words a late card carries. Colour alone would not reach a reader
211
+ * who cannot see it, or cannot tell red from amber.
212
+ *
213
+ * @param {object} card The card's row.
214
+ * @return {string} "Overdue", "Due soon", or '' for a card that is fine.
215
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
216
+ */
217
+ dueLabel(card) {
218
+ const state = this.dueState(card);
219
+ if (state === 'overdue') {
220
+ return translate('nextcloud-vue', 'Overdue')
221
+ }
222
+ return state === 'soon' ? translate('nextcloud-vue', 'Due soon') : ''
223
+ },
224
+
179
225
  /**
180
226
  * Open one card. The pointer and the keyboard both come through here,
181
227
  * so a reader on Enter reaches the same record as a reader on click.
@@ -1 +1 @@
1
- {"version":3,"file":"CnBoardView.vue2.js","sources":["../../../../src/components/CnBoardView/CnBoardView.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-board-view\" data-testid=\"cn-board-view\">\n\t\t<p v-if=\"!usable\" class=\"cn-board-view__unusable\" data-testid=\"cn-board-unusable\">\n\t\t\t{{ unusableText }}\n\t\t</p>\n\n\t\t<div\n\t\t\tv-for=\"lane in lanes\"\n\t\t\tv-else\n\t\t\t:key=\"`lane-${lane.key}`\"\n\t\t\tclass=\"cn-board-view__lane\"\n\t\t\tdata-testid=\"cn-board-lane\"\n\t\t\t:data-lane=\"lane.key\">\n\t\t\t<button\n\t\t\t\tv-if=\"lane.key !== ''\"\n\t\t\t\tclass=\"cn-board-view__lane-header\"\n\t\t\t\tdata-testid=\"cn-board-lane-header\"\n\t\t\t\t:aria-expanded=\"String(!isCollapsed(lane.key))\"\n\t\t\t\t@click=\"toggleLane(lane.key)\">\n\t\t\t\t{{ lane.label }} ({{ lane.count }})\n\t\t\t</button>\n\n\t\t\t<div\n\t\t\t\tv-show=\"!isCollapsed(lane.key)\"\n\t\t\t\tclass=\"cn-board-view__columns\"\n\t\t\t\trole=\"list\"\n\t\t\t\t:aria-label=\"lane.key === '' ? boardLabel : lane.label\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"column in lane.columns\"\n\t\t\t\t\t:key=\"`col-${lane.key}-${column.key}`\"\n\t\t\t\t\tclass=\"cn-board-view__column\"\n\t\t\t\t\trole=\"listitem\"\n\t\t\t\t\tdata-testid=\"cn-board-column\"\n\t\t\t\t\t:data-column=\"column.key\"\n\t\t\t\t\t@dragover.prevent\n\t\t\t\t\t@drop=\"onDrop(column, $event)\">\n\t\t\t\t\t<h3 class=\"cn-board-view__column-header\">\n\t\t\t\t\t\t{{ column.label }}\n\t\t\t\t\t\t<span class=\"cn-board-view__count\" data-testid=\"cn-board-count\">\n\t\t\t\t\t\t\t{{ countLabel(column) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</h3>\n\n\t\t\t\t\t<!--\n\t\t\t\t\t\tA list of its own, so a reader is told how many cards\n\t\t\t\t\t\tthis column holds. The column is a listitem of the\n\t\t\t\t\t\tboard's list of columns; the cards are a list inside\n\t\t\t\t\t\tit, not more items of that same list.\n\t\t\t\t\t-->\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-if=\"column.cards.length > 0\"\n\t\t\t\t\t\tclass=\"cn-board-view__cards\"\n\t\t\t\t\t\trole=\"list\"\n\t\t\t\t\t\t:aria-label=\"column.label\">\n\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\tThe card is a CONTAINER, and every action on it is\n\t\t\t\t\t\t\tits own control. It carries the drag and nothing\n\t\t\t\t\t\t\telse: no tabindex, no click, no key handler, and\n\t\t\t\t\t\t\tnever role=\"button\".\n\n\t\t\t\t\t\t\tšŸ”“ role=\"button\" HERE WOULD BREAK THE MOVE. An\n\t\t\t\t\t\t\telement with that role has presentational children,\n\t\t\t\t\t\t\tso the <select> below would be an interactive\n\t\t\t\t\t\t\tcontrol inside a button. That is invalid, and it\n\t\t\t\t\t\t\ttakes away the only way a keyboard user can move a\n\t\t\t\t\t\t\tcard. It would also collapse every field on the\n\t\t\t\t\t\t\tcard into one button label. See\n\t\t\t\t\t\t\topenspec/changes/board-card-role-and-keyboard.\n\n\t\t\t\t\t\t\tMeasured rather than argued: putting role=\"button\"\n\t\t\t\t\t\t\there makes axe report nested-interactive (serious,\n\t\t\t\t\t\t\t\"Interactive controls must not be nested\") and\n\t\t\t\t\t\t\taria-required-children (critical), and four tests\n\t\t\t\t\t\t\tin tests/a11y/CnBoardView.a11y.spec.js go red.\n\t\t\t\t\t\t\tGate 32 suggests exactly this change. The gate is a\n\t\t\t\t\t\t\tmechanical floor, not the standard.\n\t\t\t\t\t\t-->\n\t\t\t\t\t\t<article\n\t\t\t\t\t\t\tv-for=\"card in column.cards\"\n\t\t\t\t\t\t\t:key=\"`card-${cardKey(card)}`\"\n\t\t\t\t\t\t\tclass=\"cn-board-view__card\"\n\t\t\t\t\t\t\trole=\"listitem\"\n\t\t\t\t\t\t\tdata-testid=\"cn-board-card\"\n\t\t\t\t\t\t\t:data-card-id=\"cardKey(card)\"\n\t\t\t\t\t\t\t:draggable=\"canMove\"\n\t\t\t\t\t\t\t@dragstart=\"onDragStart(card, column, $event)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"field in cardFields\"\n\t\t\t\t\t\t\t\t:key=\"`f-${cardKey(card)}-${field}`\"\n\t\t\t\t\t\t\t\tclass=\"cn-board-view__card-field\">\n\t\t\t\t\t\t\t\t{{ card[field] }}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\t\tOpening the card. A native button, so Enter and\n\t\t\t\t\t\t\t\tSpace both work: the old markup handled Enter\n\t\t\t\t\t\t\t\tonly, and Space is what most people try on\n\t\t\t\t\t\t\t\tsomething that looks like a button.\n\t\t\t\t\t\t\t-->\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"cn-board-view__card-open\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-board-card-open\"\n\t\t\t\t\t\t\t\t:data-card-id=\"cardKey(card)\"\n\t\t\t\t\t\t\t\t:aria-label=\"cardLabel(card, column)\"\n\t\t\t\t\t\t\t\t@click=\"openCard(card, $event)\"\n\t\t\t\t\t\t\t\t@mousedown=\"preventMiddleClickAutoscroll\"\n\t\t\t\t\t\t\t\t@auxclick=\"onCardAuxClick(card, $event)\">\n\t\t\t\t\t\t\t\t{{ openLabel }}\n\t\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\t\tThe keyboard's way to do what a drag does. A board\n\t\t\t\t\t\t\t\twhose only move is a drag is a board a keyboard\n\t\t\t\t\t\t\t\tuser cannot use at all, and \"drag the card\" is not\n\t\t\t\t\t\t\t\tan instruction you can follow with a keyboard.\n\t\t\t\t\t\t\t-->\n\t\t\t\t\t\t\t<label v-if=\"canMove\" class=\"cn-board-view__move\">\n\t\t\t\t\t\t\t\t<span class=\"cn-board-view__move-label\">{{ moveLabel }}</span>\n\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-board-move\"\n\t\t\t\t\t\t\t\t\t:data-card-id=\"cardKey(card)\"\n\t\t\t\t\t\t\t\t\t:value=\"column.key\"\n\t\t\t\t\t\t\t\t\t@change=\"onMoveTo(card, column, $event)\">\n\t\t\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\t\t\tv-for=\"target in lane.columns\"\n\t\t\t\t\t\t\t\t\t\t:key=\"`t-${cardKey(card)}-${target.key}`\"\n\t\t\t\t\t\t\t\t\t\t:value=\"target.key\">\n\t\t\t\t\t\t\t\t\t\t{{ target.label }}\n\t\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</article>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<p\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\tclass=\"cn-board-view__empty\"\n\t\t\t\t\t\tdata-testid=\"cn-board-column-empty\">\n\t\t\t\t\t\t{{ emptyColumnText }}\n\t\t\t\t\t</p>\n\t\t\t\t</section>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<p\n\t\t\tv-if=\"refusal\"\n\t\t\tclass=\"cn-board-view__refusal\"\n\t\t\tdata-testid=\"cn-board-refusal\"\n\t\t\trole=\"alert\">\n\t\t\t{{ refusal }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { buildBoardColumns } from '../../utils/boardColumns.js'\nimport { buildSwimlanes } from '../../utils/boardSwimlanes.js'\nimport { DROP_OUTCOMES, runBoardDrop } from '../../utils/boardTransition.js'\nimport { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'\n\n/**\n * CnBoardView — the index page's rows as a board, one column per stage.\n *\n * THE COLUMNS ARE THE SCHEMA'S, NOT THE DATA'S. `buildBoardColumns` decides\n * them and is tested on its own; this renders what it returns. A board that\n * built its columns from the values present would lose the stage nothing is\n * in, which is the one you drag a card into.\n *\n * šŸ”“ A MOVE GOES THROUGH THE HOST'S TRANSITION AND NEVER WRITES THE STATUS\n * FIELD. Writing it here would skip every guard, side effect and audit entry\n * the transition carries, and the board would become a way to move a case past\n * a rule the case page enforces. `runBoardDrop` owns that contract; this\n * component owns the gestures that reach it.\n *\n * šŸ”“ THERE ARE TWO GESTURES, ALWAYS. A board whose only move is a drag is a\n * board a keyboard user cannot use, and \"drag the card\" is not an instruction\n * anybody can follow with a keyboard. Move to on the card runs the identical\n * call.\n *\n * @event {object} card-click — A card was opened. Payload: the row.\n * @event {object} moved — A card moved. Payload: `{ card, toKey }`.\n * @event {object} refused — A move was refused. Payload: `{ card, message }`.\n * @event {object} stale — The card had already moved. Payload: `{ card }`.\n */\nexport default {\n\tname: 'CnBoardView',\n\n\tprops: {\n\t\t/** The rows the list holds. */\n\t\trows: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The status field's schema: its enum or its lifecycle states. */\n\t\tstatusFieldSchema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The property a row's status lives on. */\n\t\tstatusField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'status',\n\t\t},\n\n\t\t/** The fields a card shows. */\n\t\tcardFields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** A second field to group the board into rows by. */\n\t\tswimlaneField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The row key, so a card is identifiable. */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * The host's transition. Absent, the board is read-only: no drag, no\n\t\t * Move to, because a gesture that cannot do anything is worse than no\n\t\t * gesture at all.\n\t\t */\n\t\trunTransition: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Re-read one card, to check it has not moved under the dragger. */\n\t\treread: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the counts are of one page. A count of a page shown as\n\t\t * though it were the total is a number somebody quotes in a meeting.\n\t\t */\n\t\tpaged: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: ['card-click', 'card-aux-click', 'moved', 'refused', 'stale'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Lanes the reader has collapsed, for as long as they are here. */\n\t\t\tcollapsed: [],\n\t\t\t/** The guard's own words, when it refused the last move. */\n\t\t\trefusal: '',\n\t\t\t/** The card being dragged, and where from. */\n\t\t\tdragging: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} Whether the status field can back a board at all. */\n\t\tusable() {\n\t\t\treturn this.board.usable\n\t\t},\n\n\t\t/** @return {object} The columns, from the schema. */\n\t\tboard() {\n\t\t\treturn buildBoardColumns({\n\t\t\t\tfield: this.statusFieldSchema,\n\t\t\t\trows: this.rows,\n\t\t\t\tstatusField: this.statusField,\n\t\t\t\toffBoardLabel: t('nextcloud-vue', 'Elsewhere'),\n\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} The board as rows of columns. */\n\t\tlanes() {\n\t\t\treturn buildSwimlanes({\n\t\t\t\tcolumns: this.board.columns,\n\t\t\t\tswimlaneField: this.swimlaneField,\n\t\t\t\tunassignedLabel: t('nextcloud-vue', 'Unassigned'),\n\t\t\t})\n\t\t},\n\n\t\t/** @return {boolean} Whether a move is possible at all. */\n\t\tcanMove() {\n\t\t\treturn typeof this.runTransition === 'function'\n\t\t},\n\n\t\t/** @return {string} What to say when the field cannot back a board. */\n\t\tunusableText() {\n\t\t\treturn t('nextcloud-vue', 'This list has no status field with stages, so it cannot be shown as a board.')\n\t\t},\n\n\t\t/** @return {string} The board's accessible name. */\n\t\tboardLabel() {\n\t\t\treturn t('nextcloud-vue', 'Board')\n\t\t},\n\n\t\t/** @return {string} The Move to control's label. */\n\t\tmoveLabel() {\n\t\t\treturn t('nextcloud-vue', 'Move to')\n\t\t},\n\n\t\t/**\n\t\t * The visible text on a card's opening control.\n\t\t *\n\t\t * Short, because it repeats on every card. Which card it opens is\n\t\t * said by the control's `aria-label`, so a reader hearing a list of\n\t\t * buttons hears the card's own name rather than \"Open\" nine times.\n\t\t *\n\t\t * @return {string} The label.\n\t\t */\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\t/** @return {string} What an empty column says. */\n\t\temptyColumnText() {\n\t\t\treturn t('nextcloud-vue', 'Nothing here')\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tpreventMiddleClickAutoscroll,\n\n\t\t/**\n\t\t * Open one card. The pointer and the keyboard both come through here,\n\t\t * so a reader on Enter reaches the same record as a reader on click.\n\t\t *\n\t\t * @param {object} card The card the reader picked.\n\t\t * @param {MouseEvent|KeyboardEvent} [event] The originating event.\n\t\t * @return {void} Nothing.\n\t\t */\n\t\topenCard(card, event) {\n\t\t\t/**\n\t\t\t * @event card-click Emitted when a reader opens a card, by click or Enter. Payload: `(card, event)` — the card row and the native event, so the host can open it in a new tab on a ctrl/cmd/shift click. A middle click emits `card-aux-click` instead.\n\t\t\t */\n\t\t\tthis.$emit('card-click', card, event)\n\t\t},\n\n\t\t/**\n\t\t * Middle click on a card's open button: emits `card-aux-click`, not\n\t\t * `card-click`, so an existing listener that navigates never moves the\n\t\t * current tab away.\n\t\t *\n\t\t * @param {object} card The card the reader picked.\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t * @return {void} Nothing.\n\t\t */\n\t\tonCardAuxClick(card, event) {\n\t\t\tif (isRowMiddleClick(event)) {\n\t\t\t\t/**\n\t\t\t\t * @event card-aux-click Emitted when a reader middle-clicks a card, for opening it in a new tab (see `openRowTarget`). Payload: `(card, event)` — the card row and the native auxclick event.\n\t\t\t\t */\n\t\t\t\tthis.$emit('card-aux-click', card, event)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A card's identity.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @return {string} Its key.\n\t\t */\n\t\tcardKey(card) {\n\t\t\treturn String(card?.[this.rowKey] ?? '')\n\t\t},\n\n\t\t/**\n\t\t * A column's count, said as a page count when that is what it is.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {string} The count.\n\t\t */\n\t\tcountLabel(column) {\n\t\t\treturn this.paged\n\t\t\t\t? t('nextcloud-vue', '{count} on this page', { count: column.count })\n\t\t\t\t: String(column.count)\n\t\t},\n\n\t\t/**\n\t\t * What a card is called to somebody who cannot see which column it is in.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {object} column Its column.\n\t\t * @return {string} The accessible name.\n\t\t */\n\t\tcardLabel(card, column) {\n\t\t\tconst name = this.cardFields.map((field) => card?.[field]).filter(Boolean).join(', ')\n\t\t\treturn t('nextcloud-vue', '{name}, in {column}', { name: name || this.cardKey(card), column: column.label })\n\t\t},\n\n\t\t/**\n\t\t * Whether a lane is collapsed.\n\t\t *\n\t\t * @param {string} key The lane.\n\t\t * @return {boolean} True when collapsed.\n\t\t */\n\t\tisCollapsed(key) {\n\t\t\treturn this.collapsed.includes(key)\n\t\t},\n\n\t\t/**\n\t\t * Collapse a lane, or open it again.\n\t\t *\n\t\t * Held in the component for as long as the reader is on the view, and\n\t\t * deliberately not persisted: a collapsed lane somebody forgot they\n\t\t * collapsed is work that has gone missing for them.\n\t\t *\n\t\t * @param {string} key The lane.\n\t\t */\n\t\ttoggleLane(key) {\n\t\t\tthis.collapsed = this.isCollapsed(key)\n\t\t\t\t? this.collapsed.filter((entry) => entry !== key)\n\t\t\t\t: [...this.collapsed, key]\n\t\t},\n\n\t\t/**\n\t\t * Remember which card is being dragged and where from.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {object} column Its column.\n\t\t * @param {DragEvent} event The event.\n\t\t */\n\t\tonDragStart(card, column, event) {\n\t\t\tthis.dragging = { card, fromKey: column.key }\n\t\t\t// Firefox drops a drag that carries no data.\n\t\t\tevent?.dataTransfer?.setData?.('text/plain', this.cardKey(card))\n\t\t},\n\n\t\t/**\n\t\t * A drop on a column.\n\t\t *\n\t\t * @param {object} column The column dropped on.\n\t\t * @return {Promise<void>} Nothing.\n\t\t */\n\t\tasync onDrop(column) {\n\t\t\tif (!this.dragging) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { card, fromKey } = this.dragging\n\t\t\tthis.dragging = null\n\t\t\tawait this.move(card, fromKey, column.key)\n\t\t},\n\n\t\t/**\n\t\t * Move to, from the card's own menu.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {object} column Its column.\n\t\t * @param {Event} event The change event.\n\t\t * @return {Promise<void>} Nothing.\n\t\t */\n\t\tasync onMoveTo(card, column, event) {\n\t\t\tawait this.move(card, column.key, event?.target?.value ?? '')\n\t\t},\n\n\t\t/**\n\t\t * The one move, whichever gesture asked for it.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {string} fromKey Where it was.\n\t\t * @param {string} toKey Where it is going.\n\t\t * @return {Promise<void>} Nothing.\n\t\t */\n\t\tasync move(card, fromKey, toKey) {\n\t\t\tthis.refusal = ''\n\n\t\t\tconst result = await runBoardDrop({\n\t\t\t\tcard,\n\t\t\t\tfromKey,\n\t\t\t\ttoKey,\n\t\t\t\tstatusField: this.statusField,\n\t\t\t\trunTransition: this.runTransition,\n\t\t\t\treread: this.reread,\n\t\t\t})\n\n\t\t\tif (result.outcome === DROP_OUTCOMES.MOVED) {\n\t\t\t\t/**\n\t\t\t\t * @event moved Emitted when the host's transition accepted the move and the card now sits in the new lane. Payload: `{ card, toKey }`.\n\t\t\t\t */\n\t\t\t\tthis.$emit('moved', { card: result.card, toKey })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (result.outcome === DROP_OUTCOMES.REFUSED) {\n\t\t\t\t// The guard's own sentence when it gave one. Our own only when\n\t\t\t\t// it did not, so the two are never confused.\n\t\t\t\tthis.refusal = result.message || t('nextcloud-vue', 'That move was refused.')\n\t\t\t\t/**\n\t\t\t\t * @event refused Emitted when the guard turned the move down. Payload: `{ card, message }`, the message being the guard's own sentence when it gave one.\n\t\t\t\t */\n\t\t\t\tthis.$emit('refused', { card: result.card, message: this.refusal })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (result.outcome === DROP_OUTCOMES.STALE) {\n\t\t\t\tthis.refusal = t('nextcloud-vue', 'Somebody else moved this card. It is shown where it is now.')\n\t\t\t\t/**\n\t\t\t\t * @event stale Emitted when somebody else moved the card first, so the board shows it where it is now. Payload: `{ card }`.\n\t\t\t\t */\n\t\t\t\tthis.$emit('stale', { card: result.card })\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped lang=\"scss\">\n.cn-board-view__columns {\n\tdisplay: flex;\n\tgap: 12px;\n\toverflow-x: auto;\n\talign-items: flex-start;\n}\n\n.cn-board-view__column {\n\tflex: 0 0 260px;\n\tbackground-color: var(--color-background-hover);\n\tborder-radius: var(--border-radius-large);\n\tpadding: 8px;\n}\n\n.cn-board-view__column-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 1rem;\n\tmargin: 0 0 8px;\n}\n\n.cn-board-view__count,\n.cn-board-view__empty,\n.cn-board-view__unusable {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-board-view__card {\n\tdisplay: block;\n\tbackground-color: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 8px;\n\tmargin-bottom: 8px;\n}\n\n.cn-board-view__card-field {\n\tdisplay: block;\n}\n\n/*\n * The card's opening control. The cursor and the focus ring live here now,\n * on the thing that actually does something, rather than on the box.\n */\n.cn-board-view__card-open {\n\tbackground: none;\n\tborder: none;\n\tpadding: 0;\n\tcursor: pointer;\n\tcolor: var(--color-primary-element);\n\tfont-size: 0.8rem;\n}\n\n.cn-board-view__card-open:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-board-view__lane-header {\n\tbackground: none;\n\tborder: none;\n\tfont-weight: bold;\n\tpadding: 8px 0;\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n}\n\n.cn-board-view__move-label {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8rem;\n}\n\n.cn-board-view__refusal {\n\tcolor: var(--color-error);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnBoardView.vue2.js","sources":["../../../../src/components/CnBoardView/CnBoardView.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-board-view\" data-testid=\"cn-board-view\">\n\t\t<p v-if=\"!usable\" class=\"cn-board-view__unusable\" data-testid=\"cn-board-unusable\">\n\t\t\t{{ unusableText }}\n\t\t</p>\n\n\t\t<div\n\t\t\tv-for=\"lane in lanes\"\n\t\t\tv-else\n\t\t\t:key=\"`lane-${lane.key}`\"\n\t\t\tclass=\"cn-board-view__lane\"\n\t\t\tdata-testid=\"cn-board-lane\"\n\t\t\t:data-lane=\"lane.key\">\n\t\t\t<button\n\t\t\t\tv-if=\"lane.key !== ''\"\n\t\t\t\tclass=\"cn-board-view__lane-header\"\n\t\t\t\tdata-testid=\"cn-board-lane-header\"\n\t\t\t\t:aria-expanded=\"String(!isCollapsed(lane.key))\"\n\t\t\t\t@click=\"toggleLane(lane.key)\">\n\t\t\t\t{{ lane.label }} ({{ lane.count }})\n\t\t\t</button>\n\n\t\t\t<div\n\t\t\t\tv-show=\"!isCollapsed(lane.key)\"\n\t\t\t\tclass=\"cn-board-view__columns\"\n\t\t\t\trole=\"list\"\n\t\t\t\t:aria-label=\"lane.key === '' ? boardLabel : lane.label\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"column in lane.columns\"\n\t\t\t\t\t:key=\"`col-${lane.key}-${column.key}`\"\n\t\t\t\t\tclass=\"cn-board-view__column\"\n\t\t\t\t\trole=\"listitem\"\n\t\t\t\t\tdata-testid=\"cn-board-column\"\n\t\t\t\t\t:data-column=\"column.key\"\n\t\t\t\t\t@dragover.prevent\n\t\t\t\t\t@drop=\"onDrop(column, $event)\">\n\t\t\t\t\t<h3 class=\"cn-board-view__column-header\">\n\t\t\t\t\t\t{{ column.label }}\n\t\t\t\t\t\t<span class=\"cn-board-view__count\" data-testid=\"cn-board-count\">\n\t\t\t\t\t\t\t{{ countLabel(column) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</h3>\n\n\t\t\t\t\t<!--\n\t\t\t\t\t\tA list of its own, so a reader is told how many cards\n\t\t\t\t\t\tthis column holds. The column is a listitem of the\n\t\t\t\t\t\tboard's list of columns; the cards are a list inside\n\t\t\t\t\t\tit, not more items of that same list.\n\t\t\t\t\t-->\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-if=\"column.cards.length > 0\"\n\t\t\t\t\t\tclass=\"cn-board-view__cards\"\n\t\t\t\t\t\trole=\"list\"\n\t\t\t\t\t\t:aria-label=\"column.label\">\n\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\tThe card is a CONTAINER, and every action on it is\n\t\t\t\t\t\t\tits own control. It carries the drag and nothing\n\t\t\t\t\t\t\telse: no tabindex, no click, no key handler, and\n\t\t\t\t\t\t\tnever role=\"button\".\n\n\t\t\t\t\t\t\tšŸ”“ role=\"button\" HERE WOULD BREAK THE MOVE. An\n\t\t\t\t\t\t\telement with that role has presentational children,\n\t\t\t\t\t\t\tso the <select> below would be an interactive\n\t\t\t\t\t\t\tcontrol inside a button. That is invalid, and it\n\t\t\t\t\t\t\ttakes away the only way a keyboard user can move a\n\t\t\t\t\t\t\tcard. It would also collapse every field on the\n\t\t\t\t\t\t\tcard into one button label. See\n\t\t\t\t\t\t\topenspec/changes/board-card-role-and-keyboard.\n\n\t\t\t\t\t\t\tMeasured rather than argued: putting role=\"button\"\n\t\t\t\t\t\t\there makes axe report nested-interactive (serious,\n\t\t\t\t\t\t\t\"Interactive controls must not be nested\") and\n\t\t\t\t\t\t\taria-required-children (critical), and four tests\n\t\t\t\t\t\t\tin tests/a11y/CnBoardView.a11y.spec.js go red.\n\t\t\t\t\t\t\tGate 32 suggests exactly this change. The gate is a\n\t\t\t\t\t\t\tmechanical floor, not the standard.\n\t\t\t\t\t\t-->\n\t\t\t\t\t\t<article\n\t\t\t\t\t\t\tv-for=\"card in column.cards\"\n\t\t\t\t\t\t\t:key=\"`card-${cardKey(card)}`\"\n\t\t\t\t\t\t\tclass=\"cn-board-view__card\"\n\t\t\t\t\t\t\t:class=\"dueState(card) ? `cn-board-view__card--${dueState(card)}` : null\"\n\t\t\t\t\t\t\trole=\"listitem\"\n\t\t\t\t\t\t\tdata-testid=\"cn-board-card\"\n\t\t\t\t\t\t\t:data-card-id=\"cardKey(card)\"\n\t\t\t\t\t\t\t:draggable=\"canMove\"\n\t\t\t\t\t\t\t@dragstart=\"onDragStart(card, column, $event)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"dueLabel(card)\"\n\t\t\t\t\t\t\t\tclass=\"cn-board-view__due\"\n\t\t\t\t\t\t\t\t:class=\"`cn-board-view__due--${dueState(card)}`\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-board-due\">\n\t\t\t\t\t\t\t\t{{ dueLabel(card) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"field in cardFields\"\n\t\t\t\t\t\t\t\t:key=\"`f-${cardKey(card)}-${field}`\"\n\t\t\t\t\t\t\t\tclass=\"cn-board-view__card-field\">\n\t\t\t\t\t\t\t\t{{ card[field] }}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\t\tOpening the card. A native button, so Enter and\n\t\t\t\t\t\t\t\tSpace both work: the old markup handled Enter\n\t\t\t\t\t\t\t\tonly, and Space is what most people try on\n\t\t\t\t\t\t\t\tsomething that looks like a button.\n\t\t\t\t\t\t\t-->\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"cn-board-view__card-open\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-board-card-open\"\n\t\t\t\t\t\t\t\t:data-card-id=\"cardKey(card)\"\n\t\t\t\t\t\t\t\t:aria-label=\"cardLabel(card, column)\"\n\t\t\t\t\t\t\t\t@click=\"openCard(card, $event)\"\n\t\t\t\t\t\t\t\t@mousedown=\"preventMiddleClickAutoscroll\"\n\t\t\t\t\t\t\t\t@auxclick=\"onCardAuxClick(card, $event)\">\n\t\t\t\t\t\t\t\t{{ openLabel }}\n\t\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t\t<!--\n\t\t\t\t\t\t\t\tThe keyboard's way to do what a drag does. A board\n\t\t\t\t\t\t\t\twhose only move is a drag is a board a keyboard\n\t\t\t\t\t\t\t\tuser cannot use at all, and \"drag the card\" is not\n\t\t\t\t\t\t\t\tan instruction you can follow with a keyboard.\n\t\t\t\t\t\t\t-->\n\t\t\t\t\t\t\t<label v-if=\"canMove\" class=\"cn-board-view__move\">\n\t\t\t\t\t\t\t\t<span class=\"cn-board-view__move-label\">{{ moveLabel }}</span>\n\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-board-move\"\n\t\t\t\t\t\t\t\t\t:data-card-id=\"cardKey(card)\"\n\t\t\t\t\t\t\t\t\t:value=\"column.key\"\n\t\t\t\t\t\t\t\t\t@change=\"onMoveTo(card, column, $event)\">\n\t\t\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\t\t\tv-for=\"target in lane.columns\"\n\t\t\t\t\t\t\t\t\t\t:key=\"`t-${cardKey(card)}-${target.key}`\"\n\t\t\t\t\t\t\t\t\t\t:value=\"target.key\">\n\t\t\t\t\t\t\t\t\t\t{{ target.label }}\n\t\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t</article>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<p\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\tclass=\"cn-board-view__empty\"\n\t\t\t\t\t\tdata-testid=\"cn-board-column-empty\">\n\t\t\t\t\t\t{{ emptyColumnText }}\n\t\t\t\t\t</p>\n\t\t\t\t</section>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<p\n\t\t\tv-if=\"refusal\"\n\t\t\tclass=\"cn-board-view__refusal\"\n\t\t\tdata-testid=\"cn-board-refusal\"\n\t\t\trole=\"alert\">\n\t\t\t{{ refusal }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { buildBoardColumns } from '../../utils/boardColumns.js'\nimport { buildSwimlanes } from '../../utils/boardSwimlanes.js'\nimport { DROP_OUTCOMES, runBoardDrop } from '../../utils/boardTransition.js'\nimport { dueStateForRow } from '../../utils/dueRule.js'\nimport { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'\n\n/**\n * CnBoardView — the index page's rows as a board, one column per stage.\n *\n * THE COLUMNS ARE THE SCHEMA'S, NOT THE DATA'S. `buildBoardColumns` decides\n * them and is tested on its own; this renders what it returns. A board that\n * built its columns from the values present would lose the stage nothing is\n * in, which is the one you drag a card into.\n *\n * šŸ”“ A MOVE GOES THROUGH THE HOST'S TRANSITION AND NEVER WRITES THE STATUS\n * FIELD. Writing it here would skip every guard, side effect and audit entry\n * the transition carries, and the board would become a way to move a case past\n * a rule the case page enforces. `runBoardDrop` owns that contract; this\n * component owns the gestures that reach it.\n *\n * šŸ”“ THERE ARE TWO GESTURES, ALWAYS. A board whose only move is a drag is a\n * board a keyboard user cannot use, and \"drag the card\" is not an instruction\n * anybody can follow with a keyboard. Move to on the card runs the identical\n * call.\n *\n * @event {object} card-click — A card was opened. Payload: the row.\n * @event {object} moved — A card moved. Payload: `{ card, toKey }`.\n * @event {object} refused — A move was refused. Payload: `{ card, message }`.\n * @event {object} stale — The card had already moved. Payload: `{ card }`.\n */\nexport default {\n\tname: 'CnBoardView',\n\n\tprops: {\n\t\t/** The rows the list holds. */\n\t\trows: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The status field's schema: its enum or its lifecycle states. */\n\t\tstatusFieldSchema: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The property a row's status lives on. */\n\t\tstatusField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'status',\n\t\t},\n\n\t\t/** The fields a card shows. */\n\t\tcardFields: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** A second field to group the board into rows by. */\n\t\tswimlaneField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The row key, so a card is identifiable. */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * The host's transition. Absent, the board is read-only: no drag, no\n\t\t * Move to, because a gesture that cannot do anything is worse than no\n\t\t * gesture at all.\n\t\t */\n\t\trunTransition: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Re-read one card, to check it has not moved under the dragger. */\n\t\treread: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the counts are of one page. A count of a page shown as\n\t\t * though it were the total is a number somebody quotes in a meeting.\n\t\t */\n\t\tpaged: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\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', 'card-aux-click', 'moved', 'refused', 'stale'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Lanes the reader has collapsed, for as long as they are here. */\n\t\t\tcollapsed: [],\n\t\t\t/** The guard's own words, when it refused the last move. */\n\t\t\trefusal: '',\n\t\t\t/** The card being dragged, and where from. */\n\t\t\tdragging: 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\t/** @return {boolean} Whether the status field can back a board at all. */\n\t\tusable() {\n\t\t\treturn this.board.usable\n\t\t},\n\n\t\t/** @return {object} The columns, from the schema. */\n\t\tboard() {\n\t\t\treturn buildBoardColumns({\n\t\t\t\tfield: this.statusFieldSchema,\n\t\t\t\trows: this.rows,\n\t\t\t\tstatusField: this.statusField,\n\t\t\t\toffBoardLabel: t('nextcloud-vue', 'Elsewhere'),\n\t\t\t})\n\t\t},\n\n\t\t/** @return {Array<object>} The board as rows of columns. */\n\t\tlanes() {\n\t\t\treturn buildSwimlanes({\n\t\t\t\tcolumns: this.board.columns,\n\t\t\t\tswimlaneField: this.swimlaneField,\n\t\t\t\tunassignedLabel: t('nextcloud-vue', 'Unassigned'),\n\t\t\t})\n\t\t},\n\n\t\t/** @return {boolean} Whether a move is possible at all. */\n\t\tcanMove() {\n\t\t\treturn typeof this.runTransition === 'function'\n\t\t},\n\n\t\t/** @return {string} What to say when the field cannot back a board. */\n\t\tunusableText() {\n\t\t\treturn t('nextcloud-vue', 'This list has no status field with stages, so it cannot be shown as a board.')\n\t\t},\n\n\t\t/** @return {string} The board's accessible name. */\n\t\tboardLabel() {\n\t\t\treturn t('nextcloud-vue', 'Board')\n\t\t},\n\n\t\t/** @return {string} The Move to control's label. */\n\t\tmoveLabel() {\n\t\t\treturn t('nextcloud-vue', 'Move to')\n\t\t},\n\n\t\t/**\n\t\t * The visible text on a card's opening control.\n\t\t *\n\t\t * Short, because it repeats on every card. Which card it opens is\n\t\t * said by the control's `aria-label`, so a reader hearing a list of\n\t\t * buttons hears the card's own name rather than \"Open\" nine times.\n\t\t *\n\t\t * @return {string} The label.\n\t\t */\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\t/** @return {string} What an empty column says. */\n\t\temptyColumnText() {\n\t\t\treturn t('nextcloud-vue', 'Nothing here')\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tpreventMiddleClickAutoscroll,\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 * Open one card. The pointer and the keyboard both come through here,\n\t\t * so a reader on Enter reaches the same record as a reader on click.\n\t\t *\n\t\t * @param {object} card The card the reader picked.\n\t\t * @param {MouseEvent|KeyboardEvent} [event] The originating event.\n\t\t * @return {void} Nothing.\n\t\t */\n\t\topenCard(card, event) {\n\t\t\t/**\n\t\t\t * @event card-click Emitted when a reader opens a card, by click or Enter. Payload: `(card, event)` — the card row and the native event, so the host can open it in a new tab on a ctrl/cmd/shift click. A middle click emits `card-aux-click` instead.\n\t\t\t */\n\t\t\tthis.$emit('card-click', card, event)\n\t\t},\n\n\t\t/**\n\t\t * Middle click on a card's open button: emits `card-aux-click`, not\n\t\t * `card-click`, so an existing listener that navigates never moves the\n\t\t * current tab away.\n\t\t *\n\t\t * @param {object} card The card the reader picked.\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t * @return {void} Nothing.\n\t\t */\n\t\tonCardAuxClick(card, event) {\n\t\t\tif (isRowMiddleClick(event)) {\n\t\t\t\t/**\n\t\t\t\t * @event card-aux-click Emitted when a reader middle-clicks a card, for opening it in a new tab (see `openRowTarget`). Payload: `(card, event)` — the card row and the native auxclick event.\n\t\t\t\t */\n\t\t\t\tthis.$emit('card-aux-click', card, event)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A card's identity.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @return {string} Its key.\n\t\t */\n\t\tcardKey(card) {\n\t\t\treturn String(card?.[this.rowKey] ?? '')\n\t\t},\n\n\t\t/**\n\t\t * A column's count, said as a page count when that is what it is.\n\t\t *\n\t\t * @param {object} column The column.\n\t\t * @return {string} The count.\n\t\t */\n\t\tcountLabel(column) {\n\t\t\treturn this.paged\n\t\t\t\t? t('nextcloud-vue', '{count} on this page', { count: column.count })\n\t\t\t\t: String(column.count)\n\t\t},\n\n\t\t/**\n\t\t * What a card is called to somebody who cannot see which column it is in.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {object} column Its column.\n\t\t * @return {string} The accessible name.\n\t\t */\n\t\tcardLabel(card, column) {\n\t\t\tconst name = this.cardFields.map((field) => card?.[field]).filter(Boolean).join(', ')\n\t\t\treturn t('nextcloud-vue', '{name}, in {column}', { name: name || this.cardKey(card), column: column.label })\n\t\t},\n\n\t\t/**\n\t\t * Whether a lane is collapsed.\n\t\t *\n\t\t * @param {string} key The lane.\n\t\t * @return {boolean} True when collapsed.\n\t\t */\n\t\tisCollapsed(key) {\n\t\t\treturn this.collapsed.includes(key)\n\t\t},\n\n\t\t/**\n\t\t * Collapse a lane, or open it again.\n\t\t *\n\t\t * Held in the component for as long as the reader is on the view, and\n\t\t * deliberately not persisted: a collapsed lane somebody forgot they\n\t\t * collapsed is work that has gone missing for them.\n\t\t *\n\t\t * @param {string} key The lane.\n\t\t */\n\t\ttoggleLane(key) {\n\t\t\tthis.collapsed = this.isCollapsed(key)\n\t\t\t\t? this.collapsed.filter((entry) => entry !== key)\n\t\t\t\t: [...this.collapsed, key]\n\t\t},\n\n\t\t/**\n\t\t * Remember which card is being dragged and where from.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {object} column Its column.\n\t\t * @param {DragEvent} event The event.\n\t\t */\n\t\tonDragStart(card, column, event) {\n\t\t\tthis.dragging = { card, fromKey: column.key }\n\t\t\t// Firefox drops a drag that carries no data.\n\t\t\tevent?.dataTransfer?.setData?.('text/plain', this.cardKey(card))\n\t\t},\n\n\t\t/**\n\t\t * A drop on a column.\n\t\t *\n\t\t * @param {object} column The column dropped on.\n\t\t * @return {Promise<void>} Nothing.\n\t\t */\n\t\tasync onDrop(column) {\n\t\t\tif (!this.dragging) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { card, fromKey } = this.dragging\n\t\t\tthis.dragging = null\n\t\t\tawait this.move(card, fromKey, column.key)\n\t\t},\n\n\t\t/**\n\t\t * Move to, from the card's own menu.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {object} column Its column.\n\t\t * @param {Event} event The change event.\n\t\t * @return {Promise<void>} Nothing.\n\t\t */\n\t\tasync onMoveTo(card, column, event) {\n\t\t\tawait this.move(card, column.key, event?.target?.value ?? '')\n\t\t},\n\n\t\t/**\n\t\t * The one move, whichever gesture asked for it.\n\t\t *\n\t\t * @param {object} card The row.\n\t\t * @param {string} fromKey Where it was.\n\t\t * @param {string} toKey Where it is going.\n\t\t * @return {Promise<void>} Nothing.\n\t\t */\n\t\tasync move(card, fromKey, toKey) {\n\t\t\tthis.refusal = ''\n\n\t\t\tconst result = await runBoardDrop({\n\t\t\t\tcard,\n\t\t\t\tfromKey,\n\t\t\t\ttoKey,\n\t\t\t\tstatusField: this.statusField,\n\t\t\t\trunTransition: this.runTransition,\n\t\t\t\treread: this.reread,\n\t\t\t})\n\n\t\t\tif (result.outcome === DROP_OUTCOMES.MOVED) {\n\t\t\t\t/**\n\t\t\t\t * @event moved Emitted when the host's transition accepted the move and the card now sits in the new lane. Payload: `{ card, toKey }`.\n\t\t\t\t */\n\t\t\t\tthis.$emit('moved', { card: result.card, toKey })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (result.outcome === DROP_OUTCOMES.REFUSED) {\n\t\t\t\t// The guard's own sentence when it gave one. Our own only when\n\t\t\t\t// it did not, so the two are never confused.\n\t\t\t\tthis.refusal = result.message || t('nextcloud-vue', 'That move was refused.')\n\t\t\t\t/**\n\t\t\t\t * @event refused Emitted when the guard turned the move down. Payload: `{ card, message }`, the message being the guard's own sentence when it gave one.\n\t\t\t\t */\n\t\t\t\tthis.$emit('refused', { card: result.card, message: this.refusal })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (result.outcome === DROP_OUTCOMES.STALE) {\n\t\t\t\tthis.refusal = t('nextcloud-vue', 'Somebody else moved this card. It is shown where it is now.')\n\t\t\t\t/**\n\t\t\t\t * @event stale Emitted when somebody else moved the card first, so the board shows it where it is now. Payload: `{ card }`.\n\t\t\t\t */\n\t\t\t\tthis.$emit('stale', { card: result.card })\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped lang=\"scss\">\n.cn-board-view__columns {\n\tdisplay: flex;\n\tgap: 12px;\n\toverflow-x: auto;\n\talign-items: flex-start;\n}\n\n.cn-board-view__column {\n\tflex: 0 0 260px;\n\tbackground-color: var(--color-background-hover);\n\tborder-radius: var(--border-radius-large);\n\tpadding: 8px;\n}\n\n.cn-board-view__column-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 1rem;\n\tmargin: 0 0 8px;\n}\n\n.cn-board-view__count,\n.cn-board-view__empty,\n.cn-board-view__unusable {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-board-view__card {\n\tdisplay: block;\n\tbackground-color: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 8px;\n\tmargin-bottom: 8px;\n}\n\n.cn-board-view__card-field {\n\tdisplay: block;\n}\n\n/* A late card: an edge in the error colour, drawn as an inset shadow so the\n card keeps its size and its rounded corners. The label below carries the\n meaning; the edge only helps a sighted reader find it. */\n.cn-board-view__card--overdue {\n\tborder-color: var(--color-error);\n\tbox-shadow: inset 3px 0 0 var(--color-error);\n}\n\n.cn-board-view__due {\n\tdisplay: block;\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n}\n\n.cn-board-view__due--overdue {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-board-view__due--soon {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n\tfont-weight: 600;\n}\n\n/*\n * The card's opening control. The cursor and the focus ring live here now,\n * on the thing that actually does something, rather than on the box.\n */\n.cn-board-view__card-open {\n\tbackground: none;\n\tborder: none;\n\tpadding: 0;\n\tcursor: pointer;\n\tcolor: var(--color-primary-element);\n\tfont-size: 0.8rem;\n}\n\n.cn-board-view__card-open:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-board-view__lane-header {\n\tbackground: none;\n\tborder: none;\n\tfont-weight: bold;\n\tpadding: 8px 0;\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n}\n\n.cn-board-view__move-label {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8rem;\n}\n\n.cn-board-view__refusal {\n\tcolor: var(--color-error);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { openBlock, createElementBlock, toDisplayString, Fragment, renderList, createCommentVNode, withDirectives, createElementVNode, withModifiers, createTextVNode, vShow } from 'vue';
1
+ import { openBlock, createElementBlock, toDisplayString, Fragment, renderList, createCommentVNode, withDirectives, createElementVNode, withModifiers, createTextVNode, normalizeClass, vShow } from 'vue';
2
2
 
3
3
  const _hoisted_1 = {
4
4
  class: "cn-board-view",
@@ -22,7 +22,7 @@ const _hoisted_9 = ["aria-label"];
22
22
  const _hoisted_10 = ["data-card-id", "draggable", "onDragstart"];
23
23
  const _hoisted_11 = ["data-card-id", "aria-label", "onClick", "onAuxclick"];
24
24
  const _hoisted_12 = {
25
- key: 0,
25
+ key: 1,
26
26
  class: "cn-board-view__move"
27
27
  };
28
28
  const _hoisted_13 = { class: "cn-board-view__move-label" };
@@ -89,13 +89,20 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
89
89
  (openBlock(true), createElementBlock(Fragment, null, renderList(column.cards, (card) => {
90
90
  return (openBlock(), createElementBlock("article", {
91
91
  key: `card-${$options.cardKey(card)}`,
92
- class: "cn-board-view__card",
92
+ class: normalizeClass(["cn-board-view__card", $options.dueState(card) ? `cn-board-view__card--${$options.dueState(card)}` : null]),
93
93
  role: "listitem",
94
94
  "data-testid": "cn-board-card",
95
95
  "data-card-id": $options.cardKey(card),
96
96
  draggable: $options.canMove,
97
97
  onDragstart: $event => ($options.onDragStart(card, column, $event))
98
98
  }, [
99
+ ($options.dueLabel(card))
100
+ ? (openBlock(), createElementBlock("span", {
101
+ key: 0,
102
+ class: normalizeClass(["cn-board-view__due", `cn-board-view__due--${$options.dueState(card)}`]),
103
+ "data-testid": "cn-board-due"
104
+ }, toDisplayString($options.dueLabel(card)), 3 /* TEXT, CLASS */))
105
+ : createCommentVNode("v-if", true),
99
106
  (openBlock(true), createElementBlock(Fragment, null, renderList($props.cardFields, (field) => {
100
107
  return (openBlock(), createElementBlock("span", {
101
108
  key: `f-${$options.cardKey(card)}-${field}`,
@@ -130,7 +137,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
130
137
  ], 40 /* PROPS, NEED_HYDRATION */, _hoisted_14)
131
138
  ]))
132
139
  : createCommentVNode("v-if", true)
133
- ], 40 /* PROPS, NEED_HYDRATION */, _hoisted_10))
140
+ ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_10))
134
141
  }), 128 /* KEYED_FRAGMENT */))
135
142
  ], 8 /* PROPS */, _hoisted_9))
136
143
  : (openBlock(), createElementBlock("p", _hoisted_16, toDisplayString($options.emptyColumnText), 1 /* TEXT */))