@conduction/nextcloud-vue 2.62.0 → 2.64.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 (209) hide show
  1. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
  2. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
  3. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
  4. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
  5. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
  6. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +425 -39
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +119 -42
  10. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +186 -9
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +65 -42
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  18. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
  19. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
  21. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
  23. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  24. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  25. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  26. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  27. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -0
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +152 -135
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  33. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  34. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  35. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  36. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  37. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  38. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +253 -9
  39. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +126 -50
  41. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +66 -14
  43. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +23 -14
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  48. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  49. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  50. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  52. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  54. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  55. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  56. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  57. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +81 -4
  58. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +22 -5
  60. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +81 -2
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +7 -3
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  65. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  66. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  67. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +41 -2
  68. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +8 -3
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  72. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  73. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  74. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  75. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  76. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  77. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  78. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
  80. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
  82. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
  84. package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
  85. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +52 -2
  86. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  87. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -6
  88. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  89. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
  90. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
  92. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  93. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  94. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  95. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  96. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  97. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  98. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  99. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  100. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  101. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  102. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  103. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  104. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
  110. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  111. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
  112. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  113. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  114. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  115. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  116. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  117. package/dist/esm/composables/useDependencyCheck.js +65 -0
  118. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  119. package/dist/esm/composables/useWalkthrough.js +218 -6
  120. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  121. package/dist/esm/index.js +2 -0
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/l10n/en.json.js +45 -1
  124. package/dist/esm/l10n/en.json.js.map +1 -1
  125. package/dist/esm/l10n/nl.json.js +45 -1
  126. package/dist/esm/l10n/nl.json.js.map +1 -1
  127. package/dist/esm/nextcloud-vue.css +780 -81
  128. package/dist/esm/store/useObjectStore.js +8 -0
  129. package/dist/esm/store/useObjectStore.js.map +1 -1
  130. package/dist/esm/utils/columnFilters.js +311 -0
  131. package/dist/esm/utils/columnFilters.js.map +1 -0
  132. package/dist/esm/utils/groupAutocomplete.js +123 -0
  133. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  134. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  135. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  136. package/dist/esm/utils/pickerOptions.js +198 -0
  137. package/dist/esm/utils/pickerOptions.js.map +1 -0
  138. package/dist/esm/utils/schema.js +83 -7
  139. package/dist/esm/utils/schema.js.map +1 -1
  140. package/dist/esm/utils/schemaRefSlug.js +19 -3
  141. package/dist/esm/utils/schemaRefSlug.js.map +1 -1
  142. package/dist/esm/utils/timelineEvents.js +174 -0
  143. package/dist/esm/utils/timelineEvents.js.map +1 -0
  144. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  145. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  146. package/dist/esm/utils/walkthroughSignals.js +88 -0
  147. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  148. package/dist/nextcloud-vue.cjs.js +13088 -7937
  149. package/dist/nextcloud-vue.cjs.js.map +2 -2
  150. package/dist/nextcloud-vue.css +780 -81
  151. package/l10n/en.json +45 -0
  152. package/l10n/nl.json +45 -0
  153. package/package.json +1 -1
  154. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  155. package/src/components/CnAdminActionCard/index.js +4 -0
  156. package/src/components/CnAppNav/CnAppNav.vue +568 -43
  157. package/src/components/CnAppRoot/CnAppRoot.md +4 -0
  158. package/src/components/CnAppRoot/CnAppRoot.vue +223 -16
  159. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
  160. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
  161. package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
  162. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  163. package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -4
  164. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  165. package/src/components/CnDataTable/CnDataTable.md +3 -0
  166. package/src/components/CnDataTable/CnDataTable.vue +305 -5
  167. package/src/components/CnDetailPage/CnDetailPage.md +4 -0
  168. package/src/components/CnDetailPage/CnDetailPage.vue +60 -1
  169. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  170. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  171. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  172. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +116 -6
  173. package/src/components/CnIndexPage/CnIndexPage.vue +87 -4
  174. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  175. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +46 -2
  176. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  177. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  178. package/src/components/CnStagesWidget/CnStagesWidget.vue +180 -2
  179. package/src/components/CnStagesWidget/stagesModel.js +5 -2
  180. package/src/components/CnStatWidget/CnStatWidget.vue +57 -3
  181. package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
  182. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  183. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  184. package/src/components/CnTimelineWidget/index.js +4 -0
  185. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  186. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  187. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  188. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
  189. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
  190. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  191. package/src/components/index.js +2 -0
  192. package/src/composables/useDependencyCheck.js +61 -0
  193. package/src/composables/useWalkthrough.js +217 -5
  194. package/src/css/dashboard.css +49 -0
  195. package/src/css/detail-page.css +29 -4
  196. package/src/css/kpi-card.css +24 -1
  197. package/src/css/table.css +99 -6
  198. package/src/index.js +2 -0
  199. package/src/schemas/app-manifest-v2.schema.json +135 -3
  200. package/src/store/useObjectStore.js +8 -0
  201. package/src/utils/columnFilters.js +324 -0
  202. package/src/utils/groupAutocomplete.js +119 -0
  203. package/src/utils/libraryWidgetKeys.js +2 -0
  204. package/src/utils/pickerOptions.js +194 -0
  205. package/src/utils/schema.js +83 -7
  206. package/src/utils/schemaRefSlug.js +19 -3
  207. package/src/utils/timelineEvents.js +171 -0
  208. package/src/utils/validateManifestV2.compiled.js +2 -2
  209. package/src/utils/walkthroughSignals.js +94 -0
@@ -199,6 +199,162 @@ export function persistWalkthroughSeenVersion(appId, configKey, version, options
199
199
  }
200
200
  }
201
201
 
202
+ /**
203
+ * localStorage key prefix for the per-browser mirror of tour progress.
204
+ */
205
+ export const WALKTHROUGH_PROGRESS_STORAGE_PREFIX = 'cn-walkthrough-progress:'
206
+
207
+ /**
208
+ * The preference key that holds tour progress, next to the completion key.
209
+ *
210
+ * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
211
+ * @return {string} `<configKey>-progress`, or '' when no key is declared.
212
+ */
213
+ export function walkthroughProgressKey(configKey) {
214
+ return configKey ? configKey + '-progress' : ''
215
+ }
216
+
217
+ /**
218
+ * Normalise stored progress (an object, or the JSON string a preference
219
+ * holds) into `{ tourId, stepId, index, version }`, or null.
220
+ *
221
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
222
+ * @param {unknown} value The stored value.
223
+ * @return {{tourId: string, stepId: string, index: number, version: string}|null} The progress.
224
+ */
225
+ export function normaliseWalkthroughProgress(value) {
226
+ let data = value
227
+ if (typeof data === 'string') {
228
+ if (data === '') {
229
+ return null
230
+ }
231
+ try {
232
+ data = JSON.parse(data)
233
+ } catch {
234
+ return null
235
+ }
236
+ }
237
+ if (!isPlainObject(data) || typeof data.tourId !== 'string' || data.tourId === '') {
238
+ return null
239
+ }
240
+ return {
241
+ tourId: data.tourId,
242
+ stepId: typeof data.stepId === 'string' ? data.stepId : '',
243
+ index: Number.isInteger(data.index) && data.index >= 0 ? data.index : 0,
244
+ version: typeof data.version === 'string' ? data.version : '',
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Read the per-browser mirror of the tour progress. Synchronous.
250
+ *
251
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
252
+ * @param {string} appId The Nextcloud app id.
253
+ * @param {Storage} [storage] Injectable storage backend.
254
+ * @return {object|null} The progress, or null.
255
+ */
256
+ export function readLocalWalkthroughProgress(appId, storage) {
257
+ const s = resolveStorage(storage)
258
+ if (!s) {
259
+ return null
260
+ }
261
+ try {
262
+ return normaliseWalkthroughProgress(s.getItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId))
263
+ } catch {
264
+ return null
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Load the user's tour progress: the per-user preference
270
+ * `<completionConfigKey>-progress` when declared, else the local mirror.
271
+ *
272
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
273
+ * @param {string} appId The Nextcloud app id.
274
+ * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
275
+ * @param {object} [options] `{ http, storage }` injection points for tests.
276
+ * @return {Promise<object|null>} The progress, or null.
277
+ */
278
+ export async function loadWalkthroughProgress(appId, configKey, options = {}) {
279
+ const local = readLocalWalkthroughProgress(appId, options.storage)
280
+ const key = walkthroughProgressKey(configKey)
281
+ if (!key) {
282
+ return local
283
+ }
284
+ const http = options.http || axios
285
+ try {
286
+ const { data } = await http.get(walkthroughPreferenceUrl(appId, key))
287
+ if (!isPlainObject(data) || !('value' in data)) {
288
+ return local
289
+ }
290
+ return normaliseWalkthroughProgress(data.value) || local
291
+ } catch {
292
+ return local
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Persist the user's tour progress, or clear it with `null`. Writes the
298
+ * local mirror and, with a completion key, the per-user preference. Never
299
+ * throws.
300
+ *
301
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
302
+ * @param {string} appId The Nextcloud app id.
303
+ * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
304
+ * @param {object|null} progress `{ tourId, stepId, index, version }`, or null to clear.
305
+ * @param {object} [options] `{ http, storage }` injection points for tests.
306
+ * @return {Promise<boolean>} True when the server preference was written.
307
+ */
308
+ export function persistWalkthroughProgress(appId, configKey, progress, options = {}) {
309
+ const value = progress ? normaliseWalkthroughProgress(progress) : null
310
+ const raw = value ? JSON.stringify(value) : ''
311
+ const s = resolveStorage(options.storage)
312
+ if (s) {
313
+ try {
314
+ if (raw) {
315
+ s.setItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId, raw)
316
+ } else {
317
+ s.removeItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId)
318
+ }
319
+ } catch {
320
+ /* quota / private mode: best-effort */
321
+ }
322
+ }
323
+ const key = walkthroughProgressKey(configKey)
324
+ if (!key) {
325
+ return Promise.resolve(false)
326
+ }
327
+ const http = options.http || axios
328
+ try {
329
+ return Promise.resolve(http.put(walkthroughPreferenceUrl(appId, key), { value: raw }))
330
+ .then(() => true)
331
+ .catch(() => false)
332
+ } catch {
333
+ return Promise.resolve(false)
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Whether an `object-created` signal satisfies a step's `advanceOn`.
339
+ *
340
+ * The signal's own `register` / `schema` (the slugs the creating surface
341
+ * knows) are compared first; an object's `@self` values are numeric ids on
342
+ * OpenRegister and only match a step that also uses ids.
343
+ *
344
+ * @param {object} advanceOn The step's `advanceOn`.
345
+ * @param {object} signal `{ register?, schema?, object? }`.
346
+ * @return {boolean} True when it matches.
347
+ */
348
+ function objectCreatedMatches(advanceOn, signal) {
349
+ const obj = signal.object || {}
350
+ const self = obj['@self'] || {}
351
+ const same = (want, candidates) => !want || candidates
352
+ .filter((c) => c !== null && c !== undefined && c !== '')
353
+ .some((c) => String(c).toLowerCase() === String(want).toLowerCase())
354
+ return same(advanceOn.register, [signal.register, obj.register, self.register])
355
+ && same(advanceOn.schema, [signal.schema, obj.schema, self.schema])
356
+ }
357
+
202
358
  /**
203
359
  * Compare two semver-ish strings. Missing / unparseable parts sort as 0.
204
360
  *
@@ -276,6 +432,9 @@ export function useWalkthrough(appId, manifest, options = {}) {
276
432
  const currentIndex = ref(0)
277
433
  const context = ref({})
278
434
  const running = ref(false)
435
+ // True while the tour is hidden by ESC or a backdrop click. The tour
436
+ // and its step stay, so it continues where it was.
437
+ const paused = ref(false)
279
438
  // True while a user-initiated replay is active (the "Restart tutorial"
280
439
  // entry). A replay shows the FULL tour regardless of the persisted
281
440
  // seen-version — otherwise a returning user, whose seenVersion already
@@ -479,6 +638,7 @@ export function useWalkthrough(appId, manifest, options = {}) {
479
638
  */
480
639
  function start(tourId, startIndex = 0) {
481
640
  replaying.value = false
641
+ paused.value = false
482
642
  activeTourId.value = tourId
483
643
  currentIndex.value = startIndex
484
644
  context.value = {}
@@ -503,9 +663,59 @@ export function useWalkthrough(appId, manifest, options = {}) {
503
663
  */
504
664
  function dismiss() {
505
665
  running.value = false
666
+ paused.value = false
506
667
  activeTourId.value = null
507
668
  replaying.value = false
508
669
  }
670
+ /**
671
+ * Hide the active tour without ending it (ESC, a click on the dim).
672
+ * The tour and step are kept, so `resumePaused()` continues there.
673
+ *
674
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
675
+ * @return {void}
676
+ */
677
+ function pause() {
678
+ if (!activeTourId.value) {
679
+ return
680
+ }
681
+ running.value = false
682
+ paused.value = true
683
+ }
684
+ /**
685
+ * Show a paused tour again at the step it was on.
686
+ *
687
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
688
+ * @return {boolean} True when there was a paused tour to show.
689
+ */
690
+ function resumePaused() {
691
+ if (!paused.value || !activeTourId.value) {
692
+ return false
693
+ }
694
+ paused.value = false
695
+ running.value = true
696
+ return true
697
+ }
698
+ /**
699
+ * Start a tour at a remembered step ("continue where you left off").
700
+ * Runs in replay mode so a returning user sees the full tour, and
701
+ * falls back to the first step when the step no longer exists.
702
+ *
703
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
704
+ * @param {string} tourId The tour id.
705
+ * @param {string} [stepId] The step to continue at.
706
+ * @return {boolean} True when the tour exists and started.
707
+ */
708
+ function resumeAt(tourId, stepId) {
709
+ const tour = tours.find((t) => t.id === tourId)
710
+ if (!tour) {
711
+ return false
712
+ }
713
+ replaying.value = true
714
+ const idx = composeSteps(tour).findIndex((s) => s.id === stepId)
715
+ start(tourId, Math.max(0, idx))
716
+ replaying.value = true
717
+ return true
718
+ }
509
719
  /**
510
720
  * Mark the active tour complete: stop running and invoke the
511
721
  * `onComplete(appVersion)` persistence callback so the seen version is
@@ -515,6 +725,7 @@ export function useWalkthrough(appId, manifest, options = {}) {
515
725
  */
516
726
  function complete() {
517
727
  running.value = false
728
+ paused.value = false
518
729
  activeTourId.value = null
519
730
  replaying.value = false
520
731
  if (typeof options.onComplete === 'function') {
@@ -542,12 +753,9 @@ export function useWalkthrough(appId, manifest, options = {}) {
542
753
  runCapture(step, { params: signal.params || {} })
543
754
  }
544
755
  } else if (signal.kind === 'object-created' && a.type === 'object-created') {
545
- const obj = signal.object || {}
546
- const reg = obj.register ?? obj['@self']?.register
547
- const sch = obj.schema ?? obj['@self']?.schema
548
- match = (!a.register || reg === a.register) && (!a.schema || sch === a.schema)
756
+ match = objectCreatedMatches(a, signal)
549
757
  if (match) {
550
- runCapture(step, { object: obj })
758
+ runCapture(step, { object: signal.object || {} })
551
759
  }
552
760
  } else if (signal.kind === 'element' && a.type === 'element-appears') {
553
761
  match = true
@@ -575,9 +783,13 @@ export function useWalkthrough(appId, manifest, options = {}) {
575
783
  isLast,
576
784
  context,
577
785
  running,
786
+ paused,
578
787
  replaying,
579
788
  start,
580
789
  restart,
790
+ pause,
791
+ resumePaused,
792
+ resumeAt,
581
793
  next,
582
794
  back,
583
795
  skip,
@@ -80,3 +80,52 @@
80
80
  .cn-dashboard-list__item:hover {
81
81
  background: var(--color-background-hover);
82
82
  }
83
+
84
+ /* "Install <app>" placeholder in a widget whose app is missing.
85
+ *
86
+ * NcEmptyContent centres its content with `justify-content: center`. In a
87
+ * two-row dashboard tile (about 150px inside an `overflow: hidden` wrapper)
88
+ * the icon, name, description and button are taller than the tile, and plain
89
+ * `center` overflows on BOTH sides, so the top ran out of the widget and the
90
+ * name was cut off. `safe center` falls back to the start edge when the
91
+ * content does not fit, the spacing is compact, and what still does not fit
92
+ * scrolls instead of being clipped. Shared by CnDashboardPage and
93
+ * CnDetailWidgetHost (`cn-requires-app`). The class is doubled to outrank
94
+ * NcEmptyContent's own scoped rules (`.empty-content[data-v-…]`) whatever
95
+ * order the stylesheets load in. */
96
+ .cn-requires-app.cn-requires-app.empty-content,
97
+ .cn-requires-app.cn-requires-app .empty-content {
98
+ justify-content: safe center;
99
+ min-height: 0;
100
+ max-height: 100%;
101
+ overflow: auto;
102
+ margin: 0;
103
+ padding: calc(2 * var(--default-grid-baseline, 4px));
104
+ gap: var(--default-grid-baseline, 4px);
105
+ box-sizing: border-box;
106
+ }
107
+
108
+ .cn-requires-app.cn-requires-app .empty-content__icon {
109
+ width: auto;
110
+ height: auto;
111
+ min-height: 0;
112
+ margin: 0;
113
+ opacity: 0.7;
114
+ }
115
+
116
+ .cn-requires-app.cn-requires-app .empty-content__name {
117
+ margin: 0;
118
+ font-size: var(--default-font-size, 15px);
119
+ font-weight: 600;
120
+ text-align: center;
121
+ }
122
+
123
+ .cn-requires-app.cn-requires-app .empty-content__description {
124
+ margin: 0;
125
+ text-align: center;
126
+ font-size: 0.9em;
127
+ }
128
+
129
+ .cn-requires-app.cn-requires-app .empty-content__action {
130
+ margin-top: var(--default-grid-baseline, 4px);
131
+ }
@@ -8,6 +8,12 @@
8
8
  position: relative;
9
9
  }
10
10
 
11
+ /* The breadcrumb line above the header (`config.breadcrumb`). */
12
+ .cn-detail-page__breadcrumbs {
13
+ margin-bottom: calc(2 * var(--default-grid-baseline));
14
+ color: var(--color-text-maxcontrast);
15
+ }
16
+
11
17
  /* Header */
12
18
  .cn-detail-page__header {
13
19
  display: flex;
@@ -106,6 +112,25 @@
106
112
  flex-shrink: 0;
107
113
  }
108
114
 
115
+ /* Every control in the header sits on one line at one height. The lifecycle
116
+ bar carries `margin: 0 0 8px` for its place above a page body; in the
117
+ header that margin pushed its buttons up and made the row 8px taller, so
118
+ the Edit button beside it read as the bigger one. A consumer's actions
119
+ component can bring its own margins too, so they are reset here as well. */
120
+ .cn-detail-page .cn-detail-page__header-actions .cn-lifecycle-actions {
121
+ margin: 0;
122
+ flex-wrap: nowrap;
123
+ }
124
+
125
+ .cn-detail-page .cn-detail-page__header-actions > * {
126
+ margin-block: 0;
127
+ }
128
+
129
+ .cn-detail-page .cn-detail-page__header-actions .button-vue {
130
+ min-height: var(--default-clickable-area);
131
+ block-size: var(--default-clickable-area);
132
+ }
133
+
109
134
  /* The sentence a refused lock acquire left behind, under the locked banner. */
110
135
  .cn-detail-page__lock-refusal {
111
136
  margin: 0 0 calc(3 * var(--default-grid-baseline));
@@ -156,7 +181,7 @@
156
181
 
157
182
  .cn-detail-page__stats-table thead th {
158
183
  padding: calc(1.5 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline));
159
- text-align: left;
184
+ text-align: start;
160
185
  font-weight: 600;
161
186
  font-size: 0.9em;
162
187
  color: var(--color-text-maxcontrast);
@@ -181,7 +206,7 @@
181
206
  }
182
207
 
183
208
  .cn-detail-page__stats-cell--indented {
184
- padding-left: calc(5 * var(--default-grid-baseline)) !important;
209
+ padding-inline-start: calc(5 * var(--default-grid-baseline)) !important;
185
210
  }
186
211
 
187
212
  .cn-detail-page__stats-cell--center {
@@ -189,7 +214,7 @@
189
214
  }
190
215
 
191
216
  .cn-detail-page__stats-cell--right {
192
- text-align: right;
217
+ text-align: end;
193
218
  }
194
219
 
195
220
  /* Body & content */
@@ -323,7 +348,7 @@
323
348
 
324
349
  .cn-detail-page__sidebar-toggle {
325
350
  position: fixed;
326
- right: calc(5 * var(--default-grid-baseline));
351
+ inset-inline-end: calc(5 * var(--default-grid-baseline));
327
352
  top: 80px;
328
353
  z-index: 10;
329
354
  }
@@ -133,7 +133,7 @@
133
133
  padding: var(--cn-kpi-padding-flat, 8px);
134
134
  box-sizing: border-box;
135
135
  overflow: hidden;
136
- text-align: left;
136
+ text-align: start;
137
137
  text-decoration: none;
138
138
  color: inherit;
139
139
  background: transparent;
@@ -248,6 +248,29 @@
248
248
  white-space: nowrap;
249
249
  }
250
250
 
251
+ /* A caption coloured by a rule (`captionVariant` / `overrides[].captionVariant`):
252
+ "1 due today" in the error colour. Foreground tokens, which stay readable in
253
+ dark mode. */
254
+ .cn-kpi-card__label--primary {
255
+ color: var(--color-primary-element);
256
+ font-weight: 600;
257
+ }
258
+
259
+ .cn-kpi-card__label--success {
260
+ color: var(--color-text-success, var(--color-success-text));
261
+ font-weight: 600;
262
+ }
263
+
264
+ .cn-kpi-card__label--warning {
265
+ color: var(--color-element-warning, var(--color-warning-text));
266
+ font-weight: 600;
267
+ }
268
+
269
+ .cn-kpi-card__label--error {
270
+ color: var(--color-text-error, var(--color-error-text));
271
+ font-weight: 600;
272
+ }
273
+
251
274
  /* Semantic variants. Each re-points the ONE accent token, so the icon tint
252
275
  and the number move together by construction — there is no second place to
253
276
  forget. The inline KPI below shares the same modifiers for the same reason:
package/src/css/table.css CHANGED
@@ -155,7 +155,7 @@
155
155
  .cn-data-table th,
156
156
  .cn-data-table td {
157
157
  padding: calc(3 * var(--default-grid-baseline));
158
- text-align: left;
158
+ text-align: start;
159
159
  border-bottom: 1px solid var(--color-border);
160
160
  vertical-align: middle;
161
161
  max-width: var(--cn-table-cell-max-width, 22rem);
@@ -174,6 +174,18 @@
174
174
  white-space: normal;
175
175
  }
176
176
 
177
+ /* The secondary line of a cell (`columns[].secondary`): muted, smaller, on its
178
+ own line under the value. Theme hooks: --cn-table-secondary-size and
179
+ --cn-table-secondary-color. */
180
+ .cn-data-table .cn-table-cell__secondary {
181
+ display: block;
182
+ color: var(--cn-table-secondary-color, var(--color-text-maxcontrast));
183
+ font-size: var(--cn-table-secondary-size, 0.9em);
184
+ line-height: 1.35;
185
+ overflow: hidden;
186
+ text-overflow: ellipsis;
187
+ }
188
+
177
189
  /* Back to wrapping, for a column holding prose rather than a label:
178
190
  `columns: [{ key: 'description', cellClass: 'cn-cell--wrap' }]`. */
179
191
  .cn-data-table th.cn-cell--wrap,
@@ -235,7 +247,7 @@
235
247
  }
236
248
 
237
249
  .cn-data-table td.cn-cell--end {
238
- text-align: right;
250
+ text-align: end;
239
251
  white-space: nowrap;
240
252
  }
241
253
 
@@ -273,7 +285,7 @@
273
285
  }
274
286
 
275
287
  .cn-table-sort-indicator {
276
- margin-left: var(--default-grid-baseline);
288
+ margin-inline-start: var(--default-grid-baseline);
277
289
  opacity: 0.6;
278
290
  }
279
291
 
@@ -295,6 +307,87 @@
295
307
  outline-offset: -2px;
296
308
  }
297
309
 
310
+ /* Header filter button (CnDataTable `filterable`). 24px hit area with the
311
+ 16px icon; the filled icon plus the primary colour marks an active filter,
312
+ so the state never rests on colour alone. */
313
+ .cn-table-header__filter {
314
+ display: inline-flex;
315
+ align-items: center;
316
+ justify-content: center;
317
+ width: 24px;
318
+ min-width: 24px;
319
+ height: 24px;
320
+ min-height: 24px;
321
+ margin: 0 0 0 4px;
322
+ padding: 0;
323
+ border: none;
324
+ border-radius: var(--border-radius, 4px);
325
+ background: transparent;
326
+ color: var(--color-text-maxcontrast);
327
+ vertical-align: middle;
328
+ cursor: pointer;
329
+ }
330
+
331
+ .cn-table-header__filter:hover {
332
+ background-color: var(--color-background-hover);
333
+ color: var(--color-main-text);
334
+ }
335
+
336
+ .cn-table-header__filter:focus-visible {
337
+ outline: 2px solid var(--color-main-text);
338
+ outline-offset: 0;
339
+ }
340
+
341
+ .cn-table-header__filter--active {
342
+ color: var(--color-primary-element);
343
+ }
344
+
345
+ /* Active header filters above the table, one removable chip each. */
346
+ .cn-data-table__filter-chips {
347
+ display: flex;
348
+ flex-wrap: wrap;
349
+ gap: 6px;
350
+ margin: 0 0 8px;
351
+ padding: 0;
352
+ list-style: none;
353
+ }
354
+
355
+ .cn-data-table__filter-chip {
356
+ display: inline-flex;
357
+ align-items: center;
358
+ gap: 4px;
359
+ padding: 2px 4px 2px 10px;
360
+ border: 1px solid var(--color-border-dark);
361
+ border-radius: var(--border-radius-pill, 16px);
362
+ background-color: var(--color-background-dark);
363
+ color: var(--color-main-text);
364
+ }
365
+
366
+ .cn-data-table__filter-chip-remove {
367
+ display: inline-flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+ width: 24px;
371
+ min-width: 24px;
372
+ height: 24px;
373
+ min-height: 24px;
374
+ margin: 0;
375
+ padding: 0;
376
+ border: none;
377
+ border-radius: 50%;
378
+ background: transparent;
379
+ color: var(--color-main-text);
380
+ cursor: pointer;
381
+ }
382
+
383
+ .cn-data-table__filter-chip-remove:hover {
384
+ background-color: var(--color-background-hover);
385
+ }
386
+
387
+ .cn-data-table__filter-chip-remove:focus-visible {
388
+ outline: 2px solid var(--color-main-text);
389
+ }
390
+
298
391
  /* Multi-column sort priority badge (1, 2, 3), rendered only when two or more rendered, sortable columns are sorted. */
299
392
  .cn-table-sort-badge {
300
393
  display: inline-flex;
@@ -302,7 +395,7 @@
302
395
  justify-content: center;
303
396
  min-width: 14px;
304
397
  height: 14px;
305
- margin-left: 2px;
398
+ margin-inline-start: 2px;
306
399
  padding: 0 3px;
307
400
  border-radius: 7px;
308
401
  background-color: var(--color-primary-element);
@@ -367,7 +460,7 @@
367
460
 
368
461
  .cn-table-col--icon {
369
462
  width: 40px;
370
- padding-right: 0;
463
+ padding-inline-end: 0;
371
464
  text-align: center;
372
465
  color: var(--color-text-maxcontrast);
373
466
  }
@@ -430,7 +523,7 @@
430
523
 
431
524
  .viewTable { width: 100%; border-collapse: collapse; background-color: var(--color-main-background); }
432
525
 
433
- .viewTable th, .viewTable td { padding: calc(3 * var(--default-grid-baseline)); text-align: left; border-bottom: 1px solid var(--color-border); vertical-align: middle; }
526
+ .viewTable th, .viewTable td { padding: calc(3 * var(--default-grid-baseline)); text-align: start; border-bottom: 1px solid var(--color-border); vertical-align: middle; }
434
527
 
435
528
  .viewTable th { background: var(--color-background-hover); font-weight: 500; color: var(--color-text-maxcontrast); background-color: var(--color-background-dark); }
436
529
 
package/src/index.js CHANGED
@@ -35,6 +35,7 @@ export {
35
35
  CnActionsBar,
36
36
  CnActionsMenu,
37
37
  CnAddWidgetModal,
38
+ CnAdminActionCard,
38
39
  CnAdminSettingsShell,
39
40
  CnAdvancedFormDialog,
40
41
  CnAppLoading,
@@ -268,6 +269,7 @@ export {
268
269
  CnTileWidget,
269
270
  CnTimelineStages,
270
271
  CnTimelineView,
272
+ CnTimelineWidget,
271
273
  CnTranslatedBadge,
272
274
  CnTreeView,
273
275
  CnUserActionMenu,