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.
- package/package.json +1 -1
- package/src/index.js +261 -263
package/package.json
CHANGED
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
|
|
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,
|
|
20
|
-
completed: false
|
|
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
|
|
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
|
-
//
|
|
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('
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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';
|