lisichatbot 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +565 -4
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -29,7 +29,15 @@ let elements = {
|
|
|
29
29
|
let config = {
|
|
30
30
|
selectedBackground: '#667eea',
|
|
31
31
|
autoAdvanceDelay: 2000,
|
|
32
|
-
enableAnimations: true
|
|
32
|
+
enableAnimations: true,
|
|
33
|
+
customRangeErrors: {
|
|
34
|
+
minRequired: 'Minimum value is required',
|
|
35
|
+
maxRequired: 'Maximum value is required',
|
|
36
|
+
bothRequired: 'Both minimum and maximum values are required',
|
|
37
|
+
minGreaterThanMax: 'Minimum must be less than maximum',
|
|
38
|
+
minBelowConstraint: 'Minimum must be at least {min}',
|
|
39
|
+
maxAboveConstraint: 'Maximum must be at most {max}'
|
|
40
|
+
}
|
|
33
41
|
};
|
|
34
42
|
|
|
35
43
|
let flowData = null;
|
|
@@ -137,7 +145,7 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
137
145
|
console.log(` Calling editStep(${stepNumber})...`);
|
|
138
146
|
editStep(stepNumber);
|
|
139
147
|
};
|
|
140
|
-
editIcon.style.setProperty('display', '
|
|
148
|
+
editIcon.style.setProperty('display', 'flex', 'important');
|
|
141
149
|
editIcon.style.setProperty('margin-left', '8px', 'important');
|
|
142
150
|
editIcon.style.setProperty('cursor', 'pointer', 'important');
|
|
143
151
|
console.log(` ✏️ Edit icon SHOWN (step ${stepNumber} is latest previous step with input)`);
|
|
@@ -176,7 +184,7 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
176
184
|
|
|
177
185
|
// Only show if has input AND it's a previous step AND it's latest instance
|
|
178
186
|
if (hasInput && stepNumber !== null && stepNumber < chatState.step && isLatest) {
|
|
179
|
-
editIconAfterAppend.style.setProperty('display', '
|
|
187
|
+
editIconAfterAppend.style.setProperty('display', 'flex', 'important');
|
|
180
188
|
|
|
181
189
|
// Debug: Check spacing
|
|
182
190
|
setTimeout(() => {
|
|
@@ -230,7 +238,7 @@ function updateEditIcons() {
|
|
|
230
238
|
editStep(stepNumber);
|
|
231
239
|
};
|
|
232
240
|
editIcon.setAttribute('data-chat-step', stepNumber);
|
|
233
|
-
editIcon.style.setProperty('display', '
|
|
241
|
+
editIcon.style.setProperty('display', 'flex', 'important');
|
|
234
242
|
editIcon.style.setProperty('cursor', 'pointer', 'important');
|
|
235
243
|
editIcon.style.setProperty('margin-left', '8px', 'important');
|
|
236
244
|
console.log(` ✏️ Step ${stepNumber}: Icon SHOWN (latest previous step)`);
|
|
@@ -483,6 +491,492 @@ function renderColorOptions(options, field) {
|
|
|
483
491
|
});
|
|
484
492
|
}
|
|
485
493
|
|
|
494
|
+
// =============================================================================
|
|
495
|
+
// SINGLE-SELECT-CUSTOM (WITH MIN/MAX RANGE)
|
|
496
|
+
// =============================================================================
|
|
497
|
+
|
|
498
|
+
function renderCustomSelectOptions(options, field, customConfig) {
|
|
499
|
+
if (!elements.messages) return;
|
|
500
|
+
|
|
501
|
+
// Find existing single-select option element
|
|
502
|
+
const optionSelector = '[data-chat-element="single-select-input"]';
|
|
503
|
+
const existingOption = document.querySelector(optionSelector);
|
|
504
|
+
|
|
505
|
+
if (!existingOption) {
|
|
506
|
+
console.error(`Element with ${optionSelector} not found in HTML.`);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Get existing data for pre-filling
|
|
511
|
+
const existingData = chatState.data[field];
|
|
512
|
+
console.log(`📝 Pre-filling ${field} (custom):`, existingData);
|
|
513
|
+
|
|
514
|
+
// Create wrapper to hold all options
|
|
515
|
+
const optionsWrapper = document.createElement('div');
|
|
516
|
+
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
517
|
+
|
|
518
|
+
// Render regular options
|
|
519
|
+
options.forEach((option, index) => {
|
|
520
|
+
const optionName = option.name || option;
|
|
521
|
+
const optionValue = option.value !== undefined ? option.value : option;
|
|
522
|
+
const valueStr = typeof optionValue === 'object' ?
|
|
523
|
+
JSON.stringify(optionValue) : String(optionValue);
|
|
524
|
+
|
|
525
|
+
const clone = existingOption.cloneNode(true);
|
|
526
|
+
clone.style.display = '';
|
|
527
|
+
|
|
528
|
+
// Check if this option should be pre-selected
|
|
529
|
+
const shouldBeChecked = existingData !== undefined &&
|
|
530
|
+
!Array.isArray(existingData) && // Exclude array (custom range)
|
|
531
|
+
JSON.stringify(existingData) === JSON.stringify(optionValue);
|
|
532
|
+
|
|
533
|
+
if (shouldBeChecked) {
|
|
534
|
+
clone.classList.add('cf-checked');
|
|
535
|
+
clone.style.backgroundColor = config.selectedBackground;
|
|
536
|
+
console.log(` ✅ Pre-selected: ${optionName}`);
|
|
537
|
+
} else {
|
|
538
|
+
clone.classList.remove('cf-checked');
|
|
539
|
+
clone.style.backgroundColor = 'transparent';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
clone.setAttribute('data-chat-element', 'single-select-input');
|
|
543
|
+
clone.setAttribute('data-field', field);
|
|
544
|
+
clone.setAttribute('data-value', valueStr);
|
|
545
|
+
clone.setAttribute('data-name', optionName);
|
|
546
|
+
clone.setAttribute('data-index', index);
|
|
547
|
+
clone.setAttribute('data-is-custom', 'false');
|
|
548
|
+
|
|
549
|
+
const input = clone.querySelector('[data-chat-input-element="input"]');
|
|
550
|
+
if (input) {
|
|
551
|
+
input.name = field;
|
|
552
|
+
input.value = valueStr;
|
|
553
|
+
input.checked = shouldBeChecked;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
557
|
+
if (tickIcon) {
|
|
558
|
+
tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const textElement = clone.querySelector('[data-chat-input-element="text"]');
|
|
562
|
+
if (textElement) {
|
|
563
|
+
textElement.textContent = optionName;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
optionsWrapper.appendChild(clone);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Render custom option
|
|
570
|
+
if (customConfig) {
|
|
571
|
+
const customClone = existingOption.cloneNode(true);
|
|
572
|
+
customClone.style.display = '';
|
|
573
|
+
|
|
574
|
+
// Check if custom was selected before (data is array [min, max])
|
|
575
|
+
const isCustomSelected = existingData !== undefined &&
|
|
576
|
+
Array.isArray(existingData) &&
|
|
577
|
+
existingData.length === 2;
|
|
578
|
+
|
|
579
|
+
if (isCustomSelected) {
|
|
580
|
+
customClone.classList.add('cf-checked');
|
|
581
|
+
customClone.style.backgroundColor = config.selectedBackground;
|
|
582
|
+
console.log(` ✅ Pre-selected: Custom Range [${existingData[0]}, ${existingData[1]}]`);
|
|
583
|
+
} else {
|
|
584
|
+
customClone.classList.remove('cf-checked');
|
|
585
|
+
customClone.style.backgroundColor = 'transparent';
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
customClone.setAttribute('data-chat-element', 'single-select-input');
|
|
589
|
+
customClone.setAttribute('data-field', field);
|
|
590
|
+
customClone.setAttribute('data-value', customConfig.value || 'custom');
|
|
591
|
+
customClone.setAttribute('data-name', customConfig.name);
|
|
592
|
+
customClone.setAttribute('data-is-custom', 'true');
|
|
593
|
+
|
|
594
|
+
const customInput = customClone.querySelector('[data-chat-input-element="input"]');
|
|
595
|
+
if (customInput) {
|
|
596
|
+
customInput.name = field;
|
|
597
|
+
customInput.value = customConfig.value || 'custom';
|
|
598
|
+
customInput.checked = isCustomSelected;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const customTickIcon = customClone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
602
|
+
if (customTickIcon) {
|
|
603
|
+
customTickIcon.style.display = isCustomSelected ? 'block' : 'none';
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const customTextElement = customClone.querySelector('[data-chat-input-element="text"]');
|
|
607
|
+
if (customTextElement) {
|
|
608
|
+
customTextElement.textContent = customConfig.name;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
optionsWrapper.appendChild(customClone);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Append wrapper to messages
|
|
615
|
+
elements.messages.appendChild(optionsWrapper);
|
|
616
|
+
|
|
617
|
+
// Render min/max inputs
|
|
618
|
+
renderMinMaxInputs(field, customConfig, existingData);
|
|
619
|
+
|
|
620
|
+
scrollToBottom();
|
|
621
|
+
|
|
622
|
+
// Add click handlers for regular and custom options
|
|
623
|
+
const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
|
|
624
|
+
optionElements.forEach(el => {
|
|
625
|
+
el.onclick = (e) => {
|
|
626
|
+
e.stopPropagation();
|
|
627
|
+
e.preventDefault();
|
|
628
|
+
handleCustomSelectClick(el, field, customConfig);
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function renderMinMaxInputs(field, customConfig, existingData) {
|
|
634
|
+
if (!elements.messages || !customConfig) return;
|
|
635
|
+
|
|
636
|
+
// Find min/max input templates
|
|
637
|
+
const minSelector = '[data-chat-element="single-select-custom-min"]';
|
|
638
|
+
const maxSelector = '[data-chat-element="single-select-custom-max"]';
|
|
639
|
+
const minTemplate = document.querySelector(minSelector);
|
|
640
|
+
const maxTemplate = document.querySelector(maxSelector);
|
|
641
|
+
|
|
642
|
+
if (!minTemplate || !maxTemplate) {
|
|
643
|
+
console.error('Min/Max input templates not found');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Create wrapper for min/max
|
|
648
|
+
const rangeWrapper = document.createElement('div');
|
|
649
|
+
rangeWrapper.setAttribute('data-chat-element', 'range-wrapper');
|
|
650
|
+
rangeWrapper.setAttribute('data-field', field);
|
|
651
|
+
rangeWrapper.style.marginBottom = '16px'; // Add space below wrapper
|
|
652
|
+
|
|
653
|
+
// Check if should show min/max (custom option selected)
|
|
654
|
+
// Data is stored as array [min, max]
|
|
655
|
+
const showMinMax = existingData !== undefined &&
|
|
656
|
+
Array.isArray(existingData) &&
|
|
657
|
+
existingData.length === 2;
|
|
658
|
+
|
|
659
|
+
rangeWrapper.style.display = showMinMax ? '' : 'none';
|
|
660
|
+
|
|
661
|
+
// Clone and setup min input
|
|
662
|
+
const minClone = minTemplate.cloneNode(true);
|
|
663
|
+
minClone.style.display = '';
|
|
664
|
+
minClone.style.marginRight = '12px'; // Add space between inputs
|
|
665
|
+
minClone.setAttribute('data-field', field);
|
|
666
|
+
|
|
667
|
+
const minInput = minClone.querySelector('[data-chat-input-element="input"]');
|
|
668
|
+
if (minInput) {
|
|
669
|
+
minInput.setAttribute('data-field', field);
|
|
670
|
+
minInput.setAttribute('data-input-type', 'min');
|
|
671
|
+
minInput.type = 'number';
|
|
672
|
+
minInput.min = customConfig.min !== undefined ? customConfig.min : 0;
|
|
673
|
+
minInput.max = customConfig.max !== undefined ? customConfig.max : 100;
|
|
674
|
+
minInput.value = showMinMax && existingData[0] !== undefined ? existingData[0] : '';
|
|
675
|
+
minInput.placeholder = `Min (${minInput.min}+)`;
|
|
676
|
+
|
|
677
|
+
if (showMinMax) {
|
|
678
|
+
console.log(` ✅ Pre-filled min: ${existingData[0]}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Clone and setup max input
|
|
683
|
+
const maxClone = maxTemplate.cloneNode(true);
|
|
684
|
+
maxClone.style.display = '';
|
|
685
|
+
maxClone.setAttribute('data-field', field);
|
|
686
|
+
|
|
687
|
+
const maxInput = maxClone.querySelector('[data-chat-input-element="input"]');
|
|
688
|
+
if (maxInput) {
|
|
689
|
+
maxInput.setAttribute('data-field', field);
|
|
690
|
+
maxInput.setAttribute('data-input-type', 'max');
|
|
691
|
+
maxInput.type = 'number';
|
|
692
|
+
maxInput.min = customConfig.min !== undefined ? customConfig.min : 0;
|
|
693
|
+
maxInput.max = customConfig.max !== undefined ? customConfig.max : 100;
|
|
694
|
+
maxInput.value = showMinMax && existingData[1] !== undefined ? existingData[1] : '';
|
|
695
|
+
maxInput.placeholder = `Max (${maxInput.max} max)`;
|
|
696
|
+
|
|
697
|
+
if (showMinMax) {
|
|
698
|
+
console.log(` ✅ Pre-filled max: ${existingData[1]}`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
rangeWrapper.appendChild(minClone);
|
|
703
|
+
rangeWrapper.appendChild(maxClone);
|
|
704
|
+
|
|
705
|
+
// Add error message display
|
|
706
|
+
const errorDiv = document.createElement('div');
|
|
707
|
+
errorDiv.setAttribute('data-chat-element', 'range-error');
|
|
708
|
+
errorDiv.setAttribute('data-field', field);
|
|
709
|
+
errorDiv.style.color = '#ff4444';
|
|
710
|
+
errorDiv.style.fontSize = '14px';
|
|
711
|
+
errorDiv.style.marginTop = '8px';
|
|
712
|
+
errorDiv.style.display = 'none';
|
|
713
|
+
rangeWrapper.appendChild(errorDiv);
|
|
714
|
+
|
|
715
|
+
elements.messages.appendChild(rangeWrapper);
|
|
716
|
+
|
|
717
|
+
// Add handlers for both inputs
|
|
718
|
+
if (minInput && maxInput) {
|
|
719
|
+
const updateVisualFeedback = () => {
|
|
720
|
+
const minFilled = minInput.value.trim() !== '';
|
|
721
|
+
const maxFilled = maxInput.value.trim() !== '';
|
|
722
|
+
const anyFilled = minFilled || maxFilled;
|
|
723
|
+
|
|
724
|
+
// Get tick icons
|
|
725
|
+
const minTick = minClone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
726
|
+
const maxTick = maxClone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
727
|
+
|
|
728
|
+
// Update background and tick for BOTH inputs
|
|
729
|
+
if (anyFilled) {
|
|
730
|
+
minClone.style.backgroundColor = config.selectedBackground;
|
|
731
|
+
maxClone.style.backgroundColor = config.selectedBackground;
|
|
732
|
+
if (minTick) minTick.style.display = 'block';
|
|
733
|
+
if (maxTick) maxTick.style.display = 'block';
|
|
734
|
+
} else {
|
|
735
|
+
minClone.style.backgroundColor = 'transparent';
|
|
736
|
+
maxClone.style.backgroundColor = 'transparent';
|
|
737
|
+
if (minTick) minTick.style.display = 'none';
|
|
738
|
+
if (maxTick) maxTick.style.display = 'none';
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
minInput.onfocus = () => {
|
|
743
|
+
selectCustomOption(field);
|
|
744
|
+
updateVisualFeedback();
|
|
745
|
+
};
|
|
746
|
+
minInput.oninput = () => {
|
|
747
|
+
updateVisualFeedback();
|
|
748
|
+
validateMinMax(field, customConfig);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
maxInput.onfocus = () => {
|
|
752
|
+
selectCustomOption(field);
|
|
753
|
+
updateVisualFeedback();
|
|
754
|
+
};
|
|
755
|
+
maxInput.oninput = () => {
|
|
756
|
+
updateVisualFeedback();
|
|
757
|
+
validateMinMax(field, customConfig);
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function selectCustomOption(field) {
|
|
763
|
+
// Deselect all regular options
|
|
764
|
+
const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
|
|
765
|
+
allOptions.forEach(opt => {
|
|
766
|
+
const isCustom = opt.getAttribute('data-is-custom') === 'true';
|
|
767
|
+
if (isCustom) {
|
|
768
|
+
// Select custom option
|
|
769
|
+
opt.classList.add('cf-checked');
|
|
770
|
+
opt.style.setProperty('background-color', config.selectedBackground, 'important');
|
|
771
|
+
const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
|
|
772
|
+
if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
|
|
773
|
+
} else {
|
|
774
|
+
// Deselect regular options
|
|
775
|
+
opt.classList.remove('cf-checked');
|
|
776
|
+
opt.style.setProperty('background-color', 'transparent', 'important');
|
|
777
|
+
const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
|
|
778
|
+
if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// Show min/max inputs
|
|
783
|
+
const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
784
|
+
if (rangeWrapper) {
|
|
785
|
+
rangeWrapper.style.display = '';
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function handleCustomSelectClick(element, field, customConfig) {
|
|
790
|
+
const isCustom = element.getAttribute('data-is-custom') === 'true';
|
|
791
|
+
const valueStr = element.getAttribute('data-value');
|
|
792
|
+
const optionName = element.getAttribute('data-name');
|
|
793
|
+
|
|
794
|
+
console.log(`Custom-select clicked:`, { field, name: optionName, isCustom });
|
|
795
|
+
|
|
796
|
+
// Deselect all options first
|
|
797
|
+
const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
|
|
798
|
+
allOptions.forEach(opt => {
|
|
799
|
+
opt.classList.remove('cf-checked');
|
|
800
|
+
opt.style.setProperty('background-color', 'transparent', 'important');
|
|
801
|
+
const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
|
|
802
|
+
if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Select this option
|
|
806
|
+
element.classList.add('cf-checked');
|
|
807
|
+
element.style.setProperty('background-color', config.selectedBackground, 'important');
|
|
808
|
+
const tickIcon = element.querySelector('[data-chat-input-element="tick-icon"]');
|
|
809
|
+
if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
|
|
810
|
+
|
|
811
|
+
if (isCustom) {
|
|
812
|
+
// Show min/max inputs
|
|
813
|
+
const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
814
|
+
if (rangeWrapper) {
|
|
815
|
+
rangeWrapper.style.display = '';
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Don't save data yet - wait for validation
|
|
819
|
+
chatState.currentSelection = null;
|
|
820
|
+
console.log(' ℹ️ Custom selected - waiting for min/max input');
|
|
821
|
+
} else {
|
|
822
|
+
// Hide min/max inputs and clear them
|
|
823
|
+
const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
824
|
+
if (rangeWrapper) {
|
|
825
|
+
rangeWrapper.style.display = 'none';
|
|
826
|
+
|
|
827
|
+
// Clear input values
|
|
828
|
+
const minInput = rangeWrapper.querySelector('[data-input-type="min"] [data-chat-input-element="input"]');
|
|
829
|
+
const maxInput = rangeWrapper.querySelector('[data-input-type="max"] [data-chat-input-element="input"]');
|
|
830
|
+
|
|
831
|
+
if (minInput) minInput.value = '';
|
|
832
|
+
if (maxInput) maxInput.value = '';
|
|
833
|
+
|
|
834
|
+
// Reset visual feedback (background and ticks)
|
|
835
|
+
const minContainer = rangeWrapper.querySelector('[data-chat-element="single-select-custom-min"]');
|
|
836
|
+
const maxContainer = rangeWrapper.querySelector('[data-chat-element="single-select-custom-max"]');
|
|
837
|
+
|
|
838
|
+
if (minContainer) {
|
|
839
|
+
minContainer.style.backgroundColor = 'transparent';
|
|
840
|
+
const minTick = minContainer.querySelector('[data-chat-input-element="tick-icon"]');
|
|
841
|
+
if (minTick) minTick.style.display = 'none';
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (maxContainer) {
|
|
845
|
+
maxContainer.style.backgroundColor = 'transparent';
|
|
846
|
+
const maxTick = maxContainer.querySelector('[data-chat-input-element="tick-icon"]');
|
|
847
|
+
if (maxTick) maxTick.style.display = 'none';
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Hide error message
|
|
851
|
+
const errorDiv = rangeWrapper.querySelector('[data-chat-element="range-error"]');
|
|
852
|
+
if (errorDiv) errorDiv.style.display = 'none';
|
|
853
|
+
|
|
854
|
+
console.log(' 🧹 Min/max inputs cleared and hidden');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Parse and save regular value
|
|
858
|
+
let value;
|
|
859
|
+
try {
|
|
860
|
+
value = JSON.parse(valueStr);
|
|
861
|
+
} catch (e) {
|
|
862
|
+
value = valueStr;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
chatState.data[field] = value;
|
|
866
|
+
chatState.currentSelection = { field, value, name: optionName };
|
|
867
|
+
console.log(' ✅ Regular option selected:', value);
|
|
868
|
+
enableNextButton();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function validateMinMax(field, customConfig) {
|
|
873
|
+
const minInput = document.querySelector(`[data-field="${field}"][data-input-type="min"] [data-chat-input-element="input"]`);
|
|
874
|
+
const maxInput = document.querySelector(`[data-field="${field}"][data-input-type="max"] [data-chat-input-element="input"]`);
|
|
875
|
+
const errorDiv = document.querySelector(`[data-chat-element="range-error"][data-field="${field}"]`);
|
|
876
|
+
|
|
877
|
+
if (!minInput || !maxInput) return { valid: false, error: null };
|
|
878
|
+
|
|
879
|
+
const minValue = parseFloat(minInput.value);
|
|
880
|
+
const maxValue = parseFloat(maxInput.value);
|
|
881
|
+
const minFilled = minInput.value.trim() !== '';
|
|
882
|
+
const maxFilled = maxInput.value.trim() !== '';
|
|
883
|
+
|
|
884
|
+
const minConstraint = customConfig.min !== undefined ? customConfig.min : 0;
|
|
885
|
+
const maxConstraint = customConfig.max !== undefined ? customConfig.max : 100;
|
|
886
|
+
|
|
887
|
+
// Helper to show error in div
|
|
888
|
+
const showErrorDiv = (message) => {
|
|
889
|
+
if (errorDiv) {
|
|
890
|
+
errorDiv.textContent = message;
|
|
891
|
+
errorDiv.style.display = 'block';
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// Helper to hide error div
|
|
896
|
+
const hideErrorDiv = () => {
|
|
897
|
+
if (errorDiv) {
|
|
898
|
+
errorDiv.style.display = 'none';
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// Validation rules - return error message if invalid
|
|
903
|
+
|
|
904
|
+
// 1. Check if only one is filled
|
|
905
|
+
if (minFilled && !maxFilled) {
|
|
906
|
+
const error = config.customRangeErrors.maxRequired;
|
|
907
|
+
showErrorDiv(error);
|
|
908
|
+
console.log(' ❌ Validation error:', error);
|
|
909
|
+
disableNextButton();
|
|
910
|
+
return { valid: false, error };
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (!minFilled && maxFilled) {
|
|
914
|
+
const error = config.customRangeErrors.minRequired;
|
|
915
|
+
showErrorDiv(error);
|
|
916
|
+
console.log(' ❌ Validation error:', error);
|
|
917
|
+
disableNextButton();
|
|
918
|
+
return { valid: false, error };
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// 2. Check if both are empty
|
|
922
|
+
if (!minFilled && !maxFilled) {
|
|
923
|
+
hideErrorDiv();
|
|
924
|
+
disableNextButton();
|
|
925
|
+
return { valid: false, error: null };
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// 3. Check if values are valid numbers
|
|
929
|
+
if (isNaN(minValue) || isNaN(maxValue)) {
|
|
930
|
+
const error = config.customRangeErrors.bothRequired;
|
|
931
|
+
showErrorDiv(error);
|
|
932
|
+
console.log(' ❌ Validation error:', error);
|
|
933
|
+
disableNextButton();
|
|
934
|
+
return { valid: false, error };
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// 4. Check min constraint
|
|
938
|
+
if (minValue < minConstraint) {
|
|
939
|
+
const error = config.customRangeErrors.minBelowConstraint.replace('{min}', minConstraint);
|
|
940
|
+
showErrorDiv(error);
|
|
941
|
+
console.log(' ❌ Validation error:', error);
|
|
942
|
+
disableNextButton();
|
|
943
|
+
return { valid: false, error };
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// 5. Check max constraint
|
|
947
|
+
if (maxValue > maxConstraint) {
|
|
948
|
+
const error = config.customRangeErrors.maxAboveConstraint.replace('{max}', maxConstraint);
|
|
949
|
+
showErrorDiv(error);
|
|
950
|
+
console.log(' ❌ Validation error:', error);
|
|
951
|
+
disableNextButton();
|
|
952
|
+
return { valid: false, error };
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// 6. Check min < max
|
|
956
|
+
if (minValue >= maxValue) {
|
|
957
|
+
const error = config.customRangeErrors.minGreaterThanMax;
|
|
958
|
+
showErrorDiv(error);
|
|
959
|
+
console.log(' ❌ Validation error:', error);
|
|
960
|
+
disableNextButton();
|
|
961
|
+
return { valid: false, error };
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// All valid! Hide error and save data as array
|
|
965
|
+
hideErrorDiv();
|
|
966
|
+
|
|
967
|
+
// Store as array [min, max]
|
|
968
|
+
chatState.data[field] = [minValue, maxValue];
|
|
969
|
+
chatState.currentSelection = {
|
|
970
|
+
field,
|
|
971
|
+
value: [minValue, maxValue],
|
|
972
|
+
name: `${minValue}-${maxValue}`
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
console.log(' ✅ Range valid - stored as array:', [minValue, maxValue]);
|
|
976
|
+
enableNextButton();
|
|
977
|
+
return { valid: true, error: null };
|
|
978
|
+
}
|
|
979
|
+
|
|
486
980
|
// =============================================================================
|
|
487
981
|
// TEXT/NUMBER INPUT RENDERING
|
|
488
982
|
// =============================================================================
|
|
@@ -696,6 +1190,45 @@ async function handleNext() {
|
|
|
696
1190
|
return;
|
|
697
1191
|
}
|
|
698
1192
|
|
|
1193
|
+
// VALIDATION: Check for single-select-custom validation before proceeding
|
|
1194
|
+
if (currentStep.inputType === 'single-select-custom' && currentStep.input) {
|
|
1195
|
+
const field = currentStep.input.field;
|
|
1196
|
+
const customConfig = currentStep.input.custom;
|
|
1197
|
+
|
|
1198
|
+
// Check if custom range was selected (data is array)
|
|
1199
|
+
const selectedValue = chatState.data[field];
|
|
1200
|
+
const isCustomRange = Array.isArray(selectedValue);
|
|
1201
|
+
|
|
1202
|
+
if (isCustomRange && customConfig) {
|
|
1203
|
+
// Validate the custom range
|
|
1204
|
+
const validation = validateMinMax(field, customConfig);
|
|
1205
|
+
|
|
1206
|
+
if (!validation.valid && validation.error) {
|
|
1207
|
+
// Show error as bot message
|
|
1208
|
+
console.log(' ⚠️ Validation failed, showing error message');
|
|
1209
|
+
|
|
1210
|
+
// Hide current inputs (options and range wrapper)
|
|
1211
|
+
const optionsWrapper = document.querySelector('[data-chat-element="options-wrapper"]');
|
|
1212
|
+
if (optionsWrapper) {
|
|
1213
|
+
optionsWrapper.style.display = 'none';
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
1217
|
+
if (rangeWrapper) {
|
|
1218
|
+
rangeWrapper.style.display = 'none';
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Add error message as bot message
|
|
1222
|
+
addMessage(validation.error, 'bot', false, null);
|
|
1223
|
+
|
|
1224
|
+
// Re-display the same step with fresh inputs
|
|
1225
|
+
await showNextStep();
|
|
1226
|
+
|
|
1227
|
+
return; // Stop here, don't proceed
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
699
1232
|
// Call onNext validation if exists
|
|
700
1233
|
if (currentStep.onNext) {
|
|
701
1234
|
try {
|
|
@@ -745,6 +1278,13 @@ async function handleNext() {
|
|
|
745
1278
|
chatState.step = targetStep;
|
|
746
1279
|
console.log(` ✅ Jumped to step ${targetStep}`);
|
|
747
1280
|
|
|
1281
|
+
// Hide all range-wrappers
|
|
1282
|
+
const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
|
|
1283
|
+
allRangeWrappers.forEach(wrapper => {
|
|
1284
|
+
wrapper.style.display = 'none';
|
|
1285
|
+
});
|
|
1286
|
+
console.log(' 🙈 Hidden all range-wrappers');
|
|
1287
|
+
|
|
748
1288
|
// Update edit icons for the new position
|
|
749
1289
|
updateEditIcons();
|
|
750
1290
|
|
|
@@ -759,6 +1299,13 @@ async function handleNext() {
|
|
|
759
1299
|
// Normal flow: Move to next step
|
|
760
1300
|
chatState.step++;
|
|
761
1301
|
|
|
1302
|
+
// Hide all range-wrappers (custom min/max inputs)
|
|
1303
|
+
const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
|
|
1304
|
+
allRangeWrappers.forEach(wrapper => {
|
|
1305
|
+
wrapper.style.display = 'none';
|
|
1306
|
+
});
|
|
1307
|
+
console.log(' 🙈 Hidden all range-wrappers');
|
|
1308
|
+
|
|
762
1309
|
// Update edit icons for all previous steps
|
|
763
1310
|
updateEditIcons();
|
|
764
1311
|
|
|
@@ -828,6 +1375,20 @@ async function showNextStep() {
|
|
|
828
1375
|
if (!inputRequired) {
|
|
829
1376
|
enableNextButton();
|
|
830
1377
|
}
|
|
1378
|
+
} else if (inputType === 'single-select-custom') {
|
|
1379
|
+
// Render single-select with custom min/max option
|
|
1380
|
+
renderCustomSelectOptions(
|
|
1381
|
+
nextStep.input.options,
|
|
1382
|
+
nextStep.input.field,
|
|
1383
|
+
nextStep.input.custom
|
|
1384
|
+
);
|
|
1385
|
+
|
|
1386
|
+
// Disable Next button initially (wait for selection/validation)
|
|
1387
|
+
if (inputRequired) {
|
|
1388
|
+
disableNextButton();
|
|
1389
|
+
} else {
|
|
1390
|
+
enableNextButton();
|
|
1391
|
+
}
|
|
831
1392
|
} else {
|
|
832
1393
|
// Render options (single-select or multi-select)
|
|
833
1394
|
const isSingleSelect = inputType === 'single-select';
|