@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,658 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InputRenderer - Interactive Input Rendering for zDisplay in Bifrost Mode
|
|
3
|
+
*
|
|
4
|
+
* This renderer handles individual interactive inputs that the backend sends one at a time
|
|
5
|
+
* (similar to Terminal mode prompts but rendered as GUI elements).
|
|
6
|
+
*
|
|
7
|
+
* Input Types:
|
|
8
|
+
* - Basic: text, password, email, number, tel, url
|
|
9
|
+
* - Choice: selection (radio/checkbox), range, color
|
|
10
|
+
* - Date/Time: date, time, datetime-local, month, week
|
|
11
|
+
* - File: file (single/multiple)
|
|
12
|
+
*
|
|
13
|
+
* Selection Modes:
|
|
14
|
+
* - GUI mode (default): Radio buttons (single) or checkboxes (multi)
|
|
15
|
+
* - Terminal mode (opt-in): Number input like "1 3 5" (terminal_style: true)
|
|
16
|
+
*
|
|
17
|
+
* Flow:
|
|
18
|
+
* 1. Backend sends input_request event with type and prompt
|
|
19
|
+
* 2. InputRenderer displays appropriate input UI
|
|
20
|
+
* 3. User enters value and submits
|
|
21
|
+
* 4. InputRenderer validates input (frontend validation)
|
|
22
|
+
* 5. InputRenderer sends input_response WebSocket message
|
|
23
|
+
* 6. Backend receives response and resolves Future
|
|
24
|
+
*
|
|
25
|
+
* Architecture:
|
|
26
|
+
* - Uses form_primitives.js for raw HTML element creation
|
|
27
|
+
* - Uses dom_utils.js for DOM manipulation
|
|
28
|
+
* - Uses validation_utils.js for frontend validation
|
|
29
|
+
* - Applies zTheme classes for styling
|
|
30
|
+
* - Handles WebSocket communication for responses
|
|
31
|
+
*
|
|
32
|
+
* @module InputRenderer
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────
|
|
36
|
+
// Imports
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
// Layer 2: Utilities
|
|
40
|
+
import { createElement } from '../../../zSys/dom/dom_utils.js';
|
|
41
|
+
|
|
42
|
+
// Layer 0: Primitives
|
|
43
|
+
import { createInput, createLabel } from '../primitives/form_primitives.js';
|
|
44
|
+
|
|
45
|
+
export class InputRenderer {
|
|
46
|
+
constructor(client) {
|
|
47
|
+
this.client = client;
|
|
48
|
+
this.logger = client.logger;
|
|
49
|
+
this.defaultZone = 'zVaF';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Render an input request (router method)
|
|
54
|
+
* @param {Object} inputRequest - Input request from backend
|
|
55
|
+
* @param {string} inputRequest.requestId - Unique request identifier
|
|
56
|
+
* @param {string} inputRequest.type - Input type
|
|
57
|
+
* @param {string} inputRequest.prompt - Input prompt text
|
|
58
|
+
* @param {Array} inputRequest.options - Options for selection type (optional)
|
|
59
|
+
* @param {boolean} inputRequest.multi - Multi-select mode for selection (optional)
|
|
60
|
+
* @param {boolean} inputRequest.terminal_style - Use terminal-style number input (optional)
|
|
61
|
+
* @param {string|Array} inputRequest.default - Default value(s) (optional)
|
|
62
|
+
* @param {number} inputRequest.min - Min value for range/number (optional)
|
|
63
|
+
* @param {number} inputRequest.max - Max value for range/number (optional)
|
|
64
|
+
* @param {number} inputRequest.step - Step value for range/number (optional)
|
|
65
|
+
* @param {string} targetZone - Target DOM element ID (default: 'zVaF')
|
|
66
|
+
* @returns {HTMLElement} Input container element
|
|
67
|
+
*/
|
|
68
|
+
renderInput(inputRequest, targetZone = null) {
|
|
69
|
+
const zone = targetZone || this.defaultZone;
|
|
70
|
+
const container = document.getElementById(zone);
|
|
71
|
+
|
|
72
|
+
if (!container) {
|
|
73
|
+
this.logger.error(`[InputRenderer] Target zone '${zone}' not found`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const {
|
|
78
|
+
type = 'text',
|
|
79
|
+
requestId,
|
|
80
|
+
prompt,
|
|
81
|
+
options,
|
|
82
|
+
multi,
|
|
83
|
+
terminal_style = false,
|
|
84
|
+
default: defaultValue,
|
|
85
|
+
min,
|
|
86
|
+
max,
|
|
87
|
+
step
|
|
88
|
+
} = inputRequest;
|
|
89
|
+
|
|
90
|
+
this.logger.log(`[InputRenderer] Rendering ${type} input:`, { requestId, prompt });
|
|
91
|
+
|
|
92
|
+
// Route to appropriate renderer based on type
|
|
93
|
+
let inputElement;
|
|
94
|
+
if (type === 'selection') {
|
|
95
|
+
inputElement = terminal_style
|
|
96
|
+
? this._renderSelectionTerminal(requestId, prompt, options, multi, defaultValue)
|
|
97
|
+
: this._renderSelectionGUI(requestId, prompt, options, multi, defaultValue);
|
|
98
|
+
} else if (type === 'password') {
|
|
99
|
+
inputElement = this._renderPassword(requestId, prompt);
|
|
100
|
+
} else if (type === 'range') {
|
|
101
|
+
inputElement = this._renderRange(requestId, prompt, min, max, step, defaultValue);
|
|
102
|
+
} else if (type === 'color') {
|
|
103
|
+
inputElement = this._renderColor(requestId, prompt, defaultValue);
|
|
104
|
+
} else if (['date', 'time', 'datetime-local', 'month', 'week'].includes(type)) {
|
|
105
|
+
inputElement = this._renderDateTime(requestId, prompt, type, defaultValue);
|
|
106
|
+
} else if (type === 'file') {
|
|
107
|
+
inputElement = this._renderFile(requestId, prompt, multi);
|
|
108
|
+
} else {
|
|
109
|
+
// Default: text, email, number, tel, url, etc.
|
|
110
|
+
inputElement = this._renderText(requestId, prompt, type, defaultValue);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Append to zone
|
|
114
|
+
container.appendChild(inputElement);
|
|
115
|
+
|
|
116
|
+
// Auto-focus the first input field
|
|
117
|
+
const input = inputElement.querySelector('input, select, textarea');
|
|
118
|
+
if (input) {
|
|
119
|
+
setTimeout(() => input.focus(), 100);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return inputElement;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Render text-based input (text, email, number, tel, url)
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
_renderText(requestId, prompt, type = 'text', defaultValue = '') {
|
|
130
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
131
|
+
|
|
132
|
+
const label = createLabel({ for: `input-${requestId}`, class: 'zForm-label zmb-2' });
|
|
133
|
+
label.textContent = prompt;
|
|
134
|
+
|
|
135
|
+
const input = createInput(type, {
|
|
136
|
+
id: `input-${requestId}`,
|
|
137
|
+
name: 'value',
|
|
138
|
+
class: 'zForm-control zmb-2',
|
|
139
|
+
placeholder: this._getPlaceholder(type),
|
|
140
|
+
required: true,
|
|
141
|
+
value: defaultValue || ''
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
145
|
+
submitBtn.type = 'button';
|
|
146
|
+
submitBtn.textContent = '[ok] Submit';
|
|
147
|
+
|
|
148
|
+
const errorDiv = createElement('div', ['zAlert', 'zAlert-danger', 'zmt-2']);
|
|
149
|
+
errorDiv.style.display = 'none';
|
|
150
|
+
|
|
151
|
+
const handleSubmit = () => {
|
|
152
|
+
const value = input.value.trim();
|
|
153
|
+
this.logger.log(`[InputRenderer] ${type} input submitted:`, { requestId, value });
|
|
154
|
+
this._sendResponse(requestId, value);
|
|
155
|
+
this._replaceWithConfirmation(container, `[ok] Submitted: ${value}`);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
159
|
+
input.addEventListener('keypress', (e) => {
|
|
160
|
+
if (e.key === 'Enter') {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
handleSubmit();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
container.appendChild(label);
|
|
167
|
+
container.appendChild(input);
|
|
168
|
+
container.appendChild(submitBtn);
|
|
169
|
+
container.appendChild(errorDiv);
|
|
170
|
+
|
|
171
|
+
return container;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Render password input
|
|
176
|
+
* @private
|
|
177
|
+
*/
|
|
178
|
+
_renderPassword(requestId, prompt) {
|
|
179
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
180
|
+
|
|
181
|
+
const label = createLabel({ for: `input-${requestId}`, class: 'zForm-label zmb-2' });
|
|
182
|
+
label.textContent = prompt;
|
|
183
|
+
|
|
184
|
+
const input = createInput('password', {
|
|
185
|
+
id: `input-${requestId}`,
|
|
186
|
+
name: 'value',
|
|
187
|
+
class: 'zForm-control zmb-2',
|
|
188
|
+
placeholder: 'Enter password...',
|
|
189
|
+
required: true
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
193
|
+
submitBtn.type = 'button';
|
|
194
|
+
submitBtn.textContent = '[ok] Submit';
|
|
195
|
+
|
|
196
|
+
const errorDiv = createElement('div', ['zAlert', 'zAlert-danger', 'zmt-2']);
|
|
197
|
+
errorDiv.style.display = 'none';
|
|
198
|
+
|
|
199
|
+
const handleSubmit = () => {
|
|
200
|
+
const value = input.value;
|
|
201
|
+
this.logger.log('[InputRenderer] Password submitted:', { requestId, length: value.length });
|
|
202
|
+
this._sendResponse(requestId, value);
|
|
203
|
+
this._replaceWithConfirmation(container, `[ok] Password submitted (${value.length} characters)`);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
207
|
+
input.addEventListener('keypress', (e) => {
|
|
208
|
+
if (e.key === 'Enter') {
|
|
209
|
+
e.preventDefault();
|
|
210
|
+
handleSubmit();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
container.appendChild(label);
|
|
215
|
+
container.appendChild(input);
|
|
216
|
+
container.appendChild(submitBtn);
|
|
217
|
+
container.appendChild(errorDiv);
|
|
218
|
+
|
|
219
|
+
return container;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Render GUI-style selection (radio buttons or checkboxes)
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
_renderSelectionGUI(requestId, prompt, options = [], multi = false, defaultValue = null) {
|
|
227
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
228
|
+
|
|
229
|
+
const title = createElement('h4', ['zForm-label', 'zmb-3']);
|
|
230
|
+
title.textContent = prompt;
|
|
231
|
+
|
|
232
|
+
const optionsContainer = createElement('div', ['zmb-3']);
|
|
233
|
+
const selectedValues = new Set(Array.isArray(defaultValue) ? defaultValue : (defaultValue ? [defaultValue] : []));
|
|
234
|
+
|
|
235
|
+
options.forEach((option, index) => {
|
|
236
|
+
const optionDiv = createElement('div', ['zForm-check', 'zmb-2']);
|
|
237
|
+
|
|
238
|
+
const input = createInput(multi ? 'checkbox' : 'radio', {
|
|
239
|
+
id: `option-${requestId}-${index}`,
|
|
240
|
+
name: multi ? `option-${requestId}` : `selection-${requestId}`,
|
|
241
|
+
class: 'zForm-check-input',
|
|
242
|
+
value: option
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (selectedValues.has(option)) {
|
|
246
|
+
input.checked = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const label = createLabel({
|
|
250
|
+
for: `option-${requestId}-${index}`,
|
|
251
|
+
class: 'zForm-check-label'
|
|
252
|
+
});
|
|
253
|
+
label.textContent = option;
|
|
254
|
+
|
|
255
|
+
optionDiv.appendChild(input);
|
|
256
|
+
optionDiv.appendChild(label);
|
|
257
|
+
optionsContainer.appendChild(optionDiv);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
261
|
+
submitBtn.type = 'button';
|
|
262
|
+
submitBtn.textContent = '[ok] Submit';
|
|
263
|
+
|
|
264
|
+
const errorDiv = createElement('div', ['zAlert', 'zAlert-danger', 'zmt-2']);
|
|
265
|
+
errorDiv.style.display = 'none';
|
|
266
|
+
|
|
267
|
+
const handleSubmit = () => {
|
|
268
|
+
const inputs = container.querySelectorAll('input[type="checkbox"], input[type="radio"]');
|
|
269
|
+
const selected = Array.from(inputs).filter(input => input.checked).map(input => input.value);
|
|
270
|
+
|
|
271
|
+
if (selected.length === 0) {
|
|
272
|
+
this._showError(errorDiv, 'Please select at least one option');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const result = multi ? selected : selected[0];
|
|
277
|
+
this.logger.log('[InputRenderer] Selection submitted:', { requestId, result });
|
|
278
|
+
this._sendResponse(requestId, result);
|
|
279
|
+
|
|
280
|
+
const confirmationText = multi
|
|
281
|
+
? `[ok] Selected: ${selected.join(', ')}`
|
|
282
|
+
: `[ok] Selected: ${result}`;
|
|
283
|
+
this._replaceWithConfirmation(container, confirmationText);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
287
|
+
|
|
288
|
+
container.appendChild(title);
|
|
289
|
+
container.appendChild(optionsContainer);
|
|
290
|
+
container.appendChild(submitBtn);
|
|
291
|
+
container.appendChild(errorDiv);
|
|
292
|
+
|
|
293
|
+
return container;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Render terminal-style selection (number input)
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
_renderSelectionTerminal(requestId, prompt, options = [], multi = false, defaultValue = null) {
|
|
301
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
302
|
+
|
|
303
|
+
const title = createElement('h4', ['zForm-label', 'zmb-3']);
|
|
304
|
+
title.textContent = prompt;
|
|
305
|
+
|
|
306
|
+
const optionsList = createElement('div', ['zmb-3']);
|
|
307
|
+
|
|
308
|
+
options.forEach((option, index) => {
|
|
309
|
+
const optionDiv = createElement('div', ['zp-2', 'zmb-1', 'zBorder', 'zRounded']);
|
|
310
|
+
optionDiv.textContent = `${index + 1}. ${option}`;
|
|
311
|
+
optionsList.appendChild(optionDiv);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const instructions = createElement('p', ['zText-muted', 'zmb-2']);
|
|
315
|
+
instructions.textContent = multi
|
|
316
|
+
? 'Enter numbers separated by spaces (e.g., "1 3 5"):'
|
|
317
|
+
: `Enter choice (1-${options.length}):`;
|
|
318
|
+
|
|
319
|
+
const input = createInput('text', {
|
|
320
|
+
id: `input-${requestId}`,
|
|
321
|
+
name: 'value',
|
|
322
|
+
class: 'zForm-control zmb-2',
|
|
323
|
+
placeholder: multi ? 'e.g., 1 3 5' : 'e.g., 1',
|
|
324
|
+
required: true
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (defaultValue) {
|
|
328
|
+
if (Array.isArray(defaultValue)) {
|
|
329
|
+
const defaultIndices = defaultValue.map(val => options.indexOf(val) + 1).filter(idx => idx > 0);
|
|
330
|
+
input.value = defaultIndices.join(' ');
|
|
331
|
+
} else {
|
|
332
|
+
const defaultIndex = options.indexOf(defaultValue) + 1;
|
|
333
|
+
if (defaultIndex > 0) {
|
|
334
|
+
input.value = String(defaultIndex);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
340
|
+
submitBtn.type = 'button';
|
|
341
|
+
submitBtn.textContent = '[ok] Submit';
|
|
342
|
+
|
|
343
|
+
const errorDiv = createElement('div', ['zAlert', 'zAlert-danger', 'zmt-2']);
|
|
344
|
+
errorDiv.style.display = 'none';
|
|
345
|
+
|
|
346
|
+
const handleSubmit = () => {
|
|
347
|
+
const value = input.value.trim();
|
|
348
|
+
const validation = this._validateSelection(value, options.length, multi);
|
|
349
|
+
if (!validation.valid) {
|
|
350
|
+
this._showError(errorDiv, validation.message);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let result;
|
|
355
|
+
if (multi) {
|
|
356
|
+
const indices = value.split(/\s+/).map(n => parseInt(n, 10));
|
|
357
|
+
result = indices.map(idx => options[idx - 1]);
|
|
358
|
+
} else {
|
|
359
|
+
const index = parseInt(value, 10);
|
|
360
|
+
result = options[index - 1];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.logger.log('[InputRenderer] Terminal-style selection submitted:', { requestId, result });
|
|
364
|
+
this._sendResponse(requestId, result);
|
|
365
|
+
|
|
366
|
+
const confirmationText = multi
|
|
367
|
+
? `[ok] Selected: ${result.join(', ')}`
|
|
368
|
+
: `[ok] Selected: ${result}`;
|
|
369
|
+
this._replaceWithConfirmation(container, confirmationText);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
373
|
+
input.addEventListener('keypress', (e) => {
|
|
374
|
+
if (e.key === 'Enter') {
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
handleSubmit();
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
container.appendChild(title);
|
|
381
|
+
container.appendChild(optionsList);
|
|
382
|
+
container.appendChild(instructions);
|
|
383
|
+
container.appendChild(input);
|
|
384
|
+
container.appendChild(submitBtn);
|
|
385
|
+
container.appendChild(errorDiv);
|
|
386
|
+
|
|
387
|
+
return container;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Render range input
|
|
392
|
+
* @private
|
|
393
|
+
*/
|
|
394
|
+
_renderRange(requestId, prompt, min = 0, max = 100, step = 1, defaultValue = 50) {
|
|
395
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
396
|
+
|
|
397
|
+
const label = createLabel({ for: `input-${requestId}`, class: 'zForm-label zmb-2' });
|
|
398
|
+
label.textContent = prompt;
|
|
399
|
+
|
|
400
|
+
const valueDisplay = createElement('span', ['zBadge', 'zBg-primary', 'zms-2']);
|
|
401
|
+
valueDisplay.textContent = defaultValue || min;
|
|
402
|
+
label.appendChild(valueDisplay);
|
|
403
|
+
|
|
404
|
+
const input = createInput('range', {
|
|
405
|
+
id: `input-${requestId}`,
|
|
406
|
+
name: 'value',
|
|
407
|
+
class: 'zForm-range zmb-2',
|
|
408
|
+
min: String(min),
|
|
409
|
+
max: String(max),
|
|
410
|
+
step: String(step),
|
|
411
|
+
value: String(defaultValue || min)
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
input.addEventListener('input', () => {
|
|
415
|
+
valueDisplay.textContent = input.value;
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
419
|
+
submitBtn.type = 'button';
|
|
420
|
+
submitBtn.textContent = '[ok] Submit';
|
|
421
|
+
|
|
422
|
+
const handleSubmit = () => {
|
|
423
|
+
const value = input.value;
|
|
424
|
+
this.logger.log('[InputRenderer] Range submitted:', { requestId, value });
|
|
425
|
+
this._sendResponse(requestId, parseFloat(value));
|
|
426
|
+
this._replaceWithConfirmation(container, `[ok] Selected: ${value}`);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
430
|
+
|
|
431
|
+
container.appendChild(label);
|
|
432
|
+
container.appendChild(input);
|
|
433
|
+
container.appendChild(submitBtn);
|
|
434
|
+
|
|
435
|
+
return container;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Render color input
|
|
440
|
+
* @private
|
|
441
|
+
*/
|
|
442
|
+
_renderColor(requestId, prompt, defaultValue = '#000000') {
|
|
443
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
444
|
+
|
|
445
|
+
const label = createLabel({ for: `input-${requestId}`, class: 'zForm-label zmb-2' });
|
|
446
|
+
label.textContent = prompt;
|
|
447
|
+
|
|
448
|
+
const inputGroup = createElement('div', ['zD-flex', 'zGap-2', 'zFlex-items-center', 'zmb-2']);
|
|
449
|
+
|
|
450
|
+
const input = createInput('color', {
|
|
451
|
+
id: `input-${requestId}`,
|
|
452
|
+
name: 'value',
|
|
453
|
+
class: 'zForm-control-color',
|
|
454
|
+
value: defaultValue || '#000000'
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const hexDisplay = createElement('code', ['zBg-light', 'zp-2', 'zRounded']);
|
|
458
|
+
hexDisplay.textContent = defaultValue || '#000000';
|
|
459
|
+
|
|
460
|
+
input.addEventListener('input', () => {
|
|
461
|
+
hexDisplay.textContent = input.value;
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
inputGroup.appendChild(input);
|
|
465
|
+
inputGroup.appendChild(hexDisplay);
|
|
466
|
+
|
|
467
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
468
|
+
submitBtn.type = 'button';
|
|
469
|
+
submitBtn.textContent = '[ok] Submit';
|
|
470
|
+
|
|
471
|
+
const handleSubmit = () => {
|
|
472
|
+
const value = input.value;
|
|
473
|
+
this.logger.log('[InputRenderer] Color submitted:', { requestId, value });
|
|
474
|
+
this._sendResponse(requestId, value);
|
|
475
|
+
this._replaceWithConfirmation(container, `[ok] Selected color: ${value}`);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
479
|
+
|
|
480
|
+
container.appendChild(label);
|
|
481
|
+
container.appendChild(inputGroup);
|
|
482
|
+
container.appendChild(submitBtn);
|
|
483
|
+
|
|
484
|
+
return container;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Render date/time input
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
_renderDateTime(requestId, prompt, type, defaultValue = '') {
|
|
492
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
493
|
+
|
|
494
|
+
const label = createLabel({ for: `input-${requestId}`, class: 'zForm-label zmb-2' });
|
|
495
|
+
label.textContent = prompt;
|
|
496
|
+
|
|
497
|
+
const input = createInput(type, {
|
|
498
|
+
id: `input-${requestId}`,
|
|
499
|
+
name: 'value',
|
|
500
|
+
class: 'zForm-control zmb-2',
|
|
501
|
+
required: true,
|
|
502
|
+
value: defaultValue || ''
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
506
|
+
submitBtn.type = 'button';
|
|
507
|
+
submitBtn.textContent = '[ok] Submit';
|
|
508
|
+
|
|
509
|
+
const errorDiv = createElement('div', ['zAlert', 'zAlert-danger', 'zmt-2']);
|
|
510
|
+
errorDiv.style.display = 'none';
|
|
511
|
+
|
|
512
|
+
const handleSubmit = () => {
|
|
513
|
+
const value = input.value;
|
|
514
|
+
this.logger.log(`[InputRenderer] ${type} submitted:`, { requestId, value });
|
|
515
|
+
this._sendResponse(requestId, value);
|
|
516
|
+
this._replaceWithConfirmation(container, `[ok] Selected: ${value}`);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
520
|
+
|
|
521
|
+
container.appendChild(label);
|
|
522
|
+
container.appendChild(input);
|
|
523
|
+
container.appendChild(submitBtn);
|
|
524
|
+
container.appendChild(errorDiv);
|
|
525
|
+
|
|
526
|
+
return container;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Render file input
|
|
531
|
+
* @private
|
|
532
|
+
*/
|
|
533
|
+
_renderFile(requestId, prompt, multi = false) {
|
|
534
|
+
const container = createElement('div', ['zCard', 'zp-3', 'zmb-3']);
|
|
535
|
+
|
|
536
|
+
const label = createLabel({ for: `input-${requestId}`, class: 'zForm-label zmb-2' });
|
|
537
|
+
label.textContent = prompt;
|
|
538
|
+
|
|
539
|
+
const input = createInput('file', {
|
|
540
|
+
id: `input-${requestId}`,
|
|
541
|
+
name: 'value',
|
|
542
|
+
class: 'zForm-control zmb-2',
|
|
543
|
+
multiple: multi
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const submitBtn = createElement('button', ['zBtn', 'zBtn-primary']);
|
|
547
|
+
submitBtn.type = 'button';
|
|
548
|
+
submitBtn.textContent = '[ok] Submit';
|
|
549
|
+
|
|
550
|
+
const errorDiv = createElement('div', ['zAlert', 'zAlert-danger', 'zmt-2']);
|
|
551
|
+
errorDiv.style.display = 'none';
|
|
552
|
+
|
|
553
|
+
const handleSubmit = () => {
|
|
554
|
+
if (input.files.length === 0) {
|
|
555
|
+
this._showError(errorDiv, 'Please select a file');
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const fileNames = Array.from(input.files).map(f => f.name);
|
|
559
|
+
this.logger.log('[InputRenderer] File(s) selected:', { requestId, files: fileNames });
|
|
560
|
+
this._sendResponse(requestId, fileNames);
|
|
561
|
+
this._replaceWithConfirmation(container, `[ok] Selected: ${fileNames.join(', ')}`);
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
submitBtn.addEventListener('click', handleSubmit);
|
|
565
|
+
|
|
566
|
+
container.appendChild(label);
|
|
567
|
+
container.appendChild(input);
|
|
568
|
+
container.appendChild(submitBtn);
|
|
569
|
+
container.appendChild(errorDiv);
|
|
570
|
+
|
|
571
|
+
return container;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Get placeholder text for input type
|
|
576
|
+
* @private
|
|
577
|
+
*/
|
|
578
|
+
_getPlaceholder(type) {
|
|
579
|
+
const placeholders = {
|
|
580
|
+
text: 'Enter text...',
|
|
581
|
+
email: 'Enter email address...',
|
|
582
|
+
number: 'Enter number...',
|
|
583
|
+
tel: 'Enter phone number...',
|
|
584
|
+
url: 'Enter URL...'
|
|
585
|
+
};
|
|
586
|
+
return placeholders[type] || 'Enter value...';
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Validate terminal-style selection input
|
|
591
|
+
* @private
|
|
592
|
+
*/
|
|
593
|
+
_validateSelection(value, maxNum, multi) {
|
|
594
|
+
if (!value) {
|
|
595
|
+
return { valid: false, message: 'Please enter a selection' };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const numbers = value.split(/\s+/).map(n => parseInt(n, 10));
|
|
599
|
+
|
|
600
|
+
if (numbers.some(n => isNaN(n))) {
|
|
601
|
+
return { valid: false, message: 'Please enter valid numbers only' };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (numbers.some(n => n < 1 || n > maxNum)) {
|
|
605
|
+
return { valid: false, message: `Please enter numbers between 1 and ${maxNum}` };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (!multi && numbers.length > 1) {
|
|
609
|
+
return { valid: false, message: 'Please enter only one number' };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return { valid: true, message: '' };
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Send input response to backend
|
|
617
|
+
* @private
|
|
618
|
+
*/
|
|
619
|
+
_sendResponse(requestId, value) {
|
|
620
|
+
const connection = this.client?.connection;
|
|
621
|
+
|
|
622
|
+
if (!connection) {
|
|
623
|
+
this.logger.error('[InputRenderer] No WebSocket connection available');
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
connection.send(JSON.stringify({
|
|
629
|
+
event: 'input_response',
|
|
630
|
+
requestId: requestId,
|
|
631
|
+
value: value
|
|
632
|
+
}));
|
|
633
|
+
|
|
634
|
+
this.logger.log('[InputRenderer] Response sent:', { requestId, value });
|
|
635
|
+
} catch (error) {
|
|
636
|
+
this.logger.error('[InputRenderer] Failed to send response:', error);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Show error message
|
|
642
|
+
* @private
|
|
643
|
+
*/
|
|
644
|
+
_showError(errorDiv, message) {
|
|
645
|
+
errorDiv.textContent = message;
|
|
646
|
+
errorDiv.style.display = 'block';
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Replace input container with confirmation message
|
|
651
|
+
* @private
|
|
652
|
+
*/
|
|
653
|
+
_replaceWithConfirmation(container, message) {
|
|
654
|
+
const confirmation = createElement('div', ['zAlert', 'zAlert-success', 'zp-3', 'zmb-3']);
|
|
655
|
+
confirmation.textContent = message;
|
|
656
|
+
container.replaceWith(confirmation);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Rendering Module Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Form inputs, buttons, and interactive input components.
|
|
5
|
+
*
|
|
6
|
+
* @module rendering/inputs
|
|
7
|
+
* @layer 3 (Input Rendering)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from './button_renderer.js';
|
|
11
|
+
export * from './form_renderer.js';
|
|
12
|
+
export * from './input_renderer.js';
|