@zolomedia/bifrost-client 1.7.74
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/L1_Foundation/L1_Foundation.js +13 -0
- package/L1_Foundation/bootstrap/bootstrap.js +11 -0
- package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
- package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
- package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
- package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
- package/L1_Foundation/bootstrap/module_registry.js +102 -0
- package/L1_Foundation/bootstrap/prism_loader.js +164 -0
- package/L1_Foundation/config/client_config.js +110 -0
- package/L1_Foundation/config/config.js +7 -0
- package/L1_Foundation/connection/connection.js +8 -0
- package/L1_Foundation/connection/websocket_connection.js +122 -0
- package/L1_Foundation/constants/bifrost_constants.js +284 -0
- package/L1_Foundation/constants/constants.js +7 -0
- package/L1_Foundation/logger/logger.js +10 -0
- package/L2_Handling/L2_Handling.js +15 -0
- package/L2_Handling/cache/cache.js +22 -0
- package/L2_Handling/cache/cache_constants.js +69 -0
- package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
- package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
- package/L2_Handling/cache/orchestration/orchestration.js +12 -0
- package/L2_Handling/cache/storage/session_manager.js +289 -0
- package/L2_Handling/cache/storage/storage.js +10 -0
- package/L2_Handling/cache/storage/storage_manager.js +590 -0
- package/L2_Handling/display/composite/composite.js +13 -0
- package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
- package/L2_Handling/display/composite/swiper_renderer.js +564 -0
- package/L2_Handling/display/composite/terminal_renderer.js +922 -0
- package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
- package/L2_Handling/display/display.js +30 -0
- package/L2_Handling/display/feedback/feedback.js +11 -0
- package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
- package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
- package/L2_Handling/display/inputs/button_renderer.js +634 -0
- package/L2_Handling/display/inputs/form_renderer.js +583 -0
- package/L2_Handling/display/inputs/input_renderer.js +658 -0
- package/L2_Handling/display/inputs/inputs.js +12 -0
- package/L2_Handling/display/navigation/menu_renderer.js +206 -0
- package/L2_Handling/display/navigation/navigation.js +11 -0
- package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
- package/L2_Handling/display/orchestration/orchestration.js +11 -0
- package/L2_Handling/display/orchestration/renderer.js +430 -0
- package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
- package/L2_Handling/display/outputs/alert_renderer.js +161 -0
- package/L2_Handling/display/outputs/audio_renderer.js +94 -0
- package/L2_Handling/display/outputs/card_renderer.js +229 -0
- package/L2_Handling/display/outputs/code_renderer.js +66 -0
- package/L2_Handling/display/outputs/dl_renderer.js +131 -0
- package/L2_Handling/display/outputs/header_renderer.js +162 -0
- package/L2_Handling/display/outputs/icon_renderer.js +107 -0
- package/L2_Handling/display/outputs/image_renderer.js +145 -0
- package/L2_Handling/display/outputs/list_renderer.js +190 -0
- package/L2_Handling/display/outputs/outputs.js +19 -0
- package/L2_Handling/display/outputs/table_renderer.js +765 -0
- package/L2_Handling/display/outputs/text_renderer.js +818 -0
- package/L2_Handling/display/outputs/typography_renderer.js +293 -0
- package/L2_Handling/display/outputs/video_renderer.js +116 -0
- package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
- package/L2_Handling/display/primitives/form_primitives.js +526 -0
- package/L2_Handling/display/primitives/generic_containers.js +109 -0
- package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
- package/L2_Handling/display/primitives/link_primitives.js +552 -0
- package/L2_Handling/display/primitives/lists_primitives.js +262 -0
- package/L2_Handling/display/primitives/media_primitives.js +383 -0
- package/L2_Handling/display/primitives/primitives.js +19 -0
- package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
- package/L2_Handling/display/primitives/table_primitives.js +528 -0
- package/L2_Handling/display/primitives/typography_primitives.js +175 -0
- package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
- package/L2_Handling/display/specialized/specialized.js +10 -0
- package/L2_Handling/hooks/hooks.js +9 -0
- package/L2_Handling/hooks/menu_integration.js +57 -0
- package/L2_Handling/hooks/widget_hook_manager.js +292 -0
- package/L2_Handling/message/message.js +8 -0
- package/L2_Handling/message/message_handler.js +701 -0
- package/L2_Handling/navigation/navigation.js +8 -0
- package/L2_Handling/navigation/navigation_manager.js +403 -0
- package/L2_Handling/zhooks/features/cache_live.js +287 -0
- package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
- package/L2_Handling/zhooks/zhooks_manager.js +65 -0
- package/L2_Handling/zvaf/zvaf.js +8 -0
- package/L2_Handling/zvaf/zvaf_manager.js +334 -0
- package/L3_Abstraction/L3_Abstraction.js +12 -0
- package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
- package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
- package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
- package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
- package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
- package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
- package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
- package/L3_Abstraction/renderer/renderer.js +1 -0
- package/L3_Abstraction/session/session.js +1 -0
- package/L4_Orchestration/L4_Orchestration.js +11 -0
- package/L4_Orchestration/client/client.js +1 -0
- package/L4_Orchestration/facade/facade.js +9 -0
- package/L4_Orchestration/facade/manager_registry.js +118 -0
- package/L4_Orchestration/facade/renderer_registry.js +274 -0
- package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
- package/L4_Orchestration/lifecycle/initializer.js +135 -0
- package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
- package/L4_Orchestration/rendering/facade.js +94 -0
- package/L4_Orchestration/rendering/rendering.js +7 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/bifrost_client.js +204 -0
- package/bifrost_core.js +1686 -0
- package/docs/ARCHITECTURE.md +111 -0
- package/docs/PROTOCOL.md +106 -0
- package/docs/RENDERERS.md +101 -0
- package/docs/SECURITY.md +92 -0
- package/package.json +24 -0
- package/syntax/prism-zconfig.js +41 -0
- package/syntax/prism-zenv.js +69 -0
- package/syntax/prism-zolo-theme.css +288 -0
- package/syntax/prism-zolo.js +380 -0
- package/syntax/prism-zschema.js +38 -0
- package/syntax/prism-zspark.js +25 -0
- package/syntax/prism-zui.js +68 -0
- package/zSys/accessibility/accessibility.js +10 -0
- package/zSys/accessibility/emoji_accessibility.js +173 -0
- package/zSys/dom/block_utils.js +122 -0
- package/zSys/dom/container_utils.js +370 -0
- package/zSys/dom/dom.js +13 -0
- package/zSys/dom/dom_utils.js +328 -0
- package/zSys/dom/encoding_utils.js +117 -0
- package/zSys/dom/style_utils.js +71 -0
- package/zSys/errors/error_display.js +299 -0
- package/zSys/errors/errors.js +10 -0
- package/zSys/theme/color_utils.js +274 -0
- package/zSys/theme/dark_mode_utils.js +272 -0
- package/zSys/theme/size_utils.js +256 -0
- package/zSys/theme/spacing_utils.js +405 -0
- package/zSys/theme/theme.js +14 -0
- package/zSys/theme/zbase.css +1735 -0
- package/zSys/theme/zbase_inject.js +161 -0
- package/zSys/theme/ztheme_utils.js +305 -0
- package/zSys/validation/error_boundary.js +201 -0
- package/zSys/validation/validation.js +11 -0
- package/zSys/validation/validation_utils.js +238 -0
- package/zSys/zSys.js +14 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZVaFManager - Manages zVaF elements (badge, navbar, content area)
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Initialize zVaF elements (zBifrostBadge, zNavBar, zVaF)
|
|
6
|
+
* - Populate connection badge
|
|
7
|
+
* - Update badge state (connecting, connected, disconnected, error)
|
|
8
|
+
* - Populate navbar from embedded config or API
|
|
9
|
+
* - Fetch fresh navbar after auth state changes
|
|
10
|
+
*
|
|
11
|
+
* Extracted from bifrost_client.js (Phase 3.2)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { NavBarBuilder } from '../../L3_Abstraction/orchestrator/navbar_builder.js';
|
|
15
|
+
|
|
16
|
+
export class ZVaFManager {
|
|
17
|
+
constructor(client) {
|
|
18
|
+
this.client = client;
|
|
19
|
+
this.logger = client.logger;
|
|
20
|
+
this.options = client.options;
|
|
21
|
+
this.zuiConfig = client.zuiConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initialize zVaF elements.
|
|
26
|
+
*
|
|
27
|
+
* SSOT (hard cut): <zVaF> is the bifrost ROOT — the single mount an app template
|
|
28
|
+
* provides. EVERYTHING bifrost-generated lives under it, never as stray <body>
|
|
29
|
+
* children:
|
|
30
|
+
*
|
|
31
|
+
* <zVaF> ← root (template owns this tag only)
|
|
32
|
+
* <zNavBar>…</zNavBar> ← chrome (runtime-created), sticky top
|
|
33
|
+
* <div id="zVaF-content">…</div>← the render/clear target (page content)
|
|
34
|
+
* <zBifrostBadge>…</zBifrostBadge> ← chrome (runtime-created), fixed
|
|
35
|
+
* <div class="bifrost-error-container">…</div> ← error toasts (lazy)
|
|
36
|
+
*
|
|
37
|
+
* Chrome are SIBLINGS of the content host, so re-renders (which wipe the content
|
|
38
|
+
* host) never destroy them. Position/look come purely from built-in zClass in
|
|
39
|
+
* zbase.css (navbar sticky, badge/errors fixed).
|
|
40
|
+
*/
|
|
41
|
+
initZVaFElements() {
|
|
42
|
+
this.logger.debug('[ZVaFManager] Starting initialization');
|
|
43
|
+
|
|
44
|
+
if (typeof document === 'undefined') {
|
|
45
|
+
this.logger.warn('[ZVaFManager] Not in browser environment');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The <zVaF> ROOT is the only required mount (template owns the bare tag).
|
|
50
|
+
const zVaFRoot = document.querySelector(this.options.targetElement) ||
|
|
51
|
+
document.getElementById(this.options.targetElement);
|
|
52
|
+
if (!zVaFRoot) {
|
|
53
|
+
this.logger.error(`[ZVaFManager] [ERROR] <${this.options.targetElement}> root not found in DOM`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.client._zVaFRoot = zVaFRoot;
|
|
57
|
+
this.logger.debug('[ZVaFManager] zVaF root found');
|
|
58
|
+
|
|
59
|
+
// Inner content host (#zVaF-content) — THE render/clear target. Created inside
|
|
60
|
+
// the root so chrome siblings survive content re-renders.
|
|
61
|
+
let contentHost = zVaFRoot.querySelector('#zVaF-content');
|
|
62
|
+
if (!contentHost) {
|
|
63
|
+
contentHost = document.createElement('div');
|
|
64
|
+
contentHost.id = 'zVaF-content';
|
|
65
|
+
contentHost.className = 'zVaF-content';
|
|
66
|
+
zVaFRoot.appendChild(contentHost);
|
|
67
|
+
this.logger.debug('[ZVaFManager] Created content host #zVaF-content');
|
|
68
|
+
}
|
|
69
|
+
this.client._zVaFElement = contentHost;
|
|
70
|
+
|
|
71
|
+
// NavBar: sticky-top chrome → FIRST child of the root (above content).
|
|
72
|
+
const navElement = this._ensureChromeHost('zNavBar', (el) => {
|
|
73
|
+
zVaFRoot.insertBefore(el, zVaFRoot.firstChild);
|
|
74
|
+
});
|
|
75
|
+
if (navElement) {
|
|
76
|
+
this.client._zNavBarElement = navElement;
|
|
77
|
+
// Populate asynchronously (don't block initialization).
|
|
78
|
+
this.populateNavBar().catch(err => {
|
|
79
|
+
this.logger.error('[ZVaFManager] Failed to populate navbar:', err);
|
|
80
|
+
});
|
|
81
|
+
this.logger.debug('[ZVaFManager] NavBar host ready, populating');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Badge: fixed chrome → appended to the root (DOM position is cosmetic; it's
|
|
85
|
+
// viewport-fixed via zbase.css). The badge is a core zHook — ON by default,
|
|
86
|
+
// opt out with zHooks: { badge: false }. When off, the element is never
|
|
87
|
+
// created and every updateBadgeState/updateRenderState call no-ops via its
|
|
88
|
+
// own _zConnectionBadge null-guard.
|
|
89
|
+
if (this._badgeEnabled()) {
|
|
90
|
+
const badgeElement = this._ensureChromeHost('zBifrostBadge', (el) => {
|
|
91
|
+
zVaFRoot.appendChild(el);
|
|
92
|
+
});
|
|
93
|
+
if (badgeElement) {
|
|
94
|
+
this.client._zConnectionBadge = badgeElement;
|
|
95
|
+
this.populateConnectionBadge();
|
|
96
|
+
this.logger.debug('[ZVaFManager] Badge host ready');
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
this.logger.debug('[ZVaFManager] Badge zHook disabled (zHooks.badge:false)');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Client-side navigation (SPA link interception + popstate handler) is
|
|
103
|
+
// PAGE-LEVEL infrastructure — independent of whether this page renders a
|
|
104
|
+
// navbar. Enable it unconditionally here. Previously it was only wired from
|
|
105
|
+
// populateNavBar()'s navbar branches, so a zNavBar:false page loaded directly
|
|
106
|
+
// (hard reload) never registered the popstate listener → server-driven
|
|
107
|
+
// history.back() rewrote the URL without re-rendering. (populateNavBar still
|
|
108
|
+
// calls it too; enablement is idempotent.)
|
|
109
|
+
this.client._enableClientSideNavigation().catch(err => {
|
|
110
|
+
this.logger.error('[ZVaFManager] Failed to enable client-side navigation:', err);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.logger.log('[ZVaFManager] All elements initialized under <zVaF> root');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Is the connection-badge zHook enabled? Default-on; opt out via
|
|
118
|
+
* zHooks: { badge: false }. (Core zHook — gated here, not loaded as a module.)
|
|
119
|
+
* @returns {boolean}
|
|
120
|
+
*/
|
|
121
|
+
_badgeEnabled() {
|
|
122
|
+
const zHooks = this.options.zHooks || {};
|
|
123
|
+
return zHooks.badge !== false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Ensure a runtime-owned chrome host element exists, returning it.
|
|
128
|
+
*
|
|
129
|
+
* Reuses an existing element if present (legacy templates); otherwise creates it
|
|
130
|
+
* and places it via `place(el)`. Hard-cut SSOT — <zNavBar>/<zBifrostBadge> are no
|
|
131
|
+
* longer required in app templates; the runtime creates them inside <zVaF>.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} tagName - Custom element tag (zNavBar | zBifrostBadge)
|
|
134
|
+
* @param {(el: HTMLElement) => void} place - Inserts the freshly created element
|
|
135
|
+
* @returns {HTMLElement}
|
|
136
|
+
*/
|
|
137
|
+
_ensureChromeHost(tagName, place) {
|
|
138
|
+
let el = document.querySelector(tagName);
|
|
139
|
+
if (el) return el;
|
|
140
|
+
el = document.createElement(tagName);
|
|
141
|
+
place(el);
|
|
142
|
+
this.logger.debug(`[ZVaFManager] Created runtime chrome host <${tagName}>`);
|
|
143
|
+
return el;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Populate connection badge content (v1.6.0: Simplified - element exists, just set content)
|
|
148
|
+
*/
|
|
149
|
+
populateConnectionBadge() {
|
|
150
|
+
if (!this.client._zConnectionBadge) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Set initial badge content (will be updated by connection hooks)
|
|
155
|
+
this.client._zConnectionBadge.className = 'zConnection zBadge zBadge-connection zBadge-pending';
|
|
156
|
+
this.client._zConnectionBadge.innerHTML = `
|
|
157
|
+
<svg class="zIcon zIcon-sm zBadge-dot" aria-hidden="true">
|
|
158
|
+
<use xlink:href="#icon-circle-fill"></use>
|
|
159
|
+
</svg>
|
|
160
|
+
<span class="zBadge-text">Connecting...</span>
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
this.logger.log('[ConnectionBadge] Badge populated with initial state');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Update badge state (v1.6.0: Helper method called from hooks)
|
|
168
|
+
* @param {string} state - 'connecting', 'connected', 'disconnected', 'error'
|
|
169
|
+
*/
|
|
170
|
+
updateBadgeState(state) {
|
|
171
|
+
if (!this.client._zConnectionBadge) {
|
|
172
|
+
// Badge is opt-in (zHooks.badge). When disabled the element is never
|
|
173
|
+
// created — that's expected, not an error. Stay quiet (matches
|
|
174
|
+
// updateRenderState's silent no-op) so it doesn't spam the console.
|
|
175
|
+
this.logger.debug('[ConnectionBadge] No badge element (badge disabled) — skipping update');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const badge = this.client._zConnectionBadge;
|
|
180
|
+
const badgeText = badge.querySelector('.zBadge-text');
|
|
181
|
+
|
|
182
|
+
if (!badgeText) {
|
|
183
|
+
this.logger.warn('[ConnectionBadge] Cannot update - badge text element not found');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.logger.debug(`[ConnectionBadge] Updating badge to: ${state}`);
|
|
188
|
+
|
|
189
|
+
// Remove all state classes
|
|
190
|
+
badge.classList.remove('zBadge-pending', 'zBadge-success', 'zBadge-error');
|
|
191
|
+
|
|
192
|
+
// Apply new state
|
|
193
|
+
switch (state) {
|
|
194
|
+
case 'connected':
|
|
195
|
+
badge.classList.add('zBadge-success');
|
|
196
|
+
badgeText.textContent = 'Connected';
|
|
197
|
+
this.logger.log('[ConnectionBadge] Connected');
|
|
198
|
+
break;
|
|
199
|
+
case 'disconnected':
|
|
200
|
+
badge.classList.add('zBadge-pending');
|
|
201
|
+
badgeText.textContent = 'Disconnected';
|
|
202
|
+
this.logger.debug('[ConnectionBadge] Disconnected');
|
|
203
|
+
break;
|
|
204
|
+
case 'error':
|
|
205
|
+
badge.classList.add('zBadge-error');
|
|
206
|
+
badgeText.textContent = 'Error';
|
|
207
|
+
this.logger.debug('[ConnectionBadge] Error');
|
|
208
|
+
break;
|
|
209
|
+
case 'connecting':
|
|
210
|
+
default:
|
|
211
|
+
badge.classList.add('zBadge-pending');
|
|
212
|
+
badgeText.textContent = 'Connecting...';
|
|
213
|
+
this.logger.debug('[ConnectionBadge] Connecting');
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Update the badge with bifrost RENDER status (distinct from connection state).
|
|
220
|
+
*
|
|
221
|
+
* This is the zOS↔user "page is painting" contract: as the runtime streams a
|
|
222
|
+
* page in, the badge reads "Rendering k/N", then snaps back to the connected
|
|
223
|
+
* state when the last section lands. It reuses the same pending/success chip —
|
|
224
|
+
* no new chrome, no layout impact.
|
|
225
|
+
*
|
|
226
|
+
* @param {Object} opts
|
|
227
|
+
* @param {number} [opts.current] - Sections painted so far
|
|
228
|
+
* @param {number} [opts.total] - Total sections in this render
|
|
229
|
+
* @param {boolean} [opts.done] - Render finished → restore connected state
|
|
230
|
+
*/
|
|
231
|
+
updateRenderState({ current = 0, total = 0, done = false } = {}) {
|
|
232
|
+
if (done) {
|
|
233
|
+
this.updateBadgeState('connected');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const badge = this.client._zConnectionBadge;
|
|
237
|
+
if (!badge) return;
|
|
238
|
+
const badgeText = badge.querySelector('.zBadge-text');
|
|
239
|
+
if (!badgeText) return;
|
|
240
|
+
|
|
241
|
+
badge.classList.remove('zBadge-success', 'zBadge-error');
|
|
242
|
+
badge.classList.add('zBadge-pending');
|
|
243
|
+
badgeText.textContent = total > 0 ? `Rendering ${current}/${total}` : 'Rendering…';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Populate navbar from embedded config.
|
|
248
|
+
* 3A: Prefers server-built nav_html; falls back to array-based builder.
|
|
249
|
+
*/
|
|
250
|
+
async populateNavBar() {
|
|
251
|
+
if (!this.client._zNavBarElement) return;
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const navHtml = this.zuiConfig?.nav_html;
|
|
255
|
+
|
|
256
|
+
if (navHtml) {
|
|
257
|
+
// 3A path: Python sent pre-built HTML — inject + wire events
|
|
258
|
+
this.client._zNavBarElement.innerHTML = navHtml;
|
|
259
|
+
NavBarBuilder.wireNavBarEvents(
|
|
260
|
+
this.client._zNavBarElement.firstElementChild,
|
|
261
|
+
this.client,
|
|
262
|
+
this.logger
|
|
263
|
+
);
|
|
264
|
+
this.logger.log('[NavBar] NavBar populated from server HTML (3A)');
|
|
265
|
+
} else if (this.zuiConfig?.zNavBar) {
|
|
266
|
+
// Legacy path: build from items array
|
|
267
|
+
const navElement = await this.client._renderMetaNavBarHTML(this.zuiConfig.zNavBar);
|
|
268
|
+
this.client._zNavBarElement.innerHTML = '';
|
|
269
|
+
if (navElement) {
|
|
270
|
+
this.client._zNavBarElement.appendChild(navElement);
|
|
271
|
+
this.logger.log('[NavBar] NavBar populated from embedded config (DOM element):', this.zuiConfig.zNavBar);
|
|
272
|
+
} else {
|
|
273
|
+
this.logger.warn('[NavBar] renderMetaNavBarHTML returned null');
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
this.logger.warn('[NavBar] No nav_html or zNavBar in embedded zuiConfig');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await this.client._enableClientSideNavigation();
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.logger.error('[NavBar] Failed to populate:', error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Re-populate navbar from connection_info nav_html (3A) or API fallback.
|
|
288
|
+
* Called after auth state changes / reconnect.
|
|
289
|
+
*/
|
|
290
|
+
async fetchAndPopulateNavBar(navHtmlFromServer = null) {
|
|
291
|
+
if (!this.client._zNavBarElement) return;
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
if (navHtmlFromServer) {
|
|
295
|
+
// 3A path: server pushed updated HTML via connection_info
|
|
296
|
+
this.client._zNavBarElement.innerHTML = navHtmlFromServer;
|
|
297
|
+
NavBarBuilder.wireNavBarEvents(
|
|
298
|
+
this.client._zNavBarElement.firstElementChild,
|
|
299
|
+
this.client,
|
|
300
|
+
this.logger
|
|
301
|
+
);
|
|
302
|
+
this.logger.log('[NavBar] NavBar updated from connection_info nav_html (3A)');
|
|
303
|
+
await this.client._enableClientSideNavigation();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Client-side bounce-back / refresh has no new server round-trip, so reuse
|
|
308
|
+
// the RBAC-filtered nav_html the server embedded in the page head
|
|
309
|
+
// (zui-config). This is the SSOT: the server resolves the navbar and ships
|
|
310
|
+
// it inline (and via connection_info / route-config). The legacy
|
|
311
|
+
// /api/zui/config endpoint was removed — the client no longer re-fetches a
|
|
312
|
+
// parallel copy of server-owned config.
|
|
313
|
+
const embeddedNav = this.zuiConfig?.nav_html || this.client?.zuiConfig?.nav_html;
|
|
314
|
+
if (embeddedNav) {
|
|
315
|
+
this.client._zNavBarElement.innerHTML = embeddedNav;
|
|
316
|
+
NavBarBuilder.wireNavBarEvents(
|
|
317
|
+
this.client._zNavBarElement.firstElementChild,
|
|
318
|
+
this.client,
|
|
319
|
+
this.logger
|
|
320
|
+
);
|
|
321
|
+
this.logger.log('[NavBar] NavBar refreshed from embedded zui-config nav_html');
|
|
322
|
+
await this.client._enableClientSideNavigation();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.logger.warn('[NavBar] No embedded nav_html — nothing to refresh');
|
|
327
|
+
} catch (error) {
|
|
328
|
+
this.logger.error('[NavBar] Failed to populate:', error);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export default ZVaFManager;
|
|
334
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L3_Abstraction - High-Level Abstractions Layer
|
|
3
|
+
*
|
|
4
|
+
* Coordinates L2 handlers, provides unified interfaces.
|
|
5
|
+
* Depends on: L1_Foundation, L2_Handling
|
|
6
|
+
* Provides: Orchestration, Rendering, Session management
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Export subdirectories (will be populated in Step 4)
|
|
10
|
+
export * from './orchestrator/orchestrator.js';
|
|
11
|
+
export * from './renderer/renderer.js';
|
|
12
|
+
export * from './session/session.js';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L3_Abstraction/orchestrator/container_unwrapper.js
|
|
3
|
+
*
|
|
4
|
+
* Container Unwrapping Logic
|
|
5
|
+
*
|
|
6
|
+
* Prevents double-nesting and unnecessary wrapper divs by detecting
|
|
7
|
+
* when a container and its child have identical classes or when a
|
|
8
|
+
* container has no styling purpose.
|
|
9
|
+
*
|
|
10
|
+
* Unwrapping rules:
|
|
11
|
+
* 1. If container and element have identical classes → unwrap
|
|
12
|
+
* 2. If container has no classes AND no styles → unwrap
|
|
13
|
+
* 3. If element has all container classes (superset) → unwrap
|
|
14
|
+
* 4. Otherwise → keep wrapper
|
|
15
|
+
*
|
|
16
|
+
* Extracted from zdisplay_orchestrator.js (Phase 4.4c)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ContainerUnwrapper - Detects and handles unnecessary wrapper divs
|
|
21
|
+
*/
|
|
22
|
+
export class ContainerUnwrapper {
|
|
23
|
+
constructor(logger) {
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if container should be unwrapped (element appended directly to parent)
|
|
29
|
+
* @param {HTMLElement} container - The wrapper container
|
|
30
|
+
* @param {HTMLElement} element - The child element
|
|
31
|
+
* @returns {boolean} True if should unwrap
|
|
32
|
+
*/
|
|
33
|
+
shouldUnwrap(container, element) {
|
|
34
|
+
// Never unwrap comment nodes (delegated elements)
|
|
35
|
+
if (element.nodeType === Node.COMMENT_NODE) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const containerClasses = container.className ? container.className.split(' ').filter(c => c.trim()) : [];
|
|
40
|
+
const elementClasses = element.className ? element.className.split(' ').filter(c => c.trim()) : [];
|
|
41
|
+
const hasAllContainerClasses = containerClasses.length > 0 && containerClasses.every(cls => elementClasses.includes(cls));
|
|
42
|
+
|
|
43
|
+
// Case 1: Container and element have identical classes (or element has all container classes)
|
|
44
|
+
if (container.className && element.className && (container.className === element.className || hasAllContainerClasses)) {
|
|
45
|
+
this.logger.debug(`[ContainerUnwrapper] Should unwrap: element has same/superset classes`);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Case 2: Container has no classes AND no styles (no styling purpose)
|
|
50
|
+
if ((!container.className || container.className === '') && !container.getAttribute('style')) {
|
|
51
|
+
this.logger.debug(`[ContainerUnwrapper] Should unwrap: container has no styling`);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Case 3: Keep wrapper
|
|
56
|
+
this.logger.debug(`[ContainerUnwrapper] Keep wrapper (classes: ${container.className || 'none'})`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Unwrap a container by transferring metadata to element and returning element
|
|
62
|
+
* @param {HTMLElement} container - The wrapper container
|
|
63
|
+
* @param {HTMLElement} element - The child element
|
|
64
|
+
* @param {string} key - The data-zkey value
|
|
65
|
+
* @returns {HTMLElement} The unwrapped element
|
|
66
|
+
*/
|
|
67
|
+
unwrap(container, element, key) {
|
|
68
|
+
// Transfer data-zkey and id to the element
|
|
69
|
+
element.setAttribute('data-zkey', key);
|
|
70
|
+
if (!element.id) {
|
|
71
|
+
element.setAttribute('id', key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.logger.debug(`[ContainerUnwrapper] Unwrapped ${key}: appending directly to parent`);
|
|
75
|
+
return element;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Process element unwrapping and return result
|
|
80
|
+
* Returns { shouldAppendContainer: boolean, elementToAppend: HTMLElement }
|
|
81
|
+
* @param {HTMLElement} container - The wrapper container
|
|
82
|
+
* @param {HTMLElement} element - The child element
|
|
83
|
+
* @param {string} key - The data-zkey value
|
|
84
|
+
* @returns {{shouldAppendContainer: boolean, elementToAppend: HTMLElement}}
|
|
85
|
+
*/
|
|
86
|
+
processUnwrapping(container, element, key) {
|
|
87
|
+
if (this.shouldUnwrap(container, element)) {
|
|
88
|
+
return {
|
|
89
|
+
shouldAppendContainer: false,
|
|
90
|
+
elementToAppend: this.unwrap(container, element, key)
|
|
91
|
+
};
|
|
92
|
+
} else {
|
|
93
|
+
this.logger.debug(`[ContainerUnwrapper] Keeping wrapper for ${key} (classes: ${container.className || 'none'})`);
|
|
94
|
+
container.appendChild(element);
|
|
95
|
+
return {
|
|
96
|
+
shouldAppendContainer: true,
|
|
97
|
+
elementToAppend: container
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|