@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,467 @@
1
+ /**
2
+ * Input Request Renderer - Interactive input forms for user input
3
+ *
4
+ * Renders interactive input request forms from backend:
5
+ * - Text input (string, password)
6
+ * - Selection (radio/checkbox)
7
+ * - Button confirmation
8
+ *
9
+ * Extracted from zdisplay_renderer.js (Phase 4)
10
+ *
11
+ * @module rendering/specialized/input_request_renderer
12
+ * @layer 3 (Specialized Rendering)
13
+ */
14
+
15
+ import { TYPOGRAPHY } from '../../../L1_Foundation/constants/bifrost_constants.js';
16
+
17
+ export class InputRequestRenderer {
18
+ constructor(logger = null, defaultZone = 'zVaF-content') {
19
+ this.logger = logger || console;
20
+ this.defaultZone = defaultZone;
21
+ }
22
+
23
+ /**
24
+ * Render input request as HTML form
25
+ * @param {Object} inputRequest - Input request event from backend
26
+ * @param {string} targetZone - Target DOM element ID
27
+ */
28
+ renderInputRequest(inputRequest, targetZone = null) {
29
+ const zone = targetZone || this.defaultZone;
30
+ const container = document.getElementById(zone);
31
+
32
+ if (!container) {
33
+ this.logger.error(`[InputRequestRenderer] Cannot render input - zone not found: ${zone}`);
34
+ return;
35
+ }
36
+
37
+ // Extract input details
38
+ const requestId = inputRequest.requestId || inputRequest.data?.requestId;
39
+ const inputType = inputRequest.type || inputRequest.data?.type || 'string';
40
+ const prompt = inputRequest.prompt || inputRequest.data?.prompt || 'Enter input:';
41
+ const masked = inputRequest.masked || inputRequest.data?.masked || (inputType === 'password');
42
+
43
+ this.logger.log('[InputRequestRenderer] Rendering input form:', { requestId, inputType, prompt, masked });
44
+
45
+ // Create form container
46
+ const form = document.createElement('form');
47
+ form.className = 'zInputForm';
48
+ form.style.cssText = `
49
+ margin: 1rem 0;
50
+ padding: 1rem;
51
+ border: 2px solid var(--color-primary, #00D4FF);
52
+ border-radius: 8px;
53
+ background-color: var(--color-base, #fff);
54
+ `;
55
+
56
+ // Create label
57
+ const label = document.createElement('label');
58
+ label.textContent = prompt;
59
+ label.style.cssText = `
60
+ display: block;
61
+ margin-bottom: 0.5rem;
62
+ font-weight: bold;
63
+ color: var(--color-darkgray, #333);
64
+ `;
65
+
66
+ // Create input field
67
+ const input = document.createElement('input');
68
+ input.type = masked ? 'password' : 'text';
69
+ input.placeholder = masked ? '••••••••' : 'Type here...';
70
+ input.required = true;
71
+ input.style.cssText = `
72
+ width: 100%;
73
+ padding: 0.5rem;
74
+ margin-bottom: 1rem;
75
+ border: 1px solid var(--color-gray, #ccc);
76
+ border-radius: 4px;
77
+ font-size: 1rem;
78
+ `;
79
+
80
+ // Create submit button
81
+ const submitBtn = document.createElement('button');
82
+ submitBtn.type = 'submit';
83
+ submitBtn.textContent = '[ok] Submit';
84
+ submitBtn.className = 'zoloButton zBtnPrimary';
85
+ submitBtn.style.cssText = `
86
+ padding: 0.5rem 1.5rem;
87
+ cursor: pointer;
88
+ `;
89
+
90
+ // Handle form submission
91
+ form.addEventListener('submit', (e) => {
92
+ e.preventDefault();
93
+ const value = input.value.trim();
94
+
95
+ this.logger.log('[InputRequestRenderer] Input submitted:', { requestId, value: masked ? '***' : value });
96
+
97
+ // Send input_response back to server (one-way, no response expected)
98
+ if (window.bifrostClient && window.bifrostClient.connection) {
99
+ try {
100
+ const payload = {
101
+ event: 'input_response',
102
+ requestId: requestId,
103
+ value: value
104
+ };
105
+ this.logger.log('[InputRequestRenderer] Sending input_response:', payload);
106
+ window.bifrostClient.connection.send(JSON.stringify(payload));
107
+ this.logger.log('[InputRequestRenderer] Input response sent successfully (one-way)');
108
+ } catch (error) {
109
+ this.logger.error('[InputRequestRenderer] [ERROR] Failed to send input response:', error);
110
+ }
111
+ } else {
112
+ this.logger.error('[InputRequestRenderer] Cannot send input response - bifrostClient not found on window');
113
+ }
114
+
115
+ // Replace form with confirmation message
116
+ const confirmation = document.createElement('p');
117
+ confirmation.style.cssText = `
118
+ margin: 1rem 0;
119
+ padding: 0.75rem;
120
+ background-color: var(--color-success-light, #d4edda);
121
+ border: 1px solid var(--color-success, #28a745);
122
+ border-radius: 4px;
123
+ color: var(--color-success-dark, #155724);
124
+ `;
125
+ confirmation.textContent = masked
126
+ ? `[ok] Password submitted (${value.length} characters)`
127
+ : `[ok] Submitted: ${value}`;
128
+
129
+ form.replaceWith(confirmation);
130
+ });
131
+
132
+ // Assemble form
133
+ form.appendChild(label);
134
+ form.appendChild(input);
135
+ form.appendChild(submitBtn);
136
+
137
+ // Add to container
138
+ container.appendChild(form);
139
+
140
+ // Focus input
141
+ input.focus();
142
+
143
+ this.logger.log('[InputRequestRenderer] Input form rendered');
144
+ }
145
+
146
+ /**
147
+ * Render selection request as HTML form with radio/checkboxes
148
+ * @param {Object} selectionRequest - Selection request event from backend
149
+ * @param {string} targetZone - Target DOM element ID
150
+ */
151
+ renderSelectionRequest(selectionRequest, targetZone = null) {
152
+ const zone = targetZone || this.defaultZone;
153
+ const container = document.getElementById(zone);
154
+
155
+ if (!container) {
156
+ this.logger.error(`[InputRequestRenderer] Cannot render selection - zone not found: ${zone}`);
157
+ return;
158
+ }
159
+
160
+ // Extract selection details
161
+ const requestId = selectionRequest.requestId || selectionRequest.data?.requestId;
162
+ const prompt = selectionRequest.prompt || selectionRequest.data?.prompt || 'Select:';
163
+ const options = selectionRequest.options || selectionRequest.data?.options || [];
164
+ const multi = selectionRequest.multi || selectionRequest.data?.multi || false;
165
+ const defaultVal = selectionRequest.default || selectionRequest.data?.default;
166
+
167
+ this.logger.log('[InputRequestRenderer] Rendering selection form:', { requestId, prompt, options, multi });
168
+
169
+ // Create form container
170
+ const form = document.createElement('form');
171
+ form.className = 'zSelectionForm';
172
+ form.style.cssText = `
173
+ margin: 1rem 0;
174
+ padding: 1rem;
175
+ border: 2px solid var(--color-primary, #00D4FF);
176
+ border-radius: 8px;
177
+ background-color: var(--color-base, #fff);
178
+ `;
179
+
180
+ // Create label
181
+ const label = document.createElement('label');
182
+ label.textContent = prompt;
183
+ label.style.cssText = `
184
+ display: block;
185
+ margin-bottom: 1rem;
186
+ font-weight: bold;
187
+ color: var(--color-darkgray, #333);
188
+ `;
189
+ form.appendChild(label);
190
+
191
+ // Create options container
192
+ const optionsContainer = document.createElement('div');
193
+ optionsContainer.style.cssText = `
194
+ margin-bottom: 1rem;
195
+ max-height: 300px;
196
+ overflow-y: auto;
197
+ `;
198
+
199
+ // Create option elements
200
+ const inputType = multi ? 'checkbox' : 'radio';
201
+ const inputName = `selection_${requestId}`;
202
+
203
+ options.forEach((option, index) => {
204
+ const optionDiv = document.createElement('div');
205
+ optionDiv.style.cssText = `
206
+ padding: 0.5rem;
207
+ margin: 0.25rem 0;
208
+ border-radius: 4px;
209
+ cursor: pointer;
210
+ display: flex;
211
+ align-items: center;
212
+ `;
213
+ optionDiv.onmouseover = () => optionDiv.style.backgroundColor = 'var(--color-lightgray, #f8f9fa)';
214
+ optionDiv.onmouseout = () => optionDiv.style.backgroundColor = 'transparent';
215
+
216
+ const input = document.createElement('input');
217
+ input.type = inputType;
218
+ input.name = inputName;
219
+ input.value = option;
220
+ input.id = `${inputName}_${index}`;
221
+ input.style.cssText = `
222
+ margin-right: 0.5rem;
223
+ cursor: pointer;
224
+ `;
225
+
226
+ // Set default selection
227
+ if (defaultVal) {
228
+ if (multi && Array.isArray(defaultVal)) {
229
+ input.checked = defaultVal.includes(option);
230
+ } else if (!multi && defaultVal === option) {
231
+ input.checked = true;
232
+ }
233
+ }
234
+
235
+ const optionLabel = document.createElement('label');
236
+ optionLabel.htmlFor = input.id;
237
+ optionLabel.textContent = option;
238
+ optionLabel.style.cssText = `
239
+ cursor: pointer;
240
+ flex: 1;
241
+ `;
242
+
243
+ optionDiv.appendChild(input);
244
+ optionDiv.appendChild(optionLabel);
245
+ optionDiv.onclick = () => input.checked = !input.checked;
246
+
247
+ optionsContainer.appendChild(optionDiv);
248
+ });
249
+
250
+ form.appendChild(optionsContainer);
251
+
252
+ // Create submit button
253
+ const submitBtn = document.createElement('button');
254
+ submitBtn.type = 'submit';
255
+ submitBtn.textContent = '[ok] Submit';
256
+ submitBtn.className = 'zoloButton zBtnPrimary';
257
+ submitBtn.style.cssText = `
258
+ padding: 0.5rem 1.5rem;
259
+ cursor: pointer;
260
+ `;
261
+ form.appendChild(submitBtn);
262
+
263
+ // Handle form submission
264
+ form.addEventListener('submit', (e) => {
265
+ e.preventDefault();
266
+
267
+ // Get selected values
268
+ const selectedInputs = form.querySelectorAll(`input[name="${inputName}"]:checked`);
269
+ const selectedValues = Array.from(selectedInputs).map(input => input.value);
270
+
271
+ // Return appropriate format
272
+ const value = multi ? selectedValues : (selectedValues[0] || null);
273
+
274
+ this.logger.log('[InputRequestRenderer] Selection submitted:', { requestId, value });
275
+
276
+ // Send selection_response back to server (one-way)
277
+ if (window.bifrostClient && window.bifrostClient.connection) {
278
+ try {
279
+ const payload = {
280
+ event: 'input_response',
281
+ requestId: requestId,
282
+ value: value
283
+ };
284
+ this.logger.log('[InputRequestRenderer] Sending selection response:', payload);
285
+ window.bifrostClient.connection.send(JSON.stringify(payload));
286
+ this.logger.log('[InputRequestRenderer] Selection response sent successfully');
287
+ } catch (error) {
288
+ this.logger.error('[InputRequestRenderer] [ERROR] Failed to send selection response:', error);
289
+ }
290
+ } else {
291
+ this.logger.error('[InputRequestRenderer] Cannot send selection response - bifrostClient not found');
292
+ }
293
+
294
+ // Replace form with confirmation message
295
+ const confirmation = document.createElement('p');
296
+ confirmation.style.cssText = `
297
+ margin: 1rem 0;
298
+ padding: 0.75rem;
299
+ background-color: var(--color-success-light, #d4edda);
300
+ border: 1px solid var(--color-success, #28a745);
301
+ border-radius: 4px;
302
+ color: var(--color-success-dark, #155724);
303
+ `;
304
+
305
+ if (multi) {
306
+ confirmation.textContent = selectedValues.length > 0
307
+ ? `[ok] Selected: ${selectedValues.join(', ')}`
308
+ : '[ok] No selections made';
309
+ } else {
310
+ confirmation.textContent = value ? `[ok] Selected: ${value}` : '[ok] No selection made';
311
+ }
312
+
313
+ form.replaceWith(confirmation);
314
+ });
315
+
316
+ // Add to container
317
+ container.appendChild(form);
318
+
319
+ this.logger.log('[InputRequestRenderer] Selection form rendered');
320
+ }
321
+
322
+ /**
323
+ * Render button request as interactive confirmation button
324
+ * @param {Object} buttonRequest - Button request event from backend
325
+ * @param {string} targetZone - Target DOM element ID
326
+ */
327
+ renderButtonRequest(buttonRequest, targetZone = null) {
328
+ const zone = targetZone || this.defaultZone;
329
+ const container = document.getElementById(zone);
330
+
331
+ if (!container) {
332
+ this.logger.error(`[InputRequestRenderer] Cannot render button - zone not found: ${zone}`);
333
+ return;
334
+ }
335
+
336
+ // Extract button details
337
+ const requestId = buttonRequest.requestId || buttonRequest.data?.requestId;
338
+ const label = buttonRequest.prompt || buttonRequest.data?.prompt || 'Click Me';
339
+ const action = buttonRequest.action || buttonRequest.data?.action || null;
340
+ const color = buttonRequest.color || buttonRequest.data?.color || 'primary';
341
+ const style = buttonRequest.style || buttonRequest.data?.style || 'default';
342
+
343
+ this.logger.log('[InputRequestRenderer] Rendering button:', { requestId, label, action, color, style });
344
+
345
+ // Create button container
346
+ const buttonContainer = document.createElement('div');
347
+ buttonContainer.className = 'zButtonContainer';
348
+ buttonContainer.style.cssText = `
349
+ margin: 1rem 0;
350
+ padding: 1rem;
351
+ display: flex;
352
+ align-items: center;
353
+ gap: 1rem;
354
+ `;
355
+
356
+ // Create the button with zTheme classes
357
+ const button = document.createElement('button');
358
+ button.type = 'button';
359
+ button.textContent = label;
360
+
361
+ // Apply zTheme button classes based on color
362
+ const colorClass = {
363
+ 'primary': 'zBtnPrimary',
364
+ 'success': 'zBtnSuccess',
365
+ 'danger': 'zBtnDanger',
366
+ 'warning': 'zBtnWarning',
367
+ 'info': 'zBtnInfo',
368
+ 'secondary': 'zBtnSecondary'
369
+ }[color] || 'zBtnPrimary';
370
+
371
+ button.className = `zoloButton ${colorClass}`;
372
+ button.style.cssText = `
373
+ padding: 0.5rem 1.5rem;
374
+ font-size: 1rem;
375
+ cursor: pointer;
376
+ transition: transform 0.1s ease;
377
+ `;
378
+
379
+ // Add hover effect
380
+ button.addEventListener('mouseenter', () => {
381
+ button.style.transform = 'scale(1.02)';
382
+ });
383
+ button.addEventListener('mouseleave', () => {
384
+ button.style.transform = 'scale(1)';
385
+ });
386
+
387
+ // Handle button click
388
+ button.addEventListener('click', () => {
389
+ this.logger.log('[InputRequestRenderer] Button clicked:', label);
390
+
391
+ // Send response back to server (True = clicked)
392
+ if (window.bifrostClient && window.bifrostClient.connection) {
393
+ window.bifrostClient.connection.send(JSON.stringify({
394
+ event: 'input_response',
395
+ requestId: requestId,
396
+ value: true // Button clicked = True
397
+ }));
398
+ this.logger.log('[InputRequestRenderer] Button response sent');
399
+ }
400
+
401
+ // Replace button with confirmation
402
+ const confirmation = document.createElement('p');
403
+ confirmation.style.cssText = `
404
+ margin: 0;
405
+ padding: 0.5rem 1rem;
406
+ color: var(--color-success, #10b981);
407
+ font-weight: ${TYPOGRAPHY.FONT_WEIGHTS.MEDIUM};
408
+ `;
409
+ confirmation.textContent = `[ok] ${label} clicked!`;
410
+
411
+ buttonContainer.replaceWith(confirmation);
412
+ });
413
+
414
+ // Add cancel button (optional - for explicit "No" response)
415
+ const cancelBtn = document.createElement('button');
416
+ cancelBtn.type = 'button';
417
+ cancelBtn.textContent = 'Cancel';
418
+ cancelBtn.className = 'zoloButton zBtnSecondary';
419
+ cancelBtn.style.cssText = `
420
+ padding: 0.5rem 1.5rem;
421
+ font-size: 1rem;
422
+ cursor: pointer;
423
+ transition: transform 0.1s ease;
424
+ `;
425
+
426
+ cancelBtn.addEventListener('mouseenter', () => {
427
+ cancelBtn.style.transform = 'scale(1.02)';
428
+ });
429
+ cancelBtn.addEventListener('mouseleave', () => {
430
+ cancelBtn.style.transform = 'scale(1)';
431
+ });
432
+
433
+ cancelBtn.addEventListener('click', () => {
434
+ this.logger.log('[InputRequestRenderer] Button cancelled');
435
+
436
+ // Send False response
437
+ if (window.bifrostClient && window.bifrostClient.connection) {
438
+ window.bifrostClient.connection.send(JSON.stringify({
439
+ event: 'input_response',
440
+ requestId: requestId,
441
+ value: false // Cancelled = False
442
+ }));
443
+ }
444
+
445
+ // Replace with cancellation message
446
+ const cancellation = document.createElement('p');
447
+ cancellation.style.cssText = `
448
+ margin: 0;
449
+ padding: 0.5rem 1rem;
450
+ color: var(--color-gray, #6b7280);
451
+ font-style: italic;
452
+ `;
453
+ cancellation.textContent = '[x] Cancelled';
454
+
455
+ buttonContainer.replaceWith(cancellation);
456
+ });
457
+
458
+ // Assemble container
459
+ buttonContainer.appendChild(button);
460
+ buttonContainer.appendChild(cancelBtn);
461
+
462
+ // Add to container
463
+ container.appendChild(buttonContainer);
464
+
465
+ this.logger.log('[InputRequestRenderer] Button rendered');
466
+ }
467
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Specialized Rendering Module Barrel Export
3
+ *
4
+ * Specialized renderers for complex interactive components.
5
+ *
6
+ * @module rendering/specialized
7
+ * @layer 3 (Specialized Rendering)
8
+ */
9
+
10
+ export * from './input_request_renderer.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * L2_Handling/hooks - Widget Hook System
3
+ *
4
+ * Hook registration, execution, and default hooks.
5
+ * Depends on: L1_Foundation (Logger)
6
+ */
7
+
8
+ export { WidgetHookManager } from './widget_hook_manager.js';
9
+ export { MenuIntegration } from './menu_integration.js';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Menu Integration for Bifrost Client
3
+ *
4
+ * This module registers the onMenu hook with the BifrostClient to enable
5
+ * menu rendering and interaction in Bifrost mode.
6
+ *
7
+ * Usage:
8
+ * <script type="module" src="/bifrost/src/menu_integration.js"></script>
9
+ *
10
+ * Or dynamically:
11
+ * import { registerMenuHook } from './menu_integration.js';
12
+ * registerMenuHook(bifrostClient);
13
+ */
14
+
15
+ // ─────────────────────────────────────────────────────────────────
16
+ // Imports
17
+ // ─────────────────────────────────────────────────────────────────
18
+
19
+ // Layer 3: Renderers
20
+ import { MenuRenderer } from '../display/navigation/menu_renderer.js';
21
+
22
+ /**
23
+ * Register the onMenu hook with a BifrostClient instance
24
+ * @param {BifrostClient} client - The Bifrost client instance
25
+ */
26
+ export function registerMenuHook(client) {
27
+ const logger = client.logger || console;
28
+ logger.debug('[MenuIntegration] Registering onMenu hook');
29
+
30
+ // Create menu renderer
31
+ const menuRenderer = new MenuRenderer(client);
32
+
33
+ // Register the onMenu hook
34
+ // New-format zMenu events have flat 'options' array and 'title'.
35
+ // Legacy menu events have 'menu_key' and nested options with breadcrumbs.
36
+ client.registerHook('onMenu', (message) => {
37
+ logger.debug('[MenuIntegration] onMenu hook called with message:', message);
38
+ if (Array.isArray(message.options) && !message.menu_key) {
39
+ menuRenderer.renderZMenu(message);
40
+ } else {
41
+ menuRenderer.renderMenu(message);
42
+ }
43
+ });
44
+
45
+ logger.info('[MenuIntegration] onMenu hook registered successfully');
46
+ }
47
+
48
+ /**
49
+ * Auto-register if BifrostClient is already initialized
50
+ * This allows the script to be loaded after the client is created
51
+ */
52
+ if (typeof window !== 'undefined' && window.bifrostClient) {
53
+ const logger = window.bifrostClient.logger || console;
54
+ logger.debug('[MenuIntegration] Auto-registering with existing BifrostClient');
55
+ registerMenuHook(window.bifrostClient);
56
+ }
57
+