@conduction/nextcloud-vue 2.56.0-beta.31 → 2.56.0-beta.32

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 (178) hide show
  1. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +55 -4
  2. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  3. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +51 -15
  4. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +13 -2
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  10. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  11. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  12. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  13. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  14. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  15. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  16. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  17. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  18. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  19. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  20. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  21. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  22. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  23. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  24. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -2
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -0
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +20 -3
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +7 -1
  37. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +77 -3
  40. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
  42. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +0 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  49. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  51. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  52. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  53. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  54. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  55. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  56. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  57. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  58. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +18 -1
  59. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
  61. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  62. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  63. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  64. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  65. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  66. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  67. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  68. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  69. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  70. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  71. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  72. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  73. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  74. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  75. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  76. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  77. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  78. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  79. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  80. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  81. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  84. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  85. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  86. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  87. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  88. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  89. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  90. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  91. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
  92. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
  94. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  95. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  96. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  97. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  98. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  99. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  100. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  101. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  102. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  103. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  104. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  105. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  106. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  110. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +4 -2
  111. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  112. package/dist/esm/index.js +7 -0
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/l10n/en.json.js +1 -0
  115. package/dist/esm/l10n/en.json.js.map +1 -1
  116. package/dist/esm/l10n/nl.json.js +1 -0
  117. package/dist/esm/l10n/nl.json.js.map +1 -1
  118. package/dist/esm/nextcloud-vue.css +843 -4
  119. package/dist/esm/utils/contentHeaderLink.js +51 -0
  120. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  121. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  122. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  123. package/dist/esm/utils/initials.js +32 -0
  124. package/dist/esm/utils/initials.js.map +1 -0
  125. package/dist/esm/utils/libraryWidgetKeys.js +7 -0
  126. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  127. package/dist/esm/utils/objectWrite.js +59 -0
  128. package/dist/esm/utils/objectWrite.js.map +1 -0
  129. package/dist/esm/utils/widgetDispatch.js +1 -1
  130. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  131. package/dist/nextcloud-vue.cjs.js +10510 -6830
  132. package/dist/nextcloud-vue.cjs.js.map +2 -2
  133. package/dist/nextcloud-vue.css +843 -4
  134. package/l10n/en.json +1 -0
  135. package/l10n/nl.json +1 -0
  136. package/package.json +1 -1
  137. package/src/components/CnBannerWidget/CnBannerWidget.vue +93 -13
  138. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  139. package/src/components/CnCellRenderer/CnCellRenderer.vue +43 -3
  140. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  141. package/src/components/CnChipsFieldWidget/index.js +25 -0
  142. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  143. package/src/components/CnChoicePickerWidget/index.js +26 -0
  144. package/src/components/CnDashboardPage/CnDashboardPage.vue +38 -2
  145. package/src/components/CnDataTable/CnDataTable.vue +2 -1
  146. package/src/components/CnDetailPage/CnDetailPage.vue +45 -2
  147. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -3
  148. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +265 -4
  149. package/src/components/CnIndexPage/CnIndexPage.vue +0 -1
  150. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  151. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  152. package/src/components/CnRadioCardsWidget/index.js +27 -0
  153. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +43 -1
  154. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  155. package/src/components/CnSavedViewsWidget/index.js +29 -0
  156. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  157. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  158. package/src/components/CnSettingsFormWidget/index.js +26 -0
  159. package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
  160. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  161. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  162. package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
  163. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  164. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  165. package/src/components/CnToggleTableWidget/index.js +26 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  167. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +28 -1
  168. package/src/components/index.js +7 -0
  169. package/src/css/look-board-detail.css +36 -2
  170. package/src/css/look-board-index.css +23 -0
  171. package/src/css/look-board.css +72 -0
  172. package/src/index.js +7 -0
  173. package/src/utils/contentHeaderLink.js +48 -0
  174. package/src/utils/fetchFilterCounts.js +39 -4
  175. package/src/utils/initials.js +29 -0
  176. package/src/utils/libraryWidgetKeys.js +7 -0
  177. package/src/utils/objectWrite.js +55 -0
  178. package/src/utils/widgetDispatch.js +1 -1
@@ -90,7 +90,9 @@
90
90
  :modelValue="selectedOption"
91
91
  :options="displayOptions"
92
92
  :clearable="false"
93
- :inputLabel="label || writeKey"
93
+ :inputLabel="content.inline ? '' : (label || writeKey)"
94
+ :labelOutside="content.inline === true"
95
+ :ariaLabelCombobox="label || writeKey"
94
96
  label="label"
95
97
  data-testid="cn-workspace-filter-select"
96
98
  @update:modelValue="onSelect" />
@@ -928,4 +930,29 @@ export default {
928
930
  color: var(--color-text-maxcontrast);
929
931
  font-size: 0.85em;
930
932
  }
933
+
934
+ /* On a phone the bar's controls take a row each (the chips already wrap), and
935
+ a switch and a select are 44px touch targets. */
936
+ @media (max-width: 640px) {
937
+ .cn-workspace-filter-widget--bar > .cn-workspace-filter-widget--inline {
938
+ flex: 1 1 100%;
939
+ }
940
+
941
+ .cn-workspace-filter-widget--bar > .cn-workspace-filter-widget--end {
942
+ margin-inline-start: 0;
943
+ }
944
+
945
+ .cn-workspace-filter-widget--inline .cn-workspace-filter-widget__toggle {
946
+ min-height: 44px;
947
+ }
948
+
949
+ .cn-workspace-filter-widget--inline :deep(.v-select) {
950
+ flex: 1 1 auto;
951
+ min-width: 0;
952
+ }
953
+
954
+ .cn-workspace-filter-widget--inline :deep(.vs__dropdown-toggle) {
955
+ min-height: 44px;
956
+ }
957
+ }
931
958
  </style>
@@ -219,6 +219,12 @@ export { CnTasksWidgetForm } from './CnTasksWidgetForm/index.js'
219
219
  export { CnSectionsWidget } from './CnSectionsWidget/index.js'
220
220
  export { CnFactsWidget } from './CnFactsWidget/index.js'
221
221
  export { CnFieldListWidget } from './CnFieldListWidget/index.js'
222
+ export { CnSettingsFormWidget } from './CnSettingsFormWidget/index.js'
223
+ export { CnSwitchRowsWidget } from './CnSwitchRowsWidget/index.js'
224
+ export { CnRadioCardsWidget } from './CnRadioCardsWidget/index.js'
225
+ export { CnChipsFieldWidget } from './CnChipsFieldWidget/index.js'
226
+ export { CnToggleTableWidget } from './CnToggleTableWidget/index.js'
227
+ export { CnChoicePickerWidget } from './CnChoicePickerWidget/index.js'
222
228
  export { CnRecordListWidget } from './CnRecordListWidget/index.js'
223
229
  export { CnBannerWidget } from './CnBannerWidget/index.js'
224
230
  export { default as CnConversationWidget } from './CnConversationThread/CnConversationWidget.vue'
@@ -236,6 +242,7 @@ export { CnMarkdownWidget } from './CnMarkdownWidget/index.js'
236
242
  export { CnMarkdownWidgetForm } from './CnMarkdownWidgetForm/index.js'
237
243
  export { CnObjectListWidget } from './CnObjectListWidget/index.js'
238
244
  export { CnObjectListWidgetForm } from './CnObjectListWidgetForm/index.js'
245
+ export { CnSavedViewsWidget } from './CnSavedViewsWidget/index.js'
239
246
  export { CnSavedViewWidget } from './CnSavedViewWidget/index.js'
240
247
  export { CnSavedViewWidgetForm } from './CnSavedViewWidgetForm/index.js'
241
248
  export { CnChartWidgetForm } from './CnChartWidgetForm/index.js'
@@ -202,6 +202,15 @@
202
202
  gap: 4px;
203
203
  }
204
204
 
205
+ /* No inner scroller: tabs that do not fit the width wrap onto a second row
206
+ instead of scrolling sideways (the library's default strip), so the strip
207
+ has no scrollbar, no clipped tab and no fade or chevron to find the rest. */
208
+ .cn-look-board .cn-tabs--board .cn-tabs__nav {
209
+ flex-wrap: wrap;
210
+ overflow: visible;
211
+ row-gap: 4px;
212
+ }
213
+
205
214
  .cn-look-board .cn-tabs--board .cn-tabs__nav-item {
206
215
  background-color: var(--color-background-dark);
207
216
  border: 1px solid var(--color-border-dark);
@@ -347,12 +356,28 @@
347
356
  padding: 0 0 10px;
348
357
  }
349
358
 
350
- .cn-look-board .cn-object-data-widget__label {
351
- color: var(--color-text-maxcontrast);
359
+ /* Outranks the component's own `.cn-object-data-widget__label[data-v-...]`
360
+ (weight 500, 0.85em, which has the same specificity as the look's rule and
361
+ wins on source order wherever a bundle, such as a companion app's, loads
362
+ after this stylesheet), through the grid the labels sit in. Measured live:
363
+ the labels read bold and dark where the boards (DqContact, PqKlant, ShKlant)
364
+ draw them 13px, regular weight and light grey.
365
+
366
+ The colour carries `!important`: a theme (nldesign's element overrides) sets
367
+ the ink of every `div` outside the header with a selector that has an id in
368
+ its `:not()`, which no class rule outranks. */
369
+ .cn-look-board .cn-object-data-widget__grid .cn-object-data-widget__label {
370
+ color: var(--color-text-maxcontrast) !important;
352
371
  font-size: 13px;
353
372
  font-weight: 400;
354
373
  }
355
374
 
375
+ /* The facts card's labels: the same light grey, regular weight. */
376
+ .cn-look-board .cn-facts-widget .cn-facts-widget__label {
377
+ color: var(--color-text-maxcontrast);
378
+ font-weight: 400;
379
+ }
380
+
356
381
  .cn-look-board .cn-object-data-widget__value {
357
382
  font-weight: 500;
358
383
  }
@@ -610,3 +635,12 @@
610
635
  font-size: 13px;
611
636
  margin: 2px 0 0;
612
637
  }
638
+
639
+ /* The sidebar's own open toggle (NcAppSidebar's `.app-sidebar__toggle`, drawn
640
+ when the sidebar is closed) is positioned over the content's top-right
641
+ corner, which in this look is the header's last button: a stray square icon
642
+ on "More". The board draws none, so it is hidden on a detail page and the
643
+ header menu offers "Open sidebar" instead. */
644
+ .cn-look-board:has(.cn-detail-page) > .app-sidebar__toggle {
645
+ display: none;
646
+ }
@@ -557,6 +557,29 @@
557
557
  font-size: 13px;
558
558
  }
559
559
 
560
+ /* An avatar in the title column (PtAccounts): a 32px chip circle, 10px before
561
+ the name, the e-mail under the name beside it. The name keeps the title's
562
+ weight and size, and its underline: an inline-flex box is atomic, so the
563
+ cell's own underline does not reach it. */
564
+ .cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar {
565
+ gap: 10px;
566
+ }
567
+
568
+ .cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar-initials {
569
+ background: var(--color-background-dark);
570
+ color: var(--color-main-text);
571
+ font-size: 12px;
572
+ }
573
+
574
+ .cn-look-board .cn-table-container--board .cn-data-table td.cn-table-col--title .cn-cell-renderer__avatar-name {
575
+ text-decoration: underline;
576
+ text-underline-offset: 2px;
577
+ }
578
+
579
+ .cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title .cn-cell-renderer__avatar-name {
580
+ text-decoration: none;
581
+ }
582
+
560
583
  /* A status pill: 3px 10px, radius 12, 13px weight 600. The colours stay the
561
584
  status colour map's. */
562
585
  .cn-look-board .cn-table-container--board .cn-data-table .cn-status-badge {
@@ -555,3 +555,75 @@
555
555
  .cn-look-board .cn-cell-renderer__age:not(.cn-look-nextcloud *) {
556
556
  font-weight: 600;
557
557
  }
558
+
559
+ /* ---- Record list on a dashboard card (PtDashboard) ---------------------- */
560
+ /* The dashboard's card body is flush, so the rows carry their own inset: the
561
+ board's card pads 24px at the sides and each row 14px, a soft hairline
562
+ between rows, the title 15px weight 600 and the meta 13px muted. A row is a
563
+ link, but only the hover says so: no underline on any text run, however the
564
+ host styles its links (`!important`, because a host rule such as
565
+ `#content a` outranks any class). The pill and the date sit at the right. */
566
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list {
567
+ padding: 0 24px 10px;
568
+ }
569
+
570
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--rows .cn-record-list__item + .cn-record-list__item {
571
+ border-top: 1px solid var(--color-border-dark, var(--color-border));
572
+ }
573
+
574
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row {
575
+ padding: 14px 0;
576
+ flex-wrap: wrap;
577
+ gap: 8px 16px;
578
+ }
579
+
580
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row,
581
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:hover,
582
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:focus-visible,
583
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row * {
584
+ color: inherit;
585
+ text-decoration: none !important;
586
+ }
587
+
588
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__title {
589
+ color: var(--color-main-text);
590
+ font-size: 15px;
591
+ font-weight: 600;
592
+ }
593
+
594
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__meta {
595
+ color: var(--color-text-maxcontrast);
596
+ font-size: 13px;
597
+ }
598
+
599
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__trailing {
600
+ color: var(--color-text-maxcontrast);
601
+ font-size: 13px;
602
+ font-weight: 600;
603
+ }
604
+
605
+ /* Hover: the whole row takes the hover ground, 8px beyond the text so the
606
+ text does not move. */
607
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link {
608
+ margin-inline: -8px;
609
+ padding-inline: 8px;
610
+ border-radius: 8px;
611
+ }
612
+
613
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover,
614
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:focus-visible {
615
+ background: var(--color-background-hover);
616
+ }
617
+
618
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover .cn-record-list__title {
619
+ text-decoration: none !important;
620
+ }
621
+
622
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__aside {
623
+ margin-inline-start: auto;
624
+ }
625
+
626
+ /* The board draws no chevron after the pill and date. */
627
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__chevron {
628
+ display: none;
629
+ }
package/src/index.js CHANGED
@@ -60,7 +60,9 @@ export {
60
60
  CnChartWidgetForm,
61
61
  CnChatPage,
62
62
  CnChildRecordsField,
63
+ CnChipsFieldWidget,
63
64
  CnChoiceCards,
65
+ CnChoicePickerWidget,
64
66
  CnColorPicker,
65
67
  CnCommandPalette,
66
68
  CnConfigurationCard,
@@ -245,6 +247,7 @@ export {
245
247
  CnQuickFilterBar,
246
248
  CnQuicklinksWidget,
247
249
  CnQuicklinksWidgetForm,
250
+ CnRadioCardsWidget,
248
251
  CnRecordListWidget,
249
252
  CnReferencePreview,
250
253
  CnRegisterMapping,
@@ -263,6 +266,7 @@ export {
263
266
  CnSavedViewsControl,
264
267
  CnSavedViewShareDialog,
265
268
  CnSavedViewShareFields,
269
+ CnSavedViewsWidget,
266
270
  CnSavedViewWidget,
267
271
  CnSavedViewWidgetForm,
268
272
  CnSaveViewDialog,
@@ -270,6 +274,7 @@ export {
270
274
  CnSearchPage,
271
275
  CnSectionsWidget,
272
276
  CnSettingsCard,
277
+ CnSettingsFormWidget,
273
278
  CnSettingsPage,
274
279
  CnSettingsSection,
275
280
  CnSetupWizard,
@@ -289,6 +294,7 @@ export {
289
294
  CnSubObjectsField,
290
295
  CnSummaryAggregates,
291
296
  CnSupportDialog,
297
+ CnSwitchRowsWidget,
292
298
  CnTab,
293
299
  CnTabbedFormDialog,
294
300
  CnTableWidget,
@@ -309,6 +315,7 @@ export {
309
315
  CnTimelineStages,
310
316
  CnTimelineView,
311
317
  CnTimelineWidget,
318
+ CnToggleTableWidget,
312
319
  CnTranslatedBadge,
313
320
  CnTreeView,
314
321
  CnUnreadMarker,
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * contentHeaderLink — the text link a widget's content asks its card header
6
+ * to carry: `content.headerLink`, or the shorter `viewAllRoute` (with an
7
+ * optional `viewAllLabel`) that an inbox-style widget uses for its "View all".
8
+ *
9
+ * @module utils/contentHeaderLink
10
+ */
11
+
12
+ /**
13
+ * The header link a widget's `content` declares, in the shape
14
+ * CnWidgetWrapper's `headerLink` takes, or null when it declares none.
15
+ * The label is left as written: the wrapper translates it.
16
+ *
17
+ * `viewAllRoute` is a route name, or a location `{ name, params?, query? }`.
18
+ *
19
+ * @param {object|null|undefined} content The widget's content.
20
+ * @return {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
21
+ */
22
+ export function contentHeaderLink(content) {
23
+ if (!content || typeof content !== 'object') {
24
+ return null
25
+ }
26
+ const declared = content.headerLink
27
+ if (declared && typeof declared === 'object' && typeof declared.label === 'string' && declared.label !== '') {
28
+ return declared
29
+ }
30
+ const view = content.viewAllRoute
31
+ const name = typeof view === 'string' ? view : (view && typeof view === 'object' ? view.name : '')
32
+ if (typeof name !== 'string' || name === '') {
33
+ return null
34
+ }
35
+ const link = {
36
+ label: typeof content.viewAllLabel === 'string' && content.viewAllLabel !== '' ? content.viewAllLabel : 'View all',
37
+ route: name,
38
+ }
39
+ if (view && typeof view === 'object') {
40
+ if (view.params && typeof view.params === 'object') {
41
+ link.params = view.params
42
+ }
43
+ if (view.query && typeof view.query === 'object') {
44
+ link.query = view.query
45
+ }
46
+ }
47
+ return link
48
+ }
@@ -9,8 +9,9 @@
9
9
  * `status: closed`, ...). Those are answered by one `/grouped` request on that
10
10
  * field. A filter that narrows on something else (two fields, an operator, a
11
11
  * list of values) cannot be read off a grouped result, so each of those costs
12
- * one `/value` count. A filter that holds a LIST of values is counted through
13
- * the list endpoint, because the aggregation endpoints cannot express it.
12
+ * one `/value` count. A filter that holds a LIST of values, or tests a field
13
+ * for no value (`isnull`), is counted through the list endpoint, because the
14
+ * aggregation endpoints cannot express it.
14
15
  * Nothing is requested for an entry that did not ask.
15
16
  *
16
17
  * @module utils/fetchFilterCounts
@@ -38,6 +39,37 @@ export function hasListValue(filter) {
38
39
  return Object.values(filter).some((value) => Array.isArray(value))
39
40
  }
40
41
 
42
+ /**
43
+ * Whether a filter tests a field for "no value" (`{ assignee: null }` or
44
+ * `{ supersededBy: { isnull: true } }`).
45
+ *
46
+ * OpenRegister's aggregation endpoints have no null operator: `isnull` is
47
+ * refused there and an empty value is dropped or matches nothing, so such a
48
+ * filter counted 0 while the list showed rows. The list endpoint does read
49
+ * `field[isnull]=true`, so a filter like this is counted through it.
50
+ *
51
+ * @param {object} filter A filter map.
52
+ * @return {boolean} True when any value is null or carries an `isnull` operator.
53
+ */
54
+ export function hasNullTest(filter) {
55
+ if (!filter || typeof filter !== 'object') {
56
+ return false
57
+ }
58
+ return Object.values(filter).some((value) => value === null
59
+ || (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).some((op) => op.toLowerCase() === 'isnull')))
60
+ }
61
+
62
+ /**
63
+ * Whether the aggregation endpoints cannot express a filter, so it is counted
64
+ * through the list endpoint: a list of values, or a test for no value.
65
+ *
66
+ * @param {object} filter A filter map.
67
+ * @return {boolean} True when the filter needs the list endpoint.
68
+ */
69
+ export function needsListEndpoint(filter) {
70
+ return hasListValue(filter) || hasNullTest(filter)
71
+ }
72
+
41
73
  /**
42
74
  * Count the records a filter matches by asking the LIST endpoint for one
43
75
  * record and reading its `total`. The filter is sent the way the list request
@@ -59,6 +91,9 @@ export async function fetchListTotal(register, schema, filter, ctx) {
59
91
  for (const [op, operand] of Object.entries(value)) {
60
92
  params[`${key}[${op}]`] = operand
61
93
  }
94
+ } else if (value === null) {
95
+ // "No value": the list endpoint reads it as the isnull operator.
96
+ params[`${key}[isnull]`] = 'true'
62
97
  } else {
63
98
  params[key] = value
64
99
  }
@@ -125,12 +160,12 @@ export async function fetchFilterCounts({ register, schema, entries, baseFilter,
125
160
  // and `/value` returns exactly its number.
126
161
  // A list of values anywhere in a request rules the aggregation endpoints
127
162
  // out for it. A base filter with one rules them out for every entry.
128
- const baseHasList = hasListValue(base)
163
+ const baseHasList = needsListEndpoint(base)
129
164
  const byField = {}
130
165
  const single = []
131
166
  const viaList = []
132
167
  for (const entry of entries) {
133
- if (baseHasList || hasListValue(entry.filter)) {
168
+ if (baseHasList || needsListEndpoint(entry.filter)) {
134
169
  viaList.push(entry)
135
170
  continue
136
171
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * initials: the letters an avatar shows for a free name.
6
+ *
7
+ * @module utils/initials
8
+ */
9
+
10
+ /**
11
+ * The initials of a name: the first letters of its first and its last word,
12
+ * upper-cased ("Sanne de Vries" reads "SV", "Jan" reads "J").
13
+ *
14
+ * @param {unknown} name The name.
15
+ * @return {string} One or two letters, or '' when there is no name.
16
+ * @spec openspec/changes/screens-record-list-avatar/specs/record-list-avatar/spec.md#requirement-a-record-list-row-can-carry-an-avatar
17
+ */
18
+ export function initialsOf(name) {
19
+ if (typeof name !== 'string') {
20
+ return ''
21
+ }
22
+ const words = name.trim().split(/\s+/).filter((word) => word !== '')
23
+ if (words.length === 0) {
24
+ return ''
25
+ }
26
+ const first = Array.from(words[0])[0] || ''
27
+ const last = words.length > 1 ? (Array.from(words[words.length - 1])[0] || '') : ''
28
+ return (first + last).toLocaleUpperCase()
29
+ }
@@ -93,6 +93,8 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
93
93
  'banner',
94
94
  'calendar',
95
95
  'chart',
96
+ 'chips-field',
97
+ 'choice-picker',
96
98
  'container',
97
99
  'conversation',
98
100
  'countdown',
@@ -125,21 +127,26 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
125
127
  'object-table',
126
128
  'people',
127
129
  'quicklinks',
130
+ 'radio-cards',
128
131
  'record-list',
129
132
  'related',
130
133
  'saved-view',
134
+ 'saved-views',
131
135
  'sections',
136
+ 'settings-form',
132
137
  'spend-analytics',
133
138
  'stacked-bar',
134
139
  'stages',
135
140
  'stat',
136
141
  'stats-block',
142
+ 'switch-rows',
137
143
  'table',
138
144
  'tabs',
139
145
  'tasks',
140
146
  'text',
141
147
  'tile',
142
148
  'timeline',
149
+ 'toggle-table',
143
150
  'video',
144
151
  'week-strip',
145
152
  'workspace-filter',
@@ -0,0 +1,55 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * objectWrite: the one-object PATCH the settings widgets share.
6
+ *
7
+ * @module utils/objectWrite
8
+ */
9
+
10
+ import { prefixUrl } from './headers.js'
11
+
12
+ /**
13
+ * The OpenRegister URL of one object, or '' when register, schema or id is missing.
14
+ *
15
+ * @param {string|object} register Register slug.
16
+ * @param {string|object} schema Schema slug.
17
+ * @param {string|number} id Object id.
18
+ * @return {string} The object URL.
19
+ */
20
+ export function objectUrlOf(register, schema, id) {
21
+ if (typeof register !== 'string' || typeof schema !== 'string' || !register || !schema || id === undefined || id === null || id === '') {
22
+ return ''
23
+ }
24
+ return `/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}/${encodeURIComponent(String(id))}`
25
+ }
26
+
27
+ /**
28
+ * PATCH only the given fields of one object.
29
+ *
30
+ * @param {string} url The object URL (see objectUrlOf).
31
+ * @param {object} patch The fields to write.
32
+ * @return {Promise<object>} The object the server answered with.
33
+ */
34
+ export async function patchObject(url, patch) {
35
+ const { default: axios } = await import('@nextcloud/axios')
36
+ const { data } = await axios.patch(prefixUrl(url), patch)
37
+ return data && typeof data === 'object' ? data : {}
38
+ }
39
+
40
+ /**
41
+ * The fields of `next` that differ from `base`, compared by value.
42
+ *
43
+ * @param {object} base What the server last held.
44
+ * @param {object} next What the form holds.
45
+ * @return {object} The changed fields only.
46
+ */
47
+ export function changedFields(base, next) {
48
+ const patch = {}
49
+ for (const [key, value] of Object.entries(next)) {
50
+ if (JSON.stringify(value === undefined ? null : value) !== JSON.stringify(base[key] === undefined ? null : base[key])) {
51
+ patch[key] = value
52
+ }
53
+ }
54
+ return patch
55
+ }
@@ -48,7 +48,7 @@ import { canonicalWidgetType } from './widgetTypeAliases.js'
48
48
  *
49
49
  * @type {string[]}
50
50
  */
51
- export const CONTENT_ONLY_TYPES = Object.freeze(['object-list', 'table', 'files', 'flow-runs', 'tasks', 'facts', 'field-list', 'record-list'])
51
+ export const CONTENT_ONLY_TYPES = Object.freeze(['object-list', 'table', 'files', 'flow-runs', 'tasks', 'facts', 'field-list', 'record-list', 'settings-form', 'switch-rows', 'radio-cards', 'chips-field', 'toggle-table', 'choice-picker'])
52
52
 
53
53
  /**
54
54
  * The stored content/config blob for a widget definition.