lisichatbot 1.3.7 → 1.3.8

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 +261 -263
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisichatbot",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "exports": {
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 + MULTI-SELECT-DROPDOWN
5
+ * WITH MULTI-SELECT-DROPDOWN - COMPLETE FIXED VERSION
6
6
  */
7
7
 
8
8
  'use strict';
@@ -16,15 +16,15 @@ let chatState = {
16
16
  data: {},
17
17
  history: [],
18
18
  currentSelection: null,
19
- returnToStep: null, // Track where to return after editing
20
- completed: false // Track if flow is completed
19
+ returnToStep: null,
20
+ completed: false
21
21
  };
22
22
 
23
23
  let elements = {
24
24
  container: null,
25
25
  messages: null,
26
26
  nextBtn: null,
27
- originalNextBtnText: null // Store original button text
27
+ originalNextBtnText: null
28
28
  };
29
29
 
30
30
  let config = {
@@ -78,7 +78,6 @@ function disableNextButton() {
78
78
  function addMessage(content, type = 'bot', hasInput = false, stepNumber = null) {
79
79
  if (!elements.messages) return;
80
80
 
81
- // Find the wrapper element directly by data-chat-element
82
81
  const wrapperSelector = `[data-chat-element="${type}-message-wrapper"]`;
83
82
  const existingWrapper = document.querySelector(wrapperSelector);
84
83
 
@@ -87,20 +86,16 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
87
86
  return;
88
87
  }
89
88
 
90
- // Clone the existing wrapper
91
89
  const clone = existingWrapper.cloneNode(true);
92
90
 
93
- // Immediately hide any edit icons in the clone (regardless of template state)
94
91
  const allEditIcons = clone.querySelectorAll('[data-chat-element="bot-edit-icon"]');
95
92
  allEditIcons.forEach(icon => {
96
93
  icon.style.setProperty('display', 'none', 'important');
97
94
  });
98
95
 
99
- // Add step number if provided
100
96
  if (stepNumber !== null) {
101
97
  clone.setAttribute('data-chat-step', stepNumber);
102
98
 
103
- // Mark all previous instances of this step as NOT latest
104
99
  const previousInstances = elements.messages.querySelectorAll(
105
100
  `[data-chat-element="${type}-message-wrapper"][data-chat-step="${stepNumber}"]`
106
101
  );
@@ -108,11 +103,9 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
108
103
  prev.removeAttribute('data-chat-latest');
109
104
  });
110
105
 
111
- // Mark this as the latest instance of this step
112
106
  clone.setAttribute('data-chat-latest', 'true');
113
107
  }
114
108
 
115
- // Find text element in clone and set content
116
109
  const textElement = clone.querySelector(`[data-chat-element="${type}-message-text"]`);
117
110
  if (textElement) {
118
111
  textElement.textContent = content;
@@ -120,7 +113,6 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
120
113
  console.error(`Element with data-chat-element="${type}-message-text" not found in wrapper`);
121
114
  }
122
115
 
123
- // Handle edit icon for bot messages
124
116
  if (type === 'bot') {
125
117
  console.log(`\nšŸ” Processing bot message - Step ${stepNumber || 'N/A'}, hasInput: ${hasInput}`);
126
118
 
@@ -128,14 +120,11 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
128
120
  if (editIcon) {
129
121
  console.log(' āœ“ Edit icon element found');
130
122
 
131
- // ALWAYS hide first with !important to override any CSS
132
123
  editIcon.style.setProperty('display', 'none', 'important');
133
124
  console.log(' āœ“ Edit icon hidden (display: none !important)');
134
125
 
135
- // Check if this is the latest instance (it should be since we just marked it)
136
126
  const isLatest = clone.hasAttribute('data-chat-latest');
137
127
 
138
- // Only show if this step has input AND it's a PREVIOUS step AND it's the latest instance
139
128
  if (hasInput && stepNumber !== null && stepNumber < chatState.step && isLatest) {
140
129
  editIcon.setAttribute('data-chat-step', stepNumber);
141
130
  editIcon.onclick = (e) => {
@@ -163,13 +152,9 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
163
152
  }
164
153
  }
165
154
 
166
- // Make clone visible
167
155
  clone.style.display = '';
168
-
169
- // Append to messages container
170
156
  elements.messages.appendChild(clone);
171
157
 
172
- // IMPORTANT: Set edit icon display AFTER appending to DOM
173
158
  if (type === 'bot') {
174
159
  const editIconAfterAppend = clone.querySelector('[data-chat-element="bot-edit-icon"]');
175
160
  if (editIconAfterAppend) {
@@ -246,206 +231,7 @@ function updateEditIcons() {
246
231
  }
247
232
 
248
233
  // =============================================================================
249
- // OPTIONS RENDERING WITH CUSTOM ATTRIBUTES
250
- // =============================================================================
251
-
252
- function renderOptions(options, field, isSingleSelect = true) {
253
- if (!elements.messages) return;
254
-
255
- const inputTypeAttr = isSingleSelect ? 'single-select-input' : 'multi-select-input';
256
- const optionSelector = `[data-chat-element="${inputTypeAttr}"]`;
257
- const existingOption = document.querySelector(optionSelector);
258
-
259
- if (!existingOption) {
260
- console.error(`Element with ${optionSelector} not found in HTML. Please add it to your HTML.`);
261
- return;
262
- }
263
-
264
- const existingData = chatState.data[field];
265
- console.log(`šŸ“ Pre-filling ${field}:`, existingData);
266
-
267
- const optionsWrapper = document.createElement('div');
268
- optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
269
- optionsWrapper.style.display = 'flex';
270
- optionsWrapper.style.flexDirection = 'column';
271
- optionsWrapper.style.alignItems = 'flex-start';
272
- optionsWrapper.style.gap = '8px';
273
-
274
- options.forEach((option, index) => {
275
- const optionName = option.name || option;
276
- const optionValue = option.value !== undefined ? option.value : option;
277
- const valueStr = typeof optionValue === 'object' ?
278
- JSON.stringify(optionValue) : String(optionValue);
279
-
280
- const clone = existingOption.cloneNode(true);
281
- clone.style.display = '';
282
-
283
- let shouldBeChecked = false;
284
- if (isSingleSelect) {
285
- shouldBeChecked = existingData !== undefined &&
286
- JSON.stringify(existingData) === JSON.stringify(optionValue);
287
- } else {
288
- shouldBeChecked = Array.isArray(existingData) &&
289
- existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
290
- }
291
-
292
- if (shouldBeChecked) {
293
- clone.classList.add('cf-checked');
294
- clone.style.backgroundColor = config.selectedBackground;
295
- console.log(` āœ… Pre-selected: ${optionName}`);
296
- } else {
297
- clone.classList.remove('cf-checked');
298
- clone.style.backgroundColor = 'transparent';
299
- }
300
-
301
- clone.setAttribute('data-chat-element', inputTypeAttr);
302
- clone.setAttribute('data-field', field);
303
- clone.setAttribute('data-value', valueStr);
304
- clone.setAttribute('data-name', optionName);
305
- clone.setAttribute('data-index', index);
306
-
307
- const input = clone.querySelector('[data-chat-input-element="input"]');
308
- if (input) {
309
- input.setAttribute('data-chat-element', inputTypeAttr);
310
- input.name = field;
311
- input.value = valueStr;
312
- input.checked = shouldBeChecked;
313
- input.onclick = (e) => e.stopPropagation();
314
- }
315
-
316
- const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
317
- if (tickIcon) {
318
- tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
319
- }
320
-
321
- const textElement = clone.querySelector('[data-chat-input-element="text"]');
322
- if (textElement) {
323
- textElement.textContent = optionName;
324
- textElement.style.display = '';
325
- } else {
326
- console.error('Text element not found in option');
327
- }
328
-
329
- optionsWrapper.appendChild(clone);
330
- });
331
-
332
- elements.messages.appendChild(optionsWrapper);
333
- scrollToBottom();
334
-
335
- const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
336
- optionElements.forEach(el => {
337
- el.onclick = null;
338
- el.onclick = (e) => {
339
- e.stopPropagation();
340
- e.preventDefault();
341
- handleOptionClick(el, field, isSingleSelect);
342
- };
343
- });
344
- }
345
-
346
- // =============================================================================
347
- // MULTI-SELECT-COLOR OPTIONS RENDERING
348
- // =============================================================================
349
-
350
- function renderColorOptions(options, field) {
351
- if (!elements.messages) return;
352
-
353
- const optionSelector = '[data-chat-element="multi-select-color"]';
354
- const existingOption = document.querySelector(optionSelector);
355
-
356
- if (!existingOption) {
357
- console.error(`Element with ${optionSelector} not found in HTML. Please add it to your HTML.`);
358
- return;
359
- }
360
-
361
- const existingData = chatState.data[field];
362
- console.log(`šŸ“ Pre-filling ${field} (color):`, existingData);
363
-
364
- const optionsWrapper = document.createElement('div');
365
- optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
366
- optionsWrapper.style.display = 'flex';
367
- optionsWrapper.style.flexDirection = 'column';
368
- optionsWrapper.style.alignItems = 'flex-start';
369
- optionsWrapper.style.gap = '8px';
370
-
371
- options.forEach((option, index) => {
372
- const optionName = option.name || option;
373
- const optionValue = option.value !== undefined ? option.value : option;
374
- const optionColor = option.color || '#cccccc';
375
- const valueStr = typeof optionValue === 'object' ?
376
- JSON.stringify(optionValue) : String(optionValue);
377
-
378
- const clone = existingOption.cloneNode(true);
379
- clone.style.display = '';
380
-
381
- const shouldBeChecked = Array.isArray(existingData) &&
382
- existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
383
-
384
- if (shouldBeChecked) {
385
- clone.classList.add('cf-checked');
386
- clone.style.backgroundColor = config.selectedBackground;
387
- console.log(` āœ… Pre-selected color: ${optionName}`);
388
- } else {
389
- clone.classList.remove('cf-checked');
390
- clone.style.backgroundColor = 'transparent';
391
- }
392
-
393
- clone.setAttribute('data-chat-element', 'multi-select-color');
394
- clone.setAttribute('data-field', field);
395
- clone.setAttribute('data-value', valueStr);
396
- clone.setAttribute('data-name', optionName);
397
- clone.setAttribute('data-index', index);
398
- clone.setAttribute('data-color', optionColor);
399
-
400
- const input = clone.querySelector('[data-chat-input-element="input"]');
401
- if (input) {
402
- input.setAttribute('data-chat-element', 'multi-select-color');
403
- input.name = field;
404
- input.value = valueStr;
405
- input.checked = shouldBeChecked;
406
- input.onclick = (e) => e.stopPropagation();
407
- }
408
-
409
- const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
410
- if (tickIcon) {
411
- tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
412
- }
413
-
414
- const colorBlock = clone.querySelector('[data-chat-input-element="color-block"]');
415
- if (colorBlock) {
416
- colorBlock.style.backgroundColor = optionColor;
417
- colorBlock.style.display = '';
418
- } else {
419
- console.warn('Color block element not found in multi-select-color template');
420
- }
421
-
422
- const textElement = clone.querySelector('[data-chat-input-element="text"]');
423
- if (textElement) {
424
- textElement.textContent = optionName;
425
- textElement.style.display = '';
426
- } else {
427
- console.error('Text element not found in option');
428
- }
429
-
430
- optionsWrapper.appendChild(clone);
431
- });
432
-
433
- elements.messages.appendChild(optionsWrapper);
434
- scrollToBottom();
435
-
436
- const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
437
- optionElements.forEach(el => {
438
- el.onclick = null;
439
- el.onclick = (e) => {
440
- e.stopPropagation();
441
- e.preventDefault();
442
- handleOptionClick(el, field, false);
443
- };
444
- });
445
- }
446
-
447
- // =============================================================================
448
- // MULTI-SELECT DROPDOWN RENDERING
234
+ // MULTI-SELECT DROPDOWN RENDERING - COMPLETE FIXED VERSION
449
235
  // =============================================================================
450
236
 
451
237
  function renderMultiSelectDropdown(options, field) {
@@ -454,44 +240,48 @@ function renderMultiSelectDropdown(options, field) {
454
240
  console.log(`\nšŸŽØ renderMultiSelectDropdown for field: ${field}`);
455
241
  console.log(`Options:`, options);
456
242
 
457
- // Find template
458
243
  const template = document.querySelector('[data-chat-element="multi-select-dropdown"]');
459
244
  if (!template) {
460
245
  console.error('multi-select-dropdown template not found');
461
246
  return;
462
247
  }
463
248
 
464
- // Clone template
465
249
  const clone = template.cloneNode(true);
466
250
  clone.style.display = '';
467
251
  clone.setAttribute('data-field', field);
468
252
 
469
- // Get all elements
470
253
  const dropdown = clone.querySelector('[data-chat-input-element="dropdown"]');
471
254
  const optionsWrapper = clone.querySelector('[data-chat-input-element="dropdown-options-wrapper"]');
472
255
  const searchBar = clone.querySelector('[data-chat-input-element="search-bar"]');
473
- const optionsContainer = clone.querySelector('[data-chat-input-element="dropdown-options-container"]');
256
+ const optionsContainer = clone.querySelector('.lisi-app--dropdown_options-list');
474
257
  const optionTemplate = clone.querySelector('[data-chat-input-element="dropdown-option-wrapper"]');
475
258
  const tagsContainer = clone.querySelector('[data-chat-input-element="tags-container"]');
476
259
  const tagTemplate = clone.querySelector('[data-chat-input-element="tag-wrapper"]');
477
260
 
478
261
  if (!dropdown || !optionsWrapper || !searchBar || !optionsContainer || !optionTemplate || !tagsContainer || !tagTemplate) {
479
262
  console.error('Multi-select dropdown: Missing required elements');
263
+ console.error({
264
+ dropdown: !!dropdown,
265
+ optionsWrapper: !!optionsWrapper,
266
+ searchBar: !!searchBar,
267
+ optionsContainer: !!optionsContainer,
268
+ optionTemplate: !!optionTemplate,
269
+ tagsContainer: !!tagsContainer,
270
+ tagTemplate: !!tagTemplate
271
+ });
480
272
  return;
481
273
  }
482
274
 
483
- // Store original template option and tag, then remove from DOM
484
275
  const optionTemplateClone = optionTemplate.cloneNode(true);
485
276
  const tagTemplateClone = tagTemplate.cloneNode(true);
486
277
  optionTemplate.remove();
487
278
  tagTemplate.remove();
488
279
 
489
- // Get existing selections for pre-filling
280
+ // āœ… Get existing data for pre-filling (edit mode)
490
281
  const existingData = chatState.data[field] || [];
491
282
  const selectedValues = new Set(existingData);
492
- console.log(`šŸ“ Pre-filling selections:`, Array.from(selectedValues));
283
+ console.log(`šŸ“ Pre-filling selections for ${field}:`, Array.from(selectedValues));
493
284
 
494
- // Store all options for filtering
495
285
  const allOptions = [];
496
286
 
497
287
  // Render options
@@ -506,15 +296,21 @@ function renderMultiSelectDropdown(options, field) {
506
296
 
507
297
  if (nameEl) nameEl.textContent = option.name;
508
298
 
509
- // Check if pre-selected
299
+ // āœ… Check if pre-selected (for edit mode)
510
300
  const isSelected = selectedValues.has(option.value);
511
301
  if (isSelected) {
512
302
  optionEl.style.backgroundColor = config.selectedBackground;
513
- if (tickIcon) tickIcon.style.display = '';
303
+ if (tickIcon) {
304
+ // āœ… FIX: Use display: block instead of empty string
305
+ tickIcon.style.display = 'block';
306
+ }
514
307
  console.log(` āœ… Pre-selected: ${option.name}`);
308
+ } else {
309
+ if (tickIcon) {
310
+ tickIcon.style.display = 'none';
311
+ }
515
312
  }
516
313
 
517
- // Click handler - prevents dropdown from closing
518
314
  optionEl.onclick = (e) => {
519
315
  e.stopPropagation();
520
316
  toggleOption(option, optionEl, tickIcon, field);
@@ -524,7 +320,6 @@ function renderMultiSelectDropdown(options, field) {
524
320
  allOptions.push({ element: optionEl, option });
525
321
  });
526
322
 
527
- // Toggle dropdown visibility - click to open/close
528
323
  dropdown.onclick = (e) => {
529
324
  e.stopPropagation();
530
325
  const isVisible = optionsWrapper.style.display !== 'none';
@@ -547,7 +342,6 @@ function renderMultiSelectDropdown(options, field) {
547
342
  }
548
343
  };
549
344
 
550
- // Close dropdown when clicking outside
551
345
  const closeDropdownHandler = (e) => {
552
346
  if (!clone.contains(e.target)) {
553
347
  if (optionsWrapper.style.display !== 'none') {
@@ -560,7 +354,6 @@ function renderMultiSelectDropdown(options, field) {
560
354
  document.addEventListener('click', closeDropdownHandler);
561
355
  clone.dataset.closeHandler = 'attached';
562
356
 
563
- // Search functionality - filters options by name or value
564
357
  searchBar.oninput = (e) => {
565
358
  const searchTerm = e.target.value.toLowerCase();
566
359
  console.log(`šŸ” Searching for: "${searchTerm}"`);
@@ -574,12 +367,10 @@ function renderMultiSelectDropdown(options, field) {
574
367
  });
575
368
  };
576
369
 
577
- // Prevent search bar clicks from closing dropdown
578
370
  searchBar.onclick = (e) => {
579
371
  e.stopPropagation();
580
372
  };
581
373
 
582
- // Toggle option selection
583
374
  function toggleOption(option, optionEl, tickIcon, field) {
584
375
  const currentSelections = chatState.data[field] || [];
585
376
  const isCurrentlySelected = currentSelections.includes(option.value);
@@ -587,20 +378,23 @@ function renderMultiSelectDropdown(options, field) {
587
378
  console.log(`šŸŽÆ Toggle option: ${option.name}`, { isCurrentlySelected });
588
379
 
589
380
  if (isCurrentlySelected) {
590
- // Deselect
591
381
  chatState.data[field] = currentSelections.filter(v => v !== option.value);
592
382
  optionEl.style.backgroundColor = '';
593
- if (tickIcon) tickIcon.style.display = 'none';
383
+ if (tickIcon) {
384
+ // āœ… FIX: Explicitly set to 'none'
385
+ tickIcon.style.display = 'none';
386
+ }
594
387
  console.log(` āŒ Deselected: ${option.name}`);
595
388
  } else {
596
- // Select
597
389
  chatState.data[field] = [...currentSelections, option.value];
598
390
  optionEl.style.backgroundColor = config.selectedBackground;
599
- if (tickIcon) tickIcon.style.display = '';
391
+ if (tickIcon) {
392
+ // āœ… FIX: Explicitly set to 'block'
393
+ tickIcon.style.display = 'block';
394
+ }
600
395
  console.log(` āœ… Selected: ${option.name}`);
601
396
  }
602
397
 
603
- // Update current selection for handleNext
604
398
  chatState.currentSelection = {
605
399
  field,
606
400
  value: chatState.data[field],
@@ -612,20 +406,24 @@ function renderMultiSelectDropdown(options, field) {
612
406
 
613
407
  console.log(` šŸ“Š Current selections:`, chatState.data[field]);
614
408
 
615
- // Re-render tags
616
409
  renderTags(field, options, tagsContainer, tagTemplateClone, optionsContainer);
617
410
 
618
- // Enable Next button if at least one selected
619
411
  if (chatState.data[field].length > 0) {
620
412
  enableNextButton();
621
413
  } else {
622
- disableNextButton();
414
+ // āœ… FIX: Check if input is required before disabling
415
+ const currentStep = flowData.flow[chatState.step];
416
+ const inputRequired = currentStep.inputRequired === true;
417
+
418
+ if (inputRequired) {
419
+ disableNextButton();
420
+ } else {
421
+ enableNextButton();
422
+ }
623
423
  }
624
424
  }
625
425
 
626
- // Render tags - displays selected options as removable tags
627
426
  function renderTags(field, options, tagsContainer, tagTemplate, optionsContainer) {
628
- // Clear existing tags (except template)
629
427
  const existingTags = tagsContainer.querySelectorAll('[data-chat-input-element="tag-wrapper"]');
630
428
  existingTags.forEach(tag => {
631
429
  if (tag.style.display !== 'none') {
@@ -651,32 +449,28 @@ function renderMultiSelectDropdown(options, field) {
651
449
  textEl.textContent = option.name;
652
450
  }
653
451
 
654
- // āœ… CRITICAL: Set cross icon display to BLOCK and handle click
655
452
  if (crossIcon) {
656
- // Set display to block (as requested by user)
657
453
  crossIcon.style.display = 'block';
658
454
  console.log(` āœ… Cross icon visible for: ${option.name}`);
659
455
 
660
- // Handle click to remove tag
661
456
  crossIcon.onclick = (e) => {
662
457
  e.stopPropagation();
663
458
  console.log(`šŸ—‘ļø Removing tag: ${option.name}`);
664
459
 
665
- // Remove from selections array
666
460
  chatState.data[field] = chatState.data[field].filter(v => v !== value);
667
461
 
668
- // Update option UI in dropdown - remove background and tick
669
462
  const optionEl = optionsContainer.querySelector(`[data-value='${JSON.stringify(value)}']`);
670
463
  if (optionEl) {
671
464
  optionEl.style.backgroundColor = '';
672
465
  const tickIcon = optionEl.querySelector('[data-chat-input-element="dropdown-option-tick-icon"]');
673
- if (tickIcon) tickIcon.style.display = 'none';
466
+ if (tickIcon) {
467
+ // āœ… FIX: Explicitly set to 'none'
468
+ tickIcon.style.display = 'none';
469
+ }
674
470
  }
675
471
 
676
- // Remove tag from DOM
677
472
  tagEl.remove();
678
473
 
679
- // Update current selection
680
474
  chatState.currentSelection = {
681
475
  field,
682
476
  value: chatState.data[field],
@@ -688,9 +482,16 @@ function renderMultiSelectDropdown(options, field) {
688
482
 
689
483
  console.log(` šŸ“Š Remaining selections:`, chatState.data[field]);
690
484
 
691
- // Disable Next button if no selections remain
692
485
  if (chatState.data[field].length === 0) {
693
- disableNextButton();
486
+ // āœ… FIX: Check if input is required before disabling
487
+ const currentStep = flowData.flow[chatState.step];
488
+ const inputRequired = currentStep.inputRequired === true;
489
+
490
+ if (inputRequired) {
491
+ disableNextButton();
492
+ } else {
493
+ enableNextButton();
494
+ }
694
495
  }
695
496
  };
696
497
  }
@@ -699,19 +500,217 @@ function renderMultiSelectDropdown(options, field) {
699
500
  });
700
501
  }
701
502
 
702
- // Initial tag render if pre-filled
503
+ // āœ… Initial tag render if pre-filled (edit mode)
703
504
  if (selectedValues.size > 0) {
704
505
  renderTags(field, options, tagsContainer, tagTemplateClone, optionsContainer);
705
- enableNextButton();
506
+ console.log(` āœ… Pre-filled ${selectedValues.size} selections, Next button enabled`);
706
507
  }
707
508
 
708
- // Append to messages
709
509
  elements.messages.appendChild(clone);
710
510
  scrollToBottom();
711
511
 
712
512
  console.log(`āœ… Multi-select dropdown rendered for ${field}`);
713
513
  }
714
514
 
515
+ // =============================================================================
516
+ // OPTIONS RENDERING WITH CUSTOM ATTRIBUTES
517
+ // =============================================================================
518
+
519
+ function renderOptions(options, field, isSingleSelect = true) {
520
+ if (!elements.messages) return;
521
+
522
+ const inputTypeAttr = isSingleSelect ? 'single-select-input' : 'multi-select-input';
523
+ const optionSelector = `[data-chat-element="${inputTypeAttr}"]`;
524
+ const existingOption = document.querySelector(optionSelector);
525
+
526
+ if (!existingOption) {
527
+ console.error(`Element with ${optionSelector} not found in HTML. Please add it to your HTML.`);
528
+ return;
529
+ }
530
+
531
+ const existingData = chatState.data[field];
532
+ console.log(`šŸ“ Pre-filling ${field}:`, existingData);
533
+
534
+ const optionsWrapper = document.createElement('div');
535
+ optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
536
+ optionsWrapper.style.display = 'flex';
537
+ optionsWrapper.style.flexDirection = 'column';
538
+ optionsWrapper.style.alignItems = 'flex-start';
539
+ optionsWrapper.style.gap = '8px';
540
+
541
+ options.forEach((option, index) => {
542
+ const optionName = option.name || option;
543
+ const optionValue = option.value !== undefined ? option.value : option;
544
+ const valueStr = typeof optionValue === 'object' ?
545
+ JSON.stringify(optionValue) : String(optionValue);
546
+
547
+ const clone = existingOption.cloneNode(true);
548
+ clone.style.display = '';
549
+
550
+ let shouldBeChecked = false;
551
+ if (isSingleSelect) {
552
+ shouldBeChecked = existingData !== undefined &&
553
+ JSON.stringify(existingData) === JSON.stringify(optionValue);
554
+ } else {
555
+ shouldBeChecked = Array.isArray(existingData) &&
556
+ existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
557
+ }
558
+
559
+ if (shouldBeChecked) {
560
+ clone.classList.add('cf-checked');
561
+ clone.style.backgroundColor = config.selectedBackground;
562
+ console.log(` āœ… Pre-selected: ${optionName}`);
563
+ } else {
564
+ clone.classList.remove('cf-checked');
565
+ clone.style.backgroundColor = 'transparent';
566
+ }
567
+
568
+ clone.setAttribute('data-chat-element', inputTypeAttr);
569
+ clone.setAttribute('data-field', field);
570
+ clone.setAttribute('data-value', valueStr);
571
+ clone.setAttribute('data-name', optionName);
572
+ clone.setAttribute('data-index', index);
573
+
574
+ const input = clone.querySelector('[data-chat-input-element="input"]');
575
+ if (input) {
576
+ input.setAttribute('data-chat-element', inputTypeAttr);
577
+ input.name = field;
578
+ input.value = valueStr;
579
+ input.checked = shouldBeChecked;
580
+ input.onclick = (e) => e.stopPropagation();
581
+ }
582
+
583
+ const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
584
+ if (tickIcon) {
585
+ tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
586
+ }
587
+
588
+ const textElement = clone.querySelector('[data-chat-input-element="text"]');
589
+ if (textElement) {
590
+ textElement.textContent = optionName;
591
+ textElement.style.display = '';
592
+ } else {
593
+ console.error('Text element not found in option');
594
+ }
595
+
596
+ optionsWrapper.appendChild(clone);
597
+ });
598
+
599
+ elements.messages.appendChild(optionsWrapper);
600
+ scrollToBottom();
601
+
602
+ const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
603
+ optionElements.forEach(el => {
604
+ el.onclick = null;
605
+ el.onclick = (e) => {
606
+ e.stopPropagation();
607
+ e.preventDefault();
608
+ handleOptionClick(el, field, isSingleSelect);
609
+ };
610
+ });
611
+ }
612
+
613
+ // =============================================================================
614
+ // MULTI-SELECT-COLOR OPTIONS RENDERING
615
+ // =============================================================================
616
+
617
+ function renderColorOptions(options, field) {
618
+ if (!elements.messages) return;
619
+
620
+ const optionSelector = '[data-chat-element="multi-select-color"]';
621
+ const existingOption = document.querySelector(optionSelector);
622
+
623
+ if (!existingOption) {
624
+ console.error(`Element with ${optionSelector} not found in HTML. Please add it to your HTML.`);
625
+ return;
626
+ }
627
+
628
+ const existingData = chatState.data[field];
629
+ console.log(`šŸ“ Pre-filling ${field} (color):`, existingData);
630
+
631
+ const optionsWrapper = document.createElement('div');
632
+ optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
633
+ optionsWrapper.style.display = 'flex';
634
+ optionsWrapper.style.flexDirection = 'column';
635
+ optionsWrapper.style.alignItems = 'flex-start';
636
+ optionsWrapper.style.gap = '8px';
637
+
638
+ options.forEach((option, index) => {
639
+ const optionName = option.name || option;
640
+ const optionValue = option.value !== undefined ? option.value : option;
641
+ const optionColor = option.color || '#cccccc';
642
+ const valueStr = typeof optionValue === 'object' ?
643
+ JSON.stringify(optionValue) : String(optionValue);
644
+
645
+ const clone = existingOption.cloneNode(true);
646
+ clone.style.display = '';
647
+
648
+ const shouldBeChecked = Array.isArray(existingData) &&
649
+ existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
650
+
651
+ if (shouldBeChecked) {
652
+ clone.classList.add('cf-checked');
653
+ clone.style.backgroundColor = config.selectedBackground;
654
+ console.log(` āœ… Pre-selected color: ${optionName}`);
655
+ } else {
656
+ clone.classList.remove('cf-checked');
657
+ clone.style.backgroundColor = 'transparent';
658
+ }
659
+
660
+ clone.setAttribute('data-chat-element', 'multi-select-color');
661
+ clone.setAttribute('data-field', field);
662
+ clone.setAttribute('data-value', valueStr);
663
+ clone.setAttribute('data-name', optionName);
664
+ clone.setAttribute('data-index', index);
665
+ clone.setAttribute('data-color', optionColor);
666
+
667
+ const input = clone.querySelector('[data-chat-input-element="input"]');
668
+ if (input) {
669
+ input.setAttribute('data-chat-element', 'multi-select-color');
670
+ input.name = field;
671
+ input.value = valueStr;
672
+ input.checked = shouldBeChecked;
673
+ input.onclick = (e) => e.stopPropagation();
674
+ }
675
+
676
+ const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
677
+ if (tickIcon) {
678
+ tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
679
+ }
680
+
681
+ const colorBlock = clone.querySelector('[data-chat-input-element="color-block"]');
682
+ if (colorBlock) {
683
+ colorBlock.style.backgroundColor = optionColor;
684
+ colorBlock.style.display = '';
685
+ } else {
686
+ console.warn('Color block element not found in multi-select-color template');
687
+ }
688
+
689
+ const textElement = clone.querySelector('[data-chat-input-element="text"]');
690
+ if (textElement) {
691
+ textElement.textContent = optionName;
692
+ textElement.style.display = '';
693
+ } else {
694
+ console.error('Text element not found in option');
695
+ }
696
+
697
+ optionsWrapper.appendChild(clone);
698
+ });
699
+
700
+ elements.messages.appendChild(optionsWrapper);
701
+ scrollToBottom();
702
+
703
+ const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
704
+ optionElements.forEach(el => {
705
+ el.onclick = null;
706
+ el.onclick = (e) => {
707
+ e.stopPropagation();
708
+ e.preventDefault();
709
+ handleOptionClick(el, field, false);
710
+ };
711
+ });
712
+ }
713
+
715
714
  // =============================================================================
716
715
  // SINGLE-SELECT-CUSTOM (WITH MIN/MAX RANGE)
717
716
  // =============================================================================
@@ -737,7 +736,6 @@ function renderCustomSelectOptions(options, field, customConfig) {
737
736
  optionsWrapper.style.alignItems = 'flex-start';
738
737
  optionsWrapper.style.gap = '8px';
739
738
 
740
- // Render regular options
741
739
  options.forEach((option, index) => {
742
740
  const optionName = option.name || option;
743
741
  const optionValue = option.value !== undefined ? option.value : option;
@@ -787,7 +785,6 @@ function renderCustomSelectOptions(options, field, customConfig) {
787
785
  optionsWrapper.appendChild(clone);
788
786
  });
789
787
 
790
- // Render custom option
791
788
  if (customConfig) {
792
789
  const customClone = existingOption.cloneNode(true);
793
790
  customClone.style.display = '';
@@ -1476,7 +1473,6 @@ async function handleNext() {
1476
1473
  return;
1477
1474
  }
1478
1475
 
1479
- // VALIDATION: Check for single-select-custom validation before proceeding
1480
1476
  if (currentStep.inputType === 'single-select-custom' && currentStep.input) {
1481
1477
  const field = currentStep.input.field;
1482
1478
  const customConfig = currentStep.input.custom;
@@ -1690,14 +1686,16 @@ async function showNextStep() {
1690
1686
  enableNextButton();
1691
1687
  }
1692
1688
  } else if (inputType === 'multi-select-dropdown') {
1693
- // Render multi-select dropdown
1689
+ // āœ… Render multi-select dropdown
1694
1690
  renderMultiSelectDropdown(nextStep.input.options, nextStep.input.field);
1695
1691
 
1696
- // Disable Next button initially if required
1692
+ // āœ… FIX: Enable Next button initially if NOT required
1697
1693
  if (inputRequired) {
1698
1694
  disableNextButton();
1695
+ console.log(' šŸ”’ Next button disabled initially (input required)');
1699
1696
  } else {
1700
1697
  enableNextButton();
1698
+ console.log(' šŸ”“ Next button enabled (input NOT required)');
1701
1699
  }
1702
1700
  } else {
1703
1701
  const isSingleSelect = inputType === 'single-select';