@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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zbase_inject.js — zbifrost-client JS behaviors (tabs, zTheme)
|
|
3
|
+
*
|
|
4
|
+
* Called once during bifrost_core.js startup:
|
|
5
|
+
* import { injectZBase } from `${BASE_URL}zSys/theme/zbase_inject.js`;
|
|
6
|
+
* await injectZBase(BASE_URL);
|
|
7
|
+
*
|
|
8
|
+
* CSS is now injected server-side by route_dispatcher.py as a synchronous
|
|
9
|
+
* <link rel="stylesheet"> in <head> — no async fetch, no timing races.
|
|
10
|
+
* This module only exposes window.zTheme (tabs, list-group behaviors).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ── Tab behavior ──────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function _showTab(trigger) {
|
|
16
|
+
const targetSelector = trigger.getAttribute('data-bs-target') || trigger.getAttribute('href');
|
|
17
|
+
if (!targetSelector) return;
|
|
18
|
+
|
|
19
|
+
const targetPane = document.querySelector(targetSelector);
|
|
20
|
+
if (!targetPane) return;
|
|
21
|
+
|
|
22
|
+
const tabContent = targetPane.closest('.zTab-content');
|
|
23
|
+
const nav = trigger.closest('.zNav, [role="tablist"]');
|
|
24
|
+
|
|
25
|
+
// Deactivate all triggers in the same nav group
|
|
26
|
+
const allTriggers = nav
|
|
27
|
+
? nav.querySelectorAll('[data-bs-toggle="tab"]')
|
|
28
|
+
: [trigger];
|
|
29
|
+
|
|
30
|
+
allTriggers.forEach(t => {
|
|
31
|
+
t.classList.remove('zActive');
|
|
32
|
+
t.setAttribute('aria-selected', 'false');
|
|
33
|
+
t.setAttribute('tabindex', '-1');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Activate the clicked trigger
|
|
37
|
+
trigger.classList.add('zActive');
|
|
38
|
+
trigger.setAttribute('aria-selected', 'true');
|
|
39
|
+
trigger.removeAttribute('tabindex');
|
|
40
|
+
|
|
41
|
+
// Hide all panes in the same tab-content
|
|
42
|
+
if (tabContent) {
|
|
43
|
+
tabContent.querySelectorAll('.zTab-pane').forEach(p => {
|
|
44
|
+
p.classList.remove('zActive', 'zShow');
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Show target pane
|
|
49
|
+
if (targetPane.classList.contains('zFade')) {
|
|
50
|
+
targetPane.classList.add('zActive');
|
|
51
|
+
requestAnimationFrame(() => targetPane.classList.add('zShow'));
|
|
52
|
+
} else {
|
|
53
|
+
targetPane.classList.add('zActive');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fire custom event for lazy-loading hooks
|
|
57
|
+
trigger.dispatchEvent(new CustomEvent('zTabShown', {
|
|
58
|
+
bubbles: true,
|
|
59
|
+
detail: { trigger, pane: targetPane }
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function initTabs() {
|
|
64
|
+
const triggers = document.querySelectorAll('[data-bs-toggle="tab"]');
|
|
65
|
+
if (triggers.length === 0) return;
|
|
66
|
+
|
|
67
|
+
triggers.forEach(trigger => {
|
|
68
|
+
if (trigger._zTabInited) return;
|
|
69
|
+
trigger._zTabInited = true;
|
|
70
|
+
|
|
71
|
+
trigger.setAttribute('role', 'tab');
|
|
72
|
+
if (trigger.classList.contains('zActive')) {
|
|
73
|
+
trigger.setAttribute('aria-selected', 'true');
|
|
74
|
+
} else {
|
|
75
|
+
trigger.setAttribute('aria-selected', 'false');
|
|
76
|
+
trigger.setAttribute('tabindex', '-1');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
trigger.addEventListener('click', e => {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
if (!trigger.classList.contains('zActive')) _showTab(trigger);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── List-group tab behavior ───────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function initListGroup() {
|
|
89
|
+
const triggers = document.querySelectorAll('[data-bs-toggle="list"]');
|
|
90
|
+
if (triggers.length === 0) return;
|
|
91
|
+
|
|
92
|
+
triggers.forEach(trigger => {
|
|
93
|
+
if (trigger._zListInited) return;
|
|
94
|
+
trigger._zListInited = true;
|
|
95
|
+
|
|
96
|
+
trigger.setAttribute('aria-selected', trigger.classList.contains('zActive') ? 'true' : 'false');
|
|
97
|
+
|
|
98
|
+
trigger.addEventListener('click', e => {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
if (trigger.classList.contains('zActive')) return;
|
|
101
|
+
|
|
102
|
+
const targetId = trigger.getAttribute('href') || trigger.getAttribute('data-bs-target');
|
|
103
|
+
const targetPane = targetId ? document.querySelector(targetId) : null;
|
|
104
|
+
if (!targetPane) return;
|
|
105
|
+
|
|
106
|
+
const listGroup = trigger.closest('.zList-group');
|
|
107
|
+
const tabContent = targetPane.closest('.zTab-content');
|
|
108
|
+
|
|
109
|
+
if (listGroup) {
|
|
110
|
+
listGroup.querySelectorAll('.zList-group-item').forEach(item => {
|
|
111
|
+
item.classList.remove('zActive');
|
|
112
|
+
item.setAttribute('aria-selected', 'false');
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
trigger.classList.add('zActive');
|
|
116
|
+
trigger.setAttribute('aria-selected', 'true');
|
|
117
|
+
|
|
118
|
+
if (tabContent) {
|
|
119
|
+
tabContent.querySelectorAll('.zTab-pane').forEach(p => p.classList.remove('zActive', 'zShow'));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (targetPane.classList.contains('zFade')) {
|
|
123
|
+
targetPane.classList.add('zActive');
|
|
124
|
+
requestAnimationFrame(() => targetPane.classList.add('zShow'));
|
|
125
|
+
} else {
|
|
126
|
+
targetPane.classList.add('zActive');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
trigger.dispatchEvent(new CustomEvent('zTabShown', {
|
|
130
|
+
bubbles: true,
|
|
131
|
+
detail: { trigger, pane: targetPane }
|
|
132
|
+
}));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── window.zTheme public API ──────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
function _exposeWindowZTheme() {
|
|
140
|
+
if (typeof window === 'undefined') return;
|
|
141
|
+
if (window.zTheme?._zbifrost) return; // already set by this module
|
|
142
|
+
|
|
143
|
+
window.zTheme = {
|
|
144
|
+
_zbifrost: true, // sentinel: sourced from zbifrost-client, not external CDN
|
|
145
|
+
initTabs,
|
|
146
|
+
initListGroup,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Entry point ───────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Inject structural CSS and expose window.zTheme.
|
|
154
|
+
* Called once from bifrost_core.js during _onConnect().
|
|
155
|
+
*
|
|
156
|
+
* @param {string} baseUrl - CDN base URL (same as bifrost_core.js BASE_URL)
|
|
157
|
+
*/
|
|
158
|
+
export async function injectZBase(_baseUrl) {
|
|
159
|
+
_exposeWindowZTheme();
|
|
160
|
+
// CSS is injected server-side — nothing async to do here.
|
|
161
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* zTheme Utilities - zCLI to zTheme Class Mappings
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* Pure functions for mapping zCLI color/style names to zTheme CSS classes.
|
|
7
|
+
* Uses lookup tables for O(1) performance and consistency.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/ztheme_utils
|
|
10
|
+
* @layer 2
|
|
11
|
+
* @pattern Pure Functions
|
|
12
|
+
*
|
|
13
|
+
* Dependencies:
|
|
14
|
+
* - Layer 0: None (pure JavaScript)
|
|
15
|
+
*
|
|
16
|
+
* Exports:
|
|
17
|
+
* - getButtonColorClass: Map zCLI color to zTheme button class
|
|
18
|
+
* - getButtonSizeClass: Map size to zTheme button size class
|
|
19
|
+
* - getButtonStyleClass: Map style variant to zTheme button style class
|
|
20
|
+
* - getAlertColorClass: Map event type to zTheme alert class
|
|
21
|
+
* - getBadgeColorClass: Map zCLI color to zTheme badge class
|
|
22
|
+
* - getTextColorClass: Map zCLI color to zTheme text color class
|
|
23
|
+
* - isValidZThemeClass: Validate if string is valid zTheme class
|
|
24
|
+
* - indentToHeaderTag: Convert indent level to HTML header tag
|
|
25
|
+
*
|
|
26
|
+
* Example:
|
|
27
|
+
* ```javascript
|
|
28
|
+
* import { getButtonColorClass, getButtonSizeClass } from './ztheme_utils.js';
|
|
29
|
+
*
|
|
30
|
+
* const colorClass = getButtonColorClass('primary'); // 'zBtnPrimary'
|
|
31
|
+
* const sizeClass = getButtonSizeClass('lg'); // 'zBtn-lg'
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
//
|
|
36
|
+
// Color Mappings (zCLI → zTheme)
|
|
37
|
+
//
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Button color mapping: zCLI color names → zTheme button classes
|
|
41
|
+
* @const {Object<string, string>}
|
|
42
|
+
*/
|
|
43
|
+
const BUTTON_COLOR_MAP = {
|
|
44
|
+
'primary': 'zBtn-primary',
|
|
45
|
+
'secondary': 'zBtn-secondary',
|
|
46
|
+
'success': 'zBtn-success',
|
|
47
|
+
'danger': 'zBtn-danger',
|
|
48
|
+
'warning': 'zBtn-warning',
|
|
49
|
+
'info': 'zBtn-info',
|
|
50
|
+
'light': 'zBtn-light',
|
|
51
|
+
'dark': 'zBtn-dark',
|
|
52
|
+
'link': 'zBtn-link'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Alert color mapping: zCLI event types → zTheme zSignal classes
|
|
57
|
+
* Returns ONLY the color-specific class (base zSignal class added by renderer)
|
|
58
|
+
* @const {Object<string, string>}
|
|
59
|
+
*/
|
|
60
|
+
const ALERT_COLOR_MAP = {
|
|
61
|
+
'error': 'zSignal-error',
|
|
62
|
+
'warning': 'zSignal-warning',
|
|
63
|
+
'success': 'zSignal-success',
|
|
64
|
+
'info': 'zSignal-info',
|
|
65
|
+
'danger': 'zSignal-error', // danger → error (zTheme uses error)
|
|
66
|
+
'primary': 'zSignal-primary',
|
|
67
|
+
'secondary': 'zSignal-secondary',
|
|
68
|
+
'light': 'zSignal-light',
|
|
69
|
+
'dark': 'zSignal-dark'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Badge color mapping: zCLI colors → zTheme badge classes
|
|
74
|
+
* @const {Object<string, string>}
|
|
75
|
+
*/
|
|
76
|
+
const BADGE_COLOR_MAP = {
|
|
77
|
+
'primary': 'zBadge zBgPrimary',
|
|
78
|
+
'secondary': 'zBadge zBgSecondary',
|
|
79
|
+
'success': 'zBadge zBgSuccess',
|
|
80
|
+
'danger': 'zBadge zBgDanger',
|
|
81
|
+
'warning': 'zBadge zBgWarning',
|
|
82
|
+
'info': 'zBadge zBgInfo',
|
|
83
|
+
'light': 'zBadge zBgLight',
|
|
84
|
+
'dark': 'zBadge zBgDark'
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Text color mapping: zCLI colors → zTheme text color classes
|
|
89
|
+
* @const {Object<string, string>}
|
|
90
|
+
*/
|
|
91
|
+
const TEXT_COLOR_MAP = {
|
|
92
|
+
'primary': 'zText-primary',
|
|
93
|
+
'secondary': 'zText-secondary',
|
|
94
|
+
'success': 'zText-success',
|
|
95
|
+
'danger': 'zText-danger',
|
|
96
|
+
'warning': 'zText-warning',
|
|
97
|
+
'info': 'zText-info',
|
|
98
|
+
'light': 'zText-light',
|
|
99
|
+
'dark': 'zText-dark',
|
|
100
|
+
'muted': 'zText-muted',
|
|
101
|
+
'white': 'zText-white'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
//
|
|
105
|
+
// Button Utilities
|
|
106
|
+
//
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get zTheme button color class from zCLI color name
|
|
110
|
+
*
|
|
111
|
+
* @param {string} zColor - zCLI color name (primary, danger, success, etc)
|
|
112
|
+
* @returns {string} zTheme button class (zBtn-primary, zBtn-danger, etc)
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* getButtonColorClass('primary') // 'zBtn-primary'
|
|
116
|
+
* getButtonColorClass('DANGER') // 'zBtn-danger' (case-insensitive)
|
|
117
|
+
* getButtonColorClass('invalid') // 'zBtn-primary' (fallback)
|
|
118
|
+
* getButtonColorClass(null) // 'zBtn-primary' (fallback)
|
|
119
|
+
*/
|
|
120
|
+
export function getButtonColorClass(zColor) {
|
|
121
|
+
const normalized = zColor?.toLowerCase() || 'primary';
|
|
122
|
+
return BUTTON_COLOR_MAP[normalized] || 'zBtn-primary';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get zTheme button size class
|
|
127
|
+
*
|
|
128
|
+
* @param {string} size - Size (sm, md, lg)
|
|
129
|
+
* @returns {string} zTheme size class (zBtn-sm, zBtn-lg, or empty string for md)
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* getButtonSizeClass('sm') // 'zBtn-sm'
|
|
133
|
+
* getButtonSizeClass('md') // '' (default, no class needed)
|
|
134
|
+
* getButtonSizeClass('lg') // 'zBtn-lg'
|
|
135
|
+
* getButtonSizeClass(null) // '' (default)
|
|
136
|
+
*/
|
|
137
|
+
export function getButtonSizeClass(size) {
|
|
138
|
+
const normalized = size?.toLowerCase() || 'md';
|
|
139
|
+
|
|
140
|
+
const SIZE_MAP = {
|
|
141
|
+
'sm': 'zBtn-sm',
|
|
142
|
+
'small': 'zBtn-sm',
|
|
143
|
+
'md': '',
|
|
144
|
+
'medium': '',
|
|
145
|
+
'lg': 'zBtn-lg',
|
|
146
|
+
'large': 'zBtn-lg'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return SIZE_MAP[normalized] || '';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get zTheme button style variant class
|
|
154
|
+
*
|
|
155
|
+
* NOTE: For outline buttons, use getButtonOutlineClass() instead.
|
|
156
|
+
* This only handles non-color-specific styles.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} style - Style variant (default, link)
|
|
159
|
+
* @returns {string} zTheme style class or empty for default
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* getButtonStyleClass('default') // '' (no class needed)
|
|
163
|
+
* getButtonStyleClass('link') // 'zBtn-link'
|
|
164
|
+
*/
|
|
165
|
+
export function getButtonStyleClass(style) {
|
|
166
|
+
const normalized = style?.toLowerCase() || 'default';
|
|
167
|
+
|
|
168
|
+
const STYLE_MAP = {
|
|
169
|
+
'default': '',
|
|
170
|
+
'solid': '',
|
|
171
|
+
'link': 'zBtn-link'
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return STYLE_MAP[normalized] || '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get zTheme outline button class (color-specific)
|
|
179
|
+
*
|
|
180
|
+
* @param {string} zColor - zCLI color name
|
|
181
|
+
* @returns {string} zTheme outline button class (e.g., 'zBtn-outline-primary')
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* getButtonOutlineClass('primary') // 'zBtn-outline-primary'
|
|
185
|
+
* getButtonOutlineClass('danger') // 'zBtn-outline-danger'
|
|
186
|
+
*/
|
|
187
|
+
export function getButtonOutlineClass(zColor) {
|
|
188
|
+
const normalized = zColor?.toLowerCase() || 'primary';
|
|
189
|
+
return `zBtn-outline-${normalized}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//
|
|
193
|
+
// Alert Utilities
|
|
194
|
+
//
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get zTheme signal color class from zCLI event type
|
|
198
|
+
*
|
|
199
|
+
* Returns ONLY the color-specific class (e.g., 'zSignal-success').
|
|
200
|
+
* The base 'zSignal' class should be added separately by the renderer.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} eventType - zCLI event type (error, warning, success, info)
|
|
203
|
+
* @returns {string} zTheme signal color class (e.g., 'zSignal-success')
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* getAlertColorClass('error') // 'zSignal-error'
|
|
207
|
+
* getAlertColorClass('warning') // 'zSignal-warning'
|
|
208
|
+
* getAlertColorClass('success') // 'zSignal-success'
|
|
209
|
+
* getAlertColorClass('info') // 'zSignal-info'
|
|
210
|
+
*/
|
|
211
|
+
export function getAlertColorClass(eventType) {
|
|
212
|
+
const normalized = eventType?.toLowerCase() || 'info';
|
|
213
|
+
return ALERT_COLOR_MAP[normalized] || 'zSignal-info';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
//
|
|
217
|
+
// Badge Utilities
|
|
218
|
+
//
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get zTheme badge color classes from zCLI color
|
|
222
|
+
*
|
|
223
|
+
* @param {string} zColor - zCLI color name
|
|
224
|
+
* @returns {string} zTheme badge classes (e.g., 'zBadge zBgPrimary')
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* getBadgeColorClass('primary') // 'zBadge zBgPrimary'
|
|
228
|
+
* getBadgeColorClass('danger') // 'zBadge zBgDanger'
|
|
229
|
+
*/
|
|
230
|
+
export function getBadgeColorClass(zColor) {
|
|
231
|
+
const normalized = zColor?.toLowerCase() || 'primary';
|
|
232
|
+
return BADGE_COLOR_MAP[normalized] || 'zBadge zBgPrimary';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
//
|
|
236
|
+
// Text Utilities
|
|
237
|
+
//
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get zTheme text color class from zCLI color
|
|
241
|
+
*
|
|
242
|
+
* @param {string} zColor - zCLI color name
|
|
243
|
+
* @returns {string} zTheme text color class (e.g., 'zText-primary')
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* getTextColorClass('primary') // 'zText-primary'
|
|
247
|
+
* getTextColorClass('danger') // 'zText-danger'
|
|
248
|
+
* getTextColorClass('muted') // 'zText-muted'
|
|
249
|
+
*/
|
|
250
|
+
export function getTextColorClass(zColor) {
|
|
251
|
+
const normalized = zColor?.toLowerCase() || 'dark';
|
|
252
|
+
return TEXT_COLOR_MAP[normalized] || 'zText-dark';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//
|
|
256
|
+
// Typography Utilities
|
|
257
|
+
//
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Convert indent level to HTML header tag
|
|
261
|
+
*
|
|
262
|
+
* NOTE: indent 0 is reserved for special cases (flush/hero layouts).
|
|
263
|
+
* This function maps indent 1-6 directly to h1-h6 semantic HTML tags.
|
|
264
|
+
*
|
|
265
|
+
* @param {number} indent - Indent level (1-6, where 1=h1, 6=h6)
|
|
266
|
+
* @returns {string} HTML tag name ('h1' through 'h6')
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* indentToHeaderTag(1) // 'h1'
|
|
270
|
+
* indentToHeaderTag(2) // 'h2'
|
|
271
|
+
* indentToHeaderTag(6) // 'h6'
|
|
272
|
+
* indentToHeaderTag(10) // 'h6' (capped at h6)
|
|
273
|
+
*/
|
|
274
|
+
export function indentToHeaderTag(indent) {
|
|
275
|
+
// Clamp indent between 1 and 6 (indent 0 reserved for special cases)
|
|
276
|
+
const level = Math.max(1, Math.min(6, parseInt(indent) || 1));
|
|
277
|
+
return `h${level}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
//
|
|
281
|
+
// Validation Utilities
|
|
282
|
+
//
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Validate if a string is a valid zTheme class name
|
|
286
|
+
*
|
|
287
|
+
* @param {string} className - Class name to validate
|
|
288
|
+
* @returns {boolean} True if valid zTheme class
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* isValidZThemeClass('zBtn') // true
|
|
292
|
+
* isValidZThemeClass('zBtnPrimary') // true
|
|
293
|
+
* isValidZThemeClass('zAlert') // true
|
|
294
|
+
* isValidZThemeClass('not-ztheme') // false
|
|
295
|
+
* isValidZThemeClass('btn-primary') // false
|
|
296
|
+
*/
|
|
297
|
+
export function isValidZThemeClass(className) {
|
|
298
|
+
if (!className || typeof className !== 'string') {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// zTheme classes start with 'z' followed by uppercase letter
|
|
303
|
+
return /^z[A-Z]/.test(className);
|
|
304
|
+
}
|
|
305
|
+
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Error Boundary Utility - Standardized Error Handling for Renderers
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* Provides error boundary wrappers for renderer methods to ensure
|
|
7
|
+
* graceful degradation when rendering fails.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────
|
|
11
|
+
// Imports
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
// Layer 2: Utilities
|
|
15
|
+
import { createElement } from '../dom/dom_utils.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a fallback error UI element
|
|
19
|
+
* @param {Object} errorInfo - Error information
|
|
20
|
+
* @param {Error} errorInfo.error - The error that occurred
|
|
21
|
+
* @param {string} errorInfo.component - Component name that failed
|
|
22
|
+
* @param {Object} errorInfo.data - Data that was being rendered
|
|
23
|
+
* @returns {HTMLElement} Error display element
|
|
24
|
+
*/
|
|
25
|
+
export function createErrorFallback(errorInfo) {
|
|
26
|
+
const { error, component = 'Component', data: _data = {} } = errorInfo;
|
|
27
|
+
|
|
28
|
+
const container = createElement('div');
|
|
29
|
+
container.className = 'bifrost-render-error';
|
|
30
|
+
container.style.cssText = `
|
|
31
|
+
background: #fee;
|
|
32
|
+
border-left: 4px solid #c33;
|
|
33
|
+
padding: 1rem;
|
|
34
|
+
margin: 0.5rem 0;
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
37
|
+
font-size: 0.875rem;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const title = createElement('div');
|
|
41
|
+
title.style.cssText = 'font-weight: 600; color: #c33; margin-bottom: 0.5rem;';
|
|
42
|
+
title.textContent = `[WARN] ${component} Render Error`;
|
|
43
|
+
|
|
44
|
+
const message = createElement('div');
|
|
45
|
+
message.style.cssText = 'color: #666; margin-bottom: 0.5rem;';
|
|
46
|
+
message.textContent = error.message || 'An error occurred while rendering this component';
|
|
47
|
+
|
|
48
|
+
const details = createElement('details');
|
|
49
|
+
details.style.cssText = 'margin-top: 0.5rem;';
|
|
50
|
+
|
|
51
|
+
const summary = createElement('summary');
|
|
52
|
+
summary.style.cssText = 'cursor: pointer; color: #999; font-size: 0.75rem;';
|
|
53
|
+
summary.textContent = 'Show technical details';
|
|
54
|
+
|
|
55
|
+
const stack = createElement('pre');
|
|
56
|
+
stack.style.cssText = `
|
|
57
|
+
margin-top: 0.5rem;
|
|
58
|
+
padding: 0.5rem;
|
|
59
|
+
background: #f9f9f9;
|
|
60
|
+
border-radius: 4px;
|
|
61
|
+
overflow-x: auto;
|
|
62
|
+
font-size: 0.75rem;
|
|
63
|
+
color: #666;
|
|
64
|
+
`;
|
|
65
|
+
stack.textContent = error.stack || 'No stack trace available';
|
|
66
|
+
|
|
67
|
+
details.appendChild(summary);
|
|
68
|
+
details.appendChild(stack);
|
|
69
|
+
|
|
70
|
+
container.appendChild(title);
|
|
71
|
+
container.appendChild(message);
|
|
72
|
+
container.appendChild(details);
|
|
73
|
+
|
|
74
|
+
return container;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Wrap a renderer method with error boundary
|
|
79
|
+
* @param {Function} renderFn - The render function to wrap
|
|
80
|
+
* @param {Object} options - Options for error handling
|
|
81
|
+
* @param {string} options.component - Component name for error messages
|
|
82
|
+
* @param {Function} options.logger - Logger instance
|
|
83
|
+
* @param {Function} options.onError - Optional error handler callback
|
|
84
|
+
* @param {boolean} options.throwOnError - Whether to re-throw errors (default: false)
|
|
85
|
+
* @returns {Function} Wrapped render function
|
|
86
|
+
*/
|
|
87
|
+
export function withErrorBoundary(renderFn, options = {}) {
|
|
88
|
+
const {
|
|
89
|
+
component = 'Unknown',
|
|
90
|
+
logger = console,
|
|
91
|
+
onError = null,
|
|
92
|
+
throwOnError = false
|
|
93
|
+
} = options;
|
|
94
|
+
|
|
95
|
+
return function wrappedRender(...args) {
|
|
96
|
+
try {
|
|
97
|
+
const result = renderFn.apply(this, args);
|
|
98
|
+
|
|
99
|
+
// Handle async render functions
|
|
100
|
+
if (result && typeof result.then === 'function') {
|
|
101
|
+
return result.catch(error => {
|
|
102
|
+
logger.error(`[${component}] Async render error:`, error);
|
|
103
|
+
|
|
104
|
+
if (onError) {
|
|
105
|
+
try {
|
|
106
|
+
onError(error, args);
|
|
107
|
+
} catch (handlerError) {
|
|
108
|
+
logger.error(`[${component}] Error in error handler:`, handlerError);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (throwOnError) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Return fallback UI for async errors
|
|
117
|
+
return createErrorFallback({
|
|
118
|
+
error,
|
|
119
|
+
component,
|
|
120
|
+
data: args[0]
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result;
|
|
126
|
+
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logger.error(`[${component}] Render error:`, error);
|
|
129
|
+
|
|
130
|
+
if (onError) {
|
|
131
|
+
try {
|
|
132
|
+
onError(error, args);
|
|
133
|
+
} catch (handlerError) {
|
|
134
|
+
logger.error(`[${component}] Error in error handler:`, handlerError);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (throwOnError) {
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Return fallback UI for sync errors
|
|
143
|
+
return createErrorFallback({
|
|
144
|
+
error,
|
|
145
|
+
component,
|
|
146
|
+
data: args[0]
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create a safe wrapper for renderer classes
|
|
154
|
+
* @param {Class} RendererClass - The renderer class to wrap
|
|
155
|
+
* @param {Object} options - Options for error handling
|
|
156
|
+
* @returns {Class} Wrapped renderer class
|
|
157
|
+
*/
|
|
158
|
+
export function createSafeRenderer(RendererClass, options = {}) {
|
|
159
|
+
return class SafeRenderer extends RendererClass {
|
|
160
|
+
constructor(...args) {
|
|
161
|
+
super(...args);
|
|
162
|
+
|
|
163
|
+
// Wrap render method if it exists
|
|
164
|
+
if (typeof this.render === 'function') {
|
|
165
|
+
const originalRender = this.render.bind(this);
|
|
166
|
+
this.render = withErrorBoundary(originalRender, {
|
|
167
|
+
component: RendererClass.name || 'Renderer',
|
|
168
|
+
logger: this.logger || console,
|
|
169
|
+
...options
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Wrap renderHTML method if it exists
|
|
174
|
+
if (typeof this.renderHTML === 'function') {
|
|
175
|
+
const originalRenderHTML = this.renderHTML.bind(this);
|
|
176
|
+
this.renderHTML = withErrorBoundary(originalRenderHTML, {
|
|
177
|
+
component: `${RendererClass.name || 'Renderer'}.renderHTML`,
|
|
178
|
+
logger: this.logger || console,
|
|
179
|
+
...options
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Decorator for renderer methods (for future use with decorators proposal)
|
|
188
|
+
* Usage: @errorBoundary({ component: 'MyRenderer' })
|
|
189
|
+
*/
|
|
190
|
+
export function errorBoundary(options = {}) {
|
|
191
|
+
return function decorator(target, propertyKey, descriptor) {
|
|
192
|
+
const originalMethod = descriptor.value;
|
|
193
|
+
|
|
194
|
+
descriptor.value = withErrorBoundary(originalMethod, {
|
|
195
|
+
component: `${target.constructor.name}.${propertyKey}`,
|
|
196
|
+
...options
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return descriptor;
|
|
200
|
+
};
|
|
201
|
+
}
|