@conduction/nextcloud-vue 2.100.0 → 2.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +22 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  6. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
  7. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  8. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  10. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  11. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
  12. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  13. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
  14. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +70 -6
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +105 -74
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
  21. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
  22. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
  23. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
  24. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
  25. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
  26. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
  28. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  29. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
  30. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  31. package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
  32. package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
  33. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
  34. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
  35. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
  36. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
  37. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
  38. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
  39. package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
  40. package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
  41. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +320 -74
  42. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +323 -213
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/useNamedSource.js +30 -3
  46. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  47. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  48. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
  49. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  50. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  51. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
  52. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
  53. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
  54. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
  55. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  56. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
  57. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  60. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  61. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
  64. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  65. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  66. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
  67. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  68. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
  69. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  70. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
  71. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
  72. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  73. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
  74. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  75. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
  76. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
  77. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
  78. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
  79. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
  80. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
  81. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  82. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +35 -2
  83. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  84. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  85. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
  86. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  87. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  88. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  89. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  90. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
  91. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  92. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  93. package/dist/esm/composables/indexSources.js +176 -20
  94. package/dist/esm/composables/indexSources.js.map +1 -1
  95. package/dist/esm/composables/useEndpointSource.js +28 -6
  96. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  97. package/dist/esm/index.js +1 -0
  98. package/dist/esm/index.js.map +1 -1
  99. package/dist/esm/l10n/en.json.js +41 -1
  100. package/dist/esm/l10n/en.json.js.map +1 -1
  101. package/dist/esm/l10n/nl.json.js +39 -1
  102. package/dist/esm/l10n/nl.json.js.map +1 -1
  103. package/dist/esm/library-style.js +1 -1
  104. package/dist/esm/nextcloud-vue.css +939 -376
  105. package/dist/esm/utils/builtInFormatters.js +24 -4
  106. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  107. package/dist/esm/utils/languageMapText.js +50 -0
  108. package/dist/esm/utils/languageMapText.js.map +1 -0
  109. package/dist/esm/utils/libraryWidgetKeys.js +3 -0
  110. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  111. package/dist/esm/utils/mapList.js +71 -0
  112. package/dist/esm/utils/mapList.js.map +1 -0
  113. package/dist/esm/utils/rowClassRules.js +43 -0
  114. package/dist/esm/utils/rowClassRules.js.map +1 -0
  115. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  116. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  117. package/dist/esm/utils/visibleWhen.js +19 -2
  118. package/dist/esm/utils/visibleWhen.js.map +1 -1
  119. package/dist/nextcloud-vue.cjs.js +9681 -7136
  120. package/dist/nextcloud-vue.cjs.js.map +2 -2
  121. package/dist/nextcloud-vue.css +939 -376
  122. package/l10n/en.json +41 -1
  123. package/l10n/nl.json +39 -1
  124. package/package.json +1 -1
  125. package/src/components/CnActionsBar/CnActionsBar.md +1 -0
  126. package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
  127. package/src/components/CnChartWidget/CnChartWidget.md +3 -2
  128. package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
  129. package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
  130. package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
  131. package/src/components/CnDashboardPage/CnDashboardPage.vue +110 -21
  132. package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
  133. package/src/components/CnExportsWidget/index.js +7 -0
  134. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
  135. package/src/components/CnFilesBrowser/filesReview.js +57 -0
  136. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
  137. package/src/components/CnGroupedCardsWidget/index.js +28 -0
  138. package/src/components/CnIndexPage/CnIndexPage.md +5 -1
  139. package/src/components/CnIndexPage/CnIndexPage.vue +313 -9
  140. package/src/components/CnIndexPage/useNamedSource.js +30 -3
  141. package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
  142. package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
  143. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
  144. package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
  145. package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
  146. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
  147. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
  148. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
  149. package/src/components/CnSavedViewAlertWidget/index.js +7 -0
  150. package/src/components/CnSetupWizard/CnSetupWizard.vue +35 -2
  151. package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
  152. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  153. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
  154. package/src/components/index.js +3 -0
  155. package/src/composables/indexSources.js +175 -19
  156. package/src/composables/useEndpointSource.js +28 -6
  157. package/src/css/index-page.css +117 -0
  158. package/src/css/look-board-index.css +10 -0
  159. package/src/css/look-board.css +44 -0
  160. package/src/index.js +1 -0
  161. package/src/schemas/app-manifest-v2.schema.json +85 -3
  162. package/src/utils/builtInFormatters.js +24 -4
  163. package/src/utils/languageMapText.js +46 -0
  164. package/src/utils/libraryWidgetKeys.js +3 -0
  165. package/src/utils/mapList.js +67 -0
  166. package/src/utils/rowClassRules.js +39 -0
  167. package/src/utils/validateManifestV2.compiled.js +3 -3
  168. package/src/utils/visibleWhen.js +19 -2
package/l10n/en.json CHANGED
@@ -1700,7 +1700,31 @@
1700
1700
  "Run test": "Run test",
1701
1701
  "Test with a record": "Test with a record",
1702
1702
  "admin": "admin",
1703
- "Wizard": "Wizard"
1703
+ "Wizard": "Wizard",
1704
+ "{done} of {total} reviewed": "{done} of {total} reviewed",
1705
+ "{open} still to review": "{open} still to review",
1706
+ "{action}: {name}": "{action}: {name}",
1707
+ "On the map": "On the map",
1708
+ "Show them in the table": "Show them in the table",
1709
+ "Linked": "Linked",
1710
+ "Now": "Now",
1711
+ "Tell me above a number": "Tell me above a number",
1712
+ "Threshold": "Threshold",
1713
+ "A message to you when there are {threshold} or more": "A message to you when there are {threshold} or more",
1714
+ "Save this search or open a saved one to set an alert on it.": "Save this search or open a saved one to set an alert on it.",
1715
+ "The alert could not be saved.": "The alert could not be saved.",
1716
+ "Your exports could not be loaded.": "Your exports could not be loaded.",
1717
+ "You have no exports here.": "You have no exports here.",
1718
+ "Expires today": "Expires today",
1719
+ "Download": "Download",
1720
+ "Made {date}": "Made {date}",
1721
+ "expires {date}": "expires {date}",
1722
+ "Loading …": "Loading …",
1723
+ "Reorder {item}: drag, or press the up and down arrow keys": "Reorder {item}: drag, or press the up and down arrow keys",
1724
+ "No contacts linked": "No contacts linked",
1725
+ "No relations yet": "No relations yet",
1726
+ "Period": "Period",
1727
+ "On": "On"
1704
1728
  },
1705
1729
  "plurals": {
1706
1730
  "{count} selected": [
@@ -1798,6 +1822,22 @@
1798
1822
  "Showing %n flow": [
1799
1823
  "Showing %n flow",
1800
1824
  "Showing %n flows"
1825
+ ],
1826
+ "%n without an address": [
1827
+ "%n without an address",
1828
+ "%n without an address"
1829
+ ],
1830
+ "%n with an address on the map": [
1831
+ "%n with an address on the map",
1832
+ "%n with an address on the map"
1833
+ ],
1834
+ "%n result": [
1835
+ "%n result",
1836
+ "%n results"
1837
+ ],
1838
+ "downloaded %n time": [
1839
+ "downloaded %n time",
1840
+ "downloaded %n times"
1801
1841
  ]
1802
1842
  }
1803
1843
  }
package/l10n/nl.json CHANGED
@@ -1915,7 +1915,29 @@
1915
1915
  "Run test": "Test uitvoeren",
1916
1916
  "Test with a record": "Testen met een record",
1917
1917
  "admin": "beheerder",
1918
- "Wizard": "Assistent"
1918
+ "Wizard": "Assistent",
1919
+ "{done} of {total} reviewed": "{done} van {total} beoordeeld",
1920
+ "{open} still to review": "Nog {open} te beoordelen",
1921
+ "{action}: {name}": "{action}: {name}",
1922
+ "On the map": "Op de kaart",
1923
+ "Show them in the table": "Toon ze in de tabel",
1924
+ "Linked": "Gekoppeld",
1925
+ "Now": "Nu",
1926
+ "Tell me above a number": "Melden boven een aantal",
1927
+ "Threshold": "Grens",
1928
+ "A message to you when there are {threshold} or more": "Bericht aan u als het er {threshold} of meer zijn",
1929
+ "Save this search or open a saved one to set an alert on it.": "Sla deze zoekopdracht op of open een opgeslagen zoekopdracht om er een melding op te zetten.",
1930
+ "The alert could not be saved.": "De melding kon niet worden opgeslagen.",
1931
+ "Your exports could not be loaded.": "Uw exports konden niet worden geladen.",
1932
+ "You have no exports here.": "U heeft hier geen exports.",
1933
+ "Expires today": "Verloopt vandaag",
1934
+ "Made {date}": "Gemaakt {date}",
1935
+ "expires {date}": "verloopt {date}",
1936
+ "Reorder {item}: drag, or press the up and down arrow keys": "{item} verplaatsen: slepen, of de pijltoetsen omhoog en omlaag",
1937
+ "No contacts linked": "Geen contacten gekoppeld",
1938
+ "No relations yet": "Nog geen relaties",
1939
+ "Period": "Periode",
1940
+ "On": "Aan"
1919
1941
  },
1920
1942
  "plurals": {
1921
1943
  "{count} selected": [
@@ -2013,6 +2035,22 @@
2013
2035
  "Showing %n flow": [
2014
2036
  "%n flow",
2015
2037
  "%n flows"
2038
+ ],
2039
+ "%n without an address": [
2040
+ "%n zonder adres",
2041
+ "%n zonder adres"
2042
+ ],
2043
+ "%n with an address on the map": [
2044
+ "%n met een adres op de kaart",
2045
+ "%n met een adres op de kaart"
2046
+ ],
2047
+ "%n result": [
2048
+ "%n resultaat",
2049
+ "%n resultaten"
2050
+ ],
2051
+ "downloaded %n time": [
2052
+ "%n keer gedownload",
2053
+ "%n keer gedownload"
2016
2054
  ]
2017
2055
  }
2018
2056
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.100.0",
3
+ "version": "2.102.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -217,6 +217,7 @@ Controlling the inline action button count — `inlineActionCount` sets how many
217
217
  | `showBuildiqButton` | Boolean | `true` | Whether the buildiq square renders in the bar (the index page takes it into its header under the board look) |
218
218
  | `bulkNoun` | String | `''` | The plural in the bulk band lead, "With the selected <noun>" (default "items") |
219
219
  | `bulkHint` | String | `''` | The 13px hint after the bulk band's buttons |
220
+ | `bulkBandAlways` | Boolean | `false` | Board look: draw the bulk band before anything is selected too, its buttons disabled until there is a selection (DqZaken, DqWerkbord). Only when the band has an action to offer. |
220
221
 
221
222
 
222
223
  `showBuildiqButton` is documented in the prop table of the component reference (board look, screens-chrome-parity).
@@ -507,12 +507,15 @@
507
507
 
508
508
  <!--
509
509
  Board look: the bulk band is its own row between the toolbar and the
510
- table card, outside the toolbar element, and only while rows are
511
- selected. The count stays announced by the live region above.
510
+ table card, outside the toolbar element, while rows are selected; with
511
+ `bulkBandAlways` also before, its buttons disabled until there is a
512
+ selection (DqZaken, DqWerkbord). The count stays announced by the live
513
+ region above.
512
514
  -->
513
515
  <div
514
- v-if="isBoardLayout && selectable && selectedIds.length > 0"
516
+ v-if="isBoardLayout && selectable && (selectedIds.length > 0 || (bulkBandAlways && bulkBandHasActions))"
515
517
  class="cn-actions-bar__band"
518
+ :class="{ 'cn-actions-bar__band--idle': selectedIds.length === 0 }"
516
519
  role="region"
517
520
  :aria-label="t('nextcloud-vue', 'Actions for the selection')"
518
521
  data-testid="cn-bulk-band">
@@ -520,12 +523,14 @@
520
523
  <NcButton
521
524
  v-if="showMassCopy"
522
525
  variant="secondary"
526
+ :disabled="selectedIds.length === 0"
523
527
  @click="$emit('show-copy')">
524
528
  {{ t('nextcloud-vue', 'Copy selected') }}
525
529
  </NcButton>
526
530
  <NcButton
527
531
  v-if="showMassDelete"
528
532
  variant="secondary"
533
+ :disabled="selectedIds.length === 0"
529
534
  @click="$emit('show-delete')">
530
535
  {{ t('nextcloud-vue', 'Delete selected') }}
531
536
  </NcButton>
@@ -533,7 +538,7 @@
533
538
  v-for="entry in bulkActions"
534
539
  :key="entry.id"
535
540
  variant="secondary"
536
- :disabled="entry.disabled === true"
541
+ :disabled="entry.disabled === true || selectedIds.length === 0"
537
542
  :data-testid="`cn-bulk-action-${entry.id}`"
538
543
  :lang="bulkLang(entry)"
539
544
  @click="$emit('bulk-action', { id: entry.id, action: entry.id, selectedIds, count: selectedIds.length })">
@@ -719,6 +724,16 @@ export default {
719
724
  default: '',
720
725
  },
721
726
 
727
+ /**
728
+ * Board look: draw the bulk band also before anything is selected, its
729
+ * buttons disabled until there is a selection, as DqZaken and DqWerkbord
730
+ * draw it. Only when the band has an action to offer.
731
+ */
732
+ bulkBandAlways: {
733
+ type: Boolean,
734
+ default: false,
735
+ },
736
+
722
737
  /** The 13px hint after the board bulk band's buttons (`config.bulkHint`). */
723
738
  bulkHint: {
724
739
  type: String,
@@ -1117,6 +1132,18 @@ export default {
1117
1132
  return t('nextcloud-vue', '{count} active', { count: this.filterCount })
1118
1133
  },
1119
1134
 
1135
+ /**
1136
+ * Whether the bulk band has an action to offer: copy, delete, a
1137
+ * declared bulk action or the host's own buttons.
1138
+ *
1139
+ * @return {boolean}
1140
+ */
1141
+ bulkBandHasActions() {
1142
+ return this.showMassCopy || this.showMassDelete
1143
+ || (Array.isArray(this.bulkActions) && this.bulkActions.length > 0)
1144
+ || Boolean(this.$slots['selection-actions'])
1145
+ },
1146
+
1120
1147
  /**
1121
1148
  * The bold lead of the board bulk band.
1122
1149
  *
@@ -18,7 +18,7 @@ When `CnDashboardPage` resolves a widget definition with `type: "chart"`, it mou
18
18
  }
19
19
  ```
20
20
 
21
- Supported `props` keys forwarded by the dispatcher: `chartKind` (→ `type`), `series`, `categories`, `labels`, `options`, `colors`, `toolbar`, `legend`, `height`, `width`, `unavailableLabel`, plus the display passthrough `horizontal`, `legendPosition`, `valueAxisBaseline`, `valueFormat`, `colorMap`, `emptyLabel`, `views`, `endpointSource`, `barMax`, `footnote`, `highlight`, `cellSource`. The `dataSource` field resolves declaratively: `aggregate: 'count'` / raw `graphql` via GraphQL, `bucket` / `groupBy` / the OBJECT-form `aggregate` (Wave 3: group-by + topN/Other + labelResolve) via OpenRegister's REST aggregation facets, with an optional sibling `drilldown: { route, filterParam }` that navigates on segment/bar click with the raw category key in the query.
21
+ Supported `props` keys forwarded by the dispatcher: `chartKind` (→ `type`), `series`, `categories`, `labels`, `options`, `colors`, `toolbar`, `legend`, `height`, `width`, `unavailableLabel`, plus the display passthrough `horizontal`, `legendPosition`, `valueAxisBaseline`, `valueFormat`, `colorMap`, `emptyLabel`, `views`, `endpointSource`, `barMax`, `footnote`, `highlight`, `cellSource`, `emptyCellLabel`. The `dataSource` field resolves declaratively: `aggregate: 'count'` / raw `graphql` via GraphQL, `bucket` / `groupBy` / the OBJECT-form `aggregate` (Wave 3: group-by + topN/Other + labelResolve) via OpenRegister's REST aggregation facets, with an optional sibling `drilldown: { route, filterParam }` that navigates on segment/bar click with the raw category key in the query.
22
22
 
23
23
  Area chart — time series data:
24
24
 
@@ -107,7 +107,8 @@ Fallback slot — shown when ApexCharts is not available:
107
107
  | `horizontal` | Boolean | `false` | Render `type: "bar"` charts horizontally (row bars); an explicit `options.plotOptions.bar.horizontal` still wins |
108
108
  | `legendPosition` | String | `''` | Legend placement override (`top`/`bottom`/`left`/`right`); empty keeps the automatic placement |
109
109
  | `barMax` | Number | `null` | `bar-list`: the value a full track stands for (100 for percentages); null takes the largest value |
110
- | `footnote` | String | `''` | `bar-list` / `matrix`: a line under the rows, through the host's translate |
110
+ | `footnote` | String | `''` | A line under the chart, through the host's translate: under the rows of a `bar-list` or `matrix`, under the plot of a `line`, `area` or `bar` chart |
111
+ | `emptyCellLabel` | String | `''` | `matrix`: the text of a cell without a value ("geen"), through the host's translate. Empty leaves the cell blank |
111
112
  | `highlight` | Object | `null` | `matrix`: the cell to outline, `{ row, column, columnKeys? }`, tokens such as `@object.behaviour` resolved |
112
113
  | `cellSource` | Object | `null` | `matrix`: read the cells from records that each hold one cell (`{ register, schema, filter?, rowField, columnField, valueField, … }`), or with `cellsPath` from the cells inside one record, see [Bar list and matrix](#bar-list-and-matrix) |
113
114
  | `valueAxisBaseline` | String | `'auto'` | How the value axis picks its baseline, so a small difference cannot read as a dramatic one. `'auto'` anchors magnitude marks (bar/area) at zero and stops line charts zooming past a quarter of the data's magnitude; `'zero'` always anchors at zero; `'fit'` restores plain ApexCharts autoscaling — use it for series that live far from zero (a percentage hovering 95–99), where a zero baseline flattens the signal. Pie-family charts, negative series, empty data and stacked charts (`options.chart.stacked` — where the visible height is the per-category sum, not the largest datapoint) always autoscale; an explicit `options.yaxis.min`/`max` still wins. The ceiling rounds up to a nice number (7 → 8, 62 → 80). |
@@ -126,6 +126,12 @@
126
126
  </p>
127
127
  </slot>
128
128
  </div>
129
+ <p
130
+ v-if="footnote && chartComponent && !showEmptyState && type !== 'bar-list' && type !== 'matrix'"
131
+ class="cn-chart-widget__footnote"
132
+ data-testid="cn-chart-widget-footnote">
133
+ {{ hostTranslate(footnote) }}
134
+ </p>
129
135
  </div>
130
136
  </template>
131
137
 
@@ -368,8 +374,9 @@ export default {
368
374
  },
369
375
 
370
376
  /**
371
- * A line under a `bar-list` or `matrix` ("Klik een balk om de zaken
372
- * te zien"), through the host's translate.
377
+ * A line under the chart ("Klik een balk om de zaken te zien"),
378
+ * through the host's translate: under the rows of a `bar-list` or
379
+ * `matrix`, and under the plot of an ApexCharts type.
373
380
  *
374
381
  * @spec openspec/changes/chart-bar-list-and-matrix/specs/chart-bar-list-and-matrix/spec.md#requirement-a-chart-can-draw-a-bar-list
375
382
  */
@@ -605,6 +612,17 @@ export default {
605
612
  default: '',
606
613
  },
607
614
 
615
+ /**
616
+ * `matrix`: the text of a cell without a value ("geen"), through the
617
+ * host's translate. Empty (the default) leaves the cell blank.
618
+ *
619
+ * @spec openspec/changes/chart-bar-list-and-matrix/specs/chart-bar-list-and-matrix/spec.md#requirement-a-chart-can-draw-a-matrix
620
+ */
621
+ emptyCellLabel: {
622
+ type: String,
623
+ default: '',
624
+ },
625
+
608
626
  /**
609
627
  * Manifest dataSource block. When set, `series` /
610
628
  * `categories` / `labels` are resolved from the GraphQL
@@ -1442,7 +1460,7 @@ export default {
1442
1460
  const value = Number(raw)
1443
1461
  return Number.isFinite(value) && raw !== null && raw !== undefined
1444
1462
  ? { text: this.formatPlain(value), level: levelOf(value), color: '', current: isCurrent }
1445
- : { text: '', level: 0, color: '', current: isCurrent }
1463
+ : { text: this.emptyCellLabel ? this.hostTranslate(this.emptyCellLabel) : '', level: 0, color: '', current: isCurrent }
1446
1464
  }),
1447
1465
  })),
1448
1466
  }
@@ -2388,6 +2406,8 @@ export default {
2388
2406
  * Map an endpoint payload whose `rowsPath` names an array of rows:
2389
2407
  * each row is a series (name at `rowLabelPath`, values at
2390
2408
  * `rowValuesPath`), the categories are at `labelsPath`.
2409
+ * A missing value stays null; a `matrix` keeps a text value ("none")
2410
+ * as text, other types read it as 0.
2391
2411
  *
2392
2412
  * @param {unknown} payload The plucked payload.
2393
2413
  * @param {object} es The `endpointSource` block.
@@ -2398,12 +2418,23 @@ export default {
2398
2418
  const rows = getByPath(payload, es.rowsPath)
2399
2419
  const rawLabels = es.labelsPath ? getByPath(payload, es.labelsPath) : undefined
2400
2420
  const labels = Array.isArray(rawLabels) ? rawLabels.map((v) => ((v === null || v === undefined) ? '' : String(v))) : []
2421
+ const keepText = this.type === 'matrix'
2422
+ const cellOf = (v) => {
2423
+ if (v === null || v === undefined) {
2424
+ return null
2425
+ }
2426
+ const num = typeof v === 'string' && v.trim() === '' ? NaN : Number(v)
2427
+ if (Number.isFinite(num)) {
2428
+ return num
2429
+ }
2430
+ return keepText && typeof v === 'string' ? v : 0
2431
+ }
2401
2432
  const series = (Array.isArray(rows) ? rows : []).map((row) => {
2402
2433
  const name = getByPath(row, es.rowLabelPath || 'label')
2403
2434
  const values = getByPath(row, es.rowValuesPath || 'values')
2404
2435
  return {
2405
2436
  name: (name === null || name === undefined) ? '' : String(name),
2406
- data: Array.isArray(values) ? values.map((v) => (v === null || v === undefined ? null : Number(v) || 0)) : [],
2437
+ data: Array.isArray(values) ? values.map(cellOf) : [],
2407
2438
  }
2408
2439
  })
2409
2440
  return { series, categories: labels, labels }
@@ -2690,6 +2721,13 @@ export default {
2690
2721
  margin: 0;
2691
2722
  }
2692
2723
 
2724
+ /* Under an ApexCharts plot the footnote is a sibling of the canvas, so it keeps
2725
+ its own line height while the canvas takes the rest of a fitted column. */
2726
+ .cn-chart-widget > .cn-chart-widget__footnote {
2727
+ flex: 0 0 auto;
2728
+ margin-top: 8px;
2729
+ }
2730
+
2693
2731
  .cn-chart-widget__matrix {
2694
2732
  display: grid;
2695
2733
  gap: 4px;
@@ -87,4 +87,43 @@ under it through the `option-actions` slot (scope `{ option, selected }`):
87
87
  </div>
88
88
  ```
89
89
 
90
+ The chosen option's own fields can also sit inside its frame, under its text,
91
+ through the `option-details` slot (scope `{ option }`): rendered only for the
92
+ selected option, and outside the radio's label.
93
+
94
+ ```vue
95
+ <div style="max-width: 560px;">
96
+ <CnChoiceCards
97
+ label="Next step"
98
+ layout="rows"
99
+ :model-value="'suspend'"
100
+ :options="[
101
+ { value: 'suspend', label: 'Suspend', description: 'The term stops until the answer is in.' },
102
+ { value: 'abort', label: 'Abort' },
103
+ ]">
104
+ <template #option-details="{ option }">
105
+ <p v-if="option.value === 'suspend'" style="margin: 0;">Reason and return date go here.</p>
106
+ </template>
107
+ </CnChoiceCards>
108
+ </div>
109
+ ```
110
+
111
+ A recorded choice shown back with `readonly`: every option at full contrast, the
112
+ chosen one marked, nothing pickable. `disabled` greys the options out instead.
113
+
114
+ ```vue
115
+ <div style="max-width: 560px;">
116
+ <CnChoiceCards
117
+ label="Outcome"
118
+ layout="rows"
119
+ :readonly="true"
120
+ :model-value="'partial'"
121
+ :options="[
122
+ { value: 'public', label: 'Made public' },
123
+ { value: 'partial', label: 'Partly made public' },
124
+ { value: 'none', label: 'Not made public' },
125
+ ]" />
126
+ </div>
127
+ ```
128
+
90
129
  `columns` fixes the card grid's column count; `:columns="1"` stacks the cards in one column.
@@ -20,17 +20,18 @@
20
20
  <div
21
21
  v-for="option in normalizedOptions"
22
22
  :key="String(option.value)"
23
- class="cn-choice-cards__cell">
23
+ class="cn-choice-cards__cell"
24
+ :class="{ 'cn-choice-cards__cell--with-details': hasDetails(option) }">
24
25
  <label
25
26
  class="cn-choice-cards__row"
26
- :class="{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option) }">
27
+ :class="{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option), 'cn-choice-cards__row--readonly': readonly }">
27
28
  <input
28
29
  class="cn-choice-cards__row-input"
29
30
  :type="multiple ? 'checkbox' : 'radio'"
30
31
  :name="groupName"
31
32
  :value="String(option.value)"
32
33
  :checked="isSelected(option)"
33
- :disabled="isDisabled(option)"
34
+ :disabled="isDisabled(option) || readonly"
34
35
  :aria-describedby="option.disabled && option.disabledReason ? reasonId(option) : null"
35
36
  @change="onToggle(option)">
36
37
  <CnIcon v-if="option.icon"
@@ -47,6 +48,16 @@
47
48
  data-testid="cn-choice-cards-reason">{{ option.disabledReason }}</span>
48
49
  </span>
49
50
  </label>
51
+ <div v-if="hasDetails(option)"
52
+ class="cn-choice-cards__details"
53
+ :data-testid="`cn-choice-cards-details-${option.value}`">
54
+ <!-- @slot option-details The chosen option's own fields, drawn
55
+ inside the selected row's frame under its text (DqVolgendeStap:
56
+ Opschorten's reason, days and return date). Rendered only for a
57
+ selected option, outside the label so a field is not part of
58
+ the radio's label. Scope: `{ option }`. -->
59
+ <slot name="option-details" :option="option" />
60
+ </div>
50
61
  <div v-if="$slots['option-actions']" class="cn-choice-cards__actions">
51
62
  <slot name="option-actions" :option="option" :selected="isSelected(option)" />
52
63
  </div>
@@ -64,7 +75,7 @@
64
75
  class="cn-choice-cards__cell">
65
76
  <label
66
77
  class="cn-choice-cards__option"
67
- :class="{ 'cn-choice-cards__option--selected': isSelected(option) }">
78
+ :class="{ 'cn-choice-cards__option--selected': isSelected(option), 'cn-choice-cards__option--disabled': isDisabled(option) }">
68
79
  <!-- A REAL, VISIBLE input, not a styled div with a click handler.
69
80
  It carries the checked state to assistive technology and to
70
81
  Windows high-contrast mode for free, and it keeps the
@@ -76,7 +87,7 @@
76
87
  :name="groupName"
77
88
  :value="String(option.value)"
78
89
  :checked="isSelected(option)"
79
- :disabled="isDisabled(option)"
90
+ :disabled="isDisabled(option) || readonly"
80
91
  :aria-describedby="option.disabled && option.disabledReason ? reasonId(option) : null"
81
92
  @change="onToggle(option)">
82
93
  <CnCard
@@ -191,6 +202,16 @@ export default {
191
202
  default: false,
192
203
  },
193
204
 
205
+ /**
206
+ * Show the choice without letting it change: every option at full
207
+ * contrast, the selected one marked, nothing pickable (DqBesluit's
208
+ * recorded outcome). `disabled` greys the options out instead.
209
+ */
210
+ readonly: {
211
+ type: Boolean,
212
+ default: false,
213
+ },
214
+
194
215
  /** Show a spinner instead of the grid while options are being fetched. */
195
216
  loading: {
196
217
  type: Boolean,
@@ -311,6 +332,17 @@ export default {
311
332
  * @param {object} option The normalised option.
312
333
  * @return {boolean} True when selected.
313
334
  */
335
+ /**
336
+ * Whether a rows option draws the `option-details` slot in its frame:
337
+ * the slot is given and the option is the chosen one.
338
+ *
339
+ * @param {object} option The option.
340
+ * @return {boolean}
341
+ */
342
+ hasDetails(option) {
343
+ return Boolean(this.$slots['option-details']) && this.isSelected(option)
344
+ },
345
+
314
346
  /**
315
347
  * Whether an option cannot be picked: the whole group is disabled, or the option is.
316
348
  *
@@ -342,7 +374,7 @@ export default {
342
374
  * @return {void}
343
375
  */
344
376
  onToggle(option) {
345
- if (this.isDisabled(option)) {
377
+ if (this.readonly || this.isDisabled(option)) {
346
378
  return
347
379
  }
348
380
  if (!this.multiple) {
@@ -452,7 +484,7 @@ export default {
452
484
  cursor: default;
453
485
  }
454
486
 
455
- .cn-choice-cards__input:disabled + :deep(.cn-card) {
487
+ .cn-choice-cards__option--disabled .cn-choice-cards__input + :deep(.cn-card) {
456
488
  opacity: 0.6;
457
489
  }
458
490
 
@@ -462,11 +494,12 @@ export default {
462
494
  gap: 8px;
463
495
  }
464
496
 
497
+ /* 12px 14px, as the boards draw an option row (DqVolgendeStap, DqBesluit). */
465
498
  .cn-choice-cards__row {
466
499
  display: flex;
467
500
  align-items: flex-start;
468
501
  gap: 12px;
469
- padding: 14px 16px;
502
+ padding: 12px 14px;
470
503
  border: 1px solid var(--color-border);
471
504
  border-radius: var(--border-radius-large, 8px);
472
505
  background: var(--color-main-background);
@@ -485,9 +518,54 @@ export default {
485
518
  background: var(--color-primary-element-light);
486
519
  }
487
520
 
488
- .cn-choice-cards__row--disabled {
521
+ /* An option this record does not allow: the grey ground and the muted ink,
522
+ still legible (DqVolgendeStap), not faded. */
523
+ .cn-choice-cards__row--disabled,
524
+ .cn-choice-cards__row--disabled:hover {
525
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
526
+ border-color: var(--color-border);
527
+ background: var(--color-background-hover);
528
+ color: var(--color-text-maxcontrast);
489
529
  cursor: default;
490
- opacity: 0.6;
530
+ }
531
+
532
+ /* The title takes the muted ink itself: a theme that colours every label
533
+ with `!important` (thematiq) would otherwise hand it the label colour. */
534
+ .cn-choice-cards__row--disabled .cn-choice-cards__row-title {
535
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
536
+ color: var(--color-text-maxcontrast);
537
+ }
538
+
539
+ /* The chosen option's fields share the selected row's frame: the cell draws
540
+ the 2px primary border and the ground, the row inside it draws none, and
541
+ the fields sit under the text, indented to it (DqVolgendeStap). */
542
+ .cn-choice-cards__cell--with-details {
543
+ border: 1px solid var(--color-primary-element);
544
+ border-radius: var(--border-radius-large, 8px);
545
+ box-shadow: inset 0 0 0 1px var(--color-primary-element);
546
+ background: var(--color-primary-element-light);
547
+ }
548
+
549
+ .cn-choice-cards__cell--with-details .cn-choice-cards__row,
550
+ .cn-choice-cards__cell--with-details .cn-choice-cards__row:hover {
551
+ border-color: transparent;
552
+ box-shadow: none;
553
+ background: transparent;
554
+ }
555
+
556
+ .cn-choice-cards__details {
557
+ display: flex;
558
+ flex-direction: column;
559
+ gap: 14px;
560
+ padding: 0 14px 14px 44px;
561
+ }
562
+
563
+ .cn-choice-cards__row--readonly {
564
+ cursor: default;
565
+ }
566
+
567
+ .cn-choice-cards__row--readonly:not(.cn-choice-cards__row--selected):hover {
568
+ border-color: var(--color-border);
491
569
  }
492
570
 
493
571
  .cn-choice-cards__row:has(.cn-choice-cards__row-input:focus-visible) {
@@ -518,15 +596,14 @@ export default {
518
596
  font-weight: 600;
519
597
  }
520
598
 
521
- .cn-choice-cards__row-description {
522
- font-size: 13px;
523
- color: var(--color-text-maxcontrast);
524
- }
525
-
599
+ /* Regular weight on the span itself: a theme that sets every label bold
600
+ with `!important` (thematiq) reaches a span that only inherits. */
601
+ .cn-choice-cards__row-description,
526
602
  .cn-choice-cards__reason {
603
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
527
604
  font-size: 13px;
605
+ font-weight: 400;
528
606
  color: var(--color-text-maxcontrast);
529
- font-style: italic;
530
607
  }
531
608
 
532
609
  .cn-choice-cards__reason--card {