@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.
Files changed (140) hide show
  1. package/L1_Foundation/L1_Foundation.js +13 -0
  2. package/L1_Foundation/bootstrap/bootstrap.js +11 -0
  3. package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
  4. package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
  5. package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
  6. package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
  7. package/L1_Foundation/bootstrap/module_registry.js +102 -0
  8. package/L1_Foundation/bootstrap/prism_loader.js +164 -0
  9. package/L1_Foundation/config/client_config.js +110 -0
  10. package/L1_Foundation/config/config.js +7 -0
  11. package/L1_Foundation/connection/connection.js +8 -0
  12. package/L1_Foundation/connection/websocket_connection.js +122 -0
  13. package/L1_Foundation/constants/bifrost_constants.js +284 -0
  14. package/L1_Foundation/constants/constants.js +7 -0
  15. package/L1_Foundation/logger/logger.js +10 -0
  16. package/L2_Handling/L2_Handling.js +15 -0
  17. package/L2_Handling/cache/cache.js +22 -0
  18. package/L2_Handling/cache/cache_constants.js +69 -0
  19. package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
  20. package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
  21. package/L2_Handling/cache/orchestration/orchestration.js +12 -0
  22. package/L2_Handling/cache/storage/session_manager.js +289 -0
  23. package/L2_Handling/cache/storage/storage.js +10 -0
  24. package/L2_Handling/cache/storage/storage_manager.js +590 -0
  25. package/L2_Handling/display/composite/composite.js +13 -0
  26. package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
  27. package/L2_Handling/display/composite/swiper_renderer.js +564 -0
  28. package/L2_Handling/display/composite/terminal_renderer.js +922 -0
  29. package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
  30. package/L2_Handling/display/display.js +30 -0
  31. package/L2_Handling/display/feedback/feedback.js +11 -0
  32. package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
  33. package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
  34. package/L2_Handling/display/inputs/button_renderer.js +634 -0
  35. package/L2_Handling/display/inputs/form_renderer.js +583 -0
  36. package/L2_Handling/display/inputs/input_renderer.js +658 -0
  37. package/L2_Handling/display/inputs/inputs.js +12 -0
  38. package/L2_Handling/display/navigation/menu_renderer.js +206 -0
  39. package/L2_Handling/display/navigation/navigation.js +11 -0
  40. package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
  41. package/L2_Handling/display/orchestration/orchestration.js +11 -0
  42. package/L2_Handling/display/orchestration/renderer.js +430 -0
  43. package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
  44. package/L2_Handling/display/outputs/alert_renderer.js +161 -0
  45. package/L2_Handling/display/outputs/audio_renderer.js +94 -0
  46. package/L2_Handling/display/outputs/card_renderer.js +229 -0
  47. package/L2_Handling/display/outputs/code_renderer.js +66 -0
  48. package/L2_Handling/display/outputs/dl_renderer.js +131 -0
  49. package/L2_Handling/display/outputs/header_renderer.js +162 -0
  50. package/L2_Handling/display/outputs/icon_renderer.js +107 -0
  51. package/L2_Handling/display/outputs/image_renderer.js +145 -0
  52. package/L2_Handling/display/outputs/list_renderer.js +190 -0
  53. package/L2_Handling/display/outputs/outputs.js +19 -0
  54. package/L2_Handling/display/outputs/table_renderer.js +765 -0
  55. package/L2_Handling/display/outputs/text_renderer.js +818 -0
  56. package/L2_Handling/display/outputs/typography_renderer.js +293 -0
  57. package/L2_Handling/display/outputs/video_renderer.js +116 -0
  58. package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
  59. package/L2_Handling/display/primitives/form_primitives.js +526 -0
  60. package/L2_Handling/display/primitives/generic_containers.js +109 -0
  61. package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
  62. package/L2_Handling/display/primitives/link_primitives.js +552 -0
  63. package/L2_Handling/display/primitives/lists_primitives.js +262 -0
  64. package/L2_Handling/display/primitives/media_primitives.js +383 -0
  65. package/L2_Handling/display/primitives/primitives.js +19 -0
  66. package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
  67. package/L2_Handling/display/primitives/table_primitives.js +528 -0
  68. package/L2_Handling/display/primitives/typography_primitives.js +175 -0
  69. package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
  70. package/L2_Handling/display/specialized/specialized.js +10 -0
  71. package/L2_Handling/hooks/hooks.js +9 -0
  72. package/L2_Handling/hooks/menu_integration.js +57 -0
  73. package/L2_Handling/hooks/widget_hook_manager.js +292 -0
  74. package/L2_Handling/message/message.js +8 -0
  75. package/L2_Handling/message/message_handler.js +701 -0
  76. package/L2_Handling/navigation/navigation.js +8 -0
  77. package/L2_Handling/navigation/navigation_manager.js +403 -0
  78. package/L2_Handling/zhooks/features/cache_live.js +287 -0
  79. package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
  80. package/L2_Handling/zhooks/zhooks_manager.js +65 -0
  81. package/L2_Handling/zvaf/zvaf.js +8 -0
  82. package/L2_Handling/zvaf/zvaf_manager.js +334 -0
  83. package/L3_Abstraction/L3_Abstraction.js +12 -0
  84. package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
  85. package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
  86. package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
  87. package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
  88. package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
  89. package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
  90. package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
  91. package/L3_Abstraction/renderer/renderer.js +1 -0
  92. package/L3_Abstraction/session/session.js +1 -0
  93. package/L4_Orchestration/L4_Orchestration.js +11 -0
  94. package/L4_Orchestration/client/client.js +1 -0
  95. package/L4_Orchestration/facade/facade.js +9 -0
  96. package/L4_Orchestration/facade/manager_registry.js +118 -0
  97. package/L4_Orchestration/facade/renderer_registry.js +274 -0
  98. package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
  99. package/L4_Orchestration/lifecycle/initializer.js +135 -0
  100. package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
  101. package/L4_Orchestration/rendering/facade.js +94 -0
  102. package/L4_Orchestration/rendering/rendering.js +7 -0
  103. package/LICENSE +21 -0
  104. package/README.md +82 -0
  105. package/bifrost_client.js +204 -0
  106. package/bifrost_core.js +1686 -0
  107. package/docs/ARCHITECTURE.md +111 -0
  108. package/docs/PROTOCOL.md +106 -0
  109. package/docs/RENDERERS.md +101 -0
  110. package/docs/SECURITY.md +92 -0
  111. package/package.json +24 -0
  112. package/syntax/prism-zconfig.js +41 -0
  113. package/syntax/prism-zenv.js +69 -0
  114. package/syntax/prism-zolo-theme.css +288 -0
  115. package/syntax/prism-zolo.js +380 -0
  116. package/syntax/prism-zschema.js +38 -0
  117. package/syntax/prism-zspark.js +25 -0
  118. package/syntax/prism-zui.js +68 -0
  119. package/zSys/accessibility/accessibility.js +10 -0
  120. package/zSys/accessibility/emoji_accessibility.js +173 -0
  121. package/zSys/dom/block_utils.js +122 -0
  122. package/zSys/dom/container_utils.js +370 -0
  123. package/zSys/dom/dom.js +13 -0
  124. package/zSys/dom/dom_utils.js +328 -0
  125. package/zSys/dom/encoding_utils.js +117 -0
  126. package/zSys/dom/style_utils.js +71 -0
  127. package/zSys/errors/error_display.js +299 -0
  128. package/zSys/errors/errors.js +10 -0
  129. package/zSys/theme/color_utils.js +274 -0
  130. package/zSys/theme/dark_mode_utils.js +272 -0
  131. package/zSys/theme/size_utils.js +256 -0
  132. package/zSys/theme/spacing_utils.js +405 -0
  133. package/zSys/theme/theme.js +14 -0
  134. package/zSys/theme/zbase.css +1735 -0
  135. package/zSys/theme/zbase_inject.js +161 -0
  136. package/zSys/theme/ztheme_utils.js +305 -0
  137. package/zSys/validation/error_boundary.js +201 -0
  138. package/zSys/validation/validation.js +11 -0
  139. package/zSys/validation/validation_utils.js +238 -0
  140. 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;