@quazardous/qdadm 2.6.1 → 2.7.1
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 +1 -1
- package/src/components/editors/JsonStructuredField.vue +4 -3
- package/src/components/layout/AppLayout.vue +38 -1
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +38 -1
- package/src/components/show/ShowPage.vue +3 -13
- package/src/composables/createShowFieldResolver.ts +14 -4
- package/src/composables/index.ts +1 -0
- package/src/composables/useEntityItemShowPage.ts +6 -3
- package/src/composables/useNavContext.ts +67 -0
- package/src/entity/EntityManager.interface.ts +1 -1
- package/src/index.ts +4 -0
- package/src/kernel/KernelContext.entities.ts +3 -2
- package/src/kernel/KernelContext.ts +4 -1
- package/src/plugin.ts +2 -0
package/package.json
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
* - mode: Optional v-model for view mode ('structured' or 'json')
|
|
29
29
|
* - label: Optional label for the field
|
|
30
30
|
* - jsonHeight: Height of JSON editor (default: 400px)
|
|
31
|
-
* - jsonMode: Mode for JSON editor - 'tree' or '
|
|
31
|
+
* - jsonMode: Mode for JSON editor - 'tree', 'text' or 'table' (default: 'text');
|
|
32
|
+
* string literals accepted so consumers don't need the vanilla-jsoneditor Mode enum (#1280)
|
|
32
33
|
* - defaultMode: Initial mode if not using v-model:mode (default: 'structured')
|
|
33
34
|
* - showToggle: Whether to show the toggle (default: true, useful to hide in nested usage)
|
|
34
35
|
* - guardInvalidJson: Block switching to structured view while JSON is invalid (default: true)
|
|
@@ -60,7 +61,7 @@ interface Props {
|
|
|
60
61
|
mode?: ViewMode | null
|
|
61
62
|
label?: string | null
|
|
62
63
|
jsonHeight?: string
|
|
63
|
-
jsonMode?: Mode
|
|
64
|
+
jsonMode?: Mode | 'tree' | 'text' | 'table'
|
|
64
65
|
defaultMode?: ViewMode
|
|
65
66
|
showToggle?: boolean
|
|
66
67
|
guardInvalidJson?: boolean
|
|
@@ -73,7 +74,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
73
74
|
mode: null,
|
|
74
75
|
label: null,
|
|
75
76
|
jsonHeight: '400px',
|
|
76
|
-
jsonMode: 'text'
|
|
77
|
+
jsonMode: 'text',
|
|
77
78
|
defaultMode: 'structured',
|
|
78
79
|
showToggle: true,
|
|
79
80
|
guardInvalidJson: true,
|
|
@@ -31,6 +31,8 @@ import { version as qdadmVersion } from '../../../package.json'
|
|
|
31
31
|
interface FeaturesConfig {
|
|
32
32
|
poweredBy?: boolean
|
|
33
33
|
breadcrumb?: boolean
|
|
34
|
+
/** Opt-in (#1332/#1341): View↔Edit toggle next to the terminal crumb */
|
|
35
|
+
breadcrumbModeToggle?: boolean
|
|
34
36
|
[key: string]: unknown
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -239,7 +241,7 @@ watch(() => route.fullPath, () => {
|
|
|
239
241
|
|
|
240
242
|
// Navigation context (breadcrumb + navlinks from route config)
|
|
241
243
|
// Entity data comes from activeStack (populated by useEntityItemPage/useEntityItemFormPage)
|
|
242
|
-
const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks } = useNavContext()
|
|
244
|
+
const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks, modeToggle } = useNavContext()
|
|
243
245
|
|
|
244
246
|
// Allow child pages to override breadcrumb/navlinks via provide/inject
|
|
245
247
|
const breadcrumbOverride = ref<BreadcrumbItem[] | null>(null)
|
|
@@ -258,6 +260,12 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
258
260
|
if (route.name === 'dashboard') return false
|
|
259
261
|
return true
|
|
260
262
|
})
|
|
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
|
|
268
|
+
)
|
|
261
269
|
</script>
|
|
262
270
|
|
|
263
271
|
<template>
|
|
@@ -382,6 +390,17 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
382
390
|
</template>
|
|
383
391
|
</Breadcrumb>
|
|
384
392
|
|
|
393
|
+
<!-- View↔Edit mode toggle (#1332/#1341) — plain navigation; the form
|
|
394
|
+
page's own route-leave guard covers unsaved changes on edit→show -->
|
|
395
|
+
<RouterLink
|
|
396
|
+
v-if="shownModeToggle"
|
|
397
|
+
:to="shownModeToggle.to"
|
|
398
|
+
class="breadcrumb-mode-toggle"
|
|
399
|
+
>
|
|
400
|
+
<i :class="shownModeToggle.target === 'edit' ? 'pi pi-pencil' : 'pi pi-eye'"></i>
|
|
401
|
+
<span>{{ shownModeToggle.label }}</span>
|
|
402
|
+
</RouterLink>
|
|
403
|
+
|
|
385
404
|
<!-- Navlinks (provided by PageNav for child routes) -->
|
|
386
405
|
<div v-if="navlinks.length > 0" class="layout-navlinks">
|
|
387
406
|
<template v-for="(link, index) in navlinks" :key="link.to?.name || index">
|
|
@@ -702,6 +721,24 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
702
721
|
padding-bottom: 0;
|
|
703
722
|
}
|
|
704
723
|
|
|
724
|
+
/* Mode toggle sits right after the crumbs; auto margin keeps navlinks pinned right */
|
|
725
|
+
.breadcrumb-mode-toggle {
|
|
726
|
+
display: inline-flex;
|
|
727
|
+
align-items: center;
|
|
728
|
+
gap: 0.35rem;
|
|
729
|
+
margin-left: 0.75rem;
|
|
730
|
+
margin-right: auto;
|
|
731
|
+
font-size: 0.875rem;
|
|
732
|
+
color: var(--p-primary-500, #3b82f6);
|
|
733
|
+
text-decoration: none;
|
|
734
|
+
transition: color var(--fad-transition-fast);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.breadcrumb-mode-toggle:hover {
|
|
738
|
+
color: var(--p-primary-600, #2563eb);
|
|
739
|
+
text-decoration: underline;
|
|
740
|
+
}
|
|
741
|
+
|
|
705
742
|
.layout-navlinks {
|
|
706
743
|
display: flex;
|
|
707
744
|
align-items: center;
|
|
@@ -15,6 +15,8 @@ import Breadcrumb from 'primevue/breadcrumb'
|
|
|
15
15
|
|
|
16
16
|
interface FeaturesConfig {
|
|
17
17
|
breadcrumb?: boolean
|
|
18
|
+
/** Opt-in (#1332): render a View↔Edit toggle next to the terminal crumb */
|
|
19
|
+
breadcrumbModeToggle?: boolean
|
|
18
20
|
[key: string]: unknown
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -22,7 +24,7 @@ const route = useRoute()
|
|
|
22
24
|
const features = inject<FeaturesConfig>('qdadmFeatures', { breadcrumb: true })
|
|
23
25
|
|
|
24
26
|
// Navigation context (breadcrumb + navlinks from route config)
|
|
25
|
-
const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks } = useNavContext()
|
|
27
|
+
const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks, modeToggle } = useNavContext()
|
|
26
28
|
|
|
27
29
|
// Allow child pages to override breadcrumb/navlinks via provide/inject
|
|
28
30
|
const breadcrumbOverride = inject<Ref<BreadcrumbItem[] | null>>('qdadmBreadcrumbOverride', ref(null))
|
|
@@ -39,6 +41,12 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
39
41
|
if (route.name === 'dashboard') return false
|
|
40
42
|
return true
|
|
41
43
|
})
|
|
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
|
|
49
|
+
)
|
|
42
50
|
</script>
|
|
43
51
|
|
|
44
52
|
<template>
|
|
@@ -56,6 +64,17 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
56
64
|
</template>
|
|
57
65
|
</Breadcrumb>
|
|
58
66
|
|
|
67
|
+
<!-- View↔Edit mode toggle (#1332) — plain navigation; the form page's
|
|
68
|
+
own route-leave guard covers unsaved changes on edit→show -->
|
|
69
|
+
<RouterLink
|
|
70
|
+
v-if="shownModeToggle"
|
|
71
|
+
:to="shownModeToggle.to"
|
|
72
|
+
class="breadcrumb-mode-toggle"
|
|
73
|
+
>
|
|
74
|
+
<i :class="shownModeToggle.target === 'edit' ? 'pi pi-pencil' : 'pi pi-eye'"></i>
|
|
75
|
+
<span>{{ shownModeToggle.label }}</span>
|
|
76
|
+
</RouterLink>
|
|
77
|
+
|
|
59
78
|
<!-- Navlinks (provided by PageNav for child routes) -->
|
|
60
79
|
<div v-if="navlinks.length > 0" class="breadcrumb-navlinks">
|
|
61
80
|
<template v-for="(link, index) in navlinks" :key="link.to?.name || index">
|
|
@@ -92,6 +111,24 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
92
111
|
font-size: 0.875rem;
|
|
93
112
|
}
|
|
94
113
|
|
|
114
|
+
/* Mode toggle sits right after the crumbs; auto margin keeps navlinks pinned right */
|
|
115
|
+
.breadcrumb-mode-toggle {
|
|
116
|
+
display: inline-flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 0.35rem;
|
|
119
|
+
margin-left: 0.75rem;
|
|
120
|
+
margin-right: auto;
|
|
121
|
+
font-size: 0.875rem;
|
|
122
|
+
color: var(--p-primary-500, #3b82f6);
|
|
123
|
+
text-decoration: none;
|
|
124
|
+
transition: color 0.15s;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.breadcrumb-mode-toggle:hover {
|
|
128
|
+
color: var(--p-primary-700, #1d4ed8);
|
|
129
|
+
text-decoration: underline;
|
|
130
|
+
}
|
|
131
|
+
|
|
95
132
|
.navlinks-separator {
|
|
96
133
|
color: var(--p-surface-400, #94a3b8);
|
|
97
134
|
}
|
|
@@ -46,6 +46,9 @@ import Message from 'primevue/message'
|
|
|
46
46
|
import FieldGroups from '../item/FieldGroups.vue'
|
|
47
47
|
import ShowField from './ShowField.vue'
|
|
48
48
|
import type { ButtonSeverity } from '../../types'
|
|
49
|
+
// Single source of truth (#1281): the props must accept exactly what the
|
|
50
|
+
// composable emits — a local redeclaration is where the two drifted apart.
|
|
51
|
+
import type { ResolvedAction } from '../../composables/useEntityItemShowPage'
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
54
|
* Page title parts for PageHeader
|
|
@@ -56,19 +59,6 @@ interface PageTitleParts {
|
|
|
56
59
|
entityLabel: string | undefined
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
/**
|
|
60
|
-
* Action configuration
|
|
61
|
-
*/
|
|
62
|
-
interface ResolvedAction {
|
|
63
|
-
name: string
|
|
64
|
-
label: string
|
|
65
|
-
icon?: string
|
|
66
|
-
severity?: ButtonSeverity
|
|
67
|
-
isLoading: boolean
|
|
68
|
-
isDisabled: boolean
|
|
69
|
-
onClick: () => void | Promise<void>
|
|
70
|
-
}
|
|
71
|
-
|
|
72
62
|
/**
|
|
73
63
|
* Resolved field config
|
|
74
64
|
*/
|
|
@@ -39,7 +39,8 @@ export interface ShowFieldDefinition {
|
|
|
39
39
|
type?: string
|
|
40
40
|
schemaType?: string
|
|
41
41
|
label?: string
|
|
42
|
-
|
|
42
|
+
/** Canonical FieldConfig object form, or the entity name as a bare string */
|
|
43
|
+
reference?: string | { entity: string; labelField?: string; valueField?: string }
|
|
43
44
|
referenceRoute?: string | ((value: unknown) => { name: string; params: Record<string, unknown> })
|
|
44
45
|
referenceLabel?: string | ((value: unknown, option?: unknown) => string)
|
|
45
46
|
options?: unknown[]
|
|
@@ -73,6 +74,8 @@ export interface ShowResolvedFieldConfig extends ShowFieldDefinition {
|
|
|
73
74
|
*/
|
|
74
75
|
export interface ShowResolverOrchestrator {
|
|
75
76
|
get: (entityName: string) => ShowResolverManager | undefined
|
|
77
|
+
/** Real Orchestrator.get THROWS on unknown entities — probe first when available */
|
|
78
|
+
has?: (entityName: string) => boolean
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
/**
|
|
@@ -133,13 +136,20 @@ export function createShowFieldResolver(
|
|
|
133
136
|
let resolvedDisplayType = SHOW_TYPE_MAPPINGS[type] || SHOW_TYPE_MAPPINGS[schemaType] || 'text'
|
|
134
137
|
const resolvedLabel = label || snakeCaseToTitle(name)
|
|
135
138
|
|
|
136
|
-
// Auto-set reference route if reference entity is specified
|
|
139
|
+
// Auto-set reference route if reference entity is specified.
|
|
140
|
+
// FieldConfig.reference is canonically { entity, ... }; the bare-string
|
|
141
|
+
// form stays accepted. Passing the object to orchestrator.get produced
|
|
142
|
+
// '[object Object]' lookups that THREW during generateFields (#1341).
|
|
137
143
|
let referenceRoute = rest.referenceRoute
|
|
138
144
|
if (reference && !referenceRoute) {
|
|
139
|
-
const
|
|
145
|
+
const refEntity = typeof reference === 'string' ? reference : reference.entity
|
|
146
|
+
const refManager =
|
|
147
|
+
refEntity && (!orchestrator.has || orchestrator.has(refEntity))
|
|
148
|
+
? orchestrator.get(refEntity)
|
|
149
|
+
: undefined
|
|
140
150
|
if (refManager) {
|
|
141
151
|
referenceRoute = (value: unknown) => ({
|
|
142
|
-
name: `${refManager.routePrefix ||
|
|
152
|
+
name: `${refManager.routePrefix || refEntity}-show`,
|
|
143
153
|
params: { [refManager.idField]: value },
|
|
144
154
|
})
|
|
145
155
|
}
|
package/src/composables/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
type ShowResolvedFieldConfig,
|
|
56
56
|
} from './createShowFieldResolver'
|
|
57
57
|
import type { OrchestratorLike } from '../entity/EntityManager.interface'
|
|
58
|
+
import type { ButtonSeverity } from '../types'
|
|
58
59
|
import { useActionRegistry } from './useActionRegistry'
|
|
59
60
|
|
|
60
61
|
// #1191 — shared structural view (manager tier from useEntityItemPage)
|
|
@@ -81,7 +82,8 @@ export interface ActionConfig {
|
|
|
81
82
|
name: string
|
|
82
83
|
label: string
|
|
83
84
|
icon?: string
|
|
84
|
-
severity
|
|
85
|
+
/** PrimeVue Button severity — the shared union, not bare string (#1281) */
|
|
86
|
+
severity?: ButtonSeverity
|
|
85
87
|
onClick: () => void | Promise<void>
|
|
86
88
|
visible?: (ctx: { canEdit: boolean; canDelete: boolean }) => boolean
|
|
87
89
|
disabled?: (ctx: { loading: boolean }) => boolean
|
|
@@ -99,7 +101,8 @@ export interface LazyActionConfig {
|
|
|
99
101
|
name: string
|
|
100
102
|
label: string
|
|
101
103
|
icon?: string
|
|
102
|
-
severity
|
|
104
|
+
/** PrimeVue Button severity — the shared union, not bare string (#1281) */
|
|
105
|
+
severity?: ButtonSeverity
|
|
103
106
|
onClick: () => void | Promise<void>
|
|
104
107
|
resolve: (data: unknown) => Promise<{ visible?: boolean; disabled?: boolean }>
|
|
105
108
|
loading?: () => boolean
|
|
@@ -129,7 +132,7 @@ export interface ShowPageProps {
|
|
|
129
132
|
loading: boolean
|
|
130
133
|
title: string
|
|
131
134
|
titleParts: PageTitleParts
|
|
132
|
-
badges: Array<{ label: string; severity?:
|
|
135
|
+
badges: Array<{ label: string; severity?: ButtonSeverity }>
|
|
133
136
|
fields: ResolvedFieldConfig[]
|
|
134
137
|
groups: FieldGroup<ResolvedFieldConfig>[]
|
|
135
138
|
data: Record<string, unknown> | null
|
|
@@ -21,6 +21,7 @@ import { useRoute, useRouter, type RouteParamsRawGeneric } from 'vue-router'
|
|
|
21
21
|
import { getSiblingRoutes } from '../module/moduleRegistry'
|
|
22
22
|
import { useSemanticBreadcrumb, type SemanticBreadcrumbItem } from './useSemanticBreadcrumb'
|
|
23
23
|
import { useStackHydrator, type HydratedLevel } from '../chain/useStackHydrator'
|
|
24
|
+
import { useI18n } from '../i18n/useI18n'
|
|
24
25
|
import type { EntityManagerLike, OrchestratorLike } from '../entity/EntityManager.interface'
|
|
25
26
|
|
|
26
27
|
// #1191 — shared minimal structural views (was: local redeclarations)
|
|
@@ -64,6 +65,29 @@ export interface BreadcrumbItem {
|
|
|
64
65
|
icon?: string
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* View↔Edit toggle for the breadcrumb terminal (#1332)
|
|
70
|
+
*
|
|
71
|
+
* Non-null only when the current route is an entity item mode
|
|
72
|
+
* (`entity-show` / `entity-edit`), the twin-mode route exists
|
|
73
|
+
* (`${routePrefix}-edit` / `-show` + `router.hasRoute`), and — for the
|
|
74
|
+
* Edit affordance — `manager.canUpdate()` allows it. The permission check
|
|
75
|
+
* passes the hydrated entity when available, so ownership-conditional
|
|
76
|
+
* permissions are honored once the item is loaded.
|
|
77
|
+
*/
|
|
78
|
+
export interface BreadcrumbModeToggle {
|
|
79
|
+
/** Mode of the current route */
|
|
80
|
+
current: 'show' | 'edit'
|
|
81
|
+
/** Mode the toggle navigates to */
|
|
82
|
+
target: 'show' | 'edit'
|
|
83
|
+
/** Twin-route location — a plain router.push; any dirty-form protection
|
|
84
|
+
* comes from the form page's own onBeforeRouteLeave guard */
|
|
85
|
+
to: { name: string; params: RouteParamsRawGeneric }
|
|
86
|
+
/** Locale-reactive label: i18n `breadcrumb.view` / `breadcrumb.edit`,
|
|
87
|
+
* falling back to "View" / "Edit" */
|
|
88
|
+
label: string
|
|
89
|
+
}
|
|
90
|
+
|
|
67
91
|
/**
|
|
68
92
|
* Nav link item
|
|
69
93
|
*/
|
|
@@ -105,6 +129,7 @@ export interface UseNavContextReturn {
|
|
|
105
129
|
// Navigation
|
|
106
130
|
breadcrumb: ComputedRef<BreadcrumbItem[]>
|
|
107
131
|
navlinks: ComputedRef<NavLinkItem[]>
|
|
132
|
+
modeToggle: ComputedRef<BreadcrumbModeToggle | null>
|
|
108
133
|
|
|
109
134
|
// Helpers
|
|
110
135
|
parentConfig: ComputedRef<ParentConfig | undefined>
|
|
@@ -125,6 +150,9 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
|
|
|
125
150
|
// Stack hydrator for entity data (has async-loaded data and labels)
|
|
126
151
|
const hydrator = useStackHydrator()
|
|
127
152
|
|
|
153
|
+
// i18n for the mode-toggle labels (no-op shim outside a kernel)
|
|
154
|
+
const { i18n: kernelI18n, locale: i18nLocale } = useI18n()
|
|
155
|
+
|
|
128
156
|
function getManager(entityName: string): EntityManager | null {
|
|
129
157
|
return orchestrator?.get(entityName) ?? null
|
|
130
158
|
}
|
|
@@ -414,6 +442,44 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
|
|
|
414
442
|
return null
|
|
415
443
|
})
|
|
416
444
|
|
|
445
|
+
// ============================================================================
|
|
446
|
+
// MODE TOGGLE (#1332)
|
|
447
|
+
// ============================================================================
|
|
448
|
+
|
|
449
|
+
function toggleLabel(target: 'show' | 'edit'): string {
|
|
450
|
+
const trace = kernelI18n?.resolve(target === 'edit' ? 'breadcrumb.edit' : 'breadcrumb.view')
|
|
451
|
+
if (trace?.hit) return trace.result
|
|
452
|
+
return target === 'edit' ? 'Edit' : 'View'
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const modeToggle = computed<BreadcrumbModeToggle | null>(() => {
|
|
456
|
+
void i18nLocale.value // label re-resolves on locale change
|
|
457
|
+
|
|
458
|
+
const semantic = semanticBreadcrumb.value
|
|
459
|
+
const terminal = semantic[semantic.length - 1]
|
|
460
|
+
if (!terminal?.entity || !terminal.id) return null
|
|
461
|
+
if (terminal.kind !== 'entity-show' && terminal.kind !== 'entity-edit') return null
|
|
462
|
+
|
|
463
|
+
const manager = getManager(terminal.entity)
|
|
464
|
+
if (!manager?.routePrefix) return null
|
|
465
|
+
|
|
466
|
+
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
|
+
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
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
current,
|
|
477
|
+
target,
|
|
478
|
+
to: { name: twinRoute, params: { [manager.idField || 'id']: terminal.id } },
|
|
479
|
+
label: toggleLabel(target),
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
|
|
417
483
|
return {
|
|
418
484
|
// Analysis
|
|
419
485
|
navChain,
|
|
@@ -427,6 +493,7 @@ export function useNavContext(_options: UseNavContextOptions = {}): UseNavContex
|
|
|
427
493
|
// Navigation
|
|
428
494
|
breadcrumb,
|
|
429
495
|
navlinks,
|
|
496
|
+
modeToggle,
|
|
430
497
|
|
|
431
498
|
// Helpers
|
|
432
499
|
parentConfig,
|
|
@@ -44,7 +44,7 @@ export interface EntityManagerBase<T = unknown> {
|
|
|
44
44
|
// Optional BY DESIGN (#1253): part of the badge/severity presentation
|
|
45
45
|
// capability; call sites guard (e.g. useEntityItemShowPage checks
|
|
46
46
|
// `if (!manager.getEntityBadges)`).
|
|
47
|
-
getEntityBadges?: (data: T) =>
|
|
47
|
+
getEntityBadges?: (data: T) => EntityBadge[]
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// ─── Permissions ─────────────────────────────────────────────────────────────
|
package/src/index.ts
CHANGED
|
@@ -128,6 +128,9 @@ export type {
|
|
|
128
128
|
RelationConfig,
|
|
129
129
|
NavConfig,
|
|
130
130
|
StorageCapabilities,
|
|
131
|
+
// PrimeVue severity union (#1281) — type ActionConfig/badge literals against
|
|
132
|
+
// this instead of bare strings.
|
|
133
|
+
ButtonSeverity,
|
|
131
134
|
} from './types'
|
|
132
135
|
|
|
133
136
|
// Manager Factory
|
|
@@ -266,6 +269,7 @@ export {
|
|
|
266
269
|
type NavChainType,
|
|
267
270
|
type BreadcrumbItem,
|
|
268
271
|
type NavLinkItem,
|
|
272
|
+
type BreadcrumbModeToggle,
|
|
269
273
|
} from './composables/useNavContext'
|
|
270
274
|
export {
|
|
271
275
|
useNavigation,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { managerFactory, type ManagerFactoryContext } from '../entity/factory.js'
|
|
8
8
|
import type { EntityManager } from '../entity/EntityManager'
|
|
9
|
+
import type { EntityRecord } from '../types'
|
|
9
10
|
import { UsersManager } from '../security/UsersManager'
|
|
10
11
|
import type { UserEntityOptions } from './KernelContext.types'
|
|
11
12
|
import type { KernelContext } from './KernelContext'
|
|
@@ -22,10 +23,10 @@ export function applyEntityMethods(KernelContextClass: { prototype: KernelContex
|
|
|
22
23
|
* Uses managerFactory to resolve config into a manager instance.
|
|
23
24
|
* Registers with Orchestrator for CRUD operations.
|
|
24
25
|
*/
|
|
25
|
-
proto.entity = function (
|
|
26
|
+
proto.entity = function <T extends EntityRecord>(
|
|
26
27
|
this: Self,
|
|
27
28
|
name: string,
|
|
28
|
-
config: string | Record<string, unknown> | EntityManager
|
|
29
|
+
config: string | Record<string, unknown> | EntityManager<T>
|
|
29
30
|
): Self {
|
|
30
31
|
const factoryContext = {
|
|
31
32
|
storageResolver: this._kernel.options.storageResolver,
|
|
@@ -46,6 +46,7 @@ import type { DeferredRegistry } from '../deferred/DeferredRegistry.js'
|
|
|
46
46
|
import type { SecurityChecker } from '../entity/auth/SecurityChecker'
|
|
47
47
|
import type { PermissionRegistry } from '../security/PermissionRegistry'
|
|
48
48
|
import type { EntityManager } from '../entity/EntityManager'
|
|
49
|
+
import type { EntityRecord } from '../types'
|
|
49
50
|
import type { I18n } from '../i18n/I18n'
|
|
50
51
|
import type { AliasPattern, MessagesBundle, TranslationProvider } from '../i18n/types'
|
|
51
52
|
|
|
@@ -195,7 +196,9 @@ export class KernelContext {
|
|
|
195
196
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
196
197
|
export interface KernelContext {
|
|
197
198
|
// Entities (KernelContext.entities.ts)
|
|
198
|
-
|
|
199
|
+
// Generic so narrowed managers pass: EntityManager<T> is invariant in T,
|
|
200
|
+
// so EntityManager<EntityRecord> would reject every typed subclass (#1281).
|
|
201
|
+
entity<T extends EntityRecord>(name: string, config: string | Record<string, unknown> | EntityManager<T>): this
|
|
199
202
|
userEntity(options: UserEntityOptions): this
|
|
200
203
|
|
|
201
204
|
// Routing (KernelContext.routing.ts)
|
package/src/plugin.ts
CHANGED