@conduction/nextcloud-vue 2.56.0-beta.11 → 2.56.0-beta.13

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 (138) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +253 -10
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +139 -46
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +87 -0
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +26 -24
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
  10. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  12. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
  13. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
  15. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
  17. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
  19. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  20. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
  21. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +154 -4
  24. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +555 -480
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
  28. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
  30. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +37 -1
  32. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  33. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +12 -4
  34. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +187 -19
  36. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +215 -135
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
  40. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
  42. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  44. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +157 -7
  45. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +23 -5
  47. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
  49. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +5 -1
  53. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  54. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  55. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  56. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
  57. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  58. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
  59. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  60. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
  61. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  62. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
  63. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
  65. package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
  66. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +74 -5
  67. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +15 -8
  69. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  70. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
  71. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  72. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
  73. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  74. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
  75. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  76. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
  77. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  78. package/dist/esm/l10n/en.json.js +5 -1
  79. package/dist/esm/l10n/en.json.js.map +1 -1
  80. package/dist/esm/l10n/nl.json.js +5 -1
  81. package/dist/esm/l10n/nl.json.js.map +1 -1
  82. package/dist/esm/mixins/pageViews.js +400 -0
  83. package/dist/esm/mixins/pageViews.js.map +1 -0
  84. package/dist/esm/nextcloud-vue.css +513 -81
  85. package/dist/esm/store/useObjectStore.js +118 -33
  86. package/dist/esm/store/useObjectStore.js.map +1 -1
  87. package/dist/esm/utils/actionsDispatcher.js +25 -2
  88. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  89. package/dist/esm/utils/columnFilters.js +27 -3
  90. package/dist/esm/utils/columnFilters.js.map +1 -1
  91. package/dist/esm/utils/schema.js +40 -2
  92. package/dist/esm/utils/schema.js.map +1 -1
  93. package/dist/esm/utils/schemaRefSlug.js +19 -3
  94. package/dist/esm/utils/schemaRefSlug.js.map +1 -1
  95. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  96. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  97. package/dist/nextcloud-vue.cjs.js +8858 -6566
  98. package/dist/nextcloud-vue.cjs.js.map +2 -2
  99. package/dist/nextcloud-vue.css +513 -81
  100. package/l10n/en.json +5 -0
  101. package/l10n/nl.json +5 -0
  102. package/package.json +1 -1
  103. package/src/components/CnAppNav/CnAppNav.vue +441 -14
  104. package/src/components/CnAppRoot/CnAppRoot.md +4 -0
  105. package/src/components/CnAppRoot/CnAppRoot.vue +94 -1
  106. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
  107. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
  108. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
  109. package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
  110. package/src/components/CnDashboardPage/CnDashboardPage.vue +237 -12
  111. package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
  112. package/src/components/CnDataTable/CnDataTable.vue +45 -1
  113. package/src/components/CnDetailPage/CnDetailPage.md +8 -0
  114. package/src/components/CnDetailPage/CnDetailPage.vue +263 -7
  115. package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
  116. package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
  117. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +206 -9
  118. package/src/components/CnIndexPage/CnIndexPage.vue +32 -2
  119. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +7 -1
  120. package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
  121. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
  122. package/src/components/CnStagesWidget/CnStagesWidget.vue +189 -2
  123. package/src/components/CnStagesWidget/stagesModel.js +5 -2
  124. package/src/components/CnStatWidget/CnStatWidget.vue +89 -7
  125. package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
  126. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
  127. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
  128. package/src/css/detail-page.css +37 -4
  129. package/src/css/kpi-card.css +71 -1
  130. package/src/css/table.css +18 -6
  131. package/src/mixins/pageViews.js +396 -0
  132. package/src/schemas/app-manifest-v2.schema.json +162 -3
  133. package/src/store/useObjectStore.js +118 -33
  134. package/src/utils/actionsDispatcher.js +25 -2
  135. package/src/utils/columnFilters.js +29 -2
  136. package/src/utils/schema.js +40 -2
  137. package/src/utils/schemaRefSlug.js +19 -3
  138. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -10,10 +10,11 @@ import OpenInNew from 'vue-material-design-icons/OpenInNew.vue';
10
10
  import Plus from 'vue-material-design-icons/Plus.vue';
11
11
  import ShieldAccountOutline from 'vue-material-design-icons/ShieldAccountOutline.vue';
12
12
  import '../CnIcon/CnIcon.vue.js';
13
- import __sfc_main from '../CnMenuWidget/CnMenuItemIcon.vue.js';
13
+ import __sfc_main$1 from '../CnMenuWidget/CnMenuItemIcon.vue.js';
14
14
  import { isAppInstalled } from '../../utils/appInstalled.js';
15
15
  import { isSvgPath } from '../../utils/iconUtils.js';
16
16
  import { passesContextPredicates } from '../../utils/visibleIfContext.js';
17
+ import __sfc_main from '../CnActionButtons/CnActionButtons.vue.js';
17
18
  import { bridgedMdiForCssIcon } from '../CnIcon/cssIconBridge.js';
18
19
  import { getSemanticIconComponent } from '../CnIcon/semanticIcons.js';
19
20
  import { isCustomIconUrl, hasRegistryIcon } from '../CnWidgetGrid/widgetIcons.js';
@@ -95,7 +96,8 @@ var script = {
95
96
  NcAppNavigationSettings,
96
97
  NcButton,
97
98
  NcCounterBubble,
98
- CnMenuItemIcon: __sfc_main,
99
+ CnActionButtons: __sfc_main,
100
+ CnMenuItemIcon: __sfc_main$1,
99
101
  Cog,
100
102
  HelpCircleOutline,
101
103
  ShieldAccountOutline,
@@ -141,6 +143,16 @@ var script = {
141
143
  * @type {{ [register: string]: { [schema: string]: number } }}
142
144
  */
143
145
  cnMenuCounts: { default: () => ({}) },
146
+ /**
147
+ * Provided by CnAppRoot — reactive `{ [menuItemId]: number }` totals
148
+ * for every menu entry whose `count` is an object
149
+ * (`{ register, schema, filter? }`): the total of that filtered
150
+ * list, fetched under the entry's own key so an index page's
151
+ * whole-schema total is left alone. Empty outside a CnAppRoot.
152
+ *
153
+ * @type {{ [itemId: string]: number }}
154
+ */
155
+ cnMenuItemCounts: { default: () => ({}) },
144
156
  },
145
157
 
146
158
  props: {
@@ -264,7 +276,7 @@ var script = {
264
276
  },
265
277
  },
266
278
 
267
- emits: ['primary-action', 'primary-action-click'],
279
+ emits: ['primary-action', 'primary-action-click', 'primary-action-created', 'card-action'],
268
280
 
269
281
  /**
270
282
  * The navigation's own state: which groups are open, and which entry
@@ -325,11 +337,70 @@ var script = {
325
337
  const text = (value) => (typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : '');
326
338
  const brand = {
327
339
  logo: typeof declared.logo === 'string' ? declared.logo : '',
340
+ // A URL, or `true` for the theme's emblem (--nldesign-emblem-url).
341
+ emblem: declared.emblem === true ? true : (typeof declared.emblem === 'string' ? declared.emblem : ''),
328
342
  name: text(declared.name),
329
343
  caption: text(declared.caption),
330
344
  alt: text(declared.alt),
331
345
  };
332
- return (brand.logo || brand.name || brand.caption) ? brand : null
346
+ return (brand.logo || brand.emblem || brand.name || brand.caption) ? brand : null
347
+ },
348
+
349
+ /**
350
+ * The card above the footer entries (`nav.card`): `{ title, text?,
351
+ * link?: { label, route?, params?, href?, action? } }`. Title, text
352
+ * and link label go through the translate function. The link is a
353
+ * router link for a `route` (with a router), an anchor for an
354
+ * `href`, and a button emitting `card-action` with the `action` id
355
+ * otherwise. Null without a title.
356
+ *
357
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
358
+ * @return {object|null}
359
+ */
360
+ resolvedCard() {
361
+ const declared = this.effectiveManifest?.nav?.card;
362
+ if (!declared || typeof declared !== 'object' || typeof declared.title !== 'string' || declared.title === '') {
363
+ return null
364
+ }
365
+ const text = (value) => (typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : '');
366
+ const card = { title: text(declared.title), text: text(declared.text), linkTag: '', linkAttrs: {}, linkLabel: '', action: '' };
367
+ const link = declared.link;
368
+ if (link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '') {
369
+ card.linkLabel = text(link.label);
370
+ if (link.route && this.$router) {
371
+ card.linkTag = 'router-link';
372
+ card.linkAttrs = { to: { name: link.route, ...(link.params && typeof link.params === 'object' ? { params: link.params } : {}) } };
373
+ } else if (typeof link.href === 'string' && link.href !== '') {
374
+ card.linkTag = 'a';
375
+ card.linkAttrs = { href: link.href };
376
+ } else if (typeof link.action === 'string' && link.action !== '') {
377
+ card.linkTag = 'button';
378
+ card.linkAttrs = { type: 'button' };
379
+ card.action = link.action;
380
+ }
381
+ }
382
+ return card
383
+ },
384
+
385
+ /**
386
+ * The help entry (`nav.help`): `{ label, route?, href? }` rendered as
387
+ * a footer entry with a help icon. Null without a label or a target.
388
+ *
389
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
390
+ * @return {{label: string, to?: object, href?: string}|null}
391
+ */
392
+ resolvedHelp() {
393
+ const declared = this.effectiveManifest?.nav?.help;
394
+ if (!declared || typeof declared !== 'object' || typeof declared.label !== 'string' || declared.label === '') {
395
+ return null
396
+ }
397
+ if (declared.route && this.$router) {
398
+ return { label: this.effectiveTranslate(declared.label), to: { name: declared.route }, href: undefined }
399
+ }
400
+ if (typeof declared.href === 'string' && declared.href !== '') {
401
+ return { label: this.effectiveTranslate(declared.label), to: undefined, href: declared.href }
402
+ }
403
+ return null
333
404
  },
334
405
 
335
406
  /**
@@ -350,20 +421,30 @@ var script = {
350
421
  * 2. `manifest.nav.primaryAction` as app-wide default
351
422
  * 3. `null` — no primary-action button renders
352
423
  *
353
- * Page-scoped declarations always win over the nav-root default.
424
+ * Page-scoped declarations always win over the nav-root default. An
425
+ * action declaring `permission` or `visibleIf` is gated like a menu
426
+ * entry.
354
427
  *
428
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
355
429
  * @return {object|null}
356
430
  */
357
431
  activePrimaryAction() {
358
432
  const pages = this.effectiveManifest?.pages ?? [];
359
433
  const routeName = this.$route?.name;
434
+ // The same `permission` / `visibleIf` gate a menu entry has: an
435
+ // action the reader may not take, or that a condition hides, is
436
+ // not drawn. A gated-out page action falls back to the nav one.
437
+ const gated = (action) => (action && this.passesPermission(action) && this.passesVisibleIf(action) ? action : null);
360
438
  if (routeName) {
361
439
  const page = pages.find((p) => p.id === routeName);
362
440
  if (page && page.primaryAction) {
363
- return page.primaryAction
441
+ const pageAction = gated(page.primaryAction);
442
+ if (pageAction) {
443
+ return pageAction
444
+ }
364
445
  }
365
446
  }
366
- return this.effectiveManifest?.nav?.primaryAction ?? null
447
+ return gated(this.effectiveManifest?.nav?.primaryAction ?? null)
367
448
  },
368
449
 
369
450
  /**
@@ -377,6 +458,31 @@ var script = {
377
458
  return this.mdiIconComponent(this.activePrimaryAction) ?? Plus
378
459
  },
379
460
 
461
+ /**
462
+ * The CnActionButtons entry for a primary action that carries an
463
+ * `action` (`$defs/action`: open-form, navigate, api-call, ...):
464
+ * the action with the primary action's `id`, `label` and `icon`
465
+ * folded in and `variant: "primary"`. Null for a plain primary
466
+ * action, which keeps its link or button.
467
+ *
468
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
469
+ * @return {object|null}
470
+ */
471
+ primaryDispatchEntry() {
472
+ const action = this.activePrimaryAction;
473
+ const inner = action && action.action;
474
+ if (!inner || typeof inner !== 'object' || typeof inner.type !== 'string') {
475
+ return null
476
+ }
477
+ return {
478
+ icon: action.icon || 'Plus',
479
+ ...inner,
480
+ id: inner.id || action.id || 'primary-action',
481
+ label: action.label,
482
+ variant: 'primary',
483
+ }
484
+ },
485
+
380
486
  /**
381
487
  * Link props for the primary action's button: an `href` opens in a
382
488
  * new tab, a `route` is a router link. Null when the action is
@@ -434,17 +540,113 @@ var script = {
434
540
  * Documentation, Features & Roadmap, About.
435
541
  */
436
542
  footerItems() {
437
- return this.visibleItems.filter((item) => item.section === 'footer')
543
+ const footer = this.visibleItems.filter((item) => item.section === 'footer');
544
+ const declared = this.declaredFooter;
545
+ if (declared === null) {
546
+ return footer
547
+ }
548
+ // A declared footer (`nav.footer`) keeps only the entries it names,
549
+ // in the order it names them.
550
+ return declared
551
+ .map((id) => footer.find((item) => item.id === id))
552
+ .filter(Boolean)
553
+ },
554
+
555
+ /**
556
+ * The footer the manifest declares (`nav.footer`): an ordered list of
557
+ * menu entry ids plus the reserved ids `help` (the `nav.help` entry)
558
+ * and `settings` (the settings foldout). Null when the manifest
559
+ * declares none, which keeps today's footer: the help entry, then
560
+ * every `section: "footer"` entry, then the foldout.
561
+ *
562
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
563
+ * @return {Array<string>|null}
564
+ */
565
+ declaredFooter() {
566
+ const declared = this.effectiveManifest?.nav?.footer;
567
+ if (!Array.isArray(declared)) {
568
+ return null
569
+ }
570
+ return declared.filter((id) => typeof id === 'string' && id !== '')
571
+ },
572
+
573
+ /**
574
+ * Footer-section entries a declared footer leaves out. They move into
575
+ * the settings foldout rather than disappearing, so a page such as
576
+ * Store or Reports stays reachable. Empty without `nav.footer`.
577
+ *
578
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
579
+ * @return {Array<object>}
580
+ */
581
+ undeclaredFooterItems() {
582
+ const declared = this.declaredFooter;
583
+ if (declared === null) {
584
+ return []
585
+ }
586
+ return this.visibleItems.filter((item) => item.section === 'footer' && !declared.includes(item.id))
587
+ },
588
+
589
+ /**
590
+ * Whether the help entry renders: whenever it resolves, unless a
591
+ * declared footer leaves `help` out.
592
+ *
593
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
594
+ * @return {boolean}
595
+ */
596
+ showsHelp() {
597
+ if (!this.resolvedHelp) {
598
+ return false
599
+ }
600
+ return this.declaredFooter === null || this.declaredFooter.includes('help')
601
+ },
602
+
603
+ /**
604
+ * Whether the help entry goes AFTER the footer entries. Only a declared
605
+ * footer that names an entry before `help` puts it there; by default
606
+ * it leads the list.
607
+ *
608
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
609
+ * @return {boolean}
610
+ */
611
+ helpAfterItems() {
612
+ const declared = this.declaredFooter;
613
+ if (declared === null) {
614
+ return false
615
+ }
616
+ const help = declared.indexOf('help');
617
+ return this.footerItems.some((item) => declared.indexOf(item.id) < help)
618
+ },
619
+
620
+ /**
621
+ * Whether the settings foldout goes ABOVE the footer list: only when a
622
+ * declared footer names `settings` before every other entry
623
+ * (`["settings", "help"]`, the board's "Instellingen" then "Hulp en
624
+ * uitleg"). Done with CSS `order` on the two siblings, so the markup
625
+ * of both stays as it is.
626
+ *
627
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
628
+ * @return {boolean}
629
+ */
630
+ settingsFirst() {
631
+ const declared = this.declaredFooter;
632
+ return declared !== null && declared.indexOf('settings') === 0
438
633
  },
439
634
 
440
635
  /**
441
636
  * Items that render INSIDE the NcAppNavigationSettings foldout
442
637
  * (section: "settings"). The foldout is the NC-native gear-icon
443
638
  * button that slides a panel open; these entries are app-level
444
- * configuration pages (Forms, Pipelines, Automations, …).
639
+ * configuration pages (Forms, Pipelines, Automations, …). A declared
640
+ * footer (`nav.footer`) adds the footer entries it leaves out.
641
+ *
642
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
643
+ * @return {Array<object>}
445
644
  */
446
645
  settingsItems() {
447
- return this.visibleItems.filter((item) => item.section === 'settings')
646
+ return [
647
+ ...this.visibleItems.filter((item) => item.section === 'settings'),
648
+ ...this.undeclaredFooterItems,
649
+ ]
448
650
  },
449
651
 
450
652
  /**
@@ -1241,6 +1443,9 @@ var script = {
1241
1443
  * `NcAppNavigationItem`).
1242
1444
  *
1243
1445
  * - Literal positive integer in `item.count` → return as-is.
1446
+ * - `item.count` is an object (`{ register, schema, filter? }`) →
1447
+ * `cnMenuItemCounts[item.id] ?? null`, the filtered total CnAppRoot
1448
+ * fetched for this entry.
1244
1449
  * - `item.count === "auto"` → look up the entry's resolved page;
1245
1450
  * when that page is `type: "index"` with a `register`/`schema`
1246
1451
  * in its `config`, return
@@ -1264,6 +1469,11 @@ var script = {
1264
1469
  if (typeof raw === 'number') {
1265
1470
  return raw > 0 ? raw : null
1266
1471
  }
1472
+ if (raw && typeof raw === 'object') {
1473
+ // A filtered count: CnAppRoot fetched it per entry id.
1474
+ const value = this.cnMenuItemCounts?.[item.id];
1475
+ return (typeof value === 'number' && value > 0) ? value : null
1476
+ }
1267
1477
  if (raw !== 'auto') {
1268
1478
  return null
1269
1479
  }
@@ -1574,6 +1784,39 @@ var script = {
1574
1784
  *
1575
1785
  * @return {void}
1576
1786
  */
1787
+ /**
1788
+ * Re-emit the object an `open-form` primary action created, so the
1789
+ * host can refresh a list or open the record.
1790
+ *
1791
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
1792
+ * @param {object} created The created object.
1793
+ * @return {void}
1794
+ */
1795
+ onPrimaryActionCreated(created) {
1796
+ /**
1797
+ * @event primary-action-created Emitted after an `open-form` primary action saves. Payload: the created object.
1798
+ */
1799
+ this.$emit('primary-action-created', created);
1800
+ },
1801
+
1802
+ /**
1803
+ * A card link that is a button (an `action` id) emits `card-action`
1804
+ * with that id for the host to run; links and anchors navigate on
1805
+ * their own.
1806
+ *
1807
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
1808
+ * @return {void}
1809
+ */
1810
+ onCardLinkClick() {
1811
+ const card = this.resolvedCard;
1812
+ if (card && card.action) {
1813
+ /**
1814
+ * @event card-action Emitted when the nav card's link is an action button. Payload: the action id.
1815
+ */
1816
+ this.$emit('card-action', card.action);
1817
+ }
1818
+ },
1819
+
1577
1820
  onPrimaryActionClick() {
1578
1821
  const action = this.activePrimaryAction;
1579
1822
  if (!action) {