lisichatbot 1.2.3 → 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.
- package/package.json +1 -1
- package/src/index.js +592 -8
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;
|
|
@@ -267,6 +275,10 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
267
275
|
return;
|
|
268
276
|
}
|
|
269
277
|
|
|
278
|
+
// Get existing data for this field (for pre-filling when editing)
|
|
279
|
+
const existingData = chatState.data[field];
|
|
280
|
+
console.log(`📝 Pre-filling ${field}:`, existingData);
|
|
281
|
+
|
|
270
282
|
// Create wrapper to hold all options
|
|
271
283
|
const optionsWrapper = document.createElement('div');
|
|
272
284
|
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
@@ -284,9 +296,27 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
284
296
|
// Make clone visible (remove display:none from template)
|
|
285
297
|
clone.style.display = '';
|
|
286
298
|
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
299
|
+
// Check if this option should be pre-selected
|
|
300
|
+
let shouldBeChecked = false;
|
|
301
|
+
if (isSingleSelect) {
|
|
302
|
+
// For single-select, check if value matches existing data
|
|
303
|
+
shouldBeChecked = existingData !== undefined &&
|
|
304
|
+
JSON.stringify(existingData) === JSON.stringify(optionValue);
|
|
305
|
+
} else {
|
|
306
|
+
// For multi-select, check if value is in array
|
|
307
|
+
shouldBeChecked = Array.isArray(existingData) &&
|
|
308
|
+
existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Set checked state
|
|
312
|
+
if (shouldBeChecked) {
|
|
313
|
+
clone.classList.add('cf-checked');
|
|
314
|
+
clone.style.backgroundColor = config.selectedBackground;
|
|
315
|
+
console.log(` ✅ Pre-selected: ${optionName}`);
|
|
316
|
+
} else {
|
|
317
|
+
clone.classList.remove('cf-checked');
|
|
318
|
+
clone.style.backgroundColor = 'transparent';
|
|
319
|
+
}
|
|
290
320
|
|
|
291
321
|
// Set data attributes on container
|
|
292
322
|
clone.setAttribute('data-chat-element', inputTypeAttr);
|
|
@@ -301,14 +331,14 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
301
331
|
input.setAttribute('data-chat-element', inputTypeAttr);
|
|
302
332
|
input.name = field;
|
|
303
333
|
input.value = valueStr;
|
|
304
|
-
input.checked =
|
|
334
|
+
input.checked = shouldBeChecked; // Pre-check if needed
|
|
305
335
|
input.onclick = (e) => e.stopPropagation(); // Prevent click bubbling
|
|
306
336
|
}
|
|
307
337
|
|
|
308
|
-
// Find and
|
|
338
|
+
// Find and set tick icon visibility
|
|
309
339
|
const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
310
340
|
if (tickIcon) {
|
|
311
|
-
tickIcon.style.display = 'none';
|
|
341
|
+
tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
|
|
312
342
|
}
|
|
313
343
|
|
|
314
344
|
// Find and set text
|
|
@@ -343,6 +373,526 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
343
373
|
});
|
|
344
374
|
}
|
|
345
375
|
|
|
376
|
+
// =============================================================================
|
|
377
|
+
// MULTI-SELECT-COLOR OPTIONS RENDERING
|
|
378
|
+
// =============================================================================
|
|
379
|
+
|
|
380
|
+
function renderColorOptions(options, field) {
|
|
381
|
+
if (!elements.messages) return;
|
|
382
|
+
|
|
383
|
+
// Find existing color option element in HTML by data-chat-element
|
|
384
|
+
const optionSelector = '[data-chat-element="multi-select-color"]';
|
|
385
|
+
const existingOption = document.querySelector(optionSelector);
|
|
386
|
+
|
|
387
|
+
if (!existingOption) {
|
|
388
|
+
console.error(`Element with ${optionSelector} not found in HTML. Please add it to your HTML.`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Get existing data for this field (for pre-filling when editing)
|
|
393
|
+
const existingData = chatState.data[field];
|
|
394
|
+
console.log(`📝 Pre-filling ${field} (color):`, existingData);
|
|
395
|
+
|
|
396
|
+
// Create wrapper to hold all options
|
|
397
|
+
const optionsWrapper = document.createElement('div');
|
|
398
|
+
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
399
|
+
|
|
400
|
+
// Clone and fill option element for each option
|
|
401
|
+
options.forEach((option, index) => {
|
|
402
|
+
const optionName = option.name || option;
|
|
403
|
+
const optionValue = option.value !== undefined ? option.value : option;
|
|
404
|
+
const optionColor = option.color || '#cccccc'; // Default gray if no color
|
|
405
|
+
const valueStr = typeof optionValue === 'object' ?
|
|
406
|
+
JSON.stringify(optionValue) : String(optionValue);
|
|
407
|
+
|
|
408
|
+
// Clone existing option element
|
|
409
|
+
const clone = existingOption.cloneNode(true);
|
|
410
|
+
|
|
411
|
+
// Make clone visible (remove display:none from template)
|
|
412
|
+
clone.style.display = '';
|
|
413
|
+
|
|
414
|
+
// Check if this option should be pre-selected (multi-select behavior)
|
|
415
|
+
const shouldBeChecked = Array.isArray(existingData) &&
|
|
416
|
+
existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
|
|
417
|
+
|
|
418
|
+
// Set checked state
|
|
419
|
+
if (shouldBeChecked) {
|
|
420
|
+
clone.classList.add('cf-checked');
|
|
421
|
+
clone.style.backgroundColor = config.selectedBackground;
|
|
422
|
+
console.log(` ✅ Pre-selected color: ${optionName}`);
|
|
423
|
+
} else {
|
|
424
|
+
clone.classList.remove('cf-checked');
|
|
425
|
+
clone.style.backgroundColor = 'transparent';
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Set data attributes on container
|
|
429
|
+
clone.setAttribute('data-chat-element', 'multi-select-color');
|
|
430
|
+
clone.setAttribute('data-field', field);
|
|
431
|
+
clone.setAttribute('data-value', valueStr);
|
|
432
|
+
clone.setAttribute('data-name', optionName);
|
|
433
|
+
clone.setAttribute('data-index', index);
|
|
434
|
+
clone.setAttribute('data-color', optionColor);
|
|
435
|
+
|
|
436
|
+
// Find and set input
|
|
437
|
+
const input = clone.querySelector('[data-chat-input-element="input"]');
|
|
438
|
+
if (input) {
|
|
439
|
+
input.setAttribute('data-chat-element', 'multi-select-color');
|
|
440
|
+
input.name = field;
|
|
441
|
+
input.value = valueStr;
|
|
442
|
+
input.checked = shouldBeChecked; // Pre-check if needed
|
|
443
|
+
input.onclick = (e) => e.stopPropagation(); // Prevent click bubbling
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Find and set tick icon visibility
|
|
447
|
+
const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
448
|
+
if (tickIcon) {
|
|
449
|
+
tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Find and set color block
|
|
453
|
+
const colorBlock = clone.querySelector('[data-chat-input-element="color-block"]');
|
|
454
|
+
if (colorBlock) {
|
|
455
|
+
colorBlock.style.backgroundColor = optionColor;
|
|
456
|
+
colorBlock.style.display = ''; // Make sure it's visible
|
|
457
|
+
} else {
|
|
458
|
+
console.warn('Color block element not found in multi-select-color template');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Find and set text
|
|
462
|
+
const textElement = clone.querySelector('[data-chat-input-element="text"]');
|
|
463
|
+
if (textElement) {
|
|
464
|
+
textElement.textContent = optionName;
|
|
465
|
+
textElement.style.display = ''; // Make sure text is visible
|
|
466
|
+
} else {
|
|
467
|
+
console.error('Text element not found in option');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Append to wrapper
|
|
471
|
+
optionsWrapper.appendChild(clone);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Append wrapper to messages
|
|
475
|
+
elements.messages.appendChild(optionsWrapper);
|
|
476
|
+
scrollToBottom();
|
|
477
|
+
|
|
478
|
+
// Add click handlers with proper event handling
|
|
479
|
+
const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
|
|
480
|
+
optionElements.forEach(el => {
|
|
481
|
+
// Clear any existing onclick
|
|
482
|
+
el.onclick = null;
|
|
483
|
+
|
|
484
|
+
// Add new click handler with stopPropagation
|
|
485
|
+
el.onclick = (e) => {
|
|
486
|
+
e.stopPropagation(); // Prevent bubbling
|
|
487
|
+
e.preventDefault(); // Prevent default behavior
|
|
488
|
+
// Use multi-select behavior (isSingleSelect = false)
|
|
489
|
+
handleOptionClick(el, field, false);
|
|
490
|
+
};
|
|
491
|
+
});
|
|
492
|
+
}
|
|
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
|
+
|
|
346
896
|
// =============================================================================
|
|
347
897
|
// TEXT/NUMBER INPUT RENDERING
|
|
348
898
|
// =============================================================================
|
|
@@ -362,6 +912,10 @@ function renderTextInput(field, inputType = 'text') {
|
|
|
362
912
|
return;
|
|
363
913
|
}
|
|
364
914
|
|
|
915
|
+
// Get existing data for this field (for pre-filling when editing)
|
|
916
|
+
const existingValue = chatState.data[field];
|
|
917
|
+
console.log(`📝 Pre-filling ${field}:`, existingValue);
|
|
918
|
+
|
|
365
919
|
// Clone existing input element
|
|
366
920
|
const clone = existingInput.cloneNode(true);
|
|
367
921
|
|
|
@@ -376,7 +930,15 @@ function renderTextInput(field, inputType = 'text') {
|
|
|
376
930
|
if (inputElement) {
|
|
377
931
|
inputElement.setAttribute('data-field', field);
|
|
378
932
|
inputElement.name = field;
|
|
379
|
-
|
|
933
|
+
|
|
934
|
+
// Pre-fill value if it exists
|
|
935
|
+
if (existingValue !== undefined && existingValue !== null) {
|
|
936
|
+
inputElement.value = existingValue;
|
|
937
|
+
console.log(` ✅ Pre-filled with: ${existingValue}`);
|
|
938
|
+
} else {
|
|
939
|
+
inputElement.value = '';
|
|
940
|
+
}
|
|
941
|
+
|
|
380
942
|
inputElement.type = inputType === 'number' ? 'number' : 'text';
|
|
381
943
|
|
|
382
944
|
// Add input event to enable Next button when user types
|
|
@@ -668,6 +1230,28 @@ async function showNextStep() {
|
|
|
668
1230
|
} else {
|
|
669
1231
|
enableNextButton();
|
|
670
1232
|
}
|
|
1233
|
+
} else if (inputType === 'multi-select-color') {
|
|
1234
|
+
// Render color options with color blocks
|
|
1235
|
+
renderColorOptions(nextStep.input.options, nextStep.input.field);
|
|
1236
|
+
|
|
1237
|
+
// Enable Next button if input not required (default behavior)
|
|
1238
|
+
if (!inputRequired) {
|
|
1239
|
+
enableNextButton();
|
|
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
|
+
}
|
|
671
1255
|
} else {
|
|
672
1256
|
// Render options (single-select or multi-select)
|
|
673
1257
|
const isSingleSelect = inputType === 'single-select';
|