@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
@@ -0,0 +1,296 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <div class="cn-saved-views-widget" data-testid="cn-saved-views-widget">
8
+ <p v-if="error" class="cn-saved-views-widget__note" data-testid="cn-saved-views-error">
9
+ {{ tr(content.errorText) || t('nextcloud-vue', 'Your saved views could not be loaded.') }}
10
+ </p>
11
+ <div v-else-if="loading" class="cn-saved-views-widget__loading">
12
+ <NcLoadingIcon :size="20" :name="t('nextcloud-vue', 'Loading …')" />
13
+ </div>
14
+ <p v-else-if="items.length === 0" class="cn-saved-views-widget__note" data-testid="cn-saved-views-empty">
15
+ {{ tr(content.emptyText) || t('nextcloud-vue', 'You have no saved views yet.') }}
16
+ </p>
17
+ <ul v-else class="cn-saved-views-widget__list">
18
+ <li v-for="item in items" :key="item.key" class="cn-saved-views-widget__item">
19
+ <component
20
+ :is="item.to ? 'router-link' : 'span'"
21
+ class="cn-saved-views-widget__link"
22
+ :to="item.to || undefined"
23
+ data-testid="cn-saved-views-link">
24
+ {{ item.label }}
25
+ </component>
26
+ </li>
27
+ </ul>
28
+ </div>
29
+ </template>
30
+
31
+ <script>
32
+ import { getCurrentUser } from '@nextcloud/auth'
33
+ import { subscribe, unsubscribe } from '@nextcloud/event-bus'
34
+ import { translate as t } from '@nextcloud/l10n'
35
+ import { NcLoadingIcon } from '@nextcloud/vue'
36
+ import { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'
37
+ import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
38
+ import { buildRouteQueryFromViewState, extractViewState, isOwnView, viewMatchesScope } from '../../utils/savedViewHelpers.js'
39
+
40
+ const PAGE_REFRESH_CHANNEL = 'cn:page:refresh'
41
+
42
+ /**
43
+ * CnSavedViewsWidget — the reader's own saved views as a list of links
44
+ * (`saved-views`), the "Opgeslagen weergaven" card of a board dashboard.
45
+ *
46
+ * `saved-view` draws the rows of ONE view that the manifest names by id. This
47
+ * one lists the views themselves and has no id to configure: it asks the Views
48
+ * API for the signed-in user's views (their own, or with `own: false` also the
49
+ * ones shared with them), keeps those that belong to the configured source,
50
+ * and links each to the index page `route` with the view's filters, search and
51
+ * sort in the query, which the index page applies on load. A view saved or
52
+ * deleted later shows up on the next load or page refresh.
53
+ *
54
+ * ```js
55
+ * { type: 'saved-views', content: { register: 'pipelinq', schema: 'ticket', route: 'Tickets', showCounts: true } }
56
+ * ```
57
+ */
58
+ export default {
59
+ name: 'CnSavedViewsWidget',
60
+
61
+ components: { NcLoadingIcon },
62
+
63
+ inject: {
64
+ /** The host app's translate function. */
65
+ cnTranslate: { default: () => (key) => key },
66
+ },
67
+
68
+ props: {
69
+ /**
70
+ * The configuration: `{ register, schema, scope?, route, own?, showCounts?,
71
+ * limit?, emptyText?, errorText? }`. `register` and `schema` (or `scope`,
72
+ * the index page's `savedViewsScope`) pick the views of one list page;
73
+ * `route` is the name of that index page's route; `own` (default true)
74
+ * keeps the signed-in user's views only; `showCounts` adds how many records
75
+ * each view matches; `limit` caps the list (default 10).
76
+ *
77
+ * @type {{register?: string, schema?: string, scope?: string, route?: (string|object), own?: boolean, showCounts?: boolean, limit?: number, emptyText?: string, errorText?: string}}
78
+ */
79
+ content: {
80
+ type: Object,
81
+ default: () => ({}),
82
+ },
83
+
84
+ /**
85
+ * Injected views API, for tests and hosts that stub the transport.
86
+ *
87
+ * @type {{fetchViews: Function}|null}
88
+ */
89
+ api: {
90
+ type: Object,
91
+ default: null,
92
+ },
93
+ },
94
+
95
+ data() {
96
+ return {
97
+ views: [],
98
+ counts: {},
99
+ loading: true,
100
+ error: false,
101
+ }
102
+ },
103
+
104
+ computed: {
105
+ /**
106
+ * The views to show: the reader's own (unless `own: false`) that belong to
107
+ * the configured source, newest first, capped at `limit`.
108
+ *
109
+ * @return {Array<object>} The View API objects.
110
+ */
111
+ shown() {
112
+ const c = this.content || {}
113
+ const uid = this.userId()
114
+ const page = { scope: c.scope, register: c.register, schema: c.schema }
115
+ const limit = Number(c.limit)
116
+ return this.views
117
+ .filter((view) => (c.own === false ? true : isOwnView(view, uid)))
118
+ .filter((view) => viewMatchesScope(view, page))
119
+ .slice(0, limit > 0 ? Math.floor(limit) : 10)
120
+ },
121
+
122
+ /**
123
+ * The rows: a label (the view's name and its count) and the location that
124
+ * applies the view on the index page.
125
+ *
126
+ * @return {Array<{key: string, label: string, to: (object|null)}>} The rows.
127
+ */
128
+ items() {
129
+ const route = this.content?.route
130
+ return this.shown.map((view) => {
131
+ const count = this.counts[this.keyOf(view)]
132
+ const query = buildRouteQueryFromViewState(extractViewState(view))
133
+ const location = typeof route === 'string' && route !== '' ? { name: route, query } : (route && typeof route === 'object' && route.name ? { ...route, query } : null)
134
+ return {
135
+ key: this.keyOf(view),
136
+ label: Number.isFinite(count) ? `${view.name} (${count})` : String(view.name ?? ''),
137
+ to: location && this.$router ? location : null,
138
+ }
139
+ })
140
+ },
141
+ },
142
+
143
+ watch: {
144
+ 'content.register': 'load',
145
+ 'content.schema': 'load',
146
+ 'content.scope': 'load',
147
+ 'content.own': 'load',
148
+ },
149
+
150
+ created() {
151
+ this.load()
152
+ },
153
+
154
+ mounted() {
155
+ this.onRefresh = () => this.load()
156
+ subscribe(PAGE_REFRESH_CHANNEL, this.onRefresh)
157
+ },
158
+
159
+ beforeUnmount() {
160
+ unsubscribe(PAGE_REFRESH_CHANNEL, this.onRefresh)
161
+ },
162
+
163
+ methods: {
164
+ t,
165
+
166
+ /**
167
+ * Translate a manifest string through the host app.
168
+ *
169
+ * @param {string} text The text.
170
+ * @return {string} The translation, or '' for no text.
171
+ */
172
+ tr(text) {
173
+ return typeof text === 'string' && text !== '' && typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : ''
174
+ },
175
+
176
+ /**
177
+ * The signed-in user's id, or '' when it cannot be read.
178
+ *
179
+ * @return {string} The id.
180
+ */
181
+ userId() {
182
+ try {
183
+ return getCurrentUser()?.uid || ''
184
+ } catch {
185
+ return ''
186
+ }
187
+ },
188
+
189
+ /**
190
+ * A view's key: its id, else its uuid.
191
+ *
192
+ * @param {object} view The View API object.
193
+ * @return {string} The key.
194
+ */
195
+ keyOf(view) {
196
+ return String(view?.id ?? view?.uuid ?? view?.name ?? '')
197
+ },
198
+
199
+ /**
200
+ * Read the views, and when asked how many records each matches. A failed
201
+ * read says so; a failed count only leaves that view without one.
202
+ *
203
+ * @return {Promise<void>}
204
+ * @spec openspec/changes/screens-own-saved-views/specs/own-saved-views/spec.md#requirement-a-dashboard-lists-the-readers-own-saved-views
205
+ */
206
+ async load() {
207
+ this.loading = true
208
+ this.error = false
209
+ try {
210
+ const api = this.api || useSavedViewsApi()
211
+ this.views = await api.fetchViews()
212
+ } catch (e) {
213
+ // eslint-disable-next-line no-console
214
+ console.warn('[CnSavedViewsWidget] failed to load saved views:', e)
215
+ this.views = []
216
+ this.error = true
217
+ this.loading = false
218
+ return
219
+ }
220
+ this.loading = false
221
+ await this.loadCounts()
222
+ },
223
+
224
+ /**
225
+ * One list request per shown view (`_limit=1`, the `total`), with the
226
+ * view's filters.
227
+ *
228
+ * @return {Promise<void>}
229
+ */
230
+ async loadCounts() {
231
+ const c = this.content || {}
232
+ if (c.showCounts !== true || !c.register || !c.schema) {
233
+ this.counts = {}
234
+ return
235
+ }
236
+ const next = {}
237
+ await Promise.all(this.shown.map(async (view) => {
238
+ try {
239
+ const total = await fetchListTotal(c.register, c.schema, extractViewState(view).filters, {})
240
+ if (total !== null) {
241
+ next[this.keyOf(view)] = total
242
+ }
243
+ } catch {
244
+ // No count for this view.
245
+ }
246
+ }))
247
+ this.counts = next
248
+ },
249
+ },
250
+ }
251
+ </script>
252
+
253
+ <style scoped>
254
+ .cn-saved-views-widget__list {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 10px;
258
+ list-style: none;
259
+ margin: 0;
260
+ padding: 0;
261
+ }
262
+
263
+ .cn-saved-views-widget__link {
264
+ color: var(--color-primary-element);
265
+ display: inline-block;
266
+ font-size: 14px;
267
+ font-weight: 600;
268
+ min-height: 24px;
269
+ text-decoration: none;
270
+ }
271
+
272
+ .cn-saved-views-widget__link:hover,
273
+ .cn-saved-views-widget__link:focus-visible {
274
+ text-decoration: underline;
275
+ }
276
+
277
+ .cn-saved-views-widget__note {
278
+ color: var(--color-text-maxcontrast);
279
+ margin: 0;
280
+ }
281
+
282
+ .cn-saved-views-widget__loading {
283
+ display: flex;
284
+ justify-content: center;
285
+ padding: 8px 0;
286
+ }
287
+
288
+ /* A phone: each link is a 44px touch target. */
289
+ @media (max-width: 640px) {
290
+ .cn-saved-views-widget__link {
291
+ align-items: center;
292
+ display: inline-flex;
293
+ min-height: 44px;
294
+ }
295
+ }
296
+ </style>
@@ -0,0 +1,29 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnSavedViewsWidget — self-registers the `saved-views` widget type (a list of
6
+ * the reader's own saved views, each a link into the index page that applies
7
+ * it) into the shared dashboardWidgetRegistry at module load.
8
+ */
9
+
10
+ import CnSavedViewsWidget from './CnSavedViewsWidget.vue'
11
+ import { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
12
+
13
+ registerDashboardWidget('saved-views', {
14
+ renderer: CnSavedViewsWidget,
15
+ form: null,
16
+ defaultContent: {
17
+ register: '',
18
+ schema: '',
19
+ route: '',
20
+ own: true,
21
+ limit: 10,
22
+ },
23
+ displayName: 'Saved views',
24
+ icon: 'ViewList',
25
+ surfaces: ['app-dashboard'],
26
+ })
27
+
28
+ export { CnSavedViewsWidget }
29
+ export default CnSavedViewsWidget
@@ -367,4 +367,17 @@ export default {
367
367
  border-color: transparent;
368
368
  box-shadow: 0 1px 2px var(--color-box-shadow);
369
369
  }
370
+
371
+ /* On a phone (screens-dashboard-phone) the compact group's segments are 44px
372
+ high touch targets, and the group wraps onto a second row instead of
373
+ running off the screen. */
374
+ @media (max-width: 640px) {
375
+ .cn-segmented-control.cn-segmented-control--compact {
376
+ flex-wrap: wrap;
377
+ }
378
+
379
+ .cn-segmented-control.cn-segmented-control--compact .cn-segmented-control__option {
380
+ min-height: 44px;
381
+ }
382
+ }
370
383
  </style>