qdadm 0.53.2 → 0.55.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 +2 -2
- package/src/chain/ActiveStack.js +133 -37
- package/src/chain/StackHydrator.js +279 -0
- package/src/chain/index.js +9 -2
- package/src/chain/useActiveStack.js +55 -98
- package/src/chain/useStackHydrator.js +105 -0
- package/src/composables/useBreadcrumb.js +15 -2
- package/src/composables/useCurrentEntity.js +11 -11
- package/src/composables/useEntityItemFormPage.js +5 -5
- package/src/composables/useEntityItemPage.js +7 -7
- package/src/composables/useForm.js +3 -3
- package/src/composables/useNavContext.js +16 -18
- package/src/composables/useSemanticBreadcrumb.js +3 -3
- package/src/kernel/Kernel.js +99 -6
- package/src/kernel/KernelContext.js +16 -0
- package/src/kernel/Module.js +32 -0
- package/src/kernel/ModuleLoader.js +5 -1
- package/src/{debug → modules/debug}/DebugModule.js +7 -1
- package/src/{debug → modules/debug}/RouterCollector.js +53 -2
- package/src/{debug → modules/debug}/components/DebugBar.vue +0 -528
- package/src/{debug → modules/debug}/components/ObjectTree.vue +0 -80
- package/src/{debug → modules/debug}/components/panels/AuthPanel.vue +0 -100
- package/src/{debug → modules/debug}/components/panels/EntitiesPanel.vue +0 -524
- package/src/{debug → modules/debug}/components/panels/EntriesPanel.vue +0 -117
- package/src/{debug → modules/debug}/components/panels/RouterPanel.vue +120 -420
- package/src/{debug → modules/debug}/components/panels/SignalsPanel.vue +0 -164
- package/src/modules/debug/components/panels/ToastsPanel.vue +34 -0
- package/src/{debug → modules/debug}/components/panels/ZonesPanel.vue +0 -131
- package/src/modules/debug/styles.scss +2469 -0
- package/src/debug/components/panels/ToastsPanel.vue +0 -112
- /package/src/{debug → modules/debug}/AuthCollector.js +0 -0
- /package/src/{debug → modules/debug}/Collector.js +0 -0
- /package/src/{debug → modules/debug}/DebugBridge.js +0 -0
- /package/src/{debug → modules/debug}/EntitiesCollector.js +0 -0
- /package/src/{debug → modules/debug}/ErrorCollector.js +0 -0
- /package/src/{debug → modules/debug}/LocalStorageAdapter.js +0 -0
- /package/src/{debug → modules/debug}/SignalCollector.js +0 -0
- /package/src/{debug → modules/debug}/ToastCollector.js +0 -0
- /package/src/{debug → modules/debug}/ZonesCollector.js +0 -0
- /package/src/{debug → modules/debug}/components/index.js +0 -0
- /package/src/{debug → modules/debug}/components/panels/index.js +0 -0
- /package/src/{debug → modules/debug}/index.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qdadm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "Vue 3 framework for admin dashboards with PrimeVue",
|
|
5
5
|
"author": "quazardous",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"./editors": "./src/editors/index.js",
|
|
28
28
|
"./module": "./src/module/index.js",
|
|
29
29
|
"./utils": "./src/utils/index.js",
|
|
30
|
-
"./debug": "./src/debug/index.js",
|
|
30
|
+
"./modules/debug": "./src/modules/debug/index.js",
|
|
31
31
|
"./styles": "./src/styles/index.scss",
|
|
32
32
|
"./styles/breakpoints": "./src/styles/_breakpoints.scss",
|
|
33
33
|
"./gen": "./src/gen/index.js",
|
package/src/chain/ActiveStack.js
CHANGED
|
@@ -1,74 +1,170 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ActiveStack -
|
|
2
|
+
* ActiveStack - Sync navigation context from route
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Pure vanilla JS container using SignalBus for events.
|
|
5
|
+
* Rebuilt from route.meta on navigation.
|
|
6
|
+
*
|
|
7
|
+
* Stack only contains levels WITH IDs (entities with context).
|
|
8
|
+
* - /bots/bot-xyz/commands → stack = [bots(id:bot-xyz)]
|
|
9
|
+
* - /bots/bot-xyz/commands/cmd-123 → stack = [bots(id:bot-xyz), commands(id:cmd-123)]
|
|
10
|
+
*
|
|
11
|
+
* Signals emitted:
|
|
12
|
+
* - stack:change - when stack levels change
|
|
13
|
+
*
|
|
14
|
+
* For Vue reactivity, use useActiveStack composable.
|
|
6
15
|
*
|
|
7
16
|
* @example
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* { entity: 'loans', id: null, data: null, label: 'Loans' }
|
|
12
|
-
* ]
|
|
17
|
+
* const stack = new ActiveStack(signalBus)
|
|
18
|
+
* signalBus.on('stack:change', ({ levels }) => console.log('Stack:', levels))
|
|
19
|
+
* stack.set([{ entity: 'bots', param: 'uuid', id: 'bot-123' }])
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} StackLevel
|
|
24
|
+
* @property {string} entity - Entity name (e.g., 'bots', 'commands')
|
|
25
|
+
* @property {string} param - Route param name (e.g., 'uuid', 'id')
|
|
26
|
+
* @property {string|null} foreignKey - Foreign key field for parent relation (null for root)
|
|
27
|
+
* @property {string|null} id - Entity ID from route params
|
|
28
|
+
*/
|
|
16
29
|
|
|
17
30
|
export class ActiveStack {
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
/**
|
|
32
|
+
* @param {import('../kernel/SignalBus.js').SignalBus} [signalBus] - Optional signal bus for events
|
|
33
|
+
*/
|
|
34
|
+
constructor(signalBus = null) {
|
|
35
|
+
/** @type {StackLevel[]} */
|
|
36
|
+
this._levels = []
|
|
37
|
+
|
|
38
|
+
/** @type {import('../kernel/SignalBus.js').SignalBus|null} */
|
|
39
|
+
this._signalBus = signalBus
|
|
20
40
|
}
|
|
21
41
|
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
// Mutators
|
|
44
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
+
|
|
22
46
|
/**
|
|
23
47
|
* Replace entire stack (called on route change)
|
|
48
|
+
* Only emits if levels actually changed (prevents duplicate emissions)
|
|
49
|
+
* @param {StackLevel[]} levels
|
|
50
|
+
*
|
|
51
|
+
* BUG: Still getting duplicate signals (4 instead of 2) on navigation.
|
|
52
|
+
* Equality check should prevent this but something is triggering multiple calls
|
|
53
|
+
* with different levels. Need to investigate router.afterEach timing.
|
|
24
54
|
*/
|
|
25
55
|
set(levels) {
|
|
26
|
-
|
|
56
|
+
// Quick equality check - same length and same entity+id pairs
|
|
57
|
+
if (this._levelsEqual(levels)) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
this._levels = levels
|
|
61
|
+
this._emit('stack:change', { levels: this._levels })
|
|
27
62
|
}
|
|
28
63
|
|
|
29
64
|
/**
|
|
30
|
-
*
|
|
65
|
+
* Check if new levels match current levels
|
|
66
|
+
* @param {StackLevel[]} newLevels
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
* @private
|
|
31
69
|
*/
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
_levelsEqual(newLevels) {
|
|
71
|
+
if (this._levels.length !== newLevels.length) return false
|
|
72
|
+
for (let i = 0; i < this._levels.length; i++) {
|
|
73
|
+
const curr = this._levels[i]
|
|
74
|
+
const next = newLevels[i]
|
|
75
|
+
if (curr.entity !== next.entity || curr.id !== next.id) {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true
|
|
37
80
|
}
|
|
38
81
|
|
|
39
82
|
/**
|
|
40
|
-
*
|
|
83
|
+
* Clear the stack
|
|
84
|
+
* Only emits if not already empty
|
|
41
85
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
86
|
+
clear() {
|
|
87
|
+
if (this._levels.length === 0) return
|
|
88
|
+
this._levels = []
|
|
89
|
+
this._emit('stack:change', { levels: this._levels })
|
|
47
90
|
}
|
|
48
91
|
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
93
|
+
// Accessors
|
|
94
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* All stack levels
|
|
98
|
+
* @returns {StackLevel[]}
|
|
99
|
+
*/
|
|
100
|
+
getLevels() {
|
|
101
|
+
return this._levels
|
|
51
102
|
}
|
|
52
103
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Get level by index
|
|
106
|
+
* @param {number} index
|
|
107
|
+
* @returns {StackLevel|null}
|
|
108
|
+
*/
|
|
109
|
+
getLevel(index) {
|
|
110
|
+
return this._levels[index] ?? null
|
|
56
111
|
}
|
|
57
112
|
|
|
58
|
-
|
|
59
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Get level by entity name
|
|
115
|
+
* @param {string} entity
|
|
116
|
+
* @returns {StackLevel|null}
|
|
117
|
+
*/
|
|
118
|
+
getLevelByEntity(entity) {
|
|
119
|
+
return this._levels.find(l => l.entity === entity) ?? null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Current (deepest) level
|
|
124
|
+
* @returns {StackLevel|null}
|
|
125
|
+
*/
|
|
126
|
+
getCurrent() {
|
|
127
|
+
return this._levels.at(-1) ?? null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Parent level (one above current)
|
|
132
|
+
* @returns {StackLevel|null}
|
|
133
|
+
*/
|
|
134
|
+
getParent() {
|
|
135
|
+
return this._levels.at(-2) ?? null
|
|
60
136
|
}
|
|
61
137
|
|
|
62
|
-
|
|
63
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Root level (first/topmost)
|
|
140
|
+
* @returns {StackLevel|null}
|
|
141
|
+
*/
|
|
142
|
+
getRoot() {
|
|
143
|
+
return this._levels[0] ?? null
|
|
64
144
|
}
|
|
65
145
|
|
|
66
|
-
|
|
67
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Stack depth
|
|
148
|
+
* @returns {number}
|
|
149
|
+
*/
|
|
150
|
+
getDepth() {
|
|
151
|
+
return this._levels.length
|
|
68
152
|
}
|
|
69
153
|
|
|
70
|
-
|
|
71
|
-
|
|
154
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
155
|
+
// Internal
|
|
156
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Emit event via SignalBus
|
|
160
|
+
* @param {string} signal
|
|
161
|
+
* @param {*} payload
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
_emit(signal, payload) {
|
|
165
|
+
if (this._signalBus) {
|
|
166
|
+
this._signalBus.emit(signal, payload)
|
|
167
|
+
}
|
|
72
168
|
}
|
|
73
169
|
}
|
|
74
170
|
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StackHydrator - Async hydration layer for ActiveStack
|
|
3
|
+
*
|
|
4
|
+
* Pure vanilla JS using SignalBus for events.
|
|
5
|
+
* Listens to stack:change and hydrates each level with entity data.
|
|
6
|
+
*
|
|
7
|
+
* Signals emitted:
|
|
8
|
+
* - stack:hydration:change - when a level's hydration completes { levels }
|
|
9
|
+
*
|
|
10
|
+
* For Vue reactivity, use useStackHydrator composable.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const hydrator = new StackHydrator(activeStack, orchestrator, signalBus)
|
|
14
|
+
* signalBus.on('stack:hydration:change', (event) => console.log('Hydrated:', event.data.levels))
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} HydratedLevel
|
|
19
|
+
* @property {string} entity - Entity name
|
|
20
|
+
* @property {string} param - Route param name
|
|
21
|
+
* @property {string|null} foreignKey - Foreign key field
|
|
22
|
+
* @property {string|null} id - Entity ID
|
|
23
|
+
* @property {Promise<void>} promise - Resolves when hydrated
|
|
24
|
+
* @property {boolean} loading - True while fetching
|
|
25
|
+
* @property {boolean} hydrated - True when data is loaded
|
|
26
|
+
* @property {Error|null} error - Error if fetch failed
|
|
27
|
+
* @property {Record<string, any>|null} data - Entity data
|
|
28
|
+
* @property {string|null} label - Resolved label
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export class StackHydrator {
|
|
32
|
+
/**
|
|
33
|
+
* @param {import('./ActiveStack.js').ActiveStack} activeStack
|
|
34
|
+
* @param {import('../orchestrator/Orchestrator.js').Orchestrator} orchestrator
|
|
35
|
+
* @param {import('../kernel/SignalBus.js').SignalBus} [signalBus]
|
|
36
|
+
*/
|
|
37
|
+
constructor(activeStack, orchestrator, signalBus = null) {
|
|
38
|
+
this._activeStack = activeStack
|
|
39
|
+
this._orchestrator = orchestrator
|
|
40
|
+
this._signalBus = signalBus
|
|
41
|
+
|
|
42
|
+
/** @type {HydratedLevel[]} */
|
|
43
|
+
this._levels = []
|
|
44
|
+
|
|
45
|
+
// Listen to stack changes via SignalBus
|
|
46
|
+
// QuarKernel passes (event, ctx) where event.data contains the payload
|
|
47
|
+
if (signalBus) {
|
|
48
|
+
this._unsubscribe = signalBus.on('stack:change', (event) => {
|
|
49
|
+
const levels = event.data?.levels
|
|
50
|
+
if (levels) {
|
|
51
|
+
this._onStackChange(levels)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
// Note: No initial check needed - stack is always empty at construction time
|
|
56
|
+
// The Kernel's router.afterEach will trigger stack:change after navigation
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Cleanup
|
|
61
|
+
*/
|
|
62
|
+
destroy() {
|
|
63
|
+
if (this._unsubscribe) {
|
|
64
|
+
this._unsubscribe()
|
|
65
|
+
this._unsubscribe = null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
70
|
+
// Stack change handling
|
|
71
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Handle stack change - rebuild hydrated levels
|
|
75
|
+
* @param {import('./ActiveStack.js').StackLevel[]} stackLevels
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
_onStackChange(stackLevels) {
|
|
79
|
+
// Create new hydrated levels (each starts its own async hydration)
|
|
80
|
+
this._levels = stackLevels.map((level, index) =>
|
|
81
|
+
this._createHydratedLevel(level, index)
|
|
82
|
+
)
|
|
83
|
+
// Don't emit here - each level will emit when hydration completes
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create a hydrated level with its own promise
|
|
88
|
+
* @param {import('./ActiveStack.js').StackLevel} stackLevel
|
|
89
|
+
* @param {number} index
|
|
90
|
+
* @returns {HydratedLevel}
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
_createHydratedLevel(stackLevel, index) {
|
|
94
|
+
const level = {
|
|
95
|
+
// Sync config (copied from stack)
|
|
96
|
+
entity: stackLevel.entity,
|
|
97
|
+
param: stackLevel.param,
|
|
98
|
+
foreignKey: stackLevel.foreignKey,
|
|
99
|
+
id: stackLevel.id,
|
|
100
|
+
|
|
101
|
+
// Async state
|
|
102
|
+
promise: null,
|
|
103
|
+
loading: false,
|
|
104
|
+
hydrated: false,
|
|
105
|
+
error: null,
|
|
106
|
+
data: null,
|
|
107
|
+
label: null,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Start hydration and store promise
|
|
111
|
+
level.promise = this._hydrate(level)
|
|
112
|
+
|
|
113
|
+
return level
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Hydrate a single level
|
|
118
|
+
* @param {HydratedLevel} level
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
* @private
|
|
121
|
+
*/
|
|
122
|
+
async _hydrate(level) {
|
|
123
|
+
// All levels in stack have IDs (that's the new contract)
|
|
124
|
+
// But check anyway for safety
|
|
125
|
+
if (!level.id) {
|
|
126
|
+
const manager = this._orchestrator?.get(level.entity)
|
|
127
|
+
level.label = manager?.labelPlural ?? level.entity
|
|
128
|
+
level.hydrated = true
|
|
129
|
+
this._emitChange()
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
level.loading = true
|
|
134
|
+
// Don't emit on loading start - reduces signal noise
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const manager = this._orchestrator?.get(level.entity)
|
|
138
|
+
if (!manager) {
|
|
139
|
+
level.label = level.id
|
|
140
|
+
level.hydrated = true
|
|
141
|
+
level.loading = false
|
|
142
|
+
this._emitChange()
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Fetch entity data
|
|
147
|
+
level.data = await manager.get(level.id)
|
|
148
|
+
level.label = manager.getEntityLabel?.(level.data) ?? level.id
|
|
149
|
+
level.hydrated = true
|
|
150
|
+
} catch (err) {
|
|
151
|
+
level.error = err
|
|
152
|
+
level.label = level.id // Fallback to ID
|
|
153
|
+
level.hydrated = true // Mark as hydrated even on error
|
|
154
|
+
} finally {
|
|
155
|
+
level.loading = false
|
|
156
|
+
this._emitChange()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Emit hydration change signal
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
_emitChange() {
|
|
165
|
+
this._emit('stack:hydration:change', { levels: this._levels })
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
169
|
+
// Accessors
|
|
170
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* All hydrated levels
|
|
174
|
+
* @returns {HydratedLevel[]}
|
|
175
|
+
*/
|
|
176
|
+
getLevels() {
|
|
177
|
+
return this._levels
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get hydrated level by index
|
|
182
|
+
* @param {number} index
|
|
183
|
+
* @returns {HydratedLevel|null}
|
|
184
|
+
*/
|
|
185
|
+
getLevel(index) {
|
|
186
|
+
return this._levels[index] ?? null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get hydrated level by entity name
|
|
191
|
+
* @param {string} entity
|
|
192
|
+
* @returns {HydratedLevel|null}
|
|
193
|
+
*/
|
|
194
|
+
getLevelByEntity(entity) {
|
|
195
|
+
return this._levels.find(l => l.entity === entity) ?? null
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Current hydrated level
|
|
200
|
+
* @returns {HydratedLevel|null}
|
|
201
|
+
*/
|
|
202
|
+
getCurrent() {
|
|
203
|
+
return this._levels.at(-1) ?? null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Parent hydrated level
|
|
208
|
+
* @returns {HydratedLevel|null}
|
|
209
|
+
*/
|
|
210
|
+
getParent() {
|
|
211
|
+
return this._levels.at(-2) ?? null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Root hydrated level
|
|
216
|
+
* @returns {HydratedLevel|null}
|
|
217
|
+
*/
|
|
218
|
+
getRoot() {
|
|
219
|
+
return this._levels[0] ?? null
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
223
|
+
// Manual update (for pages that load their own data)
|
|
224
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Manually set data for current level (skips fetch)
|
|
228
|
+
* Used by pages that already loaded the entity
|
|
229
|
+
* @param {Record<string, any>} data
|
|
230
|
+
*/
|
|
231
|
+
setCurrentData(data) {
|
|
232
|
+
const level = this.getCurrent()
|
|
233
|
+
if (!level) return
|
|
234
|
+
|
|
235
|
+
const manager = this._orchestrator?.get(level.entity)
|
|
236
|
+
level.data = data
|
|
237
|
+
level.label = manager?.getEntityLabel?.(data) ?? level.id
|
|
238
|
+
level.hydrated = true
|
|
239
|
+
level.loading = false
|
|
240
|
+
this._emit('stack:hydrated', { level })
|
|
241
|
+
this._emit('stack:hydration:change', { levels: this._levels })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Manually set data for a level by entity name
|
|
246
|
+
* @param {string} entity
|
|
247
|
+
* @param {Record<string, any>} data
|
|
248
|
+
*/
|
|
249
|
+
setEntityData(entity, data) {
|
|
250
|
+
const level = this.getLevelByEntity(entity)
|
|
251
|
+
if (!level) return
|
|
252
|
+
|
|
253
|
+
const manager = this._orchestrator?.get(entity)
|
|
254
|
+
level.data = data
|
|
255
|
+
level.label = manager?.getEntityLabel?.(data) ?? level.id
|
|
256
|
+
level.hydrated = true
|
|
257
|
+
level.loading = false
|
|
258
|
+
this._emit('stack:hydrated', { level })
|
|
259
|
+
this._emit('stack:hydration:change', { levels: this._levels })
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
263
|
+
// Internal
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Emit event via SignalBus
|
|
268
|
+
* @param {string} signal
|
|
269
|
+
* @param {*} payload
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
_emit(signal, payload) {
|
|
273
|
+
if (this._signalBus) {
|
|
274
|
+
this._signalBus.emit(signal, payload)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export default StackHydrator
|
package/src/chain/index.js
CHANGED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
* Chain Module - Active navigation stack management
|
|
3
3
|
*
|
|
4
4
|
* Provides:
|
|
5
|
-
* - ActiveStack:
|
|
6
|
-
* -
|
|
5
|
+
* - ActiveStack: Sync container for navigation context (entity, param, foreignKey, id)
|
|
6
|
+
* - StackHydrator: Async layer for entity data and labels
|
|
7
|
+
* - useActiveStack: Composable to build and access the sync stack
|
|
8
|
+
* - useStackHydrator: Composable to access hydrated data
|
|
7
9
|
*
|
|
8
10
|
* @module chain
|
|
9
11
|
*/
|
|
10
12
|
|
|
13
|
+
// Sync stack (context only)
|
|
11
14
|
export { ActiveStack } from './ActiveStack.js'
|
|
12
15
|
export { useActiveStack } from './useActiveStack.js'
|
|
16
|
+
|
|
17
|
+
// Async hydration (data + labels)
|
|
18
|
+
export { StackHydrator } from './StackHydrator.js'
|
|
19
|
+
export { useStackHydrator } from './useStackHydrator.js'
|