lisichatbot 1.3.5 → 1.3.7

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +335 -288
package/src/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Conversational Flow Builder
3
3
  * A simple, flexible conversational flow builder for multi-step chat interfaces
4
4
  * @version 1.0.0
5
- * WITH CUSTOM DATA ATTRIBUTES
5
+ * WITH CUSTOM DATA ATTRIBUTES + MULTI-SELECT-DROPDOWN
6
6
  */
7
7
 
8
8
  'use strict';
@@ -161,9 +161,6 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
161
161
  console.warn(' ⚠️ Edit icon element not found in bot-message-wrapper!');
162
162
  console.warn(' → Make sure you have: <span data-chat-element="bot-edit-icon">✏️</span>');
163
163
  }
164
-
165
- // REMOVED: No longer setting wrapper flex properties
166
- // User controls wrapper styling with their own CSS
167
164
  }
168
165
 
169
166
  // Make clone visible
@@ -173,21 +170,17 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
173
170
  elements.messages.appendChild(clone);
174
171
 
175
172
  // IMPORTANT: Set edit icon display AFTER appending to DOM
176
- // This ensures our styles override any CSS rules
177
173
  if (type === 'bot') {
178
174
  const editIconAfterAppend = clone.querySelector('[data-chat-element="bot-edit-icon"]');
179
175
  if (editIconAfterAppend) {
180
- // Force hide with !important AFTER append
181
176
  editIconAfterAppend.style.setProperty('display', 'none', 'important');
182
177
  editIconAfterAppend.style.setProperty('margin-left', '8px', 'important');
183
178
 
184
179
  const isLatest = clone.hasAttribute('data-chat-latest');
185
180
 
186
- // Only show if has input AND it's a previous step AND it's latest instance
187
181
  if (hasInput && stepNumber !== null && stepNumber < chatState.step && isLatest) {
188
182
  editIconAfterAppend.style.setProperty('display', 'flex', 'important');
189
183
 
190
- // Debug: Check spacing
191
184
  setTimeout(() => {
192
185
  const computedMargin = window.getComputedStyle(editIconAfterAppend).marginLeft;
193
186
  const computedDisplay = window.getComputedStyle(editIconAfterAppend).display;
@@ -205,7 +198,6 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
205
198
  // =============================================================================
206
199
 
207
200
  function updateEditIcons() {
208
- // Find all rendered bot message wrappers
209
201
  const allBotMessages = elements.messages.querySelectorAll('[data-chat-element="bot-message-wrapper"]');
210
202
 
211
203
  console.log(`\n🔄 Updating edit icons - Current step: ${chatState.step}, Completed: ${chatState.completed}`);
@@ -216,20 +208,16 @@ function updateEditIcons() {
216
208
  const isLatest = wrapper.hasAttribute('data-chat-latest');
217
209
 
218
210
  if (editIcon && !isNaN(stepNumber)) {
219
- // Check if this step has input by looking at the flow
220
211
  const stepData = flowData.flow[stepNumber];
221
212
  const hasInput = stepData && !!stepData.input;
222
213
 
223
- // Hide ALL icons if flow is completed
224
214
  if (chatState.completed) {
225
215
  editIcon.style.setProperty('display', 'none', 'important');
226
216
  console.log(` 🏁 Step ${stepNumber}: Icon HIDDEN (flow completed)`);
227
217
  return;
228
218
  }
229
219
 
230
- // Only show icon if: has input AND is previous step AND is latest instance
231
220
  if (hasInput && stepNumber < chatState.step && isLatest) {
232
- // Set onclick handler
233
221
  editIcon.onclick = (e) => {
234
222
  e.stopPropagation();
235
223
  e.preventDefault();
@@ -264,10 +252,7 @@ function updateEditIcons() {
264
252
  function renderOptions(options, field, isSingleSelect = true) {
265
253
  if (!elements.messages) return;
266
254
 
267
- // Determine input type attribute value
268
255
  const inputTypeAttr = isSingleSelect ? 'single-select-input' : 'multi-select-input';
269
-
270
- // Find existing option element in HTML by data-chat-element
271
256
  const optionSelector = `[data-chat-element="${inputTypeAttr}"]`;
272
257
  const existingOption = document.querySelector(optionSelector);
273
258
 
@@ -276,44 +261,34 @@ function renderOptions(options, field, isSingleSelect = true) {
276
261
  return;
277
262
  }
278
263
 
279
- // Get existing data for this field (for pre-filling when editing)
280
264
  const existingData = chatState.data[field];
281
265
  console.log(`📝 Pre-filling ${field}:`, existingData);
282
266
 
283
- // Create wrapper to hold all options
284
267
  const optionsWrapper = document.createElement('div');
285
268
  optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
286
269
  optionsWrapper.style.display = 'flex';
287
270
  optionsWrapper.style.flexDirection = 'column';
288
- optionsWrapper.style.alignItems = 'flex-start'; // Don't stretch options
271
+ optionsWrapper.style.alignItems = 'flex-start';
289
272
  optionsWrapper.style.gap = '8px';
290
273
 
291
- // Clone and fill option element for each option
292
274
  options.forEach((option, index) => {
293
275
  const optionName = option.name || option;
294
276
  const optionValue = option.value !== undefined ? option.value : option;
295
277
  const valueStr = typeof optionValue === 'object' ?
296
278
  JSON.stringify(optionValue) : String(optionValue);
297
279
 
298
- // Clone existing option element
299
280
  const clone = existingOption.cloneNode(true);
300
-
301
- // Make clone visible (remove display:none from template)
302
281
  clone.style.display = '';
303
282
 
304
- // Check if this option should be pre-selected
305
283
  let shouldBeChecked = false;
306
284
  if (isSingleSelect) {
307
- // For single-select, check if value matches existing data
308
285
  shouldBeChecked = existingData !== undefined &&
309
286
  JSON.stringify(existingData) === JSON.stringify(optionValue);
310
287
  } else {
311
- // For multi-select, check if value is in array
312
288
  shouldBeChecked = Array.isArray(existingData) &&
313
289
  existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
314
290
  }
315
291
 
316
- // Set checked state
317
292
  if (shouldBeChecked) {
318
293
  clone.classList.add('cf-checked');
319
294
  clone.style.backgroundColor = config.selectedBackground;
@@ -323,56 +298,46 @@ function renderOptions(options, field, isSingleSelect = true) {
323
298
  clone.style.backgroundColor = 'transparent';
324
299
  }
325
300
 
326
- // Set data attributes on container
327
301
  clone.setAttribute('data-chat-element', inputTypeAttr);
328
302
  clone.setAttribute('data-field', field);
329
303
  clone.setAttribute('data-value', valueStr);
330
304
  clone.setAttribute('data-name', optionName);
331
305
  clone.setAttribute('data-index', index);
332
306
 
333
- // Find and set input
334
307
  const input = clone.querySelector('[data-chat-input-element="input"]');
335
308
  if (input) {
336
309
  input.setAttribute('data-chat-element', inputTypeAttr);
337
310
  input.name = field;
338
311
  input.value = valueStr;
339
- input.checked = shouldBeChecked; // Pre-check if needed
340
- input.onclick = (e) => e.stopPropagation(); // Prevent click bubbling
312
+ input.checked = shouldBeChecked;
313
+ input.onclick = (e) => e.stopPropagation();
341
314
  }
342
315
 
343
- // Find and set tick icon visibility
344
316
  const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
345
317
  if (tickIcon) {
346
318
  tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
347
319
  }
348
320
 
349
- // Find and set text
350
321
  const textElement = clone.querySelector('[data-chat-input-element="text"]');
351
322
  if (textElement) {
352
323
  textElement.textContent = optionName;
353
- textElement.style.display = ''; // Make sure text is visible
324
+ textElement.style.display = '';
354
325
  } else {
355
326
  console.error('Text element not found in option');
356
327
  }
357
328
 
358
- // Append to wrapper
359
329
  optionsWrapper.appendChild(clone);
360
330
  });
361
331
 
362
- // Append wrapper to messages
363
332
  elements.messages.appendChild(optionsWrapper);
364
333
  scrollToBottom();
365
334
 
366
- // Add click handlers with proper event handling
367
335
  const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
368
336
  optionElements.forEach(el => {
369
- // Clear any existing onclick
370
337
  el.onclick = null;
371
-
372
- // Add new click handler with stopPropagation
373
338
  el.onclick = (e) => {
374
- e.stopPropagation(); // Prevent bubbling
375
- e.preventDefault(); // Prevent default behavior
339
+ e.stopPropagation();
340
+ e.preventDefault();
376
341
  handleOptionClick(el, field, isSingleSelect);
377
342
  };
378
343
  });
@@ -385,7 +350,6 @@ function renderOptions(options, field, isSingleSelect = true) {
385
350
  function renderColorOptions(options, field) {
386
351
  if (!elements.messages) return;
387
352
 
388
- // Find existing color option element in HTML by data-chat-element
389
353
  const optionSelector = '[data-chat-element="multi-select-color"]';
390
354
  const existingOption = document.querySelector(optionSelector);
391
355
 
@@ -394,37 +358,29 @@ function renderColorOptions(options, field) {
394
358
  return;
395
359
  }
396
360
 
397
- // Get existing data for this field (for pre-filling when editing)
398
361
  const existingData = chatState.data[field];
399
362
  console.log(`📝 Pre-filling ${field} (color):`, existingData);
400
363
 
401
- // Create wrapper to hold all options
402
364
  const optionsWrapper = document.createElement('div');
403
365
  optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
404
366
  optionsWrapper.style.display = 'flex';
405
367
  optionsWrapper.style.flexDirection = 'column';
406
- optionsWrapper.style.alignItems = 'flex-start'; // Don't stretch options
368
+ optionsWrapper.style.alignItems = 'flex-start';
407
369
  optionsWrapper.style.gap = '8px';
408
370
 
409
- // Clone and fill option element for each option
410
371
  options.forEach((option, index) => {
411
372
  const optionName = option.name || option;
412
373
  const optionValue = option.value !== undefined ? option.value : option;
413
- const optionColor = option.color || '#cccccc'; // Default gray if no color
374
+ const optionColor = option.color || '#cccccc';
414
375
  const valueStr = typeof optionValue === 'object' ?
415
376
  JSON.stringify(optionValue) : String(optionValue);
416
377
 
417
- // Clone existing option element
418
378
  const clone = existingOption.cloneNode(true);
419
-
420
- // Make clone visible (remove display:none from template)
421
379
  clone.style.display = '';
422
380
 
423
- // Check if this option should be pre-selected (multi-select behavior)
424
381
  const shouldBeChecked = Array.isArray(existingData) &&
425
382
  existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
426
383
 
427
- // Set checked state
428
384
  if (shouldBeChecked) {
429
385
  clone.classList.add('cf-checked');
430
386
  clone.style.backgroundColor = config.selectedBackground;
@@ -434,7 +390,6 @@ function renderColorOptions(options, field) {
434
390
  clone.style.backgroundColor = 'transparent';
435
391
  }
436
392
 
437
- // Set data attributes on container
438
393
  clone.setAttribute('data-chat-element', 'multi-select-color');
439
394
  clone.setAttribute('data-field', field);
440
395
  clone.setAttribute('data-value', valueStr);
@@ -442,64 +397,321 @@ function renderColorOptions(options, field) {
442
397
  clone.setAttribute('data-index', index);
443
398
  clone.setAttribute('data-color', optionColor);
444
399
 
445
- // Find and set input
446
400
  const input = clone.querySelector('[data-chat-input-element="input"]');
447
401
  if (input) {
448
402
  input.setAttribute('data-chat-element', 'multi-select-color');
449
403
  input.name = field;
450
404
  input.value = valueStr;
451
- input.checked = shouldBeChecked; // Pre-check if needed
452
- input.onclick = (e) => e.stopPropagation(); // Prevent click bubbling
405
+ input.checked = shouldBeChecked;
406
+ input.onclick = (e) => e.stopPropagation();
453
407
  }
454
408
 
455
- // Find and set tick icon visibility
456
409
  const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
457
410
  if (tickIcon) {
458
411
  tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
459
412
  }
460
413
 
461
- // Find and set color block
462
414
  const colorBlock = clone.querySelector('[data-chat-input-element="color-block"]');
463
415
  if (colorBlock) {
464
416
  colorBlock.style.backgroundColor = optionColor;
465
- colorBlock.style.display = ''; // Make sure it's visible
417
+ colorBlock.style.display = '';
466
418
  } else {
467
419
  console.warn('Color block element not found in multi-select-color template');
468
420
  }
469
421
 
470
- // Find and set text
471
422
  const textElement = clone.querySelector('[data-chat-input-element="text"]');
472
423
  if (textElement) {
473
424
  textElement.textContent = optionName;
474
- textElement.style.display = ''; // Make sure text is visible
425
+ textElement.style.display = '';
475
426
  } else {
476
427
  console.error('Text element not found in option');
477
428
  }
478
429
 
479
- // Append to wrapper
480
430
  optionsWrapper.appendChild(clone);
481
431
  });
482
432
 
483
- // Append wrapper to messages
484
433
  elements.messages.appendChild(optionsWrapper);
485
434
  scrollToBottom();
486
435
 
487
- // Add click handlers with proper event handling
488
436
  const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
489
437
  optionElements.forEach(el => {
490
- // Clear any existing onclick
491
438
  el.onclick = null;
492
-
493
- // Add new click handler with stopPropagation
494
439
  el.onclick = (e) => {
495
- e.stopPropagation(); // Prevent bubbling
496
- e.preventDefault(); // Prevent default behavior
497
- // Use multi-select behavior (isSingleSelect = false)
440
+ e.stopPropagation();
441
+ e.preventDefault();
498
442
  handleOptionClick(el, field, false);
499
443
  };
500
444
  });
501
445
  }
502
446
 
447
+ // =============================================================================
448
+ // MULTI-SELECT DROPDOWN RENDERING
449
+ // =============================================================================
450
+
451
+ function renderMultiSelectDropdown(options, field) {
452
+ if (!elements.messages) return;
453
+
454
+ console.log(`\n🎨 renderMultiSelectDropdown for field: ${field}`);
455
+ console.log(`Options:`, options);
456
+
457
+ // Find template
458
+ const template = document.querySelector('[data-chat-element="multi-select-dropdown"]');
459
+ if (!template) {
460
+ console.error('multi-select-dropdown template not found');
461
+ return;
462
+ }
463
+
464
+ // Clone template
465
+ const clone = template.cloneNode(true);
466
+ clone.style.display = '';
467
+ clone.setAttribute('data-field', field);
468
+
469
+ // Get all elements
470
+ const dropdown = clone.querySelector('[data-chat-input-element="dropdown"]');
471
+ const optionsWrapper = clone.querySelector('[data-chat-input-element="dropdown-options-wrapper"]');
472
+ const searchBar = clone.querySelector('[data-chat-input-element="search-bar"]');
473
+ const optionsContainer = clone.querySelector('[data-chat-input-element="dropdown-options-container"]');
474
+ const optionTemplate = clone.querySelector('[data-chat-input-element="dropdown-option-wrapper"]');
475
+ const tagsContainer = clone.querySelector('[data-chat-input-element="tags-container"]');
476
+ const tagTemplate = clone.querySelector('[data-chat-input-element="tag-wrapper"]');
477
+
478
+ if (!dropdown || !optionsWrapper || !searchBar || !optionsContainer || !optionTemplate || !tagsContainer || !tagTemplate) {
479
+ console.error('Multi-select dropdown: Missing required elements');
480
+ return;
481
+ }
482
+
483
+ // Store original template option and tag, then remove from DOM
484
+ const optionTemplateClone = optionTemplate.cloneNode(true);
485
+ const tagTemplateClone = tagTemplate.cloneNode(true);
486
+ optionTemplate.remove();
487
+ tagTemplate.remove();
488
+
489
+ // Get existing selections for pre-filling
490
+ const existingData = chatState.data[field] || [];
491
+ const selectedValues = new Set(existingData);
492
+ console.log(`📝 Pre-filling selections:`, Array.from(selectedValues));
493
+
494
+ // Store all options for filtering
495
+ const allOptions = [];
496
+
497
+ // Render options
498
+ options.forEach(option => {
499
+ const optionEl = optionTemplateClone.cloneNode(true);
500
+ optionEl.style.display = '';
501
+ optionEl.setAttribute('data-value', JSON.stringify(option.value));
502
+ optionEl.setAttribute('data-name', option.name);
503
+
504
+ const nameEl = optionEl.querySelector('[data-chat-input-element="dropdown-option-name"]');
505
+ const tickIcon = optionEl.querySelector('[data-chat-input-element="dropdown-option-tick-icon"]');
506
+
507
+ if (nameEl) nameEl.textContent = option.name;
508
+
509
+ // Check if pre-selected
510
+ const isSelected = selectedValues.has(option.value);
511
+ if (isSelected) {
512
+ optionEl.style.backgroundColor = config.selectedBackground;
513
+ if (tickIcon) tickIcon.style.display = '';
514
+ console.log(` ✅ Pre-selected: ${option.name}`);
515
+ }
516
+
517
+ // Click handler - prevents dropdown from closing
518
+ optionEl.onclick = (e) => {
519
+ e.stopPropagation();
520
+ toggleOption(option, optionEl, tickIcon, field);
521
+ };
522
+
523
+ optionsContainer.appendChild(optionEl);
524
+ allOptions.push({ element: optionEl, option });
525
+ });
526
+
527
+ // Toggle dropdown visibility - click to open/close
528
+ dropdown.onclick = (e) => {
529
+ e.stopPropagation();
530
+ const isVisible = optionsWrapper.style.display !== 'none';
531
+
532
+ if (isVisible) {
533
+ optionsWrapper.style.display = 'none';
534
+ console.log(' 🙈 Dropdown closed');
535
+ } else {
536
+ optionsWrapper.style.display = '';
537
+ console.log(' 👁️ Dropdown opened');
538
+
539
+ setTimeout(() => {
540
+ searchBar.focus();
541
+ }, 100);
542
+
543
+ searchBar.value = '';
544
+ allOptions.forEach(({ element }) => {
545
+ element.style.display = '';
546
+ });
547
+ }
548
+ };
549
+
550
+ // Close dropdown when clicking outside
551
+ const closeDropdownHandler = (e) => {
552
+ if (!clone.contains(e.target)) {
553
+ if (optionsWrapper.style.display !== 'none') {
554
+ optionsWrapper.style.display = 'none';
555
+ console.log(' 🙈 Dropdown closed (clicked outside)');
556
+ }
557
+ }
558
+ };
559
+
560
+ document.addEventListener('click', closeDropdownHandler);
561
+ clone.dataset.closeHandler = 'attached';
562
+
563
+ // Search functionality - filters options by name or value
564
+ searchBar.oninput = (e) => {
565
+ const searchTerm = e.target.value.toLowerCase();
566
+ console.log(`🔍 Searching for: "${searchTerm}"`);
567
+
568
+ allOptions.forEach(({ element, option }) => {
569
+ const matchesName = option.name.toLowerCase().includes(searchTerm);
570
+ const matchesValue = String(option.value).toLowerCase().includes(searchTerm);
571
+ const matches = matchesName || matchesValue;
572
+
573
+ element.style.display = matches ? '' : 'none';
574
+ });
575
+ };
576
+
577
+ // Prevent search bar clicks from closing dropdown
578
+ searchBar.onclick = (e) => {
579
+ e.stopPropagation();
580
+ };
581
+
582
+ // Toggle option selection
583
+ function toggleOption(option, optionEl, tickIcon, field) {
584
+ const currentSelections = chatState.data[field] || [];
585
+ const isCurrentlySelected = currentSelections.includes(option.value);
586
+
587
+ console.log(`🎯 Toggle option: ${option.name}`, { isCurrentlySelected });
588
+
589
+ if (isCurrentlySelected) {
590
+ // Deselect
591
+ chatState.data[field] = currentSelections.filter(v => v !== option.value);
592
+ optionEl.style.backgroundColor = '';
593
+ if (tickIcon) tickIcon.style.display = 'none';
594
+ console.log(` ❌ Deselected: ${option.name}`);
595
+ } else {
596
+ // Select
597
+ chatState.data[field] = [...currentSelections, option.value];
598
+ optionEl.style.backgroundColor = config.selectedBackground;
599
+ if (tickIcon) tickIcon.style.display = '';
600
+ console.log(` ✅ Selected: ${option.name}`);
601
+ }
602
+
603
+ // Update current selection for handleNext
604
+ chatState.currentSelection = {
605
+ field,
606
+ value: chatState.data[field],
607
+ name: chatState.data[field].map(v => {
608
+ const opt = options.find(o => o.value === v);
609
+ return opt ? opt.name : v;
610
+ }).join(', ')
611
+ };
612
+
613
+ console.log(` 📊 Current selections:`, chatState.data[field]);
614
+
615
+ // Re-render tags
616
+ renderTags(field, options, tagsContainer, tagTemplateClone, optionsContainer);
617
+
618
+ // Enable Next button if at least one selected
619
+ if (chatState.data[field].length > 0) {
620
+ enableNextButton();
621
+ } else {
622
+ disableNextButton();
623
+ }
624
+ }
625
+
626
+ // Render tags - displays selected options as removable tags
627
+ function renderTags(field, options, tagsContainer, tagTemplate, optionsContainer) {
628
+ // Clear existing tags (except template)
629
+ const existingTags = tagsContainer.querySelectorAll('[data-chat-input-element="tag-wrapper"]');
630
+ existingTags.forEach(tag => {
631
+ if (tag.style.display !== 'none') {
632
+ tag.remove();
633
+ }
634
+ });
635
+
636
+ const selections = chatState.data[field] || [];
637
+ console.log(`🏷️ Rendering ${selections.length} tags`);
638
+
639
+ selections.forEach(value => {
640
+ const option = options.find(o => o.value === value);
641
+ if (!option) return;
642
+
643
+ const tagEl = tagTemplate.cloneNode(true);
644
+ tagEl.style.display = '';
645
+ tagEl.setAttribute('data-value', JSON.stringify(value));
646
+
647
+ const textEl = tagEl.querySelector('[data-chat-input-element="tag-text"]');
648
+ const crossIcon = tagEl.querySelector('[data-chat-input-element="tag-cross-icon"]');
649
+
650
+ if (textEl) {
651
+ textEl.textContent = option.name;
652
+ }
653
+
654
+ // ✅ CRITICAL: Set cross icon display to BLOCK and handle click
655
+ if (crossIcon) {
656
+ // Set display to block (as requested by user)
657
+ crossIcon.style.display = 'block';
658
+ console.log(` ✅ Cross icon visible for: ${option.name}`);
659
+
660
+ // Handle click to remove tag
661
+ crossIcon.onclick = (e) => {
662
+ e.stopPropagation();
663
+ console.log(`🗑️ Removing tag: ${option.name}`);
664
+
665
+ // Remove from selections array
666
+ chatState.data[field] = chatState.data[field].filter(v => v !== value);
667
+
668
+ // Update option UI in dropdown - remove background and tick
669
+ const optionEl = optionsContainer.querySelector(`[data-value='${JSON.stringify(value)}']`);
670
+ if (optionEl) {
671
+ optionEl.style.backgroundColor = '';
672
+ const tickIcon = optionEl.querySelector('[data-chat-input-element="dropdown-option-tick-icon"]');
673
+ if (tickIcon) tickIcon.style.display = 'none';
674
+ }
675
+
676
+ // Remove tag from DOM
677
+ tagEl.remove();
678
+
679
+ // Update current selection
680
+ chatState.currentSelection = {
681
+ field,
682
+ value: chatState.data[field],
683
+ name: chatState.data[field].map(v => {
684
+ const opt = options.find(o => o.value === v);
685
+ return opt ? opt.name : v;
686
+ }).join(', ')
687
+ };
688
+
689
+ console.log(` 📊 Remaining selections:`, chatState.data[field]);
690
+
691
+ // Disable Next button if no selections remain
692
+ if (chatState.data[field].length === 0) {
693
+ disableNextButton();
694
+ }
695
+ };
696
+ }
697
+
698
+ tagsContainer.appendChild(tagEl);
699
+ });
700
+ }
701
+
702
+ // Initial tag render if pre-filled
703
+ if (selectedValues.size > 0) {
704
+ renderTags(field, options, tagsContainer, tagTemplateClone, optionsContainer);
705
+ enableNextButton();
706
+ }
707
+
708
+ // Append to messages
709
+ elements.messages.appendChild(clone);
710
+ scrollToBottom();
711
+
712
+ console.log(`✅ Multi-select dropdown rendered for ${field}`);
713
+ }
714
+
503
715
  // =============================================================================
504
716
  // SINGLE-SELECT-CUSTOM (WITH MIN/MAX RANGE)
505
717
  // =============================================================================
@@ -507,7 +719,6 @@ function renderColorOptions(options, field) {
507
719
  function renderCustomSelectOptions(options, field, customConfig) {
508
720
  if (!elements.messages) return;
509
721
 
510
- // Find existing single-select option element
511
722
  const optionSelector = '[data-chat-element="single-select-input"]';
512
723
  const existingOption = document.querySelector(optionSelector);
513
724
 
@@ -516,16 +727,14 @@ function renderCustomSelectOptions(options, field, customConfig) {
516
727
  return;
517
728
  }
518
729
 
519
- // Get existing data for pre-filling
520
730
  const existingData = chatState.data[field];
521
731
  console.log(`📝 Pre-filling ${field} (custom):`, existingData);
522
732
 
523
- // Create wrapper to hold all options
524
733
  const optionsWrapper = document.createElement('div');
525
734
  optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
526
735
  optionsWrapper.style.display = 'flex';
527
736
  optionsWrapper.style.flexDirection = 'column';
528
- optionsWrapper.style.alignItems = 'flex-start'; // Don't stretch options
737
+ optionsWrapper.style.alignItems = 'flex-start';
529
738
  optionsWrapper.style.gap = '8px';
530
739
 
531
740
  // Render regular options
@@ -538,9 +747,8 @@ function renderCustomSelectOptions(options, field, customConfig) {
538
747
  const clone = existingOption.cloneNode(true);
539
748
  clone.style.display = '';
540
749
 
541
- // Check if this option should be pre-selected
542
750
  const shouldBeChecked = existingData !== undefined &&
543
- !Array.isArray(existingData) && // Exclude array (custom range)
751
+ !Array.isArray(existingData) &&
544
752
  JSON.stringify(existingData) === JSON.stringify(optionValue);
545
753
 
546
754
  if (shouldBeChecked) {
@@ -584,7 +792,6 @@ function renderCustomSelectOptions(options, field, customConfig) {
584
792
  const customClone = existingOption.cloneNode(true);
585
793
  customClone.style.display = '';
586
794
 
587
- // Check if custom was selected before (data is array [min, max])
588
795
  const isCustomSelected = existingData !== undefined &&
589
796
  Array.isArray(existingData) &&
590
797
  existingData.length === 2;
@@ -624,15 +831,10 @@ function renderCustomSelectOptions(options, field, customConfig) {
624
831
  optionsWrapper.appendChild(customClone);
625
832
  }
626
833
 
627
- // Append wrapper to messages
628
834
  elements.messages.appendChild(optionsWrapper);
629
-
630
- // Render min/max inputs
631
835
  renderMinMaxInputs(field, customConfig, existingData);
632
-
633
836
  scrollToBottom();
634
837
 
635
- // Add click handlers for regular and custom options
636
838
  const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
637
839
  optionElements.forEach(el => {
638
840
  el.onclick = (e) => {
@@ -646,7 +848,6 @@ function renderCustomSelectOptions(options, field, customConfig) {
646
848
  function renderMinMaxInputs(field, customConfig, existingData) {
647
849
  if (!elements.messages || !customConfig) return;
648
850
 
649
- // Find min/max input templates
650
851
  const minSelector = '[data-chat-element="single-select-custom-min"]';
651
852
  const maxSelector = '[data-chat-element="single-select-custom-max"]';
652
853
  const minTemplate = document.querySelector(minSelector);
@@ -657,37 +858,32 @@ function renderMinMaxInputs(field, customConfig, existingData) {
657
858
  return;
658
859
  }
659
860
 
660
- // Remove existing rangeWrapper if it exists (for edit flow)
661
861
  const existingRangeWrapper = elements.messages.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
662
862
  if (existingRangeWrapper) {
663
863
  console.log(' 🗑️ Removing existing rangeWrapper for re-render');
664
864
  existingRangeWrapper.remove();
665
865
  }
666
866
 
667
- // Create wrapper for min/max
668
867
  const rangeWrapper = document.createElement('div');
669
868
  rangeWrapper.setAttribute('data-chat-element', 'range-wrapper');
670
869
  rangeWrapper.setAttribute('data-field', field);
671
- rangeWrapper.style.marginBottom = '16px'; // Add space below wrapper
870
+ rangeWrapper.style.marginBottom = '16px';
672
871
  rangeWrapper.style.display = 'flex';
673
- rangeWrapper.style.gap = '16px'; // Gap between min and max
872
+ rangeWrapper.style.gap = '16px';
674
873
  rangeWrapper.style.flexWrap = 'wrap';
675
874
 
676
- // Check if should show min/max (custom option selected)
677
- // Data is stored as array [min, max]
678
875
  const showMinMax = existingData !== undefined &&
679
876
  Array.isArray(existingData) &&
680
877
  existingData.length === 2;
681
878
 
682
- rangeWrapper.style.display = showMinMax ? 'flex' : 'none'; // Keep flex when visible!
879
+ rangeWrapper.style.display = showMinMax ? 'flex' : 'none';
683
880
 
684
- // Clone and setup min input
685
881
  const minClone = minTemplate.cloneNode(true);
686
882
  minClone.style.display = '';
687
- minClone.style.marginRight = '16px'; // Horizontal gap
688
- minClone.style.marginBottom = '12px'; // Bottom margin if wraps
689
- minClone.style.width = 'auto'; // Don't stretch
690
- minClone.style.flex = 'none'; // Don't grow or shrink
883
+ minClone.style.marginRight = '16px';
884
+ minClone.style.marginBottom = '12px';
885
+ minClone.style.width = 'auto';
886
+ minClone.style.flex = 'none';
691
887
  minClone.setAttribute('data-field', field);
692
888
 
693
889
  const minInput = minClone.querySelector('[data-chat-input-element="input"]');
@@ -698,19 +894,17 @@ function renderMinMaxInputs(field, customConfig, existingData) {
698
894
  minInput.min = customConfig.min !== undefined ? customConfig.min : 0;
699
895
  minInput.max = customConfig.max !== undefined ? customConfig.max : 100;
700
896
  minInput.value = showMinMax && existingData[0] !== undefined ? existingData[0] : '';
701
- // Removed placeholder
702
897
 
703
898
  if (showMinMax) {
704
899
  console.log(` ✅ Pre-filled min: ${existingData[0]}`);
705
900
  }
706
901
  }
707
902
 
708
- // Clone and setup max input
709
903
  const maxClone = maxTemplate.cloneNode(true);
710
904
  maxClone.style.display = '';
711
- maxClone.style.marginBottom = '12px'; // Bottom margin if wraps
712
- maxClone.style.width = 'auto'; // Don't stretch
713
- maxClone.style.flex = 'none'; // Don't grow or shrink
905
+ maxClone.style.marginBottom = '12px';
906
+ maxClone.style.width = 'auto';
907
+ maxClone.style.flex = 'none';
714
908
  maxClone.setAttribute('data-field', field);
715
909
 
716
910
  const maxInput = maxClone.querySelector('[data-chat-input-element="input"]');
@@ -721,7 +915,6 @@ function renderMinMaxInputs(field, customConfig, existingData) {
721
915
  maxInput.min = customConfig.min !== undefined ? customConfig.min : 0;
722
916
  maxInput.max = customConfig.max !== undefined ? customConfig.max : 100;
723
917
  maxInput.value = showMinMax && existingData[1] !== undefined ? existingData[1] : '';
724
- // Removed placeholder
725
918
 
726
919
  if (showMinMax) {
727
920
  console.log(` ✅ Pre-filled max: ${existingData[1]}`);
@@ -731,7 +924,6 @@ function renderMinMaxInputs(field, customConfig, existingData) {
731
924
  rangeWrapper.appendChild(minClone);
732
925
  rangeWrapper.appendChild(maxClone);
733
926
 
734
- // Add error message display
735
927
  const errorDiv = document.createElement('div');
736
928
  errorDiv.setAttribute('data-chat-element', 'range-error');
737
929
  errorDiv.setAttribute('data-field', field);
@@ -743,18 +935,15 @@ function renderMinMaxInputs(field, customConfig, existingData) {
743
935
 
744
936
  elements.messages.appendChild(rangeWrapper);
745
937
 
746
- // Add handlers for both inputs
747
938
  if (minInput && maxInput) {
748
939
  const updateVisualFeedback = () => {
749
940
  const minFilled = minInput.value.trim() !== '';
750
941
  const maxFilled = maxInput.value.trim() !== '';
751
942
  const anyFilled = minFilled || maxFilled;
752
943
 
753
- // Get tick icons
754
944
  const minTick = minClone.querySelector('[data-chat-input-element="tick-icon"]');
755
945
  const maxTick = maxClone.querySelector('[data-chat-input-element="tick-icon"]');
756
946
 
757
- // Update background and tick for BOTH inputs
758
947
  if (anyFilled) {
759
948
  minClone.style.backgroundColor = config.selectedBackground;
760
949
  maxClone.style.backgroundColor = config.selectedBackground;
@@ -768,10 +957,8 @@ function renderMinMaxInputs(field, customConfig, existingData) {
768
957
  }
769
958
  };
770
959
 
771
- // Apply visual feedback immediately if pre-filled
772
960
  if (showMinMax) {
773
961
  updateVisualFeedback();
774
- // Also run validation to set currentSelection
775
962
  validateMinMax(field, customConfig);
776
963
  console.log(' ✅ Applied visual feedback for pre-filled values');
777
964
  }
@@ -797,18 +984,15 @@ function renderMinMaxInputs(field, customConfig, existingData) {
797
984
  }
798
985
 
799
986
  function selectCustomOption(field) {
800
- // Deselect all regular options
801
987
  const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
802
988
  allOptions.forEach(opt => {
803
989
  const isCustom = opt.getAttribute('data-is-custom') === 'true';
804
990
  if (isCustom) {
805
- // Select custom option
806
991
  opt.classList.add('cf-checked');
807
992
  opt.style.setProperty('background-color', config.selectedBackground, 'important');
808
993
  const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
809
994
  if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
810
995
  } else {
811
- // Deselect regular options
812
996
  opt.classList.remove('cf-checked');
813
997
  opt.style.setProperty('background-color', 'transparent', 'important');
814
998
  const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
@@ -816,10 +1000,9 @@ function selectCustomOption(field) {
816
1000
  }
817
1001
  });
818
1002
 
819
- // Show min/max inputs
820
1003
  const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
821
1004
  if (rangeWrapper) {
822
- rangeWrapper.style.display = 'flex'; // Keep flex!
1005
+ rangeWrapper.style.display = 'flex';
823
1006
  }
824
1007
  }
825
1008
 
@@ -830,7 +1013,6 @@ function handleCustomSelectClick(element, field, customConfig) {
830
1013
 
831
1014
  console.log(`Custom-select clicked:`, { field, name: optionName, isCustom });
832
1015
 
833
- // Deselect all options first
834
1016
  const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
835
1017
  allOptions.forEach(opt => {
836
1018
  opt.classList.remove('cf-checked');
@@ -839,37 +1021,25 @@ function handleCustomSelectClick(element, field, customConfig) {
839
1021
  if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
840
1022
  });
841
1023
 
842
- // Select this option
843
1024
  element.classList.add('cf-checked');
844
1025
  element.style.setProperty('background-color', config.selectedBackground, 'important');
845
1026
  const tickIcon = element.querySelector('[data-chat-input-element="tick-icon"]');
846
1027
  if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
847
1028
 
848
1029
  if (isCustom) {
849
- // Show min/max inputs
850
1030
  const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
851
- console.log(' 📦 Looking for rangeWrapper:', `[data-chat-element="range-wrapper"][data-field="${field}"]`);
852
- console.log(' 📦 Found rangeWrapper:', rangeWrapper);
853
1031
 
854
1032
  if (rangeWrapper) {
855
- console.log(' 📦 Current display:', rangeWrapper.style.display);
856
- rangeWrapper.style.display = 'flex'; // Keep flex!
857
- console.log(' ✅ Set rangeWrapper display to flex');
858
- console.log(' 📦 New display:', rangeWrapper.style.display);
859
- } else {
860
- console.log(' ❌ rangeWrapper not found!');
1033
+ rangeWrapper.style.display = 'flex';
861
1034
  }
862
1035
 
863
- // Don't save data yet - wait for validation
864
1036
  chatState.currentSelection = null;
865
1037
  console.log(' ℹ️ Custom selected - waiting for min/max input');
866
1038
  } else {
867
- // Hide min/max inputs and clear them
868
1039
  const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
869
1040
  if (rangeWrapper) {
870
1041
  rangeWrapper.style.display = 'none';
871
1042
 
872
- // Clear input values - fixed selectors
873
1043
  const minInput = document.querySelector(`[data-field="${field}"][data-input-type="min"][data-chat-input-element="input"]`);
874
1044
  const maxInput = document.querySelector(`[data-field="${field}"][data-input-type="max"][data-chat-input-element="input"]`);
875
1045
 
@@ -882,7 +1052,6 @@ function handleCustomSelectClick(element, field, customConfig) {
882
1052
  console.log(' 🧹 Cleared max input');
883
1053
  }
884
1054
 
885
- // Reset visual feedback (background and ticks)
886
1055
  const minContainer = rangeWrapper.querySelector('[data-chat-element="single-select-custom-min"]');
887
1056
  const maxContainer = rangeWrapper.querySelector('[data-chat-element="single-select-custom-max"]');
888
1057
 
@@ -898,14 +1067,12 @@ function handleCustomSelectClick(element, field, customConfig) {
898
1067
  if (maxTick) maxTick.style.display = 'none';
899
1068
  }
900
1069
 
901
- // Hide error message
902
1070
  const errorDiv = rangeWrapper.querySelector('[data-chat-element="range-error"]');
903
1071
  if (errorDiv) errorDiv.style.display = 'none';
904
1072
 
905
1073
  console.log(' 🧹 Min/max inputs cleared and hidden');
906
1074
  }
907
1075
 
908
- // Parse and save regular value
909
1076
  let value;
910
1077
  try {
911
1078
  value = JSON.parse(valueStr);
@@ -923,21 +1090,14 @@ function handleCustomSelectClick(element, field, customConfig) {
923
1090
  function validateMinMax(field, customConfig) {
924
1091
  console.log(`\n🔍 === VALIDATING MIN/MAX for field: ${field} ===`);
925
1092
 
926
- // Always use global config errors
927
1093
  const errors = config.customRangeErrors;
928
1094
 
929
- // Fixed selectors - input has all attributes on it directly
930
1095
  const minInput = document.querySelector(`[data-field="${field}"][data-input-type="min"][data-chat-input-element="input"]`);
931
1096
  const maxInput = document.querySelector(`[data-field="${field}"][data-input-type="max"][data-chat-input-element="input"]`);
932
1097
  const errorDiv = document.querySelector(`[data-chat-element="range-error"][data-field="${field}"]`);
933
1098
 
934
1099
  if (!minInput || !maxInput) {
935
1100
  console.log('❌ Min or Max input not found');
936
- console.log(' Tried selectors:');
937
- console.log(` Min: [data-field="${field}"][data-input-type="min"][data-chat-input-element="input"]`);
938
- console.log(` Max: [data-field="${field}"][data-input-type="max"][data-chat-input-element="input"]`);
939
- console.log(' Found min:', minInput);
940
- console.log(' Found max:', maxInput);
941
1101
  return { valid: false, error: null };
942
1102
  }
943
1103
 
@@ -957,28 +1117,22 @@ function validateMinMax(field, customConfig) {
957
1117
  Min constraint: >= ${minConstraint}
958
1118
  Max constraint: <= ${maxConstraint}`);
959
1119
 
960
- // Helper to show error in div and clear data
961
1120
  const showErrorDiv = (message) => {
962
1121
  if (errorDiv) {
963
1122
  errorDiv.textContent = message;
964
1123
  errorDiv.style.display = 'block';
965
1124
  }
966
- // Clear the data when validation fails
967
1125
  delete chatState.data[field];
968
1126
  chatState.currentSelection = null;
969
1127
  console.log(`🗑️ Data cleared for field: ${field}`);
970
1128
  };
971
1129
 
972
- // Helper to hide error div
973
1130
  const hideErrorDiv = () => {
974
1131
  if (errorDiv) {
975
1132
  errorDiv.style.display = 'none';
976
1133
  }
977
1134
  };
978
-
979
- // Validation rules - return error message if invalid
980
1135
 
981
- // 1. Check if only one is filled
982
1136
  if (minFilled && !maxFilled) {
983
1137
  const error = errors.maxRequired;
984
1138
  showErrorDiv(error);
@@ -995,7 +1149,6 @@ function validateMinMax(field, customConfig) {
995
1149
  return { valid: false, error };
996
1150
  }
997
1151
 
998
- // 2. Check if both are empty
999
1152
  if (!minFilled && !maxFilled) {
1000
1153
  hideErrorDiv();
1001
1154
  delete chatState.data[field];
@@ -1005,7 +1158,6 @@ function validateMinMax(field, customConfig) {
1005
1158
  return { valid: false, error: null };
1006
1159
  }
1007
1160
 
1008
- // 3. Check if values are valid numbers
1009
1161
  if (isNaN(minValue) || isNaN(maxValue)) {
1010
1162
  const error = errors.bothRequired;
1011
1163
  showErrorDiv(error);
@@ -1014,7 +1166,6 @@ function validateMinMax(field, customConfig) {
1014
1166
  return { valid: false, error };
1015
1167
  }
1016
1168
 
1017
- // 4. Check min constraint
1018
1169
  console.log(`🔍 Checking: ${minValue} < ${minConstraint}?`);
1019
1170
  if (minValue < minConstraint) {
1020
1171
  const error = errors.minBelowConstraint.replace('{min}', minConstraint);
@@ -1025,7 +1176,6 @@ function validateMinMax(field, customConfig) {
1025
1176
  }
1026
1177
  console.log(`✅ PASS: Min (${minValue}) >= constraint (${minConstraint})`);
1027
1178
 
1028
- // 5. Check max constraint
1029
1179
  console.log(`🔍 Checking: ${maxValue} > ${maxConstraint}?`);
1030
1180
  if (maxValue > maxConstraint) {
1031
1181
  const error = errors.maxAboveConstraint.replace('{max}', maxConstraint);
@@ -1036,7 +1186,6 @@ function validateMinMax(field, customConfig) {
1036
1186
  }
1037
1187
  console.log(`✅ PASS: Max (${maxValue}) <= constraint (${maxConstraint})`);
1038
1188
 
1039
- // 6. Check min < max
1040
1189
  console.log(`🔍 Checking: ${minValue} >= ${maxValue}?`);
1041
1190
  if (minValue >= maxValue) {
1042
1191
  const error = errors.minGreaterThanMax;
@@ -1047,20 +1196,16 @@ function validateMinMax(field, customConfig) {
1047
1196
  }
1048
1197
  console.log(`✅ PASS: Min (${minValue}) < Max (${maxValue})`);
1049
1198
 
1050
- // All valid! Hide error and save data as array
1051
1199
  hideErrorDiv();
1052
1200
 
1053
- // Store as array [min, max]
1054
1201
  chatState.data[field] = [minValue, maxValue];
1055
1202
 
1056
- // Get the current step's input config for formatting
1057
1203
  const currentStep = flowData.flow[chatState.step];
1058
1204
  const inputConfig = currentStep?.input || {};
1059
1205
  const valueType = inputConfig.selectedInputValueType;
1060
1206
  const prefix = inputConfig.selectedInputPrefix || '';
1061
1207
  const suffix = inputConfig.selectedInputSuffix || '';
1062
1208
 
1063
- // Format display name
1064
1209
  let displayName = `${minValue}-${maxValue}`;
1065
1210
  if (valueType === 'arrayRange') {
1066
1211
  const formattedMin = minValue.toLocaleString();
@@ -1068,7 +1213,6 @@ function validateMinMax(field, customConfig) {
1068
1213
  displayName = `${formattedMin} - ${formattedMax}`;
1069
1214
  }
1070
1215
 
1071
- // Add prefix and suffix with spaces
1072
1216
  if (prefix) displayName = `${prefix} ${displayName}`;
1073
1217
  if (suffix) displayName = `${displayName} ${suffix}`;
1074
1218
 
@@ -1094,10 +1238,7 @@ function validateMinMax(field, customConfig) {
1094
1238
  function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1095
1239
  if (!elements.messages) return;
1096
1240
 
1097
- // Determine input type attribute value
1098
1241
  const inputTypeAttr = inputType === 'number' ? 'number-input' : 'text-input';
1099
-
1100
- // Find existing input element in HTML by data-chat-element
1101
1242
  const inputSelector = `[data-chat-element="${inputTypeAttr}"]`;
1102
1243
  const existingInput = document.querySelector(inputSelector);
1103
1244
 
@@ -1106,11 +1247,9 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1106
1247
  return;
1107
1248
  }
1108
1249
 
1109
- // Get existing data for this field (for pre-filling when editing)
1110
1250
  const existingValue = chatState.data[field];
1111
1251
  console.log(`📝 Pre-filling ${field}:`, existingValue);
1112
1252
 
1113
- // Check if validation should be applied (only if input has min/max defined)
1114
1253
  const hasValidation = inputType === 'number' &&
1115
1254
  (inputConfig.min !== undefined || inputConfig.max !== undefined);
1116
1255
 
@@ -1121,22 +1260,15 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1121
1260
  });
1122
1261
  }
1123
1262
 
1124
- // Clone existing input element
1125
1263
  const clone = existingInput.cloneNode(true);
1126
-
1127
- // Make clone visible
1128
1264
  clone.style.display = '';
1129
-
1130
- // Set data attributes
1131
1265
  clone.setAttribute('data-field', field);
1132
1266
 
1133
- // Find the actual input element
1134
1267
  const inputElement = clone.querySelector('[data-chat-input-element="input"]');
1135
1268
  if (inputElement) {
1136
1269
  inputElement.setAttribute('data-field', field);
1137
1270
  inputElement.name = field;
1138
1271
 
1139
- // Set min/max attributes if validation enabled
1140
1272
  if (hasValidation) {
1141
1273
  if (inputConfig.min !== undefined) {
1142
1274
  inputElement.min = inputConfig.min;
@@ -1146,12 +1278,10 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1146
1278
  }
1147
1279
  }
1148
1280
 
1149
- // Pre-fill value if it exists
1150
1281
  if (existingValue !== undefined && existingValue !== null) {
1151
1282
  inputElement.value = existingValue;
1152
1283
  console.log(` ✅ Pre-filled with: ${existingValue}`);
1153
1284
 
1154
- // If validation enabled, validate the pre-filled value
1155
1285
  if (hasValidation) {
1156
1286
  const value = parseFloat(existingValue);
1157
1287
  const min = inputConfig.min;
@@ -1175,20 +1305,16 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1175
1305
 
1176
1306
  inputElement.type = inputType === 'number' ? 'number' : 'text';
1177
1307
 
1178
- // Add input event handler
1179
1308
  inputElement.oninput = (e) => {
1180
1309
  const value = inputType === 'number' ? parseFloat(e.target.value) : e.target.value;
1181
1310
 
1182
- // Save value
1183
1311
  chatState.data[field] = value;
1184
1312
  chatState.currentSelection = { field, value, name: value };
1185
1313
 
1186
- // Apply validation ONLY if min/max defined at input level
1187
1314
  if (hasValidation) {
1188
1315
  const min = inputConfig.min;
1189
1316
  const max = inputConfig.max;
1190
1317
 
1191
- // Check if value is valid
1192
1318
  let isValid = true;
1193
1319
  let errorMessage = '';
1194
1320
 
@@ -1211,17 +1337,14 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1211
1337
  console.log(` ❌ Number input invalid: ${errorMessage}`);
1212
1338
  }
1213
1339
  } else {
1214
- // No validation - just log
1215
1340
  console.log(` 📝 Input updated: ${value} (no validation)`);
1216
1341
  }
1217
1342
  };
1218
1343
  }
1219
1344
 
1220
- // Append to messages
1221
1345
  elements.messages.appendChild(clone);
1222
1346
  scrollToBottom();
1223
1347
 
1224
- // Focus the input
1225
1348
  if (inputElement) {
1226
1349
  setTimeout(() => inputElement.focus(), 100);
1227
1350
  }
@@ -1232,7 +1355,6 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1232
1355
  // =============================================================================
1233
1356
 
1234
1357
  function handleOptionClick(element, field, isSingleSelect) {
1235
- // Prevent rapid double-clicks
1236
1358
  const now = Date.now();
1237
1359
  const lastClick = element.getAttribute('data-last-click');
1238
1360
  if (lastClick && (now - parseInt(lastClick)) < 300) {
@@ -1241,7 +1363,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1241
1363
  }
1242
1364
  element.setAttribute('data-last-click', now.toString());
1243
1365
 
1244
- // Find elements using custom attributes
1245
1366
  const tickIcon = element.querySelector('[data-chat-input-element="tick-icon"]');
1246
1367
  const input = element.querySelector('[data-chat-input-element="input"]');
1247
1368
  const textElement = element.querySelector('[data-chat-input-element="text"]');
@@ -1257,7 +1378,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1257
1378
  name: optionName
1258
1379
  });
1259
1380
 
1260
- // Parse value
1261
1381
  let value;
1262
1382
  try {
1263
1383
  value = JSON.parse(valueStr);
@@ -1266,7 +1386,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1266
1386
  }
1267
1387
 
1268
1388
  if (isSingleSelect) {
1269
- // Uncheck all others with same field and input type
1270
1389
  const selector = `[data-field="${field}"][data-chat-element="${inputType}"]`;
1271
1390
  const allOptions = document.querySelectorAll(selector);
1272
1391
 
@@ -1281,18 +1400,15 @@ function handleOptionClick(element, field, isSingleSelect) {
1281
1400
  if (otherInput) otherInput.checked = false;
1282
1401
  });
1283
1402
 
1284
- // Check this one
1285
1403
  element.classList.add('cf-checked');
1286
1404
  element.style.setProperty('background-color', config.selectedBackground, 'important');
1287
1405
  if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
1288
1406
  if (input) input.checked = true;
1289
1407
 
1290
- // Save value
1291
1408
  chatState.data[field] = value;
1292
1409
  chatState.currentSelection = { field, value, name: optionName };
1293
1410
 
1294
1411
  } else {
1295
- // Multi-select
1296
1412
  const isChecked = !element.classList.contains('cf-checked');
1297
1413
 
1298
1414
  if (isChecked) {
@@ -1303,7 +1419,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1303
1419
  }
1304
1420
  if (input) input.checked = true;
1305
1421
 
1306
- // Add to array
1307
1422
  if (!Array.isArray(chatState.data[field])) {
1308
1423
  chatState.data[field] = [];
1309
1424
  }
@@ -1322,7 +1437,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1322
1437
  if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
1323
1438
  if (input) input.checked = false;
1324
1439
 
1325
- // Remove from array
1326
1440
  if (Array.isArray(chatState.data[field])) {
1327
1441
  chatState.data[field] = chatState.data[field].filter(v =>
1328
1442
  JSON.stringify(v) !== JSON.stringify(value)
@@ -1332,7 +1446,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1332
1446
  console.log(`❌ Removed "${optionName}" - Total selected:`, chatState.data[field]);
1333
1447
  }
1334
1448
 
1335
- // Get all selected names using custom attribute
1336
1449
  const selector = `[data-field="${field}"][data-chat-element="${inputType}"].cf-checked`;
1337
1450
  const selectedOptions = document.querySelectorAll(selector);
1338
1451
  const selectedNames = Array.from(selectedOptions).map(opt =>
@@ -1346,7 +1459,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1346
1459
  };
1347
1460
  }
1348
1461
 
1349
- // Enable Next button
1350
1462
  enableNextButton();
1351
1463
  }
1352
1464
 
@@ -1357,7 +1469,6 @@ function handleOptionClick(element, field, isSingleSelect) {
1357
1469
  async function handleNext() {
1358
1470
  const currentStep = flowData.flow[chatState.step];
1359
1471
 
1360
- // Check if input is required (default false)
1361
1472
  const inputRequired = currentStep.inputRequired === true;
1362
1473
 
1363
1474
  if (inputRequired && !chatState.currentSelection) {
@@ -1374,8 +1485,6 @@ async function handleNext() {
1374
1485
  console.log(`Field: ${field}`);
1375
1486
  console.log(`Custom config:`, customConfig);
1376
1487
 
1377
- // Check if the CUSTOM OPTION is actually selected (not just if value is array)
1378
- // Regular options can also have array values!
1379
1488
  const customOptionElement = document.querySelector(
1380
1489
  `[data-field="${field}"][data-is-custom="true"][data-chat-element="single-select-input"]`
1381
1490
  );
@@ -1387,7 +1496,6 @@ async function handleNext() {
1387
1496
  console.log(`Is custom option selected? ${isCustomOptionSelected}`);
1388
1497
 
1389
1498
  if (isCustomOptionSelected && customConfig) {
1390
- // Validate the custom range one more time before proceeding
1391
1499
  console.log(`🔄 Running final validation before proceeding...`);
1392
1500
 
1393
1501
  const validation = validateMinMax(field, customConfig);
@@ -1395,11 +1503,9 @@ async function handleNext() {
1395
1503
  console.log(`Validation result:`, validation);
1396
1504
 
1397
1505
  if (!validation.valid) {
1398
- // If there's an error message, show it
1399
1506
  if (validation.error) {
1400
1507
  console.log(' ⚠️ Validation failed in handleNext, showing error message');
1401
1508
 
1402
- // Hide current inputs (options and range wrapper)
1403
1509
  const optionsWrapper = document.querySelector('[data-chat-element="options-wrapper"]');
1404
1510
  if (optionsWrapper) {
1405
1511
  optionsWrapper.style.display = 'none';
@@ -1410,28 +1516,22 @@ async function handleNext() {
1410
1516
  rangeWrapper.style.display = 'none';
1411
1517
  }
1412
1518
 
1413
- // Add error message as bot message
1414
1519
  addMessage(validation.error, 'bot', false, null);
1415
-
1416
- // Re-display the same step with fresh inputs
1417
1520
  await showNextStep();
1418
1521
  } else {
1419
- // No error message, just incomplete data - do nothing
1420
1522
  console.log(' ⚠️ Validation incomplete, waiting for user input');
1421
1523
  }
1422
1524
 
1423
- return; // Stop here, don't proceed
1525
+ return;
1424
1526
  }
1425
1527
 
1426
1528
  console.log('✅ Validation passed, proceeding to next step');
1427
1529
  } else if (customConfig && chatState.currentSelection && chatState.currentSelection.value === customConfig.value) {
1428
- // User selected the custom option but hasn't entered valid min/max yet
1429
1530
  console.log(' ⚠️ Custom option selected but no valid range entered');
1430
- return; // Stop here
1531
+ return;
1431
1532
  }
1432
1533
  }
1433
1534
 
1434
- // Call onNext validation if exists
1435
1535
  if (currentStep.onNext) {
1436
1536
  try {
1437
1537
  disableNextButton();
@@ -1452,9 +1552,7 @@ async function handleNext() {
1452
1552
  }
1453
1553
  }
1454
1554
 
1455
- // Add user message (only if selection was made)
1456
1555
  if (chatState.currentSelection) {
1457
- // Get the step's input config
1458
1556
  const inputConfig = currentStep.input || {};
1459
1557
  const valueType = inputConfig.selectedInputValueType;
1460
1558
  const prefix = inputConfig.selectedInputPrefix || '';
@@ -1462,7 +1560,6 @@ async function handleNext() {
1462
1560
 
1463
1561
  let displayName = chatState.currentSelection.name;
1464
1562
 
1465
- // Format based on valueType
1466
1563
  if (valueType === 'arrayRange' && Array.isArray(chatState.currentSelection.value)) {
1467
1564
  const [min, max] = chatState.currentSelection.value;
1468
1565
  const formattedMin = min.toLocaleString();
@@ -1470,13 +1567,11 @@ async function handleNext() {
1470
1567
  displayName = `${formattedMin} - ${formattedMax}`;
1471
1568
  }
1472
1569
 
1473
- // Add prefix and suffix with spaces
1474
1570
  if (prefix) displayName = `${prefix} ${displayName}`;
1475
1571
  if (suffix) displayName = `${displayName} ${suffix}`;
1476
1572
 
1477
1573
  addMessage(displayName, 'user', false, chatState.step);
1478
1574
 
1479
- // Save to history
1480
1575
  chatState.history.push({
1481
1576
  step: chatState.step,
1482
1577
  field: chatState.currentSelection.field,
@@ -1485,32 +1580,25 @@ async function handleNext() {
1485
1580
  });
1486
1581
  }
1487
1582
 
1488
- // Reset selection
1489
1583
  chatState.currentSelection = null;
1490
1584
  disableNextButton();
1491
1585
 
1492
- // Check if we should return to a saved step (after editing)
1493
1586
  if (chatState.returnToStep !== null) {
1494
1587
  const targetStep = chatState.returnToStep;
1495
1588
  console.log(`\n🔙 Returning to saved step ${targetStep} (edited step complete)`);
1496
- chatState.returnToStep = null; // Clear the return step
1589
+ chatState.returnToStep = null;
1497
1590
 
1498
- // Check if target step is valid
1499
1591
  if (targetStep >= 0 && targetStep < flowData.flow.length) {
1500
1592
  chatState.step = targetStep;
1501
1593
  console.log(` ✅ Jumped to step ${targetStep}`);
1502
1594
 
1503
- // Hide all range-wrappers
1504
1595
  const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
1505
1596
  allRangeWrappers.forEach(wrapper => {
1506
1597
  wrapper.style.display = 'none';
1507
1598
  });
1508
1599
  console.log(' 🙈 Hidden all range-wrappers');
1509
1600
 
1510
- // Update edit icons for the new position
1511
1601
  updateEditIcons();
1512
-
1513
- // Show the step we returned to
1514
1602
  await showNextStep();
1515
1603
  return;
1516
1604
  } else {
@@ -1518,33 +1606,27 @@ async function handleNext() {
1518
1606
  }
1519
1607
  }
1520
1608
 
1521
- // Normal flow: Move to next step
1522
1609
  chatState.step++;
1523
1610
 
1524
- // Hide all range-wrappers (custom min/max inputs)
1525
1611
  const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
1526
1612
  allRangeWrappers.forEach(wrapper => {
1527
1613
  wrapper.style.display = 'none';
1528
1614
  });
1529
1615
  console.log(' 🙈 Hidden all range-wrappers');
1530
1616
 
1531
- // Update edit icons for all previous steps
1532
1617
  updateEditIcons();
1533
1618
 
1534
- // Check if finished
1535
1619
  if (chatState.step >= flowData.flow.length) {
1536
1620
  handleCompletion();
1537
1621
  return;
1538
1622
  }
1539
1623
 
1540
- // Show next step
1541
1624
  await showNextStep();
1542
1625
  }
1543
1626
 
1544
1627
  async function showNextStep() {
1545
1628
  const nextStep = flowData.flow[chatState.step];
1546
1629
 
1547
- // Call onStart if exists
1548
1630
  if (nextStep.onStart) {
1549
1631
  try {
1550
1632
  const canStart = await nextStep.onStart(chatState.data);
@@ -1558,87 +1640,79 @@ async function showNextStep() {
1558
1640
  }
1559
1641
  }
1560
1642
 
1561
- // Hide previous inputs (options, text inputs, number inputs)
1562
1643
  const previousInputs = elements.messages.querySelectorAll(
1563
1644
  '[data-chat-element="options-wrapper"], ' +
1564
1645
  '[data-chat-element="text-input"]:not([style*="display: none"]), ' +
1565
- '[data-chat-element="number-input"]:not([style*="display: none"])'
1646
+ '[data-chat-element="number-input"]:not([style*="display: none"]), ' +
1647
+ '[data-chat-element="multi-select-dropdown"]:not([style*="display: none"])'
1566
1648
  );
1567
1649
  previousInputs.forEach(input => {
1568
1650
  input.style.display = 'none';
1569
1651
  });
1570
1652
 
1571
- // Add bot message with edit icon if has input
1572
1653
  const hasInput = !!nextStep.input;
1573
1654
  addMessage(nextStep.message, 'bot', hasInput, chatState.step);
1574
1655
 
1575
- // Check if input is required (default false)
1576
1656
  const inputRequired = nextStep.inputRequired === true;
1577
1657
 
1578
- // Add options if input exists
1579
1658
  if (nextStep.input) {
1580
- const inputType = nextStep.inputType || 'single-select'; // Default to single-select
1659
+ const inputType = nextStep.inputType || 'single-select';
1581
1660
 
1582
1661
  if (inputType === 'text' || inputType === 'number') {
1583
- // Render text or number input
1584
1662
  renderTextInput(nextStep.input.field, inputType, nextStep.input);
1585
1663
 
1586
- // Check if validation is enabled (number input with min/max defined)
1587
1664
  const hasValidation = inputType === 'number' &&
1588
1665
  (nextStep.input.min !== undefined || nextStep.input.max !== undefined);
1589
1666
 
1590
- // Initial Next button state
1591
1667
  if (inputRequired || hasValidation) {
1592
- // Disable if input is required OR has validation rules
1593
1668
  disableNextButton();
1594
1669
  console.log(' 🔒 Next button disabled initially (input required or validation enabled)');
1595
1670
  } else {
1596
- // Enable if optional and no validation
1597
1671
  enableNextButton();
1598
1672
  console.log(' 🔓 Next button enabled (optional input, no validation)');
1599
1673
  }
1600
1674
  } else if (inputType === 'multi-select-color') {
1601
- // Render color options with color blocks
1602
1675
  renderColorOptions(nextStep.input.options, nextStep.input.field);
1603
1676
 
1604
- // Enable Next button if input not required (default behavior)
1605
1677
  if (!inputRequired) {
1606
1678
  enableNextButton();
1607
1679
  }
1608
1680
  } else if (inputType === 'single-select-custom') {
1609
- // Render single-select with custom min/max option
1610
1681
  renderCustomSelectOptions(
1611
1682
  nextStep.input.options,
1612
1683
  nextStep.input.field,
1613
1684
  nextStep.input.custom
1614
1685
  );
1615
1686
 
1616
- // Disable Next button initially (wait for selection/validation)
1687
+ if (inputRequired) {
1688
+ disableNextButton();
1689
+ } else {
1690
+ enableNextButton();
1691
+ }
1692
+ } else if (inputType === 'multi-select-dropdown') {
1693
+ // Render multi-select dropdown
1694
+ renderMultiSelectDropdown(nextStep.input.options, nextStep.input.field);
1695
+
1696
+ // Disable Next button initially if required
1617
1697
  if (inputRequired) {
1618
1698
  disableNextButton();
1619
1699
  } else {
1620
1700
  enableNextButton();
1621
1701
  }
1622
1702
  } else {
1623
- // Render options (single-select or multi-select)
1624
1703
  const isSingleSelect = inputType === 'single-select';
1625
1704
  renderOptions(nextStep.input.options, nextStep.input.field, isSingleSelect);
1626
1705
 
1627
- // Enable Next button if input not required (default behavior)
1628
1706
  if (!inputRequired) {
1629
1707
  enableNextButton();
1630
1708
  }
1631
1709
  }
1632
1710
  } else {
1633
- // No input - always auto-advance (nextButtonDisplay only controls button visibility)
1634
- // Use step-level autoAdvanceDelay if provided, otherwise use global config
1635
1711
  const delay = nextStep.autoAdvanceDelay !== undefined ? nextStep.autoAdvanceDelay : config.autoAdvanceDelay;
1636
1712
  console.log(` ⏱️ Auto-advance delay: ${delay}ms ${nextStep.autoAdvanceDelay !== undefined ? '(step-level)' : '(global)'}`);
1637
1713
 
1638
1714
  setTimeout(() => {
1639
1715
  chatState.step++;
1640
-
1641
- // Update edit icons after auto-advance
1642
1716
  updateEditIcons();
1643
1717
 
1644
1718
  if (chatState.step < flowData.flow.length) {
@@ -1649,7 +1723,6 @@ async function showNextStep() {
1649
1723
  }, delay);
1650
1724
  }
1651
1725
 
1652
- // Handle nextButtonDisplay - default is true
1653
1726
  const showNextButton = nextStep.nextButtonDisplay !== false;
1654
1727
  if (elements.nextBtn) {
1655
1728
  if (showNextButton) {
@@ -1661,20 +1734,23 @@ async function showNextStep() {
1661
1734
  }
1662
1735
  }
1663
1736
 
1664
- // Handle nextButtonText - update button text if specified at step level
1665
1737
  if (elements.nextBtn) {
1666
- if (nextStep.nextButtonText) {
1667
- elements.nextBtn.textContent = nextStep.nextButtonText;
1668
- console.log(` 📝 Next button text: "${nextStep.nextButtonText}" (step-level)`);
1669
- } else {
1670
- // Restore original text
1671
- elements.nextBtn.textContent = elements.originalNextBtnText;
1672
- console.log(` 📝 Next button text: "${elements.originalNextBtnText}" (original)`);
1738
+ const nextBtnTextElement = elements.nextBtn.querySelector('[data-chat-element="next-button-text"]');
1739
+
1740
+ if (nextBtnTextElement) {
1741
+ if (nextStep.nextButtonText) {
1742
+ nextBtnTextElement.textContent = nextStep.nextButtonText;
1743
+ console.log(` 📝 Next button text: "${nextStep.nextButtonText}" (step-level)`);
1744
+ } else {
1745
+ nextBtnTextElement.textContent = elements.originalNextBtnText;
1746
+ console.log(` 📝 Next button text: "${elements.originalNextBtnText}" (original)`);
1747
+ }
1748
+ } else if (nextStep.nextButtonText) {
1749
+ console.warn(` ⚠️ nextButtonText specified ("${nextStep.nextButtonText}") but next-button-text element not found`);
1750
+ console.warn(' Add <span data-chat-element="next-button-text">Next</span> inside your next-button');
1673
1751
  }
1674
1752
  }
1675
1753
 
1676
- // Always update edit icons at the end to ensure correct state
1677
- // Use setTimeout to ensure it runs after any DOM updates
1678
1754
  setTimeout(() => {
1679
1755
  updateEditIcons();
1680
1756
  }, 10);
@@ -1683,19 +1759,15 @@ async function showNextStep() {
1683
1759
  function handleCompletion() {
1684
1760
  console.log('✅ Flow completed. Data:', chatState.data);
1685
1761
 
1686
- // Mark flow as completed
1687
1762
  chatState.completed = true;
1688
1763
  console.log(' 🏁 Flow marked as completed');
1689
1764
 
1690
- // Hide all edit icons
1691
1765
  updateEditIcons();
1692
1766
 
1693
- // Hide Next button
1694
1767
  if (elements.nextBtn) {
1695
1768
  elements.nextBtn.style.display = 'none';
1696
1769
  }
1697
1770
 
1698
- // Trigger event
1699
1771
  if (typeof window !== 'undefined') {
1700
1772
  const event = new CustomEvent('conversationalFlowComplete', {
1701
1773
  detail: {
@@ -1706,7 +1778,6 @@ function handleCompletion() {
1706
1778
  window.dispatchEvent(event);
1707
1779
  }
1708
1780
 
1709
- // Call onComplete if provided
1710
1781
  if (flowData.onComplete && typeof flowData.onComplete === 'function') {
1711
1782
  flowData.onComplete(chatState.data);
1712
1783
  }
@@ -1717,7 +1788,6 @@ function handleCompletion() {
1717
1788
  // =============================================================================
1718
1789
 
1719
1790
  function init(flowName, flowConfig, options = {}) {
1720
- // Find container by data-chat-element="chat-wrapper"
1721
1791
  elements.container = document.querySelector('[data-chat-element="chat-wrapper"]');
1722
1792
 
1723
1793
  if (!elements.container) {
@@ -1726,19 +1796,15 @@ function init(flowName, flowConfig, options = {}) {
1726
1796
  return null;
1727
1797
  }
1728
1798
 
1729
- // Merge config
1730
1799
  if (flowConfig.config) {
1731
1800
  Object.assign(config, flowConfig.config);
1732
1801
  }
1733
1802
 
1734
- // Merge customRangeErrors - priority: options param > flow object > defaults
1735
- // 1. First, try flow object customRangeErrors
1736
1803
  if (flowConfig.customRangeErrors) {
1737
1804
  console.log('✅ Using custom range errors from flow object');
1738
1805
  Object.assign(config.customRangeErrors, flowConfig.customRangeErrors);
1739
1806
  }
1740
1807
 
1741
- // 2. Then, options parameter (overrides flow object if both provided)
1742
1808
  if (options.customRangeErrors) {
1743
1809
  console.log('✅ Using custom range errors from options parameter');
1744
1810
  Object.assign(config.customRangeErrors, options.customRangeErrors);
@@ -1751,20 +1817,16 @@ function init(flowName, flowConfig, options = {}) {
1751
1817
  customRangeErrors: config.customRangeErrors
1752
1818
  });
1753
1819
 
1754
- // Store flow data
1755
1820
  flowData = flowConfig;
1756
1821
 
1757
- // Initialize state
1758
1822
  chatState.step = 0;
1759
1823
  chatState.data = flowConfig.initialData || {};
1760
1824
  chatState.history = [];
1761
1825
  chatState.currentSelection = null;
1762
1826
 
1763
- // Find existing elements (don't create new ones)
1764
1827
  elements.messages = elements.container.querySelector('[data-chat-element="messages-container"]');
1765
1828
  elements.nextBtn = elements.container.querySelector('[data-chat-element="next-button"]');
1766
1829
 
1767
- // Validate required elements exist
1768
1830
  if (!elements.messages) {
1769
1831
  console.error('messages-container not found. Please add <div data-chat-element="messages-container"></div>');
1770
1832
  return null;
@@ -1775,27 +1837,28 @@ function init(flowName, flowConfig, options = {}) {
1775
1837
  return null;
1776
1838
  }
1777
1839
 
1778
- // Store original button text
1779
- elements.originalNextBtnText = elements.nextBtn.textContent || elements.nextBtn.innerText || 'Next';
1780
- console.log(`💾 Stored original button text: "${elements.originalNextBtnText}"`);
1840
+ const nextBtnTextElement = elements.nextBtn.querySelector('[data-chat-element="next-button-text"]');
1841
+ if (nextBtnTextElement) {
1842
+ elements.originalNextBtnText = nextBtnTextElement.textContent || nextBtnTextElement.innerText || 'Next';
1843
+ console.log(`💾 Stored original button text: "${elements.originalNextBtnText}" (from next-button-text element)`);
1844
+ } else {
1845
+ console.warn('⚠️ Element with data-chat-element="next-button-text" not found inside next-button');
1846
+ console.warn(' Button text customization will not work. Please add <span data-chat-element="next-button-text">Next</span> inside your button');
1847
+ elements.originalNextBtnText = 'Next';
1848
+ }
1781
1849
 
1782
- // Clear ONLY messages container (not entire chat-wrapper)
1783
1850
  elements.messages.innerHTML = '';
1784
1851
 
1785
- // Reset button state
1786
1852
  elements.nextBtn.disabled = true;
1787
1853
  elements.nextBtn.style.opacity = '0.5';
1788
1854
  elements.nextBtn.style.cursor = 'not-allowed';
1789
1855
 
1790
- // Add event listener
1791
1856
  elements.nextBtn.onclick = handleNext;
1792
1857
 
1793
- // Expose editStep globally for onclick handlers
1794
1858
  if (typeof window !== 'undefined') {
1795
1859
  window.editStep = editStep;
1796
1860
  }
1797
1861
 
1798
- // Start
1799
1862
  showNextStep();
1800
1863
 
1801
1864
  console.log(`✅ Flow "${flowName}" initialized`);
@@ -1847,8 +1910,6 @@ function goToStep(stepNumber) {
1847
1910
  chatState.step = stepNumber;
1848
1911
  chatState.currentSelection = null;
1849
1912
 
1850
- // DON'T clear messages - keep chat history visible!
1851
- // User can see their previous answers
1852
1913
  console.log(` Keeping messages visible (not clearing)...`);
1853
1914
 
1854
1915
  console.log(` Disabling next button and calling showNextStep()...`);
@@ -1865,27 +1926,21 @@ function editStep(stepNumber) {
1865
1926
  return;
1866
1927
  }
1867
1928
 
1868
- // Save where we are now so we can return after editing
1869
1929
  chatState.returnToStep = chatState.step;
1870
1930
  console.log(` 💾 Saved return step: ${chatState.returnToStep}`);
1871
1931
 
1872
- // Reset completed flag since we're editing
1873
1932
  if (chatState.completed) {
1874
1933
  chatState.completed = false;
1875
1934
  console.log(` 🔓 Flow unmarked as completed (editing)`);
1876
1935
  }
1877
1936
 
1878
1937
  console.log(` Clearing history from step ${stepNumber} forward...`);
1879
- // Clear history from this step forward
1880
1938
  chatState.history = chatState.history.filter(h => h.step < stepNumber);
1881
1939
 
1882
1940
  console.log(` ✅ Keeping ALL data intact for editing (no data deleted)`);
1883
1941
  console.log(` Current data:`, chatState.data);
1884
- // Don't delete any data - keep everything for pre-filling
1885
- // Data will be replaced when user submits the edited step
1886
1942
 
1887
1943
  console.log(` Calling goToStep(${stepNumber})...`);
1888
- // Go to that step
1889
1944
  goToStep(stepNumber);
1890
1945
  }
1891
1946
 
@@ -1898,7 +1953,6 @@ function injectStyles() {
1898
1953
  const style = document.createElement('style');
1899
1954
  style.id = styleId;
1900
1955
  style.textContent = `
1901
- /* User message spacing and alignment */
1902
1956
  [data-chat-element="user-message-wrapper"] {
1903
1957
  margin-bottom: 16px;
1904
1958
  display: inline-flex;
@@ -1906,21 +1960,18 @@ function injectStyles() {
1906
1960
  align-self: flex-end;
1907
1961
  }
1908
1962
 
1909
- /* User message natural width from right */
1910
1963
  [data-chat-element="user-message-text"] {
1911
1964
  width: auto;
1912
1965
  display: inline-block;
1913
1966
  text-align: left;
1914
1967
  }
1915
1968
 
1916
- /* Messages container as flex column */
1917
1969
  [data-chat-element="messages-container"] {
1918
1970
  padding: 20px;
1919
1971
  display: flex;
1920
1972
  flex-direction: column;
1921
1973
  }
1922
1974
 
1923
- /* Options wrapper spacing and gap */
1924
1975
  [data-chat-element="options-wrapper"] {
1925
1976
  margin-bottom: 20px;
1926
1977
  display: flex;
@@ -1928,17 +1979,14 @@ function injectStyles() {
1928
1979
  gap: 12px;
1929
1980
  }
1930
1981
 
1931
- /* Text input spacing */
1932
1982
  [data-chat-element="text-input"] {
1933
1983
  margin-bottom: 20px;
1934
1984
  }
1935
1985
 
1936
- /* Number input spacing */
1937
1986
  [data-chat-element="number-input"] {
1938
1987
  margin-bottom: 20px;
1939
1988
  }
1940
1989
 
1941
- /* Bot edit icon styling */
1942
1990
  [data-chat-element="bot-edit-icon"] {
1943
1991
  display: none;
1944
1992
  margin-left: 8px !important;
@@ -1955,7 +2003,6 @@ function injectStyles() {
1955
2003
  document.head.appendChild(style);
1956
2004
  }
1957
2005
 
1958
- // Auto-inject styles when imported
1959
2006
  if (typeof document !== 'undefined') {
1960
2007
  if (document.readyState === 'loading') {
1961
2008
  document.addEventListener('DOMContentLoaded', injectStyles);