@zolomedia/bifrost-client 1.7.74

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