lisichatbot 1.2.4 → 1.2.5

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 +425 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisichatbot",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "exports": {
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;
@@ -483,6 +491,408 @@ 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
+
652
+ // Check if should show min/max (custom option selected)
653
+ // Data is stored as array [min, max]
654
+ const showMinMax = existingData !== undefined &&
655
+ Array.isArray(existingData) &&
656
+ existingData.length === 2;
657
+
658
+ rangeWrapper.style.display = showMinMax ? '' : 'none';
659
+
660
+ // Clone and setup min input
661
+ const minClone = minTemplate.cloneNode(true);
662
+ minClone.style.display = '';
663
+ minClone.setAttribute('data-field', field);
664
+
665
+ const minInput = minClone.querySelector('[data-chat-input-element="input"]');
666
+ if (minInput) {
667
+ minInput.setAttribute('data-field', field);
668
+ minInput.setAttribute('data-input-type', 'min');
669
+ minInput.type = 'number';
670
+ minInput.min = customConfig.min !== undefined ? customConfig.min : 0;
671
+ minInput.max = customConfig.max !== undefined ? customConfig.max : 100;
672
+ minInput.value = showMinMax && existingData[0] !== undefined ? existingData[0] : '';
673
+ minInput.placeholder = `Min (${minInput.min}+)`;
674
+
675
+ if (showMinMax) {
676
+ console.log(` ✅ Pre-filled min: ${existingData[0]}`);
677
+ }
678
+ }
679
+
680
+ // Clone and setup max input
681
+ const maxClone = maxTemplate.cloneNode(true);
682
+ maxClone.style.display = '';
683
+ maxClone.setAttribute('data-field', field);
684
+
685
+ const maxInput = maxClone.querySelector('[data-chat-input-element="input"]');
686
+ if (maxInput) {
687
+ maxInput.setAttribute('data-field', field);
688
+ maxInput.setAttribute('data-input-type', 'max');
689
+ maxInput.type = 'number';
690
+ maxInput.min = customConfig.min !== undefined ? customConfig.min : 0;
691
+ maxInput.max = customConfig.max !== undefined ? customConfig.max : 100;
692
+ maxInput.value = showMinMax && existingData[1] !== undefined ? existingData[1] : '';
693
+ maxInput.placeholder = `Max (${maxInput.max} max)`;
694
+
695
+ if (showMinMax) {
696
+ console.log(` ✅ Pre-filled max: ${existingData[1]}`);
697
+ }
698
+ }
699
+
700
+ rangeWrapper.appendChild(minClone);
701
+ rangeWrapper.appendChild(maxClone);
702
+
703
+ // Add error message display
704
+ const errorDiv = document.createElement('div');
705
+ errorDiv.setAttribute('data-chat-element', 'range-error');
706
+ errorDiv.setAttribute('data-field', field);
707
+ errorDiv.style.color = '#ff4444';
708
+ errorDiv.style.fontSize = '14px';
709
+ errorDiv.style.marginTop = '8px';
710
+ errorDiv.style.display = 'none';
711
+ rangeWrapper.appendChild(errorDiv);
712
+
713
+ elements.messages.appendChild(rangeWrapper);
714
+
715
+ // Add focus handlers to select custom option when editing
716
+ if (minInput) {
717
+ minInput.onfocus = () => selectCustomOption(field);
718
+ minInput.oninput = () => validateMinMax(field, customConfig);
719
+ }
720
+ if (maxInput) {
721
+ maxInput.onfocus = () => selectCustomOption(field);
722
+ maxInput.oninput = () => validateMinMax(field, customConfig);
723
+ }
724
+ }
725
+
726
+ function selectCustomOption(field) {
727
+ // Deselect all regular options
728
+ const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
729
+ allOptions.forEach(opt => {
730
+ const isCustom = opt.getAttribute('data-is-custom') === 'true';
731
+ if (isCustom) {
732
+ // Select custom option
733
+ opt.classList.add('cf-checked');
734
+ opt.style.setProperty('background-color', config.selectedBackground, 'important');
735
+ const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
736
+ if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
737
+ } else {
738
+ // Deselect regular options
739
+ opt.classList.remove('cf-checked');
740
+ opt.style.setProperty('background-color', 'transparent', 'important');
741
+ const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
742
+ if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
743
+ }
744
+ });
745
+
746
+ // Show min/max inputs
747
+ const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
748
+ if (rangeWrapper) {
749
+ rangeWrapper.style.display = '';
750
+ }
751
+ }
752
+
753
+ function handleCustomSelectClick(element, field, customConfig) {
754
+ const isCustom = element.getAttribute('data-is-custom') === 'true';
755
+ const valueStr = element.getAttribute('data-value');
756
+ const optionName = element.getAttribute('data-name');
757
+
758
+ console.log(`Custom-select clicked:`, { field, name: optionName, isCustom });
759
+
760
+ // Deselect all options first
761
+ const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
762
+ allOptions.forEach(opt => {
763
+ opt.classList.remove('cf-checked');
764
+ opt.style.setProperty('background-color', 'transparent', 'important');
765
+ const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
766
+ if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
767
+ });
768
+
769
+ // Select this option
770
+ element.classList.add('cf-checked');
771
+ element.style.setProperty('background-color', config.selectedBackground, 'important');
772
+ const tickIcon = element.querySelector('[data-chat-input-element="tick-icon"]');
773
+ if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
774
+
775
+ if (isCustom) {
776
+ // Show min/max inputs
777
+ const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
778
+ if (rangeWrapper) {
779
+ rangeWrapper.style.display = '';
780
+ }
781
+
782
+ // Don't save data yet - wait for validation
783
+ chatState.currentSelection = null;
784
+ console.log(' â„šī¸ Custom selected - waiting for min/max input');
785
+ } else {
786
+ // Hide min/max inputs
787
+ const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
788
+ if (rangeWrapper) {
789
+ rangeWrapper.style.display = 'none';
790
+ }
791
+
792
+ // Parse and save regular value
793
+ let value;
794
+ try {
795
+ value = JSON.parse(valueStr);
796
+ } catch (e) {
797
+ value = valueStr;
798
+ }
799
+
800
+ chatState.data[field] = value;
801
+ chatState.currentSelection = { field, value, name: optionName };
802
+ console.log(' ✅ Regular option selected:', value);
803
+ enableNextButton();
804
+ }
805
+ }
806
+
807
+ function validateMinMax(field, customConfig) {
808
+ const minInput = document.querySelector(`[data-field="${field}"][data-input-type="min"] [data-chat-input-element="input"]`);
809
+ const maxInput = document.querySelector(`[data-field="${field}"][data-input-type="max"] [data-chat-input-element="input"]`);
810
+ const errorDiv = document.querySelector(`[data-chat-element="range-error"][data-field="${field}"]`);
811
+
812
+ if (!minInput || !maxInput) return false;
813
+
814
+ const minValue = parseFloat(minInput.value);
815
+ const maxValue = parseFloat(maxInput.value);
816
+ const minFilled = minInput.value.trim() !== '';
817
+ const maxFilled = maxInput.value.trim() !== '';
818
+
819
+ const minConstraint = customConfig.min !== undefined ? customConfig.min : 0;
820
+ const maxConstraint = customConfig.max !== undefined ? customConfig.max : 100;
821
+
822
+ // Helper to show error
823
+ const showError = (message) => {
824
+ if (errorDiv) {
825
+ errorDiv.textContent = message;
826
+ errorDiv.style.display = 'block';
827
+ }
828
+ console.log(' ❌ Validation error:', message);
829
+ disableNextButton();
830
+ return false;
831
+ };
832
+
833
+ // Helper to hide error
834
+ const hideError = () => {
835
+ if (errorDiv) {
836
+ errorDiv.style.display = 'none';
837
+ }
838
+ };
839
+
840
+ // Validation rules
841
+
842
+ // 1. Check if only one is filled
843
+ if (minFilled && !maxFilled) {
844
+ return showError(config.customRangeErrors.maxRequired);
845
+ }
846
+
847
+ if (!minFilled && maxFilled) {
848
+ return showError(config.customRangeErrors.minRequired);
849
+ }
850
+
851
+ // 2. Check if both are empty
852
+ if (!minFilled && !maxFilled) {
853
+ hideError();
854
+ disableNextButton();
855
+ return false;
856
+ }
857
+
858
+ // 3. Check if values are valid numbers
859
+ if (isNaN(minValue) || isNaN(maxValue)) {
860
+ return showError(config.customRangeErrors.bothRequired);
861
+ }
862
+
863
+ // 4. Check min constraint
864
+ if (minValue < minConstraint) {
865
+ const msg = config.customRangeErrors.minBelowConstraint.replace('{min}', minConstraint);
866
+ return showError(msg);
867
+ }
868
+
869
+ // 5. Check max constraint
870
+ if (maxValue > maxConstraint) {
871
+ const msg = config.customRangeErrors.maxAboveConstraint.replace('{max}', maxConstraint);
872
+ return showError(msg);
873
+ }
874
+
875
+ // 6. Check min < max
876
+ if (minValue >= maxValue) {
877
+ return showError(config.customRangeErrors.minGreaterThanMax);
878
+ }
879
+
880
+ // All valid! Hide error and save data as array
881
+ hideError();
882
+
883
+ // Store as array [min, max]
884
+ chatState.data[field] = [minValue, maxValue];
885
+ chatState.currentSelection = {
886
+ field,
887
+ value: [minValue, maxValue],
888
+ name: `${minValue}-${maxValue}`
889
+ };
890
+
891
+ console.log(' ✅ Range valid - stored as array:', [minValue, maxValue]);
892
+ enableNextButton();
893
+ return true;
894
+ }
895
+
486
896
  // =============================================================================
487
897
  // TEXT/NUMBER INPUT RENDERING
488
898
  // =============================================================================
@@ -828,6 +1238,20 @@ async function showNextStep() {
828
1238
  if (!inputRequired) {
829
1239
  enableNextButton();
830
1240
  }
1241
+ } else if (inputType === 'single-select-custom') {
1242
+ // Render single-select with custom min/max option
1243
+ renderCustomSelectOptions(
1244
+ nextStep.input.options,
1245
+ nextStep.input.field,
1246
+ nextStep.input.custom
1247
+ );
1248
+
1249
+ // Disable Next button initially (wait for selection/validation)
1250
+ if (inputRequired) {
1251
+ disableNextButton();
1252
+ } else {
1253
+ enableNextButton();
1254
+ }
831
1255
  } else {
832
1256
  // Render options (single-select or multi-select)
833
1257
  const isSingleSelect = inputType === 'single-select';