@quazardous/qdadm 2.8.0 → 2.9.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/README.md +17 -17
- package/package.json +3 -2
- package/src/components/index.ts +1 -0
- package/src/components/layout/AppLayout.vue +4 -57
- package/src/components/layout/NavlinksGroup.vue +153 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +4 -58
- package/src/composables/useNavContext.ts +13 -4
- package/src/composables/useSemanticBreadcrumb.ts +7 -2
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
**Vue 3 admin framework. PrimeVue. Zero boilerplate.**
|
|
4
4
|
|
|
5
|
-
Quick start: [
|
|
5
|
+
Quick start: [README](https://github.com/quazardous/qdadm/blob/main/README.md) (5 min tutorial)
|
|
6
6
|
|
|
7
|
-
Concepts & patterns: [QDADM_CREDO
|
|
7
|
+
Concepts & patterns: [QDADM_CREDO](https://github.com/quazardous/qdadm/blob/main/packages/qdadm/QDADM_CREDO.md)
|
|
8
8
|
|
|
9
|
-
Changelog: [
|
|
9
|
+
Changelog: [CHANGELOG](https://github.com/quazardous/qdadm/blob/main/CHANGELOG.md)
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
@@ -95,20 +95,20 @@ Options: `transformRequest`, `transformResponse`, `responseFormat`, `clientSideP
|
|
|
95
95
|
|
|
96
96
|
| Doc | Purpose |
|
|
97
97
|
|-----|---------|
|
|
98
|
-
| [QDADM_CREDO](QDADM_CREDO.md) | Philosophy, patterns, concepts |
|
|
99
|
-
| [Page compositions](
|
|
100
|
-
| [CRUD Pages](
|
|
101
|
-
| [Navigation](
|
|
102
|
-
| [Architecture](
|
|
103
|
-
| [Extension](
|
|
104
|
-
| [Hooks](
|
|
105
|
-
| [Signals](
|
|
106
|
-
| [Zones](
|
|
107
|
-
| [Security](
|
|
108
|
-
| [i18n](
|
|
109
|
-
| [Codegen](
|
|
110
|
-
| [Deferred](
|
|
111
|
-
| [AGENT_GUIDE](
|
|
98
|
+
| [QDADM_CREDO](https://github.com/quazardous/qdadm/blob/main/packages/qdadm/QDADM_CREDO.md) | Philosophy, patterns, concepts |
|
|
99
|
+
| [Page compositions](https://github.com/quazardous/qdadm/blob/main/docs/page-compositions.md) | **Start here** — which composition for which need |
|
|
100
|
+
| [CRUD Pages](https://github.com/quazardous/qdadm/blob/main/docs/crud.md) | List / form / show / child pages reference |
|
|
101
|
+
| [Navigation](https://github.com/quazardous/qdadm/blob/main/docs/navigation.md) | Breadcrumb, sibling navlinks, View↔Edit toggle |
|
|
102
|
+
| [Architecture](https://github.com/quazardous/qdadm/blob/main/docs/architecture.md) | PAC pattern, layers |
|
|
103
|
+
| [Extension](https://github.com/quazardous/qdadm/blob/main/docs/extension.md) | Decorators, bundles, multi-storage |
|
|
104
|
+
| [Hooks](https://github.com/quazardous/qdadm/blob/main/docs/hooks.md) | Alter / invoke registry |
|
|
105
|
+
| [Signals](https://github.com/quazardous/qdadm/blob/main/docs/signals.md) | Event bus, SSE bridge |
|
|
106
|
+
| [Zones](https://github.com/quazardous/qdadm/blob/main/docs/zones.md) | Extensible UI blocks |
|
|
107
|
+
| [Security](https://github.com/quazardous/qdadm/blob/main/docs/security.md) | Permissions, roles |
|
|
108
|
+
| [i18n](https://github.com/quazardous/qdadm/blob/main/docs/i18n.md) | Messages, providers, strategies |
|
|
109
|
+
| [Codegen](https://github.com/quazardous/qdadm/blob/main/docs/gen.md) | Generate managers (OpenAPI / manual) |
|
|
110
|
+
| [Deferred](https://github.com/quazardous/qdadm/blob/main/docs/deferred.md) | Deferred values & warmup |
|
|
111
|
+
| [AGENT_GUIDE](https://github.com/quazardous/qdadm/blob/main/docs/AGENT_GUIDE.md) | Quick index for AI agents |
|
|
112
112
|
|
|
113
113
|
## Peer Dependencies
|
|
114
114
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quazardous/qdadm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Vue 3 framework for admin dashboards with PrimeVue",
|
|
5
5
|
"author": "quazardous",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/quazardous/qdadm.git"
|
|
9
|
+
"url": "git+https://github.com/quazardous/qdadm.git",
|
|
10
|
+
"directory": "packages/qdadm"
|
|
10
11
|
},
|
|
11
12
|
"homepage": "https://github.com/quazardous/qdadm#readme",
|
|
12
13
|
"bugs": {
|
package/src/components/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { default as Breadcrumb } from './layout/Breadcrumb.vue'
|
|
|
11
11
|
export { default as PageNav } from './layout/PageNav.vue'
|
|
12
12
|
export { default as Zone } from './layout/Zone.vue'
|
|
13
13
|
export { default as SidebarBox } from './layout/SidebarBox.vue'
|
|
14
|
+
export { default as NavlinksGroup } from './layout/NavlinksGroup.vue'
|
|
14
15
|
|
|
15
16
|
// Default zone components
|
|
16
17
|
export { default as DefaultHeader } from './layout/defaults/DefaultHeader.vue'
|
|
@@ -25,6 +25,7 @@ import Breadcrumb from 'primevue/breadcrumb'
|
|
|
25
25
|
import UnsavedChangesDialog from '../dialogs/UnsavedChangesDialog.vue'
|
|
26
26
|
import SidebarBox from './SidebarBox.vue'
|
|
27
27
|
import Zone from './Zone.vue'
|
|
28
|
+
import NavlinksGroup from './NavlinksGroup.vue'
|
|
28
29
|
import qdadmLogo from '../../assets/logo.svg'
|
|
29
30
|
import { version as qdadmVersion } from '../../../package.json'
|
|
30
31
|
|
|
@@ -261,12 +262,6 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
261
262
|
return true
|
|
262
263
|
})
|
|
263
264
|
|
|
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 : []
|
|
269
|
-
)
|
|
270
265
|
</script>
|
|
271
266
|
|
|
272
267
|
<template>
|
|
@@ -392,30 +387,8 @@ const shownModeLinks = computed(() =>
|
|
|
392
387
|
</Breadcrumb>
|
|
393
388
|
|
|
394
389
|
<!-- Navlinks (provided by PageNav for child routes) + View↔Edit mode
|
|
395
|
-
links (#1332/#1341/#1353) —
|
|
396
|
-
|
|
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}`">
|
|
400
|
-
<span v-if="index > 0" class="layout-navlinks-separator">|</span>
|
|
401
|
-
<RouterLink
|
|
402
|
-
:to="mode.to"
|
|
403
|
-
class="layout-navlink breadcrumb-mode-toggle"
|
|
404
|
-
>
|
|
405
|
-
{{ mode.label }}
|
|
406
|
-
</RouterLink>
|
|
407
|
-
</template>
|
|
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>
|
|
410
|
-
<RouterLink
|
|
411
|
-
:to="link.to"
|
|
412
|
-
class="layout-navlink"
|
|
413
|
-
:class="{ 'layout-navlink--active': link.active }"
|
|
414
|
-
>
|
|
415
|
-
{{ link.label }}
|
|
416
|
-
</RouterLink>
|
|
417
|
-
</template>
|
|
418
|
-
</div>
|
|
390
|
+
links (#1332/#1341/#1353) — shared rendering + dedup (#1357) -->
|
|
391
|
+
<NavlinksGroup :navlinks="navlinks" :mode-links="modeLinks" variant="layout" />
|
|
419
392
|
</div>
|
|
420
393
|
|
|
421
394
|
<div class="page-content">
|
|
@@ -723,33 +696,7 @@ const shownModeLinks = computed(() =>
|
|
|
723
696
|
padding-bottom: 0;
|
|
724
697
|
}
|
|
725
698
|
|
|
726
|
-
.
|
|
727
|
-
display: flex;
|
|
728
|
-
align-items: center;
|
|
729
|
-
gap: 0.375rem;
|
|
730
|
-
font-size: var(--fad-font-size-sm);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
.layout-navlinks-separator {
|
|
734
|
-
color: var(--p-surface-400, #94a3b8);
|
|
735
|
-
opacity: 0.5;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
.layout-navlink {
|
|
739
|
-
color: var(--p-primary-500, #3b82f6);
|
|
740
|
-
text-decoration: none;
|
|
741
|
-
transition: color var(--fad-transition-fast);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
.layout-navlink:hover {
|
|
745
|
-
color: var(--p-primary-600, #2563eb);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
.layout-navlink--active {
|
|
749
|
-
color: var(--p-surface-600, #475569);
|
|
750
|
-
font-weight: var(--fad-font-weight-medium);
|
|
751
|
-
pointer-events: none;
|
|
752
|
-
}
|
|
699
|
+
/* Navlinks styles moved to NavlinksGroup.vue (#1357) */
|
|
753
700
|
|
|
754
701
|
/* Override PrimeVue Breadcrumb styles for flat look */
|
|
755
702
|
.layout-nav-bar :deep(.p-breadcrumb) {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* NavlinksGroup - the right-side navigation group of a breadcrumb bar (#1357)
|
|
4
|
+
*
|
|
5
|
+
* Single rendering of the contract previously copy-pasted between AppLayout's
|
|
6
|
+
* inline breadcrumb and DefaultBreadcrumb: View↔Edit mode links LEAD the
|
|
7
|
+
* group (#1332/#1341/#1353, order #j7udkh), sibling navlinks follow, pipe
|
|
8
|
+
* separators between every pair, plain text (no icons).
|
|
9
|
+
*
|
|
10
|
+
* Owns the two behaviors both call sites need:
|
|
11
|
+
* - the `qdadmFeatures.breadcrumbModeToggle` opt-in gate on mode links
|
|
12
|
+
* - dedup (#1357): a navlink whose target route is already covered by a
|
|
13
|
+
* shown mode link is dropped — e.g. the auto "Details" link resolves to
|
|
14
|
+
* the parent's edit route and would render twice ("Details" + "Edit").
|
|
15
|
+
* With the feature off, no mode links are shown and every navlink stays
|
|
16
|
+
* (back-compat).
|
|
17
|
+
*
|
|
18
|
+
* `variant` keeps the historical CSS hooks of each call site so consumer
|
|
19
|
+
* overrides keep working: 'layout' = AppLayout classes, 'breadcrumb' =
|
|
20
|
+
* DefaultBreadcrumb classes.
|
|
21
|
+
*/
|
|
22
|
+
import { computed, inject } from 'vue'
|
|
23
|
+
import { RouterLink } from 'vue-router'
|
|
24
|
+
import type { NavLinkItem, BreadcrumbModeToggle } from '../../composables/useNavContext'
|
|
25
|
+
|
|
26
|
+
interface FeaturesConfig {
|
|
27
|
+
breadcrumbModeToggle?: boolean
|
|
28
|
+
[key: string]: unknown
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(
|
|
32
|
+
defineProps<{
|
|
33
|
+
navlinks?: NavLinkItem[]
|
|
34
|
+
modeLinks?: BreadcrumbModeToggle[]
|
|
35
|
+
variant?: 'layout' | 'breadcrumb'
|
|
36
|
+
}>(),
|
|
37
|
+
{
|
|
38
|
+
navlinks: () => [],
|
|
39
|
+
modeLinks: () => [],
|
|
40
|
+
variant: 'breadcrumb',
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const features = inject<FeaturesConfig>('qdadmFeatures', {})
|
|
45
|
+
|
|
46
|
+
// Opt-in (#1332): mode links render only when the feature flag is on
|
|
47
|
+
const shownModeLinks = computed<BreadcrumbModeToggle[]>(() =>
|
|
48
|
+
features.breadcrumbModeToggle === true ? props.modeLinks : []
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
// Dedup (#1357): drop navlinks targeting a route a mode link already covers
|
|
52
|
+
const shownNavlinks = computed<NavLinkItem[]>(() => {
|
|
53
|
+
if (shownModeLinks.value.length === 0) return props.navlinks
|
|
54
|
+
const covered = new Set(shownModeLinks.value.map((m) => m.to.name))
|
|
55
|
+
return props.navlinks.filter((link) => !link.to?.name || !covered.has(link.to.name))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const cls = computed(() =>
|
|
59
|
+
props.variant === 'layout'
|
|
60
|
+
? {
|
|
61
|
+
container: 'layout-navlinks',
|
|
62
|
+
separator: 'layout-navlinks-separator',
|
|
63
|
+
link: 'layout-navlink',
|
|
64
|
+
active: 'layout-navlink--active',
|
|
65
|
+
}
|
|
66
|
+
: {
|
|
67
|
+
container: 'breadcrumb-navlinks',
|
|
68
|
+
separator: 'navlinks-separator',
|
|
69
|
+
link: 'navlink',
|
|
70
|
+
active: 'navlink--active',
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<!-- Plain navigation; the form page's own route-leave guard covers
|
|
77
|
+
unsaved changes on edit→show -->
|
|
78
|
+
<div v-if="shownNavlinks.length > 0 || shownModeLinks.length > 0" :class="cls.container">
|
|
79
|
+
<!-- mode links lead the group (#j7udkh), sibling tabs follow -->
|
|
80
|
+
<template v-for="(mode, index) in shownModeLinks" :key="`mode-${mode.target}`">
|
|
81
|
+
<span v-if="index > 0" :class="cls.separator">|</span>
|
|
82
|
+
<RouterLink :to="mode.to" class="breadcrumb-mode-toggle" :class="cls.link">
|
|
83
|
+
{{ mode.label }}
|
|
84
|
+
</RouterLink>
|
|
85
|
+
</template>
|
|
86
|
+
<template v-for="(link, index) in shownNavlinks" :key="link.to?.name || index">
|
|
87
|
+
<span v-if="shownModeLinks.length > 0 || index > 0" :class="cls.separator">|</span>
|
|
88
|
+
<RouterLink :to="link.to" :class="[cls.link, { [cls.active]: link.active }]">
|
|
89
|
+
{{ link.label }}
|
|
90
|
+
</RouterLink>
|
|
91
|
+
</template>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<style scoped>
|
|
96
|
+
/* Historical styles of each call site, keyed by variant container so the
|
|
97
|
+
look of both bars is preserved exactly (AppLayout was compact). */
|
|
98
|
+
.layout-navlinks {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 0.375rem;
|
|
102
|
+
font-size: var(--fad-font-size-sm);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.layout-navlinks-separator {
|
|
106
|
+
color: var(--p-surface-400, #94a3b8);
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.layout-navlink {
|
|
111
|
+
color: var(--p-primary-500, #3b82f6);
|
|
112
|
+
text-decoration: none;
|
|
113
|
+
transition: color var(--fad-transition-fast);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.layout-navlink:hover {
|
|
117
|
+
color: var(--p-primary-600, #2563eb);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.layout-navlink--active {
|
|
121
|
+
color: var(--p-surface-600, #475569);
|
|
122
|
+
font-weight: var(--fad-font-weight-medium);
|
|
123
|
+
pointer-events: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.breadcrumb-navlinks {
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: 0.5rem;
|
|
130
|
+
font-size: 0.875rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.navlinks-separator {
|
|
134
|
+
color: var(--p-surface-400, #94a3b8);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.navlink {
|
|
138
|
+
color: var(--p-primary-500, #3b82f6);
|
|
139
|
+
text-decoration: none;
|
|
140
|
+
transition: color 0.15s;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.navlink:hover {
|
|
144
|
+
color: var(--p-primary-700, #1d4ed8);
|
|
145
|
+
text-decoration: underline;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.navlink--active {
|
|
149
|
+
color: var(--p-surface-700, #334155);
|
|
150
|
+
font-weight: 500;
|
|
151
|
+
pointer-events: none;
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
@@ -12,11 +12,10 @@ import { computed, inject, ref, type Ref } from 'vue'
|
|
|
12
12
|
import { RouterLink, useRoute } from 'vue-router'
|
|
13
13
|
import { useNavContext, type BreadcrumbItem, type NavLinkItem } from '../../../composables/useNavContext'
|
|
14
14
|
import Breadcrumb from 'primevue/breadcrumb'
|
|
15
|
+
import NavlinksGroup from '../NavlinksGroup.vue'
|
|
15
16
|
|
|
16
17
|
interface FeaturesConfig {
|
|
17
18
|
breadcrumb?: boolean
|
|
18
|
-
/** Opt-in (#1332): render a View↔Edit toggle next to the terminal crumb */
|
|
19
|
-
breadcrumbModeToggle?: boolean
|
|
20
19
|
[key: string]: unknown
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -42,11 +41,6 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
42
41
|
return true
|
|
43
42
|
})
|
|
44
43
|
|
|
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
|
-
)
|
|
50
44
|
</script>
|
|
51
45
|
|
|
52
46
|
<template>
|
|
@@ -65,30 +59,8 @@ const shownModeLinks = computed(() =>
|
|
|
65
59
|
</Breadcrumb>
|
|
66
60
|
|
|
67
61
|
<!-- Navlinks (provided by PageNav for child routes) + View↔Edit mode
|
|
68
|
-
links (#1332/#1341/#1353) —
|
|
69
|
-
|
|
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}`">
|
|
73
|
-
<span v-if="index > 0" class="navlinks-separator">|</span>
|
|
74
|
-
<RouterLink
|
|
75
|
-
:to="mode.to"
|
|
76
|
-
class="navlink breadcrumb-mode-toggle"
|
|
77
|
-
>
|
|
78
|
-
{{ mode.label }}
|
|
79
|
-
</RouterLink>
|
|
80
|
-
</template>
|
|
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
|
-
<RouterLink
|
|
84
|
-
:to="link.to"
|
|
85
|
-
class="navlink"
|
|
86
|
-
:class="{ 'navlink--active': link.active }"
|
|
87
|
-
>
|
|
88
|
-
{{ link.label }}
|
|
89
|
-
</RouterLink>
|
|
90
|
-
</template>
|
|
91
|
-
</div>
|
|
62
|
+
links (#1332/#1341/#1353) — shared rendering + dedup (#1357) -->
|
|
63
|
+
<NavlinksGroup :navlinks="navlinks" :mode-links="modeLinks" variant="breadcrumb" />
|
|
92
64
|
</div>
|
|
93
65
|
</template>
|
|
94
66
|
|
|
@@ -105,33 +77,7 @@ const shownModeLinks = computed(() =>
|
|
|
105
77
|
padding-bottom: 0;
|
|
106
78
|
}
|
|
107
79
|
|
|
108
|
-
.
|
|
109
|
-
display: flex;
|
|
110
|
-
align-items: center;
|
|
111
|
-
gap: 0.5rem;
|
|
112
|
-
font-size: 0.875rem;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.navlinks-separator {
|
|
116
|
-
color: var(--p-surface-400, #94a3b8);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.navlink {
|
|
120
|
-
color: var(--p-primary-500, #3b82f6);
|
|
121
|
-
text-decoration: none;
|
|
122
|
-
transition: color 0.15s;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.navlink:hover {
|
|
126
|
-
color: var(--p-primary-700, #1d4ed8);
|
|
127
|
-
text-decoration: underline;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.navlink--active {
|
|
131
|
-
color: var(--p-surface-700, #334155);
|
|
132
|
-
font-weight: 500;
|
|
133
|
-
pointer-events: none;
|
|
134
|
-
}
|
|
80
|
+
/* Navlinks styles moved to NavlinksGroup.vue (#1357) */
|
|
135
81
|
|
|
136
82
|
/* Override PrimeVue Breadcrumb styles for flat look */
|
|
137
83
|
.default-breadcrumb :deep(.p-breadcrumb) {
|
|
@@ -377,6 +377,8 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
|
|
|
377
377
|
})
|
|
378
378
|
|
|
379
379
|
const navlinks = computed(() => {
|
|
380
|
+
void i18nLocale.value // "Details" label re-resolves on locale change
|
|
381
|
+
|
|
380
382
|
if (!parentConfig.value) return []
|
|
381
383
|
|
|
382
384
|
const { entity: parentEntity, param, itemRoute } = parentConfig.value
|
|
@@ -391,7 +393,7 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
|
|
|
391
393
|
// Details link - use manager's idField for param name
|
|
392
394
|
const links: NavLinkItem[] = [
|
|
393
395
|
{
|
|
394
|
-
label: 'Details',
|
|
396
|
+
label: breadcrumbLabel('details', 'Details'),
|
|
395
397
|
to: {
|
|
396
398
|
name: parentRouteName!,
|
|
397
399
|
params: { [parentManager.idField!]: parentId.value as string },
|
|
@@ -448,10 +450,17 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
|
|
|
448
450
|
// MODE TOGGLE / MODE LINKS (#1332, #1353)
|
|
449
451
|
// ============================================================================
|
|
450
452
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
+
/** Resolve a `breadcrumb.*` label from the i18n catalog, with fallback. */
|
|
454
|
+
function breadcrumbLabel(key: string, fallback: string): string {
|
|
455
|
+
const trace = kernelI18n?.resolve(`breadcrumb.${key}`)
|
|
453
456
|
if (trace?.hit) return trace.result
|
|
454
|
-
return
|
|
457
|
+
return fallback
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function toggleLabel(target: 'show' | 'edit'): string {
|
|
461
|
+
return target === 'edit'
|
|
462
|
+
? breadcrumbLabel('edit', 'Edit')
|
|
463
|
+
: breadcrumbLabel('view', 'View')
|
|
455
464
|
}
|
|
456
465
|
|
|
457
466
|
/** Build one mode link if its route exists and (for edit) permission allows. */
|
|
@@ -89,10 +89,15 @@ export function computeSemanticBreadcrumb(
|
|
|
89
89
|
items.push({ kind: actionKind, entity: lastEntity, id: pendingId })
|
|
90
90
|
pendingId = null
|
|
91
91
|
} else {
|
|
92
|
-
// No pending ID
|
|
92
|
+
// No pending ID and the last item is an id-less entity level
|
|
93
|
+
// (e.g. /books/create): append the action as its own item so the
|
|
94
|
+
// entity-list crumb survives — mutating the list item into
|
|
95
|
+
// entity-create ate the "Books" level (#1356). When the last item
|
|
96
|
+
// already carries an id (/books/1/edit) the action was consumed
|
|
97
|
+
// at the id segment; swallow it as before.
|
|
93
98
|
const lastItem = items[items.length - 1]
|
|
94
99
|
if (lastItem && lastItem.kind.startsWith('entity-') && !lastItem.id) {
|
|
95
|
-
|
|
100
|
+
items.push({ kind: actionKind, entity: lastEntity })
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
continue
|