@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,169 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ SPDX-License-Identifier: EUPL-1.2
4
+
5
+ CnIndexPageWidgets — the widgets an index page draws around its list
6
+ (screens-index-stat-row): the KPI tiles above it (`statRow`, an auto-fit row)
7
+ and the cards beside it (`sidePanel`, a column). Each entry is a dashboard
8
+ widget definition `{ id, type, title?, content, headerLink? }`, resolved like
9
+ a dashboard widget: the app's registry, then the dashboard catalog, then the
10
+ built-in widgets. Internal to CnIndexPage.
11
+ -->
12
+ <template>
13
+ <component
14
+ :is="variant === 'stat-row' ? 'CnKpiGrid' : 'aside'"
15
+ v-if="entries.length > 0"
16
+ :columns="variant === 'stat-row' ? 'auto' : undefined"
17
+ class="cn-index-page-widgets"
18
+ :class="['cn-index-page-widgets--' + variant]"
19
+ :data-testid="'cn-index-page-' + variant">
20
+ <div
21
+ v-for="entry in entries"
22
+ :key="entry.id"
23
+ class="cn-index-page-widgets__cell"
24
+ :data-testid="'cn-index-page-widget-' + entry.id">
25
+ <CnWidgetWrapper
26
+ :widgetId="entry.id"
27
+ :title="entry.title"
28
+ :showTitle="variant === 'side-panel' && entry.title !== ''"
29
+ :showActions="false"
30
+ :showRefresh="false"
31
+ :headerLink="entry.headerLink"
32
+ :flush="variant === 'stat-row'">
33
+ <component
34
+ :is="entry.renderer"
35
+ :widgetId="entry.id"
36
+ :content="entry.content"
37
+ v-bind="entry.content" />
38
+ </CnWidgetWrapper>
39
+ </div>
40
+ </component>
41
+ </template>
42
+
43
+ <script>
44
+ import CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'
45
+ import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
46
+ import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
47
+ import { BUILT_IN_WIDGETS } from '../CnWidgetGrid/builtInWidgets.js'
48
+ import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
49
+
50
+ // Populate the dashboard catalog (stat, table, people, …): an index page
51
+ // may be the first surface that resolves a widget type.
52
+ import '../CnWidgetGrid/registerDashboardWidgets.js'
53
+
54
+ export default {
55
+ name: 'CnIndexPageWidgets',
56
+
57
+ components: {
58
+ CnKpiGrid,
59
+ CnWidgetWrapper,
60
+ },
61
+
62
+ inject: {
63
+ /** Consumer widget registry provided by CnAppRoot (custom over built-in). */
64
+ cnRegistry: { default: () => ({}) },
65
+ /** Host translate function provided by CnAppRoot. */
66
+ cnTranslate: { default: () => (key) => key },
67
+ },
68
+
69
+ props: {
70
+ /**
71
+ * Widget definitions `{ id, type, title?, content?, headerLink? }`.
72
+ * An entry without an id or with a type no layer resolves is skipped.
73
+ */
74
+ widgets: {
75
+ type: Array,
76
+ default: () => [],
77
+ },
78
+
79
+ /** Where the widgets sit: `stat-row` above the list, `side-panel` beside it. */
80
+ variant: {
81
+ type: String,
82
+ default: 'stat-row',
83
+ validator: (v) => ['stat-row', 'side-panel'].includes(v),
84
+ },
85
+ },
86
+
87
+ computed: {
88
+ /**
89
+ * The renderable entries, in the listed order.
90
+ *
91
+ * @spec openspec/changes/screens-index-stat-row/specs/index-page/spec.md#requirement-an-index-page-can-draw-a-stat-row-above-the-list
92
+ * @return {Array<{id: string, title: string, content: object, headerLink: (object|null), renderer: object}>}
93
+ */
94
+ entries() {
95
+ const list = Array.isArray(this.widgets) ? this.widgets : []
96
+ const out = []
97
+ for (const def of list) {
98
+ if (!def || typeof def.id !== 'string' || def.id === '' || typeof def.type !== 'string') {
99
+ continue
100
+ }
101
+ const renderer = this.resolveRenderer(def.type)
102
+ if (!renderer) {
103
+ if (process.env.NODE_ENV !== 'production') {
104
+ // eslint-disable-next-line no-console
105
+ console.warn(`[CnIndexPage] ${this.variant} widget "${def.id}" has type "${def.type}", which no widget registry resolves; skipped.`)
106
+ }
107
+ continue
108
+ }
109
+ const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
110
+ out.push({
111
+ id: def.id,
112
+ title: typeof def.title === 'string' && def.title !== '' ? translate(def.title) : '',
113
+ content: (def.content && typeof def.content === 'object') ? def.content : {},
114
+ headerLink: (def.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null,
115
+ renderer,
116
+ })
117
+ }
118
+ return out
119
+ },
120
+ },
121
+
122
+ methods: {
123
+ /**
124
+ * Resolve a widget type: the consumer registry, then the dashboard
125
+ * catalog (the type as written, then its alias), then the built-ins.
126
+ *
127
+ * @param {string} type The widget type.
128
+ * @return {object|null} The component, or null.
129
+ */
130
+ resolveRenderer(type) {
131
+ const consumer = (this.cnRegistry || {})[type]
132
+ if (consumer) {
133
+ return consumer.component ?? consumer
134
+ }
135
+ const entry = getWidgetTypeEntry(type) || getWidgetTypeEntry(canonicalWidgetType(type))
136
+ if (entry && entry.renderer) {
137
+ return entry.renderer
138
+ }
139
+ return BUILT_IN_WIDGETS[type] || BUILT_IN_WIDGETS[canonicalWidgetType(type)] || null
140
+ },
141
+ },
142
+ }
143
+ </script>
144
+
145
+ <style scoped>
146
+ .cn-index-page-widgets--stat-row {
147
+ margin-bottom: var(--cn-board-section-gap, 20px);
148
+ }
149
+
150
+ .cn-index-page-widgets__cell {
151
+ display: flex;
152
+ min-width: 0;
153
+ }
154
+
155
+ .cn-index-page-widgets__cell > * {
156
+ flex: 1 1 auto;
157
+ min-width: 0;
158
+ }
159
+
160
+ /* The side panel column (DqTeamwachtrij: 300px, 16px between cards). Its
161
+ place beside the toolbar and the list is set by CnIndexPage
162
+ (`cn-index-page--with-side-panel`, index-page.css). */
163
+ .cn-index-page-widgets--side-panel {
164
+ display: flex;
165
+ flex-direction: column;
166
+ gap: 16px;
167
+ min-width: 0;
168
+ }
169
+ </style>
@@ -305,6 +305,7 @@ import { pageHasSplitView, pageIdForRoute, splitIdForRoute, splitRouteName } fro
305
305
  import { reportDiagnostic, reportDiagnosticOnce } from '../../utils/diagnostics.js'
306
306
  import { openRowTarget } from '../../utils/linkNavigation.js'
307
307
  import { listContextFromRoute, listContextToQuery } from '../../utils/listNavigation.js'
308
+ import { applyQueryPreset, matchQueryPreset } from '../../utils/queryPresets.js'
308
309
  import { resolveRouteSentinels } from '../../utils/resolveRouteSentinels.js'
309
310
  import { parseSortKeys } from '../../utils/routeFilters.js'
310
311
  import { buildRouteParams, routePathFor } from '../../utils/routeParams.js'
@@ -1050,7 +1051,25 @@ export default {
1050
1051
  return 'none'
1051
1052
  }
1052
1053
  const cfg = (page.config && typeof page.config === 'object' && !Array.isArray(page.config)) ? page.config : {}
1053
- return [page.id, cfg.register || '', cfg.schema || ''].join(':')
1054
+ // A query preset swaps the lenses and columns, so it remounts the
1055
+ // page: the active lens index belongs to the lenses it indexes.
1056
+ const preset = this.activeQueryPresetIndex >= 0 ? `:preset-${this.activeQueryPresetIndex}` : ''
1057
+ return [page.id, cfg.register || '', cfg.schema || ''].join(':') + preset
1058
+ },
1059
+
1060
+ /**
1061
+ * The index of the current index page's query preset that the route
1062
+ * query matches, or -1.
1063
+ *
1064
+ * @return {number}
1065
+ * @spec openspec/changes/screens-index-query-presets/specs/index-query-presets/spec.md#requirement-a-query-preset-overlays-lenses-columns-and-copy
1066
+ */
1067
+ activeQueryPresetIndex() {
1068
+ const page = this.currentPage
1069
+ if (!page || page.type !== 'index' || !page.config || !Array.isArray(page.config.queryPresets)) {
1070
+ return -1
1071
+ }
1072
+ return matchQueryPreset(page.config.queryPresets, this.$route?.query || {})
1054
1073
  },
1055
1074
 
1056
1075
  /**
@@ -1297,6 +1316,15 @@ export default {
1297
1316
  // container before forwarding — CnIndexPage has no
1298
1317
  // `actionToggles` prop.
1299
1318
  const isIndex = page?.type === 'index'
1319
+ // A query preset (`config.queryPresets`): the menu entry's query
1320
+ // brings its own lenses, columns and copy to the same page.
1321
+ if (isIndex && Array.isArray(config.queryPresets)) {
1322
+ const preset = config.queryPresets[this.activeQueryPresetIndex] || null
1323
+ config = applyQueryPreset(config, preset)
1324
+ if (preset && typeof preset.title === 'string' && preset.title !== '') {
1325
+ topLevel.title = preset.title
1326
+ }
1327
+ }
1300
1328
  // When an index page has somewhere to open a row, make a row click
1301
1329
  // go there: set `rowClickToView` so the row body emits `row-click`
1302
1330
  // (→ onRowOpen navigates) even though the page is selectable.