@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,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypographyRenderer - Renders text, headers, and dividers
|
|
3
|
+
*
|
|
4
|
+
* Uses typography primitives for DOM creation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────
|
|
8
|
+
// Imports
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
// Layer 0: Primitives
|
|
12
|
+
import { createHeading, createParagraph } from '../primitives/typography_primitives.js';
|
|
13
|
+
import { createSemanticElement, createLanguagePre } from '../primitives/semantic_element_primitive.js';
|
|
14
|
+
import { escapeHtml } from '../../../zSys/dom/encoding_utils.js';
|
|
15
|
+
|
|
16
|
+
export class TypographyRenderer {
|
|
17
|
+
constructor(logger) {
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert newlines to <br> tags for Bifrost GUI
|
|
23
|
+
* Handles BOTH literal \n strings (from YAML without quotes) AND actual newlines (from YAML with quotes)
|
|
24
|
+
* @param {string} text - Text with potential newlines
|
|
25
|
+
* @returns {string} HTML-safe text with <br> tags
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
_convertNewlinesToBr(text) {
|
|
29
|
+
// STEP 1: Process zText semantic distinction
|
|
30
|
+
// \x1E (YAML multilines) → space (for readability)
|
|
31
|
+
// \n (explicit escapes) → <br> (line break)
|
|
32
|
+
const processedText = text.replace(/\x1E/g, ' ');
|
|
33
|
+
|
|
34
|
+
// STEP 2: Escape HTML entities for XSS safety (SSOT escapeHtml)
|
|
35
|
+
const escaped = escapeHtml(processedText);
|
|
36
|
+
|
|
37
|
+
// STEP 3: Convert explicit \n to <br> tags
|
|
38
|
+
return escaped.replace(/\n/g, '<br>');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Render text element
|
|
43
|
+
* @param {Object} eventData - Event data with content, color, indent, zId, semantic, etc.
|
|
44
|
+
* @returns {HTMLElement}
|
|
45
|
+
*/
|
|
46
|
+
renderText(eventData) {
|
|
47
|
+
const classes = this._buildTextClasses(eventData);
|
|
48
|
+
const attrs = {};
|
|
49
|
+
if (classes) {
|
|
50
|
+
attrs.class = classes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Support zId (universal), _zId (from zUI files), and _id (legacy)
|
|
54
|
+
if (eventData.zId || eventData._zId || eventData._id) {
|
|
55
|
+
attrs.id = eventData.zId || eventData._zId || eventData._id;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Support _for attribute for labels (maps to 'for' HTML attribute)
|
|
59
|
+
if (eventData._for) {
|
|
60
|
+
attrs.for = eventData._for;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Decode content once at function scope for delegation
|
|
64
|
+
const content = this._decodeUnicodeEscapes(eventData.content || '');
|
|
65
|
+
|
|
66
|
+
// Check semantic parameter (_zHTML takes precedence, semantic for backward compatibility)
|
|
67
|
+
const semantic = eventData._zHTML || eventData.semantic;
|
|
68
|
+
|
|
69
|
+
// Log deprecation warning if using legacy semantic parameter
|
|
70
|
+
if (eventData.semantic && !eventData._zHTML && this.logger) {
|
|
71
|
+
this.logger.warn(`[TypographyRenderer] DEPRECATED: Use _zHTML instead of semantic parameter`);
|
|
72
|
+
}
|
|
73
|
+
let element;
|
|
74
|
+
|
|
75
|
+
//
|
|
76
|
+
// LANGUAGE-SPECIFIC PRE SUPPORT (2026-01-28)
|
|
77
|
+
// Enables: semantic: pre-html, pre-css, pre-zolo, pre-js, etc.
|
|
78
|
+
// Creates <pre><code class="language-xxx">...</code></pre> for Prism.js
|
|
79
|
+
//
|
|
80
|
+
const preLanguageMatch = semantic && semantic.match(/^pre-(\w+)$/);
|
|
81
|
+
const isLanguagePre = !!preLanguageMatch;
|
|
82
|
+
const preLanguage = preLanguageMatch ? preLanguageMatch[1] : null;
|
|
83
|
+
|
|
84
|
+
if (isLanguagePre) {
|
|
85
|
+
// Use centralized language-specific pre primitive
|
|
86
|
+
element = createLanguagePre(preLanguage, content, attrs);
|
|
87
|
+
} else if (semantic && semantic !== 'p') {
|
|
88
|
+
// Use centralized semantic element primitive (SSOT for semantic parameter)
|
|
89
|
+
element = createSemanticElement(semantic, attrs, this.logger);
|
|
90
|
+
|
|
91
|
+
// For plain pre/code elements, use textContent to display HTML as literal text
|
|
92
|
+
if (semantic === 'pre' || semantic === 'code') {
|
|
93
|
+
element.textContent = content;
|
|
94
|
+
} else {
|
|
95
|
+
element.innerHTML = this._convertNewlinesToBr(content);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// Default: <p> for standard text
|
|
99
|
+
const p = createParagraph(attrs);
|
|
100
|
+
p.innerHTML = this._convertNewlinesToBr(content);
|
|
101
|
+
element = p;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Apply indent as margin-left (each level = 1rem)
|
|
105
|
+
if (eventData.indent > 0) {
|
|
106
|
+
element.style.marginLeft = `${eventData.indent}rem`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// _zClass / _zStyle are applied centrally by the orchestrator (SSOT) — see
|
|
110
|
+
// renderZDisplayEvent's append-mode applyMetadata pass. Nothing to do here.
|
|
111
|
+
|
|
112
|
+
// Handle _zDelegate: update target input with this text's content
|
|
113
|
+
if (eventData._zDelegate) {
|
|
114
|
+
this._handleDelegation(eventData._zDelegate, content);
|
|
115
|
+
// Return empty element to suppress visual output
|
|
116
|
+
return document.createComment('delegated');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return element;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Render header element
|
|
124
|
+
* @param {Object} eventData - Event data with label, indent (level), zId, etc.
|
|
125
|
+
* @returns {HTMLElement}
|
|
126
|
+
*/
|
|
127
|
+
renderHeader(eventData) {
|
|
128
|
+
// Backend sends 'indent' with header level (zH0=0, zH1=1, zH2=2, etc.)
|
|
129
|
+
// Use nullish coalescing to handle indent=0 correctly (0 is a valid level for h0)
|
|
130
|
+
const level = eventData.indent ?? eventData.level ?? 1;
|
|
131
|
+
const classes = this._buildTextClasses(eventData);
|
|
132
|
+
const attrs = {};
|
|
133
|
+
if (classes) {
|
|
134
|
+
attrs.class = classes;
|
|
135
|
+
}
|
|
136
|
+
// Support zId (universal), _zId (from zUI files), and _id (legacy)
|
|
137
|
+
if (eventData.zId || eventData._zId || eventData._id) {
|
|
138
|
+
attrs.id = eventData.zId || eventData._zId || eventData._id;
|
|
139
|
+
}
|
|
140
|
+
const h = createHeading(level, attrs);
|
|
141
|
+
|
|
142
|
+
// _zStyle applied centrally by the orchestrator (SSOT) — nothing to do here.
|
|
143
|
+
|
|
144
|
+
// Decode Unicode escapes and convert newlines to <br> for Bifrost
|
|
145
|
+
const content = eventData.label || eventData.content || '';
|
|
146
|
+
const decoded = this._decodeUnicodeEscapes(content);
|
|
147
|
+
h.innerHTML = this._convertNewlinesToBr(decoded);
|
|
148
|
+
|
|
149
|
+
// Handle _zDelegate: update target element with this header's content
|
|
150
|
+
if (eventData._zDelegate) {
|
|
151
|
+
this._handleDelegation(eventData._zDelegate, decoded);
|
|
152
|
+
// Return empty element to suppress visual output
|
|
153
|
+
return document.createComment('delegated');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return h;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Render divider element
|
|
161
|
+
* @param {Object} eventData - Event data with color, zId, etc.
|
|
162
|
+
* @returns {HTMLElement}
|
|
163
|
+
*/
|
|
164
|
+
renderDivider(eventData) {
|
|
165
|
+
const hr = document.createElement('hr');
|
|
166
|
+
const classes = ['zDivider'];
|
|
167
|
+
if (eventData.color) {
|
|
168
|
+
classes.push(`zBorder-${eventData.color}`);
|
|
169
|
+
}
|
|
170
|
+
hr.className = classes.join(' ');
|
|
171
|
+
// Support zId (universal), _zId (from zUI files), and _id (legacy)
|
|
172
|
+
if (eventData.zId || eventData._zId || eventData._id) {
|
|
173
|
+
hr.setAttribute('id', eventData.zId || eventData._zId || eventData._id);
|
|
174
|
+
}
|
|
175
|
+
return hr;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Build text classes from event data
|
|
180
|
+
* @private
|
|
181
|
+
*/
|
|
182
|
+
_buildTextClasses(eventData) {
|
|
183
|
+
const classes = [];
|
|
184
|
+
|
|
185
|
+
// Color: normalize to lowercase for zTheme consistency
|
|
186
|
+
if (eventData.color) {
|
|
187
|
+
const color = eventData.color.toLowerCase();
|
|
188
|
+
classes.push(`zText-${color}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// NOTE: _zClass is applied centrally by the orchestrator (SSOT, append mode),
|
|
192
|
+
// not here — this builder only owns the contextual color class.
|
|
193
|
+
|
|
194
|
+
return classes.length > 0 ? classes.join(' ') : '';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Decode Unicode escape sequences to actual characters
|
|
199
|
+
* Supports: \uXXXX (standard) and \UXXXXXXXX (extended) formats
|
|
200
|
+
*
|
|
201
|
+
* Note: Basic escape sequences (\n, \t, etc.) are handled by JSON.parse()
|
|
202
|
+
* automatically when receiving data from backend. We only need to decode
|
|
203
|
+
* custom Unicode formats that JSON doesn't handle.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} text - Text containing Unicode escapes
|
|
206
|
+
* @returns {string} - Decoded text
|
|
207
|
+
* @private
|
|
208
|
+
*/
|
|
209
|
+
_decodeUnicodeEscapes(text) {
|
|
210
|
+
if (text === null || text === undefined) return '';
|
|
211
|
+
if (typeof text !== 'string') return String(text);
|
|
212
|
+
|
|
213
|
+
// Replace \uXXXX format (standard 4-digit Unicode escape)
|
|
214
|
+
text = text.replace(/\\u([0-9A-Fa-f]{4})/g, (match, hexCode) => {
|
|
215
|
+
return String.fromCodePoint(parseInt(hexCode, 16));
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Replace \UXXXXXXXX format (extended 4-8 digit for supplementary characters & emojis)
|
|
219
|
+
text = text.replace(/\\U([0-9A-Fa-f]{4,8})/g, (match, hexCode) => {
|
|
220
|
+
return String.fromCodePoint(parseInt(hexCode, 16));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Replace basic escape sequences (literal strings like \\n, \\t, etc.)
|
|
224
|
+
// These come from JSON where Python sends "\n" which becomes "\\n" in JSON
|
|
225
|
+
text = text
|
|
226
|
+
.replace(/\\n/g, '\n') // Newline
|
|
227
|
+
.replace(/\\t/g, '\t') // Tab
|
|
228
|
+
.replace(/\\r/g, '\r') // Carriage return
|
|
229
|
+
.replace(/\\'/g, "'") // Single quote
|
|
230
|
+
.replace(/\\"/g, '"') // Double quote
|
|
231
|
+
.replace(/\\\\/g, '\\'); // Backslash (must be last!)
|
|
232
|
+
|
|
233
|
+
return text;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Handle _zDelegate: update target input/element with provided value.
|
|
238
|
+
* Resolves the delegate path (e.g., "one", "_GUI.Btn_Eq") to a DOM element
|
|
239
|
+
* and updates its value (for inputs) or textContent (for other elements).
|
|
240
|
+
* @param {string} delegatePath - The zKey path to the target element
|
|
241
|
+
* @param {string} value - The value to set
|
|
242
|
+
* @private
|
|
243
|
+
*/
|
|
244
|
+
_handleDelegation(delegatePath, value) {
|
|
245
|
+
if (!delegatePath) return;
|
|
246
|
+
|
|
247
|
+
// Resolve the target element using data-zkey attributes
|
|
248
|
+
const targetContainer = this._resolveZDelegatePath(delegatePath, document);
|
|
249
|
+
if (!targetContainer) {
|
|
250
|
+
this.logger.warn('[Delegate] Target not found for path:', delegatePath);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Find input/textarea/select within the container
|
|
255
|
+
const targetInput = targetContainer.querySelector('input, textarea, select');
|
|
256
|
+
if (targetInput) {
|
|
257
|
+
targetInput.value = value;
|
|
258
|
+
targetInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
259
|
+
this.logger.log('[Delegate] Updated input:', delegatePath, '→', value);
|
|
260
|
+
} else {
|
|
261
|
+
// Fallback: update textContent for non-input elements
|
|
262
|
+
targetContainer.textContent = value;
|
|
263
|
+
this.logger.log('[Delegate] Updated textContent:', delegatePath, '→', value);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Resolve a _zDelegate path (e.g., "one", "_GUI.Btn_Eq") to a DOM element.
|
|
269
|
+
* Uses data-zkey attributes to traverse the DOM hierarchy.
|
|
270
|
+
* @param {string} path - Dot-separated zKey path
|
|
271
|
+
* @param {HTMLElement} scope - Starting scope for resolution (usually document)
|
|
272
|
+
* @returns {HTMLElement|null}
|
|
273
|
+
* @private
|
|
274
|
+
*/
|
|
275
|
+
_resolveZDelegatePath(path, scope) {
|
|
276
|
+
if (!path) return null;
|
|
277
|
+
const parts = path.split('.');
|
|
278
|
+
let el = scope;
|
|
279
|
+
|
|
280
|
+
for (const part of parts) {
|
|
281
|
+
el = el.querySelector(`[data-zkey="${part}"]`);
|
|
282
|
+
if (!el) {
|
|
283
|
+
this.logger.warn('[Delegate] Path resolution failed at:', part, 'in', path);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return el;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export default TypographyRenderer;
|
|
293
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Video Renderer - Media Display Events
|
|
4
|
+
*
|
|
5
|
+
* Terminal-First Design:
|
|
6
|
+
* - Backend sends src, alt_text, caption (works with URLs, served paths)
|
|
7
|
+
* - Terminal displays metadata + button to open (zOpen)
|
|
8
|
+
* - Bifrost renders a native <video controls> element with zTheme styling
|
|
9
|
+
*
|
|
10
|
+
* Mirrors ImageRenderer: primitives-first, _zClass / _id passthrough,
|
|
11
|
+
* optional <figure>/<figcaption> when a caption is present.
|
|
12
|
+
*
|
|
13
|
+
* @module rendering/video_renderer
|
|
14
|
+
* @layer 3
|
|
15
|
+
* @pattern Primitives-First
|
|
16
|
+
*
|
|
17
|
+
* Exports:
|
|
18
|
+
* - VideoRenderer: Class for rendering video events
|
|
19
|
+
*
|
|
20
|
+
* Example:
|
|
21
|
+
* ```javascript
|
|
22
|
+
* import VideoRenderer from './video_renderer.js';
|
|
23
|
+
* const renderer = new VideoRenderer(logger);
|
|
24
|
+
* const el = renderer.render({
|
|
25
|
+
* src: '/zcloud-static/media/demos/reel.mp4',
|
|
26
|
+
* alt_text: 'Studio reel',
|
|
27
|
+
* caption: 'Our latest work',
|
|
28
|
+
* _zClass: 'zRounded',
|
|
29
|
+
* _id: 'reel'
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
|
|
35
|
+
|
|
36
|
+
export class VideoRenderer {
|
|
37
|
+
constructor(logger) {
|
|
38
|
+
this.logger = logger;
|
|
39
|
+
|
|
40
|
+
const originalRender = this.render.bind(this);
|
|
41
|
+
this.render = withErrorBoundary(originalRender, {
|
|
42
|
+
component: 'VideoRenderer',
|
|
43
|
+
logger: this.logger
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Render a <video> element from event primitives.
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} eventData - Event data from backend
|
|
51
|
+
* @param {string} eventData.src - Video source (URL or served path)
|
|
52
|
+
* @param {string} [eventData.alt_text] - Accessibility label (→ aria-label)
|
|
53
|
+
* @param {string} [eventData.caption] - Optional caption (→ <figcaption>)
|
|
54
|
+
* @param {boolean} [eventData.autoplay] - Autoplay (implies muted)
|
|
55
|
+
* @param {boolean} [eventData.loop] - Loop playback
|
|
56
|
+
* @param {boolean} [eventData.muted] - Mute audio track
|
|
57
|
+
* @param {string} [eventData.poster] - Poster image shown before playback
|
|
58
|
+
* @param {string} [eventData._zClass] - Custom classes for styling
|
|
59
|
+
* @param {string} [eventData._id] - Custom DOM id for targeting
|
|
60
|
+
* @returns {HTMLElement} Video element (or <figure> when captioned)
|
|
61
|
+
*/
|
|
62
|
+
render(eventData) {
|
|
63
|
+
const { src, alt_text, caption, _zClass, _id,
|
|
64
|
+
autoplay, loop, muted, poster } = eventData;
|
|
65
|
+
|
|
66
|
+
if (!src) {
|
|
67
|
+
this.logger.error('[VideoRenderer] Missing src parameter');
|
|
68
|
+
return this._createErrorElement();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.logger.debug(`[VideoRenderer] Rendering video: ${src}`);
|
|
72
|
+
|
|
73
|
+
const video = document.createElement('video');
|
|
74
|
+
video.src = src;
|
|
75
|
+
video.controls = true; // always give the user native controls
|
|
76
|
+
video.preload = 'metadata'; // fetch dimensions/duration, not the whole file
|
|
77
|
+
|
|
78
|
+
// Safe default so an unstyled video never overflows its column; _zClass wins.
|
|
79
|
+
video.style.maxWidth = '100%';
|
|
80
|
+
video.style.height = 'auto';
|
|
81
|
+
|
|
82
|
+
if (_zClass) video.className = _zClass;
|
|
83
|
+
if (_id) video.setAttribute('id', _id);
|
|
84
|
+
if (alt_text) video.setAttribute('aria-label', alt_text);
|
|
85
|
+
if (poster) video.poster = poster;
|
|
86
|
+
if (loop) video.loop = true;
|
|
87
|
+
// Autoplay only works muted in modern browsers — enforce that pairing.
|
|
88
|
+
if (autoplay) { video.autoplay = true; video.muted = true; }
|
|
89
|
+
else if (muted) video.muted = true;
|
|
90
|
+
|
|
91
|
+
if (caption) {
|
|
92
|
+
const figure = document.createElement('figure');
|
|
93
|
+
figure.appendChild(video);
|
|
94
|
+
const figcaption = document.createElement('figcaption');
|
|
95
|
+
figcaption.textContent = caption;
|
|
96
|
+
figcaption.className = 'zText-muted zText-center zmt-2';
|
|
97
|
+
figure.appendChild(figcaption);
|
|
98
|
+
// _zClass/_zStyle live on the inner <video>; skip the central pass on the wrapper.
|
|
99
|
+
figure.__zMetaScoped = true;
|
|
100
|
+
this.logger.debug('[VideoRenderer] Video with caption rendered');
|
|
101
|
+
return figure;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.logger.debug('[VideoRenderer] Video rendered');
|
|
105
|
+
return video;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_createErrorElement() {
|
|
109
|
+
const error = document.createElement('div');
|
|
110
|
+
error.className = 'zAlert zAlert-danger';
|
|
111
|
+
error.textContent = '[WARN] Video source missing';
|
|
112
|
+
return error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default VideoRenderer;
|