@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,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Header Renderer - Semantic Headers (h1-h6)
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* Renders header events from zCLI backend. Converts indent levels
|
|
7
|
+
* to semantic HTML header tags (h1-h6) with optional colors.
|
|
8
|
+
*
|
|
9
|
+
* @module rendering/header_renderer
|
|
10
|
+
* @layer 3
|
|
11
|
+
* @pattern Strategy (single event type)
|
|
12
|
+
*
|
|
13
|
+
* Philosophy:
|
|
14
|
+
* - "Terminal first" - headers structure all zCLI output
|
|
15
|
+
* - Pure rendering (no WebSocket, no state, no side effects)
|
|
16
|
+
* - Semantic HTML (indent → h1-h6)
|
|
17
|
+
* - Uses Layer 2 utilities exclusively (no inline logic)
|
|
18
|
+
*
|
|
19
|
+
* Dependencies:
|
|
20
|
+
* - Layer 2: dom_utils.js, ztheme_utils.js
|
|
21
|
+
*
|
|
22
|
+
* Exports:
|
|
23
|
+
* - HeaderRenderer: Class for rendering header events
|
|
24
|
+
*
|
|
25
|
+
* Example:
|
|
26
|
+
* ```javascript
|
|
27
|
+
* import { HeaderRenderer } from './header_renderer.js';
|
|
28
|
+
*
|
|
29
|
+
* const renderer = new HeaderRenderer(logger);
|
|
30
|
+
* renderer.render({
|
|
31
|
+
* label: 'Welcome to zCLI',
|
|
32
|
+
* color: 'primary',
|
|
33
|
+
* indent: 1 // h1
|
|
34
|
+
* }, 'zVaF');
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────
|
|
39
|
+
// Imports
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
// Layer 2: Utilities
|
|
43
|
+
import { createElement } from '../../../zSys/dom/dom_utils.js';
|
|
44
|
+
import { getTextColorClass, indentToHeaderTag } from '../../../zSys/theme/ztheme_utils.js';
|
|
45
|
+
import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
|
|
46
|
+
|
|
47
|
+
//
|
|
48
|
+
// Header Renderer Class
|
|
49
|
+
//
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* HeaderRenderer - Renders semantic header events
|
|
53
|
+
*
|
|
54
|
+
* Handles the 'header' zDisplay event. Converts indent levels
|
|
55
|
+
* to semantic HTML header tags (h1-h6) for proper document structure.
|
|
56
|
+
*
|
|
57
|
+
* Indent Mapping:
|
|
58
|
+
* - indent 0: Reserved for special cases (flush/hero layouts) - NOT a header
|
|
59
|
+
* - indent 1 → h1 (main title)
|
|
60
|
+
* - indent 2 → h2 (section)
|
|
61
|
+
* - indent 3 → h3 (subsection)
|
|
62
|
+
* - indent 4 → h4
|
|
63
|
+
* - indent 5 → h5
|
|
64
|
+
* - indent 6 → h6
|
|
65
|
+
* - indent 7+ → h6 (capped at h6)
|
|
66
|
+
*/
|
|
67
|
+
export class HeaderRenderer {
|
|
68
|
+
/**
|
|
69
|
+
* Create a HeaderRenderer instance
|
|
70
|
+
* @param {Object} logger - Logger instance for debugging
|
|
71
|
+
*/
|
|
72
|
+
constructor(logger) {
|
|
73
|
+
this.logger = logger || console;
|
|
74
|
+
this.logger.debug('[HeaderRenderer] Initialized');
|
|
75
|
+
|
|
76
|
+
// Wrap render method with error boundary
|
|
77
|
+
const originalRender = this.render.bind(this);
|
|
78
|
+
this.render = withErrorBoundary(originalRender, {
|
|
79
|
+
component: 'HeaderRenderer',
|
|
80
|
+
logger: this.logger
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Render a header event
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} data - Header event data
|
|
88
|
+
* @param {string} data.label - Header text (backend sends 'label' not 'content')
|
|
89
|
+
* @param {string} [data.color] - Header color (primary, secondary, info, success, warning, error)
|
|
90
|
+
* @param {number} [data.indent=1] - Indent level (1-6, where 1=h1, 6=h6)
|
|
91
|
+
* Note: indent 0 is reserved for special cases
|
|
92
|
+
* @param {string} [data.class] - Custom CSS class (optional)
|
|
93
|
+
* @param {string} zone - Target DOM element ID
|
|
94
|
+
* @returns {HTMLElement|null} Created header element or null if failed
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* renderer.render({ label: 'Main Title', indent: 1 }, 'zVaF'); // h1
|
|
98
|
+
* renderer.render({ label: 'Section', indent: 2, color: 'primary' }, 'zVaF'); // h2
|
|
99
|
+
* renderer.render({ label: 'Subsection', indent: 3 }, 'zVaF'); // h3
|
|
100
|
+
*/
|
|
101
|
+
render(data, zone) {
|
|
102
|
+
// Backend sends 'label' for headers (not 'content')
|
|
103
|
+
const { label, color, indent = 1, class: customClass } = data;
|
|
104
|
+
|
|
105
|
+
// Validate required parameters
|
|
106
|
+
if (!label) {
|
|
107
|
+
this.logger.error('[HeaderRenderer] [ERROR] Missing required parameter: label');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle indent 0 as special case (reserved for flush/hero layouts)
|
|
112
|
+
if (indent === 0) {
|
|
113
|
+
this.logger.warn('[HeaderRenderer] [WARN] indent=0 is reserved for special cases, treating as indent=1 (h1)');
|
|
114
|
+
// Could also skip rendering or return null, but treating as h1 is safer
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Get target container
|
|
118
|
+
const container = document.getElementById(zone);
|
|
119
|
+
if (!container) {
|
|
120
|
+
this.logger.error(`[HeaderRenderer] [ERROR] Zone not found: ${zone}`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Convert indent to header tag (h1-h6) using Layer 2 utility
|
|
125
|
+
// This automatically clamps indent between 1-6
|
|
126
|
+
const headerTag = indentToHeaderTag(indent);
|
|
127
|
+
|
|
128
|
+
// Build CSS classes array
|
|
129
|
+
const classes = [];
|
|
130
|
+
|
|
131
|
+
// Add custom class if provided (from YAML)
|
|
132
|
+
if (customClass) {
|
|
133
|
+
classes.push(customClass);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Add color class if provided (uses Layer 2 utility)
|
|
137
|
+
if (color) {
|
|
138
|
+
const colorClass = getTextColorClass(color);
|
|
139
|
+
if (colorClass) {
|
|
140
|
+
classes.push(colorClass);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Create header element (using Layer 2 utility)
|
|
145
|
+
const header = createElement(headerTag, classes);
|
|
146
|
+
header.textContent = label; // Use textContent for XSS safety
|
|
147
|
+
|
|
148
|
+
// Append to container
|
|
149
|
+
container.appendChild(header);
|
|
150
|
+
|
|
151
|
+
// Log success
|
|
152
|
+
this.logger.log(`[HeaderRenderer] Rendered ${headerTag} (${label.length} chars, indent: ${indent})`);
|
|
153
|
+
|
|
154
|
+
return header;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//
|
|
159
|
+
// Default Export
|
|
160
|
+
//
|
|
161
|
+
export default HeaderRenderer;
|
|
162
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconRenderer - Bootstrap Icons for Bifrost
|
|
3
|
+
*
|
|
4
|
+
* Renders Bootstrap Icons in web mode with support for:
|
|
5
|
+
* - Icon name (with or without 'bi-' prefix)
|
|
6
|
+
* - SSOT semantic color value (primary, warning, …) — resolved via getTextColorClass
|
|
7
|
+
* - Additional CSS classes (_zClass) — also the channel for sizing
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Clean HTML generation
|
|
11
|
+
* - Proper class composition
|
|
12
|
+
* - Graceful fallback for missing icons
|
|
13
|
+
* - Accessible by default: role="img" + aria-label derived from the icon name
|
|
14
|
+
* (or an explicit alt_text / aria_label override)
|
|
15
|
+
*
|
|
16
|
+
* Author: zOS Framework
|
|
17
|
+
* Version: 1.0.0
|
|
18
|
+
* Date: 2026-03-24
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { getTextColorClass } from '../../../zSys/theme/ztheme_utils.js';
|
|
22
|
+
import { convertStyleToString } from '../../../zSys/dom/style_utils.js';
|
|
23
|
+
|
|
24
|
+
export default class IconRenderer {
|
|
25
|
+
/**
|
|
26
|
+
* @param {Object} logger - Logger instance
|
|
27
|
+
*/
|
|
28
|
+
constructor(logger) {
|
|
29
|
+
this.logger = logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Render Bootstrap Icon
|
|
34
|
+
* @param {Object} data - Icon configuration
|
|
35
|
+
* @param {string} data.name - Icon name (e.g., "tools", "bi-tools")
|
|
36
|
+
* @param {string} [data.color] - SSOT semantic color value (e.g., "primary", "warning")
|
|
37
|
+
* @param {string} [data._zClass] - Additional CSS classes (also sizing)
|
|
38
|
+
* @param {string|Object} [data._zStyle] - SSOT inline-style escape hatch
|
|
39
|
+
* @param {HTMLElement} [targetElement] - Optional parent to append into
|
|
40
|
+
* @returns {HTMLElement} The rendered node (bare <i>, or a styled <span> wrapper)
|
|
41
|
+
*/
|
|
42
|
+
render(data, targetElement) {
|
|
43
|
+
if (!data || !data.name) {
|
|
44
|
+
this.logger.warn('[IconRenderer] Missing icon name');
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Strip 'bi-' prefix if present
|
|
49
|
+
const cleanName = data.name.replace(/^bi-/, '');
|
|
50
|
+
|
|
51
|
+
// Create icon element
|
|
52
|
+
const icon = document.createElement('i');
|
|
53
|
+
icon.className = `bi bi-${cleanName}`;
|
|
54
|
+
|
|
55
|
+
// Accessibility — a bare <i> is silent to screen readers. Honour the page's
|
|
56
|
+
// "accessible by default" promise: give every icon role="img" and a readable
|
|
57
|
+
// aria-label. An explicit alt_text/aria_label wins; otherwise humanize the
|
|
58
|
+
// bi-* name (bi-heart-fill → "heart fill"), mirroring the terminal's
|
|
59
|
+
// bracketed [description] fallback.
|
|
60
|
+
const ariaLabel = (data.alt_text || data.aria_label || cleanName.replace(/-/g, ' ')).trim();
|
|
61
|
+
if (ariaLabel) {
|
|
62
|
+
icon.setAttribute('role', 'img');
|
|
63
|
+
icon.setAttribute('aria-label', ariaLabel);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Only wrap when the icon carries its OWN styling (colour / classes / inline
|
|
67
|
+
// style). A bare icon returns the raw <i> so the container-unwrapper can
|
|
68
|
+
// collapse any redundant parent frame instead of nesting a second box.
|
|
69
|
+
let node = icon;
|
|
70
|
+
if (data.color || data._zClass || data._zStyle) {
|
|
71
|
+
const wrapper = document.createElement('span');
|
|
72
|
+
|
|
73
|
+
// Build class list. color is an SSOT semantic value (primary, warning, …) —
|
|
74
|
+
// resolved through the same mapping zText/zH use, never a raw class.
|
|
75
|
+
const classes = [];
|
|
76
|
+
if (data.color) classes.push(getTextColorClass(data.color));
|
|
77
|
+
if (data._zClass) {
|
|
78
|
+
// Handle _zClass as string or array
|
|
79
|
+
if (Array.isArray(data._zClass)) {
|
|
80
|
+
classes.push(...data._zClass);
|
|
81
|
+
} else {
|
|
82
|
+
classes.push(...data._zClass.split(' ').filter(c => c));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (classes.length > 0) {
|
|
87
|
+
wrapper.className = classes.join(' ');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// _zStyle — the SSOT escape hatch, same as every other event.
|
|
91
|
+
if (data._zStyle) {
|
|
92
|
+
const cssString = convertStyleToString(data._zStyle, this.logger);
|
|
93
|
+
if (cssString) {
|
|
94
|
+
wrapper.setAttribute('style', cssString);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
wrapper.appendChild(icon);
|
|
99
|
+
node = wrapper;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (targetElement) targetElement.appendChild(node);
|
|
103
|
+
|
|
104
|
+
this.logger.debug(`[IconRenderer] Rendered icon: %s`, `bi-${cleanName}`);
|
|
105
|
+
return node;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Image Renderer - Media Display Events
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* Terminal-First Design:
|
|
7
|
+
* - Backend sends src, alt_text, caption (works with URLs and local paths)
|
|
8
|
+
* - Terminal displays metadata + button to open
|
|
9
|
+
* - Bifrost renders <img> element with zTheme styling
|
|
10
|
+
*
|
|
11
|
+
* Renders image display events from zCLI backend. Creates image
|
|
12
|
+
* elements using primitives-first architecture.
|
|
13
|
+
*
|
|
14
|
+
* @module rendering/image_renderer
|
|
15
|
+
* @layer 3
|
|
16
|
+
* @pattern Primitives-First
|
|
17
|
+
*
|
|
18
|
+
* Dependencies:
|
|
19
|
+
* - None (uses native DOM API)
|
|
20
|
+
*
|
|
21
|
+
* Exports:
|
|
22
|
+
* - ImageRenderer: Class for rendering image events
|
|
23
|
+
*
|
|
24
|
+
* Example:
|
|
25
|
+
* ```javascript
|
|
26
|
+
* import ImageRenderer from './image_renderer.js';
|
|
27
|
+
*
|
|
28
|
+
* const renderer = new ImageRenderer(logger);
|
|
29
|
+
* const element = renderer.render({
|
|
30
|
+
* src: 'https://picsum.photos/200/300',
|
|
31
|
+
* alt_text: 'Random Image',
|
|
32
|
+
* caption: 'A beautiful picture',
|
|
33
|
+
* _zClass: 'zRounded-circle',
|
|
34
|
+
* _id: 'profile-pic'
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────
|
|
40
|
+
// Imports
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
// Layer 2: Utilities
|
|
44
|
+
import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
|
|
45
|
+
|
|
46
|
+
export class ImageRenderer {
|
|
47
|
+
constructor(logger) {
|
|
48
|
+
this.logger = logger;
|
|
49
|
+
|
|
50
|
+
// Wrap render method with error boundary
|
|
51
|
+
const originalRender = this.render.bind(this);
|
|
52
|
+
this.render = withErrorBoundary(originalRender, {
|
|
53
|
+
component: 'ImageRenderer',
|
|
54
|
+
logger: this.logger
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Render image element from primitives
|
|
60
|
+
*
|
|
61
|
+
* Terminal-First Design:
|
|
62
|
+
* - src: Supports ANY URL (external) or path (local/relative)
|
|
63
|
+
* - alt_text: Accessibility text (terminal shows this as header)
|
|
64
|
+
* - caption: Optional caption (terminal shows below path)
|
|
65
|
+
* - _zClass: Bifrost-only styling (e.g., zRounded-circle)
|
|
66
|
+
* - _id: Bifrost-only DOM id for targeting
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} eventData - Event data from backend
|
|
69
|
+
* @param {string} eventData.src - Image source (URL or path)
|
|
70
|
+
* @param {string} [eventData.alt_text] - Alternative text for accessibility
|
|
71
|
+
* @param {string} [eventData.caption] - Optional caption text
|
|
72
|
+
* @param {string} [eventData._zClass] - Custom classes for styling
|
|
73
|
+
* @param {string} [eventData._id] - Custom id for targeting
|
|
74
|
+
* @returns {HTMLElement} Image container element
|
|
75
|
+
*/
|
|
76
|
+
render(eventData) {
|
|
77
|
+
const { src, alt_text, caption, _zClass, _id } = eventData;
|
|
78
|
+
|
|
79
|
+
if (!src) {
|
|
80
|
+
this.logger.error('[ImageRenderer] Missing src parameter');
|
|
81
|
+
return this._createErrorElement();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.logger.debug(`[ImageRenderer] Rendering image: ${src}`);
|
|
85
|
+
|
|
86
|
+
// Create img element (THE primitive) - align with zTheme
|
|
87
|
+
const img = document.createElement('img');
|
|
88
|
+
img.src = src; // Supports ANY URL (picsum, external, etc.) or local path
|
|
89
|
+
|
|
90
|
+
// Apply _zClass and _id directly to img (zTheme pattern)
|
|
91
|
+
if (_zClass) {
|
|
92
|
+
img.className = _zClass;
|
|
93
|
+
}
|
|
94
|
+
if (_id) {
|
|
95
|
+
img.setAttribute('id', _id);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Add alt text for accessibility
|
|
99
|
+
if (alt_text) {
|
|
100
|
+
img.alt = alt_text;
|
|
101
|
+
} else {
|
|
102
|
+
img.alt = ''; // Empty alt for decorative images
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// DON'T force inline styles - let zTheme/user control via classes
|
|
106
|
+
// If user wants object-fit, they can add "zObject-fit-cover" to _zClass
|
|
107
|
+
|
|
108
|
+
// Only wrap in <figure> if there's a caption (semantic HTML)
|
|
109
|
+
if (caption) {
|
|
110
|
+
const figure = document.createElement('figure');
|
|
111
|
+
figure.appendChild(img);
|
|
112
|
+
|
|
113
|
+
const figcaption = document.createElement('figcaption');
|
|
114
|
+
figcaption.textContent = caption;
|
|
115
|
+
figcaption.className = 'zText-muted zText-center zmt-2';
|
|
116
|
+
figure.appendChild(figcaption);
|
|
117
|
+
|
|
118
|
+
// _zClass/_zStyle already live on the inner <img> — tell the orchestrator's
|
|
119
|
+
// central metadata pass not to also stamp them onto this <figure> wrapper.
|
|
120
|
+
figure.__zMetaScoped = true;
|
|
121
|
+
|
|
122
|
+
this.logger.log('[ImageRenderer] Image with caption rendered');
|
|
123
|
+
return figure;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// No caption = return bare img (primitives-first + zTheme alignment!)
|
|
127
|
+
this.logger.debug('[ImageRenderer] Image rendered');
|
|
128
|
+
return img;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create error element when src is missing
|
|
133
|
+
* @private
|
|
134
|
+
* @returns {HTMLElement}
|
|
135
|
+
*/
|
|
136
|
+
_createErrorElement() {
|
|
137
|
+
const error = document.createElement('div');
|
|
138
|
+
error.className = 'zAlert zAlert-danger';
|
|
139
|
+
error.textContent = '[WARN] Image source missing';
|
|
140
|
+
return error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default ImageRenderer;
|
|
145
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListRenderer - Renders list elements (ul/ol) with zTheme styling
|
|
3
|
+
* Part of the modular bifrost rendering architecture
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────
|
|
7
|
+
// Imports
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
// Layer 2: Utilities
|
|
11
|
+
import { withErrorBoundary } from '../../../zSys/validation/error_boundary.js';
|
|
12
|
+
|
|
13
|
+
export class ListRenderer {
|
|
14
|
+
constructor(logger, client) {
|
|
15
|
+
this.client = client;
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
|
|
18
|
+
// Wrap render method with error boundary
|
|
19
|
+
const originalRender = this.render.bind(this);
|
|
20
|
+
this.render = withErrorBoundary(originalRender, {
|
|
21
|
+
component: 'ListRenderer',
|
|
22
|
+
logger: this.logger
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Render a list element (bulleted or numbered)
|
|
28
|
+
* Supports plain text items, nested arrays, and nested zDisplay events
|
|
29
|
+
* NEW v1.7: Supports cascading styles for nested arrays!
|
|
30
|
+
* @param {Object} eventData - zDisplay event data with items array
|
|
31
|
+
* @param {number} level - Internal: current nesting level for cascading (default: 0)
|
|
32
|
+
* @returns {Promise<HTMLElement>} - Rendered list element (ul or ol)
|
|
33
|
+
*/
|
|
34
|
+
async render(eventData, level = 0) {
|
|
35
|
+
this.logger.debug(`[ListRenderer] Rendering list: ${eventData.items?.length || 0} items`);
|
|
36
|
+
|
|
37
|
+
// Determine current style based on cascading
|
|
38
|
+
let currentStyle;
|
|
39
|
+
let cascadeStyles = null;
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(eventData.style)) {
|
|
42
|
+
// Cascading styles: cycle through array based on nesting level
|
|
43
|
+
cascadeStyles = eventData.style;
|
|
44
|
+
currentStyle = eventData.style[level % eventData.style.length];
|
|
45
|
+
this.logger.log(`[ListRenderer] Using cascading style: ${currentStyle} (level ${level})`);
|
|
46
|
+
} else {
|
|
47
|
+
// Single style: use for all levels
|
|
48
|
+
currentStyle = eventData.style || 'bullet';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Determine list element type and CSS list-style-type
|
|
52
|
+
let listElement;
|
|
53
|
+
let listStyleType = null;
|
|
54
|
+
|
|
55
|
+
if (currentStyle === 'number') {
|
|
56
|
+
listElement = document.createElement('ol');
|
|
57
|
+
} else if (currentStyle === 'letter') {
|
|
58
|
+
listElement = document.createElement('ol');
|
|
59
|
+
listStyleType = 'lower-alpha'; // a, b, c
|
|
60
|
+
} else if (currentStyle === 'roman') {
|
|
61
|
+
listElement = document.createElement('ol');
|
|
62
|
+
listStyleType = 'lower-roman'; // i, ii, iii
|
|
63
|
+
} else if (currentStyle === 'circle') {
|
|
64
|
+
listElement = document.createElement('ul');
|
|
65
|
+
listStyleType = 'circle'; //
|
|
66
|
+
} else if (currentStyle === 'square') {
|
|
67
|
+
listElement = document.createElement('ul');
|
|
68
|
+
listStyleType = 'square'; //
|
|
69
|
+
} else {
|
|
70
|
+
// bullet (default) or any other style
|
|
71
|
+
listElement = document.createElement('ul');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Apply base zTheme class
|
|
75
|
+
listElement.className = 'zList';
|
|
76
|
+
|
|
77
|
+
// Apply list-style-type if specified
|
|
78
|
+
if (listStyleType) {
|
|
79
|
+
listElement.style.listStyleType = listStyleType;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// _zClass is applied centrally by the orchestrator (SSOT, append mode) on the
|
|
83
|
+
// returned listElement — we only read it below to detect inline layout.
|
|
84
|
+
|
|
85
|
+
// TODO: DEPRECATE - Remove `indent` property from list events
|
|
86
|
+
// Rationale: Conflicts with native HTML nesting, redundant with _zClass/_zStyle,
|
|
87
|
+
// only used in 2 places (markdown parser, traceback). Natural nesting is better.
|
|
88
|
+
// NOTE: Manual removal only - do not auto-fix with agents
|
|
89
|
+
// Apply indent using zms (margin-start) classes
|
|
90
|
+
if (eventData.indent && eventData.indent > 0) {
|
|
91
|
+
listElement.className += ` zms-${eventData.indent}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Apply custom id if provided (_id parameter - ignored by terminal)
|
|
95
|
+
if (level === 0 && eventData._id) {
|
|
96
|
+
listElement.setAttribute('id', eventData._id);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if this is an inline list (for horizontal layout)
|
|
100
|
+
const isInline = level === 0 && eventData._zClass && eventData._zClass.includes('zList-inline');
|
|
101
|
+
|
|
102
|
+
// Render list items (async to support nested zDisplay events)
|
|
103
|
+
const items = eventData.items || [];
|
|
104
|
+
let lastLi = null; // Track last created <li> for appending nested lists
|
|
105
|
+
|
|
106
|
+
for (const item of items) {
|
|
107
|
+
// NEW v1.7: Handle nested arrays naturally!
|
|
108
|
+
if (Array.isArray(item)) {
|
|
109
|
+
this.logger.debug('[ListRenderer] Rendering nested array');
|
|
110
|
+
|
|
111
|
+
// Nested array should be appended to the PREVIOUS list item
|
|
112
|
+
if (lastLi) {
|
|
113
|
+
try {
|
|
114
|
+
const nestedEventData = {
|
|
115
|
+
items: item,
|
|
116
|
+
style: cascadeStyles || currentStyle, // Pass cascading styles or current style
|
|
117
|
+
indent: 0 // Nested lists use native HTML indentation
|
|
118
|
+
};
|
|
119
|
+
const nestedList = await this.render(nestedEventData, level + 1);
|
|
120
|
+
lastLi.appendChild(nestedList);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this.logger.error('[ListRenderer] Error rendering nested array:', error);
|
|
123
|
+
lastLi.textContent += ` [Error: ${error.message}]`;
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
this.logger.warn('[ListRenderer] Nested array without previous list item - creating standalone');
|
|
127
|
+
// Fallback: create a new <li> if no previous item exists
|
|
128
|
+
const li = document.createElement('li');
|
|
129
|
+
try {
|
|
130
|
+
const nestedEventData = {
|
|
131
|
+
items: item,
|
|
132
|
+
style: cascadeStyles || currentStyle,
|
|
133
|
+
indent: 0
|
|
134
|
+
};
|
|
135
|
+
const nestedList = await this.render(nestedEventData, level + 1);
|
|
136
|
+
li.appendChild(nestedList);
|
|
137
|
+
listElement.appendChild(li);
|
|
138
|
+
lastLi = li;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
this.logger.error('[ListRenderer] Error rendering nested array:', error);
|
|
141
|
+
li.textContent = `[Error: ${error.message}]`;
|
|
142
|
+
listElement.appendChild(li);
|
|
143
|
+
lastLi = li;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Don't update lastLi - nested arrays attach to previous item
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create new <li> for non-array items
|
|
151
|
+
const li = document.createElement('li');
|
|
152
|
+
|
|
153
|
+
// Apply zList-inline-item class if this is an inline list
|
|
154
|
+
if (isInline) {
|
|
155
|
+
li.className = 'zList-inline-item';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if item contains a nested zDisplay event
|
|
159
|
+
if (item && typeof item === 'object' && item.zDisplay) {
|
|
160
|
+
this.logger.debug('[ListRenderer] Rendering nested zDisplay event');
|
|
161
|
+
|
|
162
|
+
// Recursively render the nested zDisplay event
|
|
163
|
+
try {
|
|
164
|
+
const nestedElement = await this.client.zDisplayOrchestrator.renderZDisplayEvent(item.zDisplay);
|
|
165
|
+
if (nestedElement) {
|
|
166
|
+
li.appendChild(nestedElement);
|
|
167
|
+
} else {
|
|
168
|
+
this.logger.warn('[ListRenderer] Nested zDisplay returned null, using fallback');
|
|
169
|
+
li.textContent = JSON.stringify(item);
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.logger.error('[ListRenderer] Error rendering nested zDisplay:', error);
|
|
173
|
+
li.textContent = `[Error: ${error.message}]`;
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
// Plain text item (original behavior)
|
|
177
|
+
const content = typeof item === 'string' ? item : (item.content || '');
|
|
178
|
+
li.textContent = content;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
listElement.appendChild(li);
|
|
182
|
+
lastLi = li; // Track this as the last created <li>
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.logger.log(`[ListRenderer] Rendered ${currentStyle} list with ${items.length} items`);
|
|
186
|
+
return listElement;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default ListRenderer;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Rendering Module Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Data output components (tables, text, cards, alerts, images, DL, etc.).
|
|
5
|
+
*
|
|
6
|
+
* @module rendering/outputs
|
|
7
|
+
* @layer 3 (Output Rendering)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from './alert_renderer.js';
|
|
11
|
+
export * from './card_renderer.js';
|
|
12
|
+
export * from './dl_renderer.js';
|
|
13
|
+
export * from './header_renderer.js';
|
|
14
|
+
export * from './icon_renderer.js';
|
|
15
|
+
export * from './image_renderer.js';
|
|
16
|
+
export * from './list_renderer.js';
|
|
17
|
+
export * from './table_renderer.js';
|
|
18
|
+
export * from './text_renderer.js';
|
|
19
|
+
export * from './typography_renderer.js';
|