@conduction/nextcloud-vue 2.79.0 → 2.80.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 (197) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
  5. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -8
  7. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +39 -3
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +79 -1
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +133 -96
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -0
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +62 -4
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +24 -0
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
  26. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
  27. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +736 -0
  28. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
  29. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
  30. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
  31. package/dist/esm/components/CnFactsWidget/index.js +30 -0
  32. package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
  33. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
  34. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
  35. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +551 -0
  36. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
  37. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +183 -0
  38. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
  39. package/dist/esm/components/CnFieldListWidget/index.js +29 -0
  40. package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
  41. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +65 -17
  42. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
  44. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +18 -3
  46. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  48. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +305 -72
  49. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +31 -13
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
  53. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
  54. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
  55. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
  56. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
  57. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
  58. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
  59. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  60. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  61. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
  62. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
  63. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +387 -0
  64. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
  65. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +169 -0
  66. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
  67. package/dist/esm/components/CnRecordListWidget/index.js +31 -0
  68. package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
  69. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
  70. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
  71. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
  72. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
  73. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
  74. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
  75. package/dist/esm/components/CnSectionsWidget/index.js +35 -0
  76. package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
  77. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
  78. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  79. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  80. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
  82. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +122 -7
  84. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  85. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +84 -23
  86. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  87. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
  88. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  89. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +106 -2
  90. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +105 -53
  92. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  93. package/dist/esm/composables/useFormDraft.js +140 -9
  94. package/dist/esm/composables/useFormDraft.js.map +1 -1
  95. package/dist/esm/index.js +4 -0
  96. package/dist/esm/index.js.map +1 -1
  97. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
  98. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  99. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
  100. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  101. package/dist/esm/integrations/builtin/field-inspection.js +21 -0
  102. package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
  103. package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
  104. package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
  105. package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
  106. package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
  107. package/dist/esm/integrations/offline/index.js +2 -1
  108. package/dist/esm/integrations/offline/index.js.map +1 -1
  109. package/dist/esm/integrations/offline/offlineDb.js +6 -1
  110. package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
  111. package/dist/esm/integrations/offline/syncReplayService.js +8 -2
  112. package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
  113. package/dist/esm/l10n/en.json.js +12 -1
  114. package/dist/esm/l10n/en.json.js.map +1 -1
  115. package/dist/esm/l10n/nl.json.js +16 -2
  116. package/dist/esm/l10n/nl.json.js.map +1 -1
  117. package/dist/esm/nextcloud-vue.css +1000 -59
  118. package/dist/esm/utils/detailActionModel.js +60 -3
  119. package/dist/esm/utils/detailActionModel.js.map +1 -1
  120. package/dist/esm/utils/fillTemplate.js +114 -0
  121. package/dist/esm/utils/fillTemplate.js.map +1 -0
  122. package/dist/esm/utils/libraryWidgetKeys.js +4 -0
  123. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  124. package/dist/esm/utils/queryPresets.js +96 -0
  125. package/dist/esm/utils/queryPresets.js.map +1 -0
  126. package/dist/esm/utils/resolveFilterTokens.js +11 -0
  127. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  128. package/dist/esm/utils/schema.js +21 -0
  129. package/dist/esm/utils/schema.js.map +1 -1
  130. package/dist/esm/utils/sentinelTokens.js +3 -3
  131. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  132. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  133. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  134. package/dist/esm/utils/widgetDispatch.js +7 -1
  135. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  136. package/dist/nextcloud-vue.cjs.js +12368 -7717
  137. package/dist/nextcloud-vue.cjs.js.map +2 -2
  138. package/dist/nextcloud-vue.css +1000 -59
  139. package/l10n/en.json +12 -1
  140. package/l10n/nl.json +16 -2
  141. package/package.json +1 -1
  142. package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
  143. package/src/components/CnActionsBar/CnActionsBar.md +4 -0
  144. package/src/components/CnActionsBar/CnActionsBar.vue +42 -10
  145. package/src/components/CnBannerWidget/CnBannerWidget.vue +39 -3
  146. package/src/components/CnCellRenderer/CnCellRenderer.vue +188 -1
  147. package/src/components/CnDataTable/CnDataTable.vue +1 -0
  148. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  149. package/src/components/CnDetailPage/CnDetailPage.vue +63 -3
  150. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
  151. package/src/components/CnFactsWidget/CnFactsWidget.vue +1050 -0
  152. package/src/components/CnFactsWidget/index.js +29 -0
  153. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +746 -0
  154. package/src/components/CnFieldListWidget/index.js +28 -0
  155. package/src/components/CnFormDialog/CnFormDialog.md +9 -3
  156. package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
  157. package/src/components/CnFormPage/CnFormPage.vue +18 -3
  158. package/src/components/CnIndexPage/CnIndexPage.vue +254 -6
  159. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
  160. package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
  161. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +603 -0
  162. package/src/components/CnRecordListWidget/index.js +30 -0
  163. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
  164. package/src/components/CnSectionsWidget/index.js +34 -0
  165. package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
  166. package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
  167. package/src/components/CnTabsWidget/CnTabsWidget.vue +184 -8
  168. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
  169. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +188 -4
  170. package/src/components/index.js +4 -0
  171. package/src/composables/__tests__/useFormDraft.spec.js +69 -0
  172. package/src/composables/useFormDraft.js +139 -8
  173. package/src/css/form-field.css +14 -0
  174. package/src/css/index-page.css +71 -0
  175. package/src/css/kpi-card.css +32 -0
  176. package/src/css/look-board-detail.css +77 -0
  177. package/src/css/look-board-index.css +51 -0
  178. package/src/css/look-board.css +37 -2
  179. package/src/css/table.css +19 -0
  180. package/src/index.js +4 -0
  181. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
  182. package/src/integrations/builtin/field-inspection.js +21 -0
  183. package/src/integrations/offline/checklistSubmission.js +137 -0
  184. package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
  185. package/src/integrations/offline/index.js +3 -0
  186. package/src/integrations/offline/offlineDb.js +6 -1
  187. package/src/integrations/offline/syncReplayService.js +8 -2
  188. package/src/schemas/app-manifest-v2.schema.json +104 -5
  189. package/src/utils/detailActionModel.js +59 -2
  190. package/src/utils/fillTemplate.js +110 -0
  191. package/src/utils/libraryWidgetKeys.js +4 -0
  192. package/src/utils/queryPresets.js +93 -0
  193. package/src/utils/resolveFilterTokens.js +11 -0
  194. package/src/utils/schema.js +21 -0
  195. package/src/utils/sentinelTokens.js +3 -3
  196. package/src/utils/validateManifestV2.compiled.js +3 -3
  197. package/src/utils/widgetDispatch.js +7 -1
@@ -0,0 +1,10 @@
1
+ import script from './CnIndexPageWidgets.vue2.js';
2
+ import { render } from './CnIndexPageWidgets.vue3.js';
3
+
4
+ script.render = render;
5
+ script.__scopeId = "data-v-5ec0c5bc";
6
+ script.__file = "src/components/CnIndexPage/CnIndexPageWidgets.vue";
7
+ const __sfc_main = script;
8
+
9
+ export { __sfc_main as default };
10
+ //# sourceMappingURL=CnIndexPageWidgets.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnIndexPageWidgets.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -0,0 +1,99 @@
1
+ import __sfc_main from '../CnKpiGrid/CnKpiGrid.vue.js';
2
+ import __sfc_main$1 from '../CnWidgetWrapper/CnWidgetWrapper.vue.js';
3
+ import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js';
4
+ import { BUILT_IN_WIDGETS } from '../CnWidgetGrid/builtInWidgets.js';
5
+ import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js';
6
+ import '../CnWidgetGrid/registerDashboardWidgets.js';
7
+
8
+ var script = {
9
+ name: 'CnIndexPageWidgets',
10
+
11
+ components: {
12
+ CnKpiGrid: __sfc_main,
13
+ CnWidgetWrapper: __sfc_main$1,
14
+ },
15
+
16
+ inject: {
17
+ /** Consumer widget registry provided by CnAppRoot (custom over built-in). */
18
+ cnRegistry: { default: () => ({}) },
19
+ /** Host translate function provided by CnAppRoot. */
20
+ cnTranslate: { default: () => (key) => key },
21
+ },
22
+
23
+ props: {
24
+ /**
25
+ * Widget definitions `{ id, type, title?, content?, headerLink? }`.
26
+ * An entry without an id or with a type no layer resolves is skipped.
27
+ */
28
+ widgets: {
29
+ type: Array,
30
+ default: () => [],
31
+ },
32
+
33
+ /** Where the widgets sit: `stat-row` above the list, `side-panel` beside it. */
34
+ variant: {
35
+ type: String,
36
+ default: 'stat-row',
37
+ validator: (v) => ['stat-row', 'side-panel'].includes(v),
38
+ },
39
+ },
40
+
41
+ computed: {
42
+ /**
43
+ * The renderable entries, in the listed order.
44
+ *
45
+ * @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list
46
+ * @return {Array<{id: string, title: string, content: object, headerLink: (object|null), renderer: object}>}
47
+ */
48
+ entries() {
49
+ const list = Array.isArray(this.widgets) ? this.widgets : [];
50
+ const out = [];
51
+ for (const def of list) {
52
+ if (!def || typeof def.id !== 'string' || def.id === '' || typeof def.type !== 'string') {
53
+ continue
54
+ }
55
+ const renderer = this.resolveRenderer(def.type);
56
+ if (!renderer) {
57
+ if (process.env.NODE_ENV !== 'production') {
58
+ // eslint-disable-next-line no-console
59
+ console.warn(`[CnIndexPage] ${this.variant} widget "${def.id}" has type "${def.type}", which no widget registry resolves; skipped.`);
60
+ }
61
+ continue
62
+ }
63
+ const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key;
64
+ out.push({
65
+ id: def.id,
66
+ title: typeof def.title === 'string' && def.title !== '' ? translate(def.title) : '',
67
+ content: (def.content && typeof def.content === 'object') ? def.content : {},
68
+ headerLink: (def.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null,
69
+ renderer,
70
+ });
71
+ }
72
+ return out
73
+ },
74
+ },
75
+
76
+ methods: {
77
+ /**
78
+ * Resolve a widget type: the consumer registry, then the dashboard
79
+ * catalog (the type as written, then its alias), then the built-ins.
80
+ *
81
+ * @param {string} type The widget type.
82
+ * @return {object|null} The component, or null.
83
+ */
84
+ resolveRenderer(type) {
85
+ const consumer = (this.cnRegistry || {})[type];
86
+ if (consumer) {
87
+ return consumer.component ?? consumer
88
+ }
89
+ const entry = getWidgetTypeEntry(type) || getWidgetTypeEntry(canonicalWidgetType(type));
90
+ if (entry && entry.renderer) {
91
+ return entry.renderer
92
+ }
93
+ return BUILT_IN_WIDGETS[type] || BUILT_IN_WIDGETS[canonicalWidgetType(type)] || null
94
+ },
95
+ },
96
+ };
97
+
98
+ export { script as default };
99
+ //# sourceMappingURL=CnIndexPageWidgets.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnIndexPageWidgets.vue2.js","sources":["../../../../src/components/CnIndexPage/CnIndexPageWidgets.vue"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n\n CnIndexPageWidgets — the widgets an index page draws around its list\n (screens-index-stat-row): the KPI tiles above it (`statRow`, an auto-fit row)\n and the cards beside it (`sidePanel`, a column). Each entry is a dashboard\n widget definition `{ id, type, title?, content, headerLink? }`, resolved like\n a dashboard widget: the app's registry, then the dashboard catalog, then the\n built-in widgets. Internal to CnIndexPage.\n-->\n<template>\n\t<component\n\t\t:is=\"variant === 'stat-row' ? 'CnKpiGrid' : 'aside'\"\n\t\tv-if=\"entries.length > 0\"\n\t\t:columns=\"variant === 'stat-row' ? 'auto' : undefined\"\n\t\tclass=\"cn-index-page-widgets\"\n\t\t:class=\"['cn-index-page-widgets--' + variant]\"\n\t\t:data-testid=\"'cn-index-page-' + variant\">\n\t\t<div\n\t\t\tv-for=\"entry in entries\"\n\t\t\t:key=\"entry.id\"\n\t\t\tclass=\"cn-index-page-widgets__cell\"\n\t\t\t:data-testid=\"'cn-index-page-widget-' + entry.id\">\n\t\t\t<CnWidgetWrapper\n\t\t\t\t:widgetId=\"entry.id\"\n\t\t\t\t:title=\"entry.title\"\n\t\t\t\t:showTitle=\"variant === 'side-panel' && entry.title !== ''\"\n\t\t\t\t:showActions=\"false\"\n\t\t\t\t:showRefresh=\"false\"\n\t\t\t\t:headerLink=\"entry.headerLink\"\n\t\t\t\t:flush=\"variant === 'stat-row'\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"entry.renderer\"\n\t\t\t\t\t:widgetId=\"entry.id\"\n\t\t\t\t\t:content=\"entry.content\"\n\t\t\t\t\tv-bind=\"entry.content\" />\n\t\t\t</CnWidgetWrapper>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'\nimport CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'\nimport { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'\nimport { BUILT_IN_WIDGETS } from '../CnWidgetGrid/builtInWidgets.js'\nimport { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\n\n// Populate the dashboard catalog (stat, table, people, …): an index page\n// may be the first surface that resolves a widget type.\nimport '../CnWidgetGrid/registerDashboardWidgets.js'\n\nexport default {\n\tname: 'CnIndexPageWidgets',\n\n\tcomponents: {\n\t\tCnKpiGrid,\n\t\tCnWidgetWrapper,\n\t},\n\n\tinject: {\n\t\t/** Consumer widget registry provided by CnAppRoot (custom over built-in). */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Host translate function provided by CnAppRoot. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Widget definitions `{ id, type, title?, content?, headerLink? }`.\n\t\t * An entry without an id or with a type no layer resolves is skipped.\n\t\t */\n\t\twidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Where the widgets sit: `stat-row` above the list, `side-panel` beside it. */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'stat-row',\n\t\t\tvalidator: (v) => ['stat-row', 'side-panel'].includes(v),\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The renderable entries, in the listed order.\n\t\t *\n\t\t * @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list\n\t\t * @return {Array<{id: string, title: string, content: object, headerLink: (object|null), renderer: object}>}\n\t\t */\n\t\tentries() {\n\t\t\tconst list = Array.isArray(this.widgets) ? this.widgets : []\n\t\t\tconst out = []\n\t\t\tfor (const def of list) {\n\t\t\t\tif (!def || typeof def.id !== 'string' || def.id === '' || typeof def.type !== 'string') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst renderer = this.resolveRenderer(def.type)\n\t\t\t\tif (!renderer) {\n\t\t\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\t\tconsole.warn(`[CnIndexPage] ${this.variant} widget \"${def.id}\" has type \"${def.type}\", which no widget registry resolves; skipped.`)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\t\tout.push({\n\t\t\t\t\tid: def.id,\n\t\t\t\t\ttitle: typeof def.title === 'string' && def.title !== '' ? translate(def.title) : '',\n\t\t\t\t\tcontent: (def.content && typeof def.content === 'object') ? def.content : {},\n\t\t\t\t\theaderLink: (def.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null,\n\t\t\t\t\trenderer,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve a widget type: the consumer registry, then the dashboard\n\t\t * catalog (the type as written, then its alias), then the built-ins.\n\t\t *\n\t\t * @param {string} type The widget type.\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\tresolveRenderer(type) {\n\t\t\tconst consumer = (this.cnRegistry || {})[type]\n\t\t\tif (consumer) {\n\t\t\t\treturn consumer.component ?? consumer\n\t\t\t}\n\t\t\tconst entry = getWidgetTypeEntry(type) || getWidgetTypeEntry(canonicalWidgetType(type))\n\t\t\tif (entry && entry.renderer) {\n\t\t\t\treturn entry.renderer\n\t\t\t}\n\t\t\treturn BUILT_IN_WIDGETS[type] || BUILT_IN_WIDGETS[canonicalWidgetType(type)] || null\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-index-page-widgets--stat-row {\n\tmargin-bottom: var(--cn-board-section-gap, 20px);\n}\n\n.cn-index-page-widgets__cell {\n\tdisplay: flex;\n\tmin-width: 0;\n}\n\n.cn-index-page-widgets__cell > * {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n/* The side panel column (DqTeamwachtrij: 300px, 16px between cards). Its\n place beside the toolbar and the list is set by CnIndexPage\n (`cn-index-page--with-side-panel`, index-page.css). */\n.cn-index-page-widgets--side-panel {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\tmin-width: 0;\n}\n</style>\n"],"names":["CnKpiGrid","CnWidgetWrapper"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { resolveComponent, openBlock, createBlock, resolveDynamicComponent, normalizeClass, withCtx, createElementBlock, Fragment, renderList, createVNode, mergeProps, createCommentVNode } from 'vue';
2
+
3
+ const _hoisted_1 = ["data-testid"];
4
+
5
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
6
+ const _component_CnWidgetWrapper = resolveComponent("CnWidgetWrapper");
7
+
8
+ return ($options.entries.length > 0)
9
+ ? (openBlock(), createBlock(resolveDynamicComponent($props.variant === 'stat-row' ? 'CnKpiGrid' : 'aside'), {
10
+ key: 0,
11
+ columns: $props.variant === 'stat-row' ? 'auto' : undefined,
12
+ class: normalizeClass(["cn-index-page-widgets", ['cn-index-page-widgets--' + $props.variant]]),
13
+ "data-testid": 'cn-index-page-' + $props.variant
14
+ }, {
15
+ default: withCtx(() => [
16
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.entries, (entry) => {
17
+ return (openBlock(), createElementBlock("div", {
18
+ key: entry.id,
19
+ class: "cn-index-page-widgets__cell",
20
+ "data-testid": 'cn-index-page-widget-' + entry.id
21
+ }, [
22
+ createVNode(_component_CnWidgetWrapper, {
23
+ widgetId: entry.id,
24
+ title: entry.title,
25
+ showTitle: $props.variant === 'side-panel' && entry.title !== '',
26
+ showActions: false,
27
+ showRefresh: false,
28
+ headerLink: entry.headerLink,
29
+ flush: $props.variant === 'stat-row'
30
+ }, {
31
+ default: withCtx(() => [
32
+ (openBlock(), createBlock(resolveDynamicComponent(entry.renderer), mergeProps({
33
+ widgetId: entry.id,
34
+ content: entry.content
35
+ }, { ref_for: true }, entry.content), null, 16 /* FULL_PROPS */, ["widgetId", "content"]))
36
+ ]),
37
+ _: 2 /* DYNAMIC */
38
+ }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["widgetId", "title", "showTitle", "headerLink", "flush"])
39
+ ], 8 /* PROPS */, _hoisted_1))
40
+ }), 128 /* KEYED_FRAGMENT */))
41
+ ]),
42
+ _: 1 /* STABLE */
43
+ }, 8 /* PROPS */, ["columns", "class", "data-testid"]))
44
+ : createCommentVNode("v-if", true)
45
+ }
46
+
47
+ export { render };
48
+ //# sourceMappingURL=CnIndexPageWidgets.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnIndexPageWidgets.vue3.js","sources":["../../../../src/components/CnIndexPage/CnIndexPageWidgets.vue?vue&type=template&id=5ec0c5bc&lang.js"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n\n CnIndexPageWidgets — the widgets an index page draws around its list\n (screens-index-stat-row): the KPI tiles above it (`statRow`, an auto-fit row)\n and the cards beside it (`sidePanel`, a column). Each entry is a dashboard\n widget definition `{ id, type, title?, content, headerLink? }`, resolved like\n a dashboard widget: the app's registry, then the dashboard catalog, then the\n built-in widgets. Internal to CnIndexPage.\n-->\n<template>\n\t<component\n\t\t:is=\"variant === 'stat-row' ? 'CnKpiGrid' : 'aside'\"\n\t\tv-if=\"entries.length > 0\"\n\t\t:columns=\"variant === 'stat-row' ? 'auto' : undefined\"\n\t\tclass=\"cn-index-page-widgets\"\n\t\t:class=\"['cn-index-page-widgets--' + variant]\"\n\t\t:data-testid=\"'cn-index-page-' + variant\">\n\t\t<div\n\t\t\tv-for=\"entry in entries\"\n\t\t\t:key=\"entry.id\"\n\t\t\tclass=\"cn-index-page-widgets__cell\"\n\t\t\t:data-testid=\"'cn-index-page-widget-' + entry.id\">\n\t\t\t<CnWidgetWrapper\n\t\t\t\t:widgetId=\"entry.id\"\n\t\t\t\t:title=\"entry.title\"\n\t\t\t\t:showTitle=\"variant === 'side-panel' && entry.title !== ''\"\n\t\t\t\t:showActions=\"false\"\n\t\t\t\t:showRefresh=\"false\"\n\t\t\t\t:headerLink=\"entry.headerLink\"\n\t\t\t\t:flush=\"variant === 'stat-row'\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"entry.renderer\"\n\t\t\t\t\t:widgetId=\"entry.id\"\n\t\t\t\t\t:content=\"entry.content\"\n\t\t\t\t\tv-bind=\"entry.content\" />\n\t\t\t</CnWidgetWrapper>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'\nimport CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'\nimport { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'\nimport { BUILT_IN_WIDGETS } from '../CnWidgetGrid/builtInWidgets.js'\nimport { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\n\n// Populate the dashboard catalog (stat, table, people, …): an index page\n// may be the first surface that resolves a widget type.\nimport '../CnWidgetGrid/registerDashboardWidgets.js'\n\nexport default {\n\tname: 'CnIndexPageWidgets',\n\n\tcomponents: {\n\t\tCnKpiGrid,\n\t\tCnWidgetWrapper,\n\t},\n\n\tinject: {\n\t\t/** Consumer widget registry provided by CnAppRoot (custom over built-in). */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Host translate function provided by CnAppRoot. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Widget definitions `{ id, type, title?, content?, headerLink? }`.\n\t\t * An entry without an id or with a type no layer resolves is skipped.\n\t\t */\n\t\twidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Where the widgets sit: `stat-row` above the list, `side-panel` beside it. */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'stat-row',\n\t\t\tvalidator: (v) => ['stat-row', 'side-panel'].includes(v),\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The renderable entries, in the listed order.\n\t\t *\n\t\t * @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list\n\t\t * @return {Array<{id: string, title: string, content: object, headerLink: (object|null), renderer: object}>}\n\t\t */\n\t\tentries() {\n\t\t\tconst list = Array.isArray(this.widgets) ? this.widgets : []\n\t\t\tconst out = []\n\t\t\tfor (const def of list) {\n\t\t\t\tif (!def || typeof def.id !== 'string' || def.id === '' || typeof def.type !== 'string') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst renderer = this.resolveRenderer(def.type)\n\t\t\t\tif (!renderer) {\n\t\t\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\t\tconsole.warn(`[CnIndexPage] ${this.variant} widget \"${def.id}\" has type \"${def.type}\", which no widget registry resolves; skipped.`)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\t\tout.push({\n\t\t\t\t\tid: def.id,\n\t\t\t\t\ttitle: typeof def.title === 'string' && def.title !== '' ? translate(def.title) : '',\n\t\t\t\t\tcontent: (def.content && typeof def.content === 'object') ? def.content : {},\n\t\t\t\t\theaderLink: (def.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null,\n\t\t\t\t\trenderer,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve a widget type: the consumer registry, then the dashboard\n\t\t * catalog (the type as written, then its alias), then the built-ins.\n\t\t *\n\t\t * @param {string} type The widget type.\n\t\t * @return {object|null} The component, or null.\n\t\t */\n\t\tresolveRenderer(type) {\n\t\t\tconst consumer = (this.cnRegistry || {})[type]\n\t\t\tif (consumer) {\n\t\t\t\treturn consumer.component ?? consumer\n\t\t\t}\n\t\t\tconst entry = getWidgetTypeEntry(type) || getWidgetTypeEntry(canonicalWidgetType(type))\n\t\t\tif (entry && entry.renderer) {\n\t\t\t\treturn entry.renderer\n\t\t\t}\n\t\t\treturn BUILT_IN_WIDGETS[type] || BUILT_IN_WIDGETS[canonicalWidgetType(type)] || null\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-index-page-widgets--stat-row {\n\tmargin-bottom: var(--cn-board-section-gap, 20px);\n}\n\n.cn-index-page-widgets__cell {\n\tdisplay: flex;\n\tmin-width: 0;\n}\n\n.cn-index-page-widgets__cell > * {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n/* The side panel column (DqTeamwachtrij: 300px, 16px between cards). Its\n place beside the toolbar and the list is set by CnIndexPage\n (`cn-index-page--with-side-panel`, index-page.css). */\n.cn-index-page-widgets--side-panel {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\tmin-width: 0;\n}\n</style>\n"],"names":["_normalizeClass"],"mappings":";;;;;;;AAyBQ,EAAA,OAAA,CAAA,QAAA,CAAA,OAAO,CAAC,MAAM,GAAA,CAAA;wDADf,MAAO,CAAA,OAAA,KAAA,UAAA,GAAA,WAAA,GAAA,OAAA,CAAA,EAAA;;QAEV,OAAM,EAAE,MAAO,CAAA,OAAA,KAAA,UAAA,GAAA,MAAA,GAAI,SAAA;aAChB,EAAAA,cAAA,CAAA,CAAC,uBAAuB,EAAA,CAAA,yBAAA,GACR,MAAA,CAAA,OAAA,CAAA,CAAA,CAAA;AACnB,QAAA,aAAU,qBAAS,MAAA,CAAA,OAAA;;+BAEI;0EAAP,QAAM,CAAA,OAAA,EAAA,CAAhB,KAAK,KAAA;;cACX,GAAG,EAAE,KAAI,CAAA,EAAA;AACR,cAAA,KAAG,EAAC,6BAAe;cACnB,aAAU,EAAA,uBAAA,GAAS,KAAA,CAAA,EAAA;;;gBAEnB,QAAQ,EAAE,KAAK,CAAC,EAAE;gBAClB,KAAK,EAAE,KAAK,CAAC,KAAE;AACf,gBAAA,SAAE,EAAA,MAAA,CAAA,OAAA,KAAA,YAAA,IAAA,KAAA,CAAA,KAAA,KAAA,EAAA;AACF,gBAAA,WAAO,EAAA,KAAA;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -14,6 +14,7 @@ import { pageIdForRoute, splitIdForRoute, pageHasSplitView, splitRouteName } fro
14
14
  import { reportDiagnosticOnce, reportDiagnostic } from '../../utils/diagnostics.js';
15
15
  import { openRowTarget } from '../../utils/linkNavigation.js';
16
16
  import { listContextFromRoute, listContextToQuery } from '../../utils/listNavigation.js';
17
+ import { matchQueryPreset, applyQueryPreset } from '../../utils/queryPresets.js';
17
18
  import { resolveRouteSentinels } from '../../utils/resolveRouteSentinels.js';
18
19
  import { parseSortKeys } from '../../utils/routeFilters.js';
19
20
  import { routePathFor, buildRouteParams } from '../../utils/routeParams.js';
@@ -760,7 +761,25 @@ var script = {
760
761
  return 'none'
761
762
  }
762
763
  const cfg = (page.config && typeof page.config === 'object' && !Array.isArray(page.config)) ? page.config : {};
763
- return [page.id, cfg.register || '', cfg.schema || ''].join(':')
764
+ // A query preset swaps the lenses and columns, so it remounts the
765
+ // page: the active lens index belongs to the lenses it indexes.
766
+ const preset = this.activeQueryPresetIndex >= 0 ? `:preset-${this.activeQueryPresetIndex}` : '';
767
+ return [page.id, cfg.register || '', cfg.schema || ''].join(':') + preset
768
+ },
769
+
770
+ /**
771
+ * The index of the current index page's query preset that the route
772
+ * query matches, or -1.
773
+ *
774
+ * @return {number}
775
+ * @spec openspec/changes/screens-index-query-presets/specs/index-query-presets/spec.md#requirement-a-query-preset-overlays-lenses-columns-and-copy
776
+ */
777
+ activeQueryPresetIndex() {
778
+ const page = this.currentPage;
779
+ if (!page || page.type !== 'index' || !page.config || !Array.isArray(page.config.queryPresets)) {
780
+ return -1
781
+ }
782
+ return matchQueryPreset(page.config.queryPresets, this.$route?.query || {})
764
783
  },
765
784
 
766
785
  /**
@@ -1007,6 +1026,15 @@ var script = {
1007
1026
  // container before forwarding — CnIndexPage has no
1008
1027
  // `actionToggles` prop.
1009
1028
  const isIndex = page?.type === 'index';
1029
+ // A query preset (`config.queryPresets`): the menu entry's query
1030
+ // brings its own lenses, columns and copy to the same page.
1031
+ if (isIndex && Array.isArray(config.queryPresets)) {
1032
+ const preset = config.queryPresets[this.activeQueryPresetIndex] || null;
1033
+ config = applyQueryPreset(config, preset);
1034
+ if (preset && typeof preset.title === 'string' && preset.title !== '') {
1035
+ topLevel.title = preset.title;
1036
+ }
1037
+ }
1010
1038
  // When an index page has somewhere to open a row, make a row click
1011
1039
  // go there: set `rowClickToView` so the row body emits `row-click`
1012
1040
  // (→ onRowOpen navigates) even though the page is selectable.