@quazardous/qdadm 2.7.3 → 2.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quazardous/qdadm",
3
- "version": "2.7.3",
3
+ "version": "2.8.0",
4
4
  "description": "Vue 3 framework for admin dashboards with PrimeVue",
5
5
  "author": "quazardous",
6
6
  "license": "MIT",
@@ -241,7 +241,7 @@ watch(() => route.fullPath, () => {
241
241
 
242
242
  // Navigation context (breadcrumb + navlinks from route config)
243
243
  // Entity data comes from activeStack (populated by useEntityItemPage/useEntityItemFormPage)
244
- const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks, modeToggle } = useNavContext()
244
+ const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks, modeLinks } = useNavContext()
245
245
 
246
246
  // Allow child pages to override breadcrumb/navlinks via provide/inject
247
247
  const breadcrumbOverride = ref<BreadcrumbItem[] | null>(null)
@@ -261,10 +261,11 @@ const showBreadcrumb = computed<boolean>(() => {
261
261
  return true
262
262
  })
263
263
 
264
- // View↔Edit toggle on the terminal crumb (#1332/#1341) — same opt-in contract
265
- // as DefaultBreadcrumb; this inline breadcrumb must honor the flag too
266
- const shownModeToggle = computed(() =>
267
- features.breadcrumbModeToggle === true ? modeToggle.value : null
264
+ // View↔Edit mode links (#1332/#1341/#1353) — same opt-in contract as
265
+ // DefaultBreadcrumb; single opposite-mode link on item pages, the parent's
266
+ // View/Edit pair on child pages
267
+ const shownModeLinks = computed(() =>
268
+ features.breadcrumbModeToggle === true ? modeLinks.value : []
268
269
  )
269
270
  </script>
270
271
 
@@ -391,26 +392,27 @@ const shownModeToggle = computed(() =>
391
392
  </Breadcrumb>
392
393
 
393
394
  <!-- Navlinks (provided by PageNav for child routes) + View↔Edit mode
394
- toggle (#1332/#1341) — plain navigation; the form page's own
395
+ links (#1332/#1341/#1353) — plain navigation; the form page's own
395
396
  route-leave guard covers unsaved changes on edit→show -->
396
- <div v-if="navlinks.length > 0 || shownModeToggle" class="layout-navlinks">
397
- <template v-for="(link, index) in navlinks" :key="link.to?.name || index">
397
+ <div v-if="navlinks.length > 0 || shownModeLinks.length > 0" class="layout-navlinks">
398
+ <!-- mode links lead the group (#j7udkh), sibling tabs follow -->
399
+ <template v-for="(mode, index) in shownModeLinks" :key="`mode-${mode.target}`">
398
400
  <span v-if="index > 0" class="layout-navlinks-separator">|</span>
399
401
  <RouterLink
400
- :to="link.to"
401
- class="layout-navlink"
402
- :class="{ 'layout-navlink--active': link.active }"
402
+ :to="mode.to"
403
+ class="layout-navlink breadcrumb-mode-toggle"
403
404
  >
404
- {{ link.label }}
405
+ {{ mode.label }}
405
406
  </RouterLink>
406
407
  </template>
407
- <template v-if="shownModeToggle">
408
- <span v-if="navlinks.length > 0" class="layout-navlinks-separator">|</span>
408
+ <template v-for="(link, index) in navlinks" :key="link.to?.name || index">
409
+ <span v-if="shownModeLinks.length > 0 || index > 0" class="layout-navlinks-separator">|</span>
409
410
  <RouterLink
410
- :to="shownModeToggle.to"
411
- class="layout-navlink breadcrumb-mode-toggle"
411
+ :to="link.to"
412
+ class="layout-navlink"
413
+ :class="{ 'layout-navlink--active': link.active }"
412
414
  >
413
- {{ shownModeToggle.label }}
415
+ {{ link.label }}
414
416
  </RouterLink>
415
417
  </template>
416
418
  </div>
@@ -24,7 +24,7 @@ const route = useRoute()
24
24
  const features = inject<FeaturesConfig>('qdadmFeatures', { breadcrumb: true })
25
25
 
26
26
  // Navigation context (breadcrumb + navlinks from route config)
27
- const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks, modeToggle } = useNavContext()
27
+ const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks, modeLinks } = useNavContext()
28
28
 
29
29
  // Allow child pages to override breadcrumb/navlinks via provide/inject
30
30
  const breadcrumbOverride = inject<Ref<BreadcrumbItem[] | null>>('qdadmBreadcrumbOverride', ref(null))
@@ -42,10 +42,10 @@ const showBreadcrumb = computed<boolean>(() => {
42
42
  return true
43
43
  })
44
44
 
45
- // View↔Edit toggle on the terminal crumb (#1332) — opt-in, and only when
46
- // useNavContext resolved a reachable, permitted twin-mode route
47
- const shownModeToggle = computed(() =>
48
- features.breadcrumbModeToggle === true ? modeToggle.value : null
45
+ // View↔Edit mode links (#1332/#1353) — opt-in; single opposite-mode link on
46
+ // item pages, the parent's View/Edit pair on child pages
47
+ const shownModeLinks = computed(() =>
48
+ features.breadcrumbModeToggle === true ? modeLinks.value : []
49
49
  )
50
50
  </script>
51
51
 
@@ -65,26 +65,27 @@ const shownModeToggle = computed(() =>
65
65
  </Breadcrumb>
66
66
 
67
67
  <!-- Navlinks (provided by PageNav for child routes) + View↔Edit mode
68
- toggle (#1332/#1341) — plain navigation; the form page's own
68
+ links (#1332/#1341/#1353) — plain navigation; the form page's own
69
69
  route-leave guard covers unsaved changes on edit→show -->
70
- <div v-if="navlinks.length > 0 || shownModeToggle" class="breadcrumb-navlinks">
71
- <template v-for="(link, index) in navlinks" :key="link.to?.name || index">
70
+ <div v-if="navlinks.length > 0 || shownModeLinks.length > 0" class="breadcrumb-navlinks">
71
+ <!-- mode links lead the group (#j7udkh), sibling tabs follow -->
72
+ <template v-for="(mode, index) in shownModeLinks" :key="`mode-${mode.target}`">
72
73
  <span v-if="index > 0" class="navlinks-separator">|</span>
73
74
  <RouterLink
74
- :to="link.to"
75
- class="navlink"
76
- :class="{ 'navlink--active': link.active }"
75
+ :to="mode.to"
76
+ class="navlink breadcrumb-mode-toggle"
77
77
  >
78
- {{ link.label }}
78
+ {{ mode.label }}
79
79
  </RouterLink>
80
80
  </template>
81
- <template v-if="shownModeToggle">
82
- <span v-if="navlinks.length > 0" class="navlinks-separator">|</span>
81
+ <template v-for="(link, index) in navlinks" :key="link.to?.name || index">
82
+ <span v-if="shownModeLinks.length > 0 || index > 0" class="navlinks-separator">|</span>
83
83
  <RouterLink
84
- :to="shownModeToggle.to"
85
- class="navlink breadcrumb-mode-toggle"
84
+ :to="link.to"
85
+ class="navlink"
86
+ :class="{ 'navlink--active': link.active }"
86
87
  >
87
- {{ shownModeToggle.label }}
88
+ {{ link.label }}
88
89
  </RouterLink>
89
90
  </template>
90
91
  </div>
@@ -76,9 +76,10 @@ export interface BreadcrumbItem {
76
76
  * permissions are honored once the item is loaded.
77
77
  */
78
78
  export interface BreadcrumbModeToggle {
79
- /** Mode of the current route */
80
- current: 'show' | 'edit'
81
- /** Mode the toggle navigates to */
79
+ /** Mode of the current route — absent on child pages (#1353), where the
80
+ * page is in neither mode of the parent item */
81
+ current?: 'show' | 'edit'
82
+ /** Mode the link navigates to */
82
83
  target: 'show' | 'edit'
83
84
  /** Twin-route location — a plain router.push; any dirty-form protection
84
85
  * comes from the form page's own onBeforeRouteLeave guard */
@@ -130,6 +131,7 @@ export interface UseNavContextReturn {
130
131
  breadcrumb: ComputedRef<BreadcrumbItem[]>
131
132
  navlinks: ComputedRef<NavLinkItem[]>
132
133
  modeToggle: ComputedRef<BreadcrumbModeToggle | null>
134
+ modeLinks: ComputedRef<BreadcrumbModeToggle[]>
133
135
 
134
136
  // Helpers
135
137
  parentConfig: ComputedRef<ParentConfig | undefined>
@@ -443,7 +445,7 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
443
445
  })
444
446
 
445
447
  // ============================================================================
446
- // MODE TOGGLE (#1332)
448
+ // MODE TOGGLE / MODE LINKS (#1332, #1353)
447
449
  // ============================================================================
448
450
 
449
451
  function toggleLabel(target: 'show' | 'edit'): string {
@@ -452,6 +454,26 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
452
454
  return target === 'edit' ? 'Edit' : 'View'
453
455
  }
454
456
 
457
+ /** Build one mode link if its route exists and (for edit) permission allows. */
458
+ function buildModeLink(
459
+ manager: EntityManager,
460
+ target: 'show' | 'edit',
461
+ id: unknown,
462
+ permissionEntity: unknown
463
+ ): BreadcrumbModeToggle | null {
464
+ const routeName = `${manager.routePrefix}-${target}`
465
+ if (!router.hasRoute(routeName)) return null
466
+ if (target === 'edit' && !manager.canUpdate(permissionEntity ?? undefined)) return null
467
+ return {
468
+ target,
469
+ to: { name: routeName, params: { [manager.idField || 'id']: id as string } },
470
+ label: toggleLabel(target),
471
+ }
472
+ }
473
+
474
+ // Item pages (#1332): the terminal is entity-show/entity-edit — offer the
475
+ // OPPOSITE mode (getDefaultItemRoute's edit-preference is a landing
476
+ // default, not a toggle).
455
477
  const modeToggle = computed<BreadcrumbModeToggle | null>(() => {
456
478
  void i18nLocale.value // label re-resolves on locale change
457
479
 
@@ -464,20 +486,29 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
464
486
  if (!manager?.routePrefix) return null
465
487
 
466
488
  const current = terminal.kind === 'entity-edit' ? 'edit' : 'show'
467
- // Target is the OPPOSITE of the current mode — getDefaultItemRoute's
468
- // edit-preference is a landing default, not a toggle.
469
489
  const target = current === 'edit' ? 'show' : 'edit'
470
- const twinRoute = `${manager.routePrefix}-${target}`
471
- if (!router.hasRoute(twinRoute)) return null
472
-
473
- if (target === 'edit' && !manager.canUpdate(entityData.value ?? undefined)) return null
490
+ const link = buildModeLink(manager, target, terminal.id, entityData.value)
491
+ return link ? { ...link, current } : null
492
+ })
474
493
 
475
- return {
476
- current,
477
- target,
478
- to: { name: twinRoute, params: { [manager.idField || 'id']: terminal.id } },
479
- label: toggleLabel(target),
480
- }
494
+ // Uniform list for rendering (#1353). Item pages: the single opposite-mode
495
+ // entry (modeToggle). Child pages (route carries meta.parent): the PARENT
496
+ // item's applicable pair — the page is in neither mode, so both View and
497
+ // Edit are offered, each under the same existence/permission rules. On a
498
+ // child page, `entityData` resolves to the parent item (last item segment
499
+ // of the chain), so the canUpdate gate is ownership-aware once hydrated.
500
+ const modeLinks = computed<BreadcrumbModeToggle[]>(() => {
501
+ const itemToggle = modeToggle.value
502
+ if (itemToggle) return [itemToggle]
503
+
504
+ void i18nLocale.value
505
+ if (!parentConfig.value || parentId.value == null) return []
506
+ const manager = getManager(parentConfig.value.entity)
507
+ if (!manager?.routePrefix) return []
508
+
509
+ return (['show', 'edit'] as const)
510
+ .map((target) => buildModeLink(manager, target, parentId.value, entityData.value))
511
+ .filter((link): link is BreadcrumbModeToggle => link !== null)
481
512
  })
482
513
 
483
514
  return {
@@ -494,6 +525,7 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
494
525
  breadcrumb,
495
526
  navlinks,
496
527
  modeToggle,
528
+ modeLinks,
497
529
 
498
530
  // Helpers
499
531
  parentConfig,