@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,564 @@
1
+ /**
2
+ * SwiperRenderer - Render interactive content carousels/slideshows
3
+ *
4
+ * Terminal-first implementation matching backend zDisplay.swiper()
5
+ *
6
+ * Backend Events (from display_event_timebased.py):
7
+ * - swiper_init: Initialize swiper with slides
8
+ * - swiper_update: Update current slide
9
+ * - swiper_complete: Finish swiper
10
+ *
11
+ * Terminal Paradigm:
12
+ * - Box-drawing UI: for beautiful bordered display
13
+ * - Arrow keys: for navigation (via termios + select)
14
+ * - Number keys: 1-9 for direct jump to slide
15
+ * - Pause toggle: 'p' key
16
+ * - Quit: 'q' key
17
+ * - Auto-advance: Background thread cycles through slides every N seconds
18
+ * - Loop mode: Optional wrap around to start
19
+ *
20
+ * Bifrost Paradigm:
21
+ * - WebSocket events trigger initialization and updates
22
+ * - Touch gestures: Swipe left/right for navigation
23
+ * - Auto-advance: CSS animations + JavaScript intervals
24
+ * - Indicators: Dots showing position (1/N, 2/N, etc.)
25
+ * - zTheme carousel: Full CSS transitions and responsive design
26
+ *
27
+ * Features:
28
+ * - Slide/fade/vertical transitions (zTheme variants)
29
+ * - Auto-advance with configurable delay
30
+ * - Loop mode
31
+ * - Prev/next controls with keyboard shortcuts
32
+ * - Indicators (dots or progress bar)
33
+ * - Pause on hover
34
+ * - Caption support
35
+ * - Multiple concurrent swipers
36
+ *
37
+ * @see https://github.com/ZoloAi/zTheme/blob/main/Manual/ztheme-carousel.html
38
+ */
39
+
40
+ // ─────────────────────────────────────────────────────────────────
41
+ // Imports
42
+ // ─────────────────────────────────────────────────────────────────
43
+
44
+ // Layer 0: Constants
45
+ import { TIMEOUTS } from '../../../L1_Foundation/constants/bifrost_constants.js';
46
+
47
+ // Layer 0: Primitives
48
+ import { createDiv } from '../primitives/generic_containers.js';
49
+ import { createButton } from '../primitives/interactive_primitives.js';
50
+ import { createSpan } from '../primitives/generic_containers.js';
51
+ import { createList } from '../primitives/lists_primitives.js';
52
+
53
+ export default class SwiperRenderer {
54
+ /**
55
+ * @param {Object} logger - Logger instance
56
+ */
57
+ constructor(logger) {
58
+ this.logger = logger;
59
+ this._activeSwipers = new Map(); // Track active swipers by swiperId
60
+ }
61
+
62
+ /**
63
+ * Initialize a swiper (swiper_init event)
64
+ * @param {Object} event - Swiper init event
65
+ * @param {string} event.swiperId - Unique swiper ID
66
+ * @param {string} event.label - Swiper label/title
67
+ * @param {Array<string>} event.slides - Array of slide content
68
+ * @param {number} [event.currentSlide=0] - Initial slide index
69
+ * @param {number} event.totalSlides - Total number of slides
70
+ * @param {boolean} [event.autoAdvance=true] - Enable auto-advance
71
+ * @param {number} [event.delay=3] - Delay between slides (seconds)
72
+ * @param {boolean} [event.loop=false] - Loop back to first slide
73
+ * @param {string} [event.container='#app'] - Target container selector
74
+ * @param {string} [event.variant='slide'] - Transition variant (slide, fade, vertical)
75
+ * @param {boolean} [event.showIndicators=true] - Show dot indicators
76
+ * @param {boolean} [event.showControls=true] - Show prev/next controls
77
+ * @returns {HTMLElement} Swiper container element
78
+ */
79
+ init(event) {
80
+ const {
81
+ swiperId,
82
+ label = 'Slides',
83
+ slides = [],
84
+ currentSlide = 0,
85
+ _totalSlides,
86
+ autoAdvance = true,
87
+ delay = 3,
88
+ loop = false,
89
+ container = '#app',
90
+ variant = 'slide',
91
+ showIndicators = true,
92
+ showControls = true
93
+ } = event;
94
+
95
+ this.logger.log('[SwiperRenderer] Initializing swiper:', {
96
+ swiperId,
97
+ label,
98
+ slidesCount: slides.length
99
+ });
100
+
101
+ if (!slides || slides.length === 0) {
102
+ this.logger.warn('[SwiperRenderer] No slides provided');
103
+ return null;
104
+ }
105
+
106
+ // Create swiper structure using primitives and zTheme classes
107
+ const swiperContainer = this._createSwiperContainer(
108
+ swiperId,
109
+ label,
110
+ slides,
111
+ currentSlide,
112
+ variant,
113
+ showIndicators,
114
+ showControls
115
+ );
116
+
117
+ // Find target container
118
+ const targetElement = document.querySelector(container) || document.body;
119
+ targetElement.appendChild(swiperContainer);
120
+
121
+ // Track active swiper
122
+ const swiperData = {
123
+ element: swiperContainer,
124
+ label,
125
+ slides,
126
+ currentSlide,
127
+ totalSlides: slides.length,
128
+ autoAdvance,
129
+ delay,
130
+ loop,
131
+ variant,
132
+ intervalId: null
133
+ };
134
+
135
+ this._activeSwipers.set(swiperId, swiperData);
136
+
137
+ // Start auto-advance if enabled
138
+ if (autoAdvance && delay > 0) {
139
+ this._startAutoAdvance(swiperId);
140
+ }
141
+
142
+ // Add keyboard navigation
143
+ this._addKeyboardNav(swiperId);
144
+
145
+ this.logger.log('[SwiperRenderer] Swiper initialized successfully');
146
+ return swiperContainer;
147
+ }
148
+
149
+ /**
150
+ * Update swiper to show specific slide (swiper_update event)
151
+ * @param {Object} event - Swiper update event
152
+ * @param {string} event.swiperId - Unique swiper ID
153
+ * @param {number} event.currentSlide - Target slide index
154
+ */
155
+ update(event) {
156
+ const { swiperId, currentSlide } = event;
157
+
158
+ this.logger.log('[SwiperRenderer] Updating swiper:', { swiperId, currentSlide });
159
+
160
+ const swiperData = this._activeSwipers.get(swiperId);
161
+ if (!swiperData) {
162
+ this.logger.warn('[SwiperRenderer] Swiper not found:', swiperId);
163
+ return;
164
+ }
165
+
166
+ this._goToSlide(swiperId, currentSlide);
167
+ }
168
+
169
+ /**
170
+ * Complete and remove swiper (swiper_complete event)
171
+ * @param {Object} event - Swiper complete event
172
+ * @param {string} event.swiperId - Unique swiper ID
173
+ */
174
+ complete(event) {
175
+ const { swiperId } = event;
176
+
177
+ this.logger.log('[SwiperRenderer] Completing swiper:', swiperId);
178
+
179
+ const swiperData = this._activeSwipers.get(swiperId);
180
+ if (!swiperData) {
181
+ this.logger.warn('[SwiperRenderer] Swiper not found:', swiperId);
182
+ return;
183
+ }
184
+
185
+ // Stop auto-advance
186
+ if (swiperData.intervalId) {
187
+ clearInterval(swiperData.intervalId);
188
+ }
189
+
190
+ // Fade out and remove
191
+ const { element } = swiperData;
192
+ element.style.transition = 'opacity 0.3s';
193
+ element.style.opacity = '0';
194
+ setTimeout(() => {
195
+ if (element.parentNode) {
196
+ element.parentNode.removeChild(element);
197
+ }
198
+ }, TIMEOUTS.FADE_TRANSITION);
199
+
200
+ // Remove from active swipers
201
+ this._activeSwipers.delete(swiperId);
202
+
203
+ this.logger.log('[SwiperRenderer] Swiper completed successfully');
204
+ }
205
+
206
+ /**
207
+ * Create swiper container using primitives and zTheme classes
208
+ * @private
209
+ */
210
+ _createSwiperContainer(swiperId, label, slides, currentSlide, variant, showIndicators, showControls) {
211
+ // Main container with wrapper for label
212
+ const wrapper = createDiv({
213
+ id: `${swiperId}-wrapper`,
214
+ class: 'zMy-3'
215
+ });
216
+
217
+ // Label/title (using primitive)
218
+ if (label) {
219
+ const labelEl = createDiv({
220
+ class: 'zH4 zMb-2'
221
+ });
222
+ labelEl.textContent = label;
223
+ wrapper.appendChild(labelEl);
224
+ }
225
+
226
+ // Carousel container (using primitive + zTheme classes)
227
+ const carousel = createDiv({
228
+ id: swiperId,
229
+ class: `zCarousel ${variant === 'fade' ? 'zCarousel-fade' : ''} ${variant === 'vertical' ? 'zCarousel-vertical' : ''}`,
230
+ 'data-ride': 'false', // We control it manually
231
+ 'data-swiper-id': swiperId
232
+ });
233
+
234
+ // Carousel inner (slide container)
235
+ const carouselInner = createDiv({
236
+ class: 'zCarousel-inner'
237
+ });
238
+
239
+ // Create slides
240
+ slides.forEach((slideContent, index) => {
241
+ const slideItem = createDiv({
242
+ class: `zCarousel-item ${index === currentSlide ? 'zActive' : ''}`,
243
+ 'data-slide-index': index
244
+ });
245
+
246
+ // Slide content wrapper
247
+ const slideContentDiv = createDiv({
248
+ class: 'zP-5 zBg-light zText-center',
249
+ style: 'min-height: 200px; display: flex; align-items: center; justify-content: center;'
250
+ });
251
+ slideContentDiv.innerHTML = slideContent; // Allow HTML in slides
252
+
253
+ slideItem.appendChild(slideContentDiv);
254
+ carouselInner.appendChild(slideItem);
255
+ });
256
+
257
+ carousel.appendChild(carouselInner);
258
+
259
+ // Add controls (prev/next buttons)
260
+ if (showControls) {
261
+ const prevControl = this._createControl('prev', swiperId);
262
+ const nextControl = this._createControl('next', swiperId);
263
+ carousel.appendChild(prevControl);
264
+ carousel.appendChild(nextControl);
265
+ }
266
+
267
+ // Add indicators (dots)
268
+ if (showIndicators) {
269
+ const indicators = this._createIndicators(swiperId, slides.length, currentSlide);
270
+ carousel.appendChild(indicators);
271
+ }
272
+
273
+ wrapper.appendChild(carousel);
274
+ return wrapper;
275
+ }
276
+
277
+ /**
278
+ * Create prev/next control button
279
+ * @private
280
+ */
281
+ _createControl(direction, swiperId) {
282
+ const control = createButton('button', {
283
+ class: `zCarousel-control-${direction}`,
284
+ type: 'button',
285
+ 'data-swiper-id': swiperId,
286
+ 'data-direction': direction
287
+ });
288
+
289
+ // Icon span
290
+ const icon = createSpan({
291
+ class: `zCarousel-control-${direction}-icon`,
292
+ 'aria-hidden': 'true'
293
+ });
294
+
295
+ // Screen reader text
296
+ const srText = createSpan({
297
+ class: 'zVisually-hidden'
298
+ });
299
+ srText.textContent = direction === 'prev' ? 'Previous' : 'Next';
300
+
301
+ control.appendChild(icon);
302
+ control.appendChild(srText);
303
+
304
+ // Add click handler
305
+ control.addEventListener('click', () => {
306
+ if (direction === 'prev') {
307
+ this._prevSlide(swiperId);
308
+ } else {
309
+ this._nextSlide(swiperId);
310
+ }
311
+ });
312
+
313
+ return control;
314
+ }
315
+
316
+ /**
317
+ * Create indicators (dots)
318
+ * @private
319
+ */
320
+ _createIndicators(swiperId, totalSlides, currentSlide) {
321
+ const indicatorsList = createList(false, {
322
+ class: 'zCarousel-indicators'
323
+ });
324
+
325
+ for (let i = 0; i < totalSlides; i++) {
326
+ const indicator = createButton('button', {
327
+ type: 'button',
328
+ class: i === currentSlide ? 'zActive' : '',
329
+ 'data-swiper-id': swiperId,
330
+ 'data-slide-to': i,
331
+ 'aria-label': `Slide ${i + 1}`
332
+ });
333
+
334
+ indicator.addEventListener('click', () => {
335
+ this._goToSlide(swiperId, i);
336
+ });
337
+
338
+ indicatorsList.appendChild(indicator);
339
+ }
340
+
341
+ return indicatorsList;
342
+ }
343
+
344
+ /**
345
+ * Go to specific slide
346
+ * @private
347
+ */
348
+ _goToSlide(swiperId, targetIndex) {
349
+ const swiperData = this._activeSwipers.get(swiperId);
350
+ if (!swiperData) {
351
+ return;
352
+ }
353
+
354
+ const { element, currentSlide, totalSlides } = swiperData;
355
+
356
+ // Validate index
357
+ if (targetIndex < 0 || targetIndex >= totalSlides) {
358
+ return;
359
+ }
360
+ if (targetIndex === currentSlide) {
361
+ return;
362
+ }
363
+
364
+ // Find carousel element
365
+ const carousel = element.querySelector(`[data-swiper-id="${swiperId}"]`);
366
+ if (!carousel) {
367
+ return;
368
+ }
369
+
370
+ // Get all slides
371
+ const slides = carousel.querySelectorAll('.zCarousel-item');
372
+ const currentSlideEl = slides[currentSlide];
373
+ const targetSlideEl = slides[targetIndex];
374
+
375
+ if (!currentSlideEl || !targetSlideEl) {
376
+ return;
377
+ }
378
+
379
+ // Apply transition classes (zTheme CSS handles animations)
380
+ currentSlideEl.classList.remove('zActive');
381
+ targetSlideEl.classList.add('zActive');
382
+
383
+ // Update indicators
384
+ const indicators = carousel.querySelectorAll('.zCarousel-indicators button');
385
+ if (indicators.length > 0) {
386
+ indicators[currentSlide]?.classList.remove('zActive');
387
+ indicators[targetIndex]?.classList.add('zActive');
388
+ }
389
+
390
+ // Update swiper data
391
+ swiperData.currentSlide = targetIndex;
392
+
393
+ this.logger.log(`[SwiperRenderer] Moved to slide ${targetIndex + 1}/${totalSlides}`);
394
+ }
395
+
396
+ /**
397
+ * Go to previous slide
398
+ * @private
399
+ */
400
+ _prevSlide(swiperId) {
401
+ const swiperData = this._activeSwipers.get(swiperId);
402
+ if (!swiperData) {
403
+ return;
404
+ }
405
+
406
+ const { currentSlide, totalSlides, loop } = swiperData;
407
+ let targetIndex = currentSlide - 1;
408
+
409
+ if (targetIndex < 0) {
410
+ if (loop) {
411
+ targetIndex = totalSlides - 1; // Wrap to last slide
412
+ } else {
413
+ return; // Can't go before first slide
414
+ }
415
+ }
416
+
417
+ this._goToSlide(swiperId, targetIndex);
418
+ }
419
+
420
+ /**
421
+ * Go to next slide
422
+ * @private
423
+ */
424
+ _nextSlide(swiperId) {
425
+ const swiperData = this._activeSwipers.get(swiperId);
426
+ if (!swiperData) {
427
+ return;
428
+ }
429
+
430
+ const { currentSlide, totalSlides, loop } = swiperData;
431
+ let targetIndex = currentSlide + 1;
432
+
433
+ if (targetIndex >= totalSlides) {
434
+ if (loop) {
435
+ targetIndex = 0; // Wrap to first slide
436
+ } else {
437
+ return; // Can't go past last slide
438
+ }
439
+ }
440
+
441
+ this._goToSlide(swiperId, targetIndex);
442
+ }
443
+
444
+ /**
445
+ * Start auto-advance interval
446
+ * @private
447
+ */
448
+ _startAutoAdvance(swiperId) {
449
+ const swiperData = this._activeSwipers.get(swiperId);
450
+ if (!swiperData) {
451
+ return;
452
+ }
453
+
454
+ const { delay } = swiperData;
455
+
456
+ // Clear any existing interval
457
+ if (swiperData.intervalId) {
458
+ clearInterval(swiperData.intervalId);
459
+ }
460
+
461
+ // Start new interval
462
+ swiperData.intervalId = setInterval(() => {
463
+ this._nextSlide(swiperId);
464
+ }, delay * 1000); // delay is in seconds, convert to ms
465
+
466
+ this.logger.log(`[SwiperRenderer] Auto-advance started (${delay}s delay)`);
467
+ }
468
+
469
+ /**
470
+ * Stop auto-advance interval
471
+ * @private
472
+ */
473
+ _stopAutoAdvance(swiperId) {
474
+ const swiperData = this._activeSwipers.get(swiperId);
475
+ if (!swiperData) {
476
+ return;
477
+ }
478
+
479
+ if (swiperData.intervalId) {
480
+ clearInterval(swiperData.intervalId);
481
+ swiperData.intervalId = null;
482
+ this.logger.log('[SwiperRenderer] Auto-advance stopped');
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Add keyboard navigation (arrow keys, number keys)
488
+ * @private
489
+ */
490
+ _addKeyboardNav(swiperId) {
491
+ const swiperData = this._activeSwipers.get(swiperId);
492
+ if (!swiperData) {
493
+ return;
494
+ }
495
+
496
+ const keyHandler = (e) => {
497
+ // Only handle if this swiper is still active
498
+ if (!this._activeSwipers.has(swiperId)) {
499
+ document.removeEventListener('keydown', keyHandler);
500
+ return;
501
+ }
502
+
503
+ switch (e.key) {
504
+ case 'ArrowLeft':
505
+ e.preventDefault();
506
+ this._prevSlide(swiperId);
507
+ break;
508
+ case 'ArrowRight':
509
+ e.preventDefault();
510
+ this._nextSlide(swiperId);
511
+ break;
512
+ case '1':
513
+ case '2':
514
+ case '3':
515
+ case '4':
516
+ case '5':
517
+ case '6':
518
+ case '7':
519
+ case '8':
520
+ case '9': {
521
+ const targetIndex = parseInt(e.key, 10) - 1;
522
+ if (targetIndex < swiperData.totalSlides) {
523
+ this._goToSlide(swiperId, targetIndex);
524
+ }
525
+ break;
526
+ }
527
+ case 'p':
528
+ case 'P':
529
+ // Toggle pause
530
+ if (swiperData.intervalId) {
531
+ this._stopAutoAdvance(swiperId);
532
+ } else if (swiperData.autoAdvance) {
533
+ this._startAutoAdvance(swiperId);
534
+ }
535
+ break;
536
+ }
537
+ };
538
+
539
+ document.addEventListener('keydown', keyHandler);
540
+
541
+ // Store handler for cleanup
542
+ swiperData.keyHandler = keyHandler;
543
+ }
544
+
545
+ /**
546
+ * Get active swiper count
547
+ * @returns {number} Number of active swipers
548
+ */
549
+ getActiveCount() {
550
+ return this._activeSwipers.size;
551
+ }
552
+
553
+ /**
554
+ * Stop all active swipers (cleanup utility)
555
+ */
556
+ stopAll() {
557
+ this.logger.log('[SwiperRenderer] Stopping all swipers');
558
+
559
+ for (const [swiperId, _data] of this._activeSwipers) {
560
+ this.complete({ swiperId });
561
+ }
562
+ }
563
+ }
564
+