lisichatbot 1.3.5 → 1.3.7
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 +335 -288
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
|
|
5
|
+
* WITH CUSTOM DATA ATTRIBUTES + MULTI-SELECT-DROPDOWN
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
'use strict';
|
|
@@ -161,9 +161,6 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
161
161
|
console.warn(' ⚠️ Edit icon element not found in bot-message-wrapper!');
|
|
162
162
|
console.warn(' → Make sure you have: <span data-chat-element="bot-edit-icon">✏️</span>');
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
// REMOVED: No longer setting wrapper flex properties
|
|
166
|
-
// User controls wrapper styling with their own CSS
|
|
167
164
|
}
|
|
168
165
|
|
|
169
166
|
// Make clone visible
|
|
@@ -173,21 +170,17 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
173
170
|
elements.messages.appendChild(clone);
|
|
174
171
|
|
|
175
172
|
// IMPORTANT: Set edit icon display AFTER appending to DOM
|
|
176
|
-
// This ensures our styles override any CSS rules
|
|
177
173
|
if (type === 'bot') {
|
|
178
174
|
const editIconAfterAppend = clone.querySelector('[data-chat-element="bot-edit-icon"]');
|
|
179
175
|
if (editIconAfterAppend) {
|
|
180
|
-
// Force hide with !important AFTER append
|
|
181
176
|
editIconAfterAppend.style.setProperty('display', 'none', 'important');
|
|
182
177
|
editIconAfterAppend.style.setProperty('margin-left', '8px', 'important');
|
|
183
178
|
|
|
184
179
|
const isLatest = clone.hasAttribute('data-chat-latest');
|
|
185
180
|
|
|
186
|
-
// Only show if has input AND it's a previous step AND it's latest instance
|
|
187
181
|
if (hasInput && stepNumber !== null && stepNumber < chatState.step && isLatest) {
|
|
188
182
|
editIconAfterAppend.style.setProperty('display', 'flex', 'important');
|
|
189
183
|
|
|
190
|
-
// Debug: Check spacing
|
|
191
184
|
setTimeout(() => {
|
|
192
185
|
const computedMargin = window.getComputedStyle(editIconAfterAppend).marginLeft;
|
|
193
186
|
const computedDisplay = window.getComputedStyle(editIconAfterAppend).display;
|
|
@@ -205,7 +198,6 @@ function addMessage(content, type = 'bot', hasInput = false, stepNumber = null)
|
|
|
205
198
|
// =============================================================================
|
|
206
199
|
|
|
207
200
|
function updateEditIcons() {
|
|
208
|
-
// Find all rendered bot message wrappers
|
|
209
201
|
const allBotMessages = elements.messages.querySelectorAll('[data-chat-element="bot-message-wrapper"]');
|
|
210
202
|
|
|
211
203
|
console.log(`\n🔄 Updating edit icons - Current step: ${chatState.step}, Completed: ${chatState.completed}`);
|
|
@@ -216,20 +208,16 @@ function updateEditIcons() {
|
|
|
216
208
|
const isLatest = wrapper.hasAttribute('data-chat-latest');
|
|
217
209
|
|
|
218
210
|
if (editIcon && !isNaN(stepNumber)) {
|
|
219
|
-
// Check if this step has input by looking at the flow
|
|
220
211
|
const stepData = flowData.flow[stepNumber];
|
|
221
212
|
const hasInput = stepData && !!stepData.input;
|
|
222
213
|
|
|
223
|
-
// Hide ALL icons if flow is completed
|
|
224
214
|
if (chatState.completed) {
|
|
225
215
|
editIcon.style.setProperty('display', 'none', 'important');
|
|
226
216
|
console.log(` 🏁 Step ${stepNumber}: Icon HIDDEN (flow completed)`);
|
|
227
217
|
return;
|
|
228
218
|
}
|
|
229
219
|
|
|
230
|
-
// Only show icon if: has input AND is previous step AND is latest instance
|
|
231
220
|
if (hasInput && stepNumber < chatState.step && isLatest) {
|
|
232
|
-
// Set onclick handler
|
|
233
221
|
editIcon.onclick = (e) => {
|
|
234
222
|
e.stopPropagation();
|
|
235
223
|
e.preventDefault();
|
|
@@ -264,10 +252,7 @@ function updateEditIcons() {
|
|
|
264
252
|
function renderOptions(options, field, isSingleSelect = true) {
|
|
265
253
|
if (!elements.messages) return;
|
|
266
254
|
|
|
267
|
-
// Determine input type attribute value
|
|
268
255
|
const inputTypeAttr = isSingleSelect ? 'single-select-input' : 'multi-select-input';
|
|
269
|
-
|
|
270
|
-
// Find existing option element in HTML by data-chat-element
|
|
271
256
|
const optionSelector = `[data-chat-element="${inputTypeAttr}"]`;
|
|
272
257
|
const existingOption = document.querySelector(optionSelector);
|
|
273
258
|
|
|
@@ -276,44 +261,34 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
276
261
|
return;
|
|
277
262
|
}
|
|
278
263
|
|
|
279
|
-
// Get existing data for this field (for pre-filling when editing)
|
|
280
264
|
const existingData = chatState.data[field];
|
|
281
265
|
console.log(`📝 Pre-filling ${field}:`, existingData);
|
|
282
266
|
|
|
283
|
-
// Create wrapper to hold all options
|
|
284
267
|
const optionsWrapper = document.createElement('div');
|
|
285
268
|
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
286
269
|
optionsWrapper.style.display = 'flex';
|
|
287
270
|
optionsWrapper.style.flexDirection = 'column';
|
|
288
|
-
optionsWrapper.style.alignItems = 'flex-start';
|
|
271
|
+
optionsWrapper.style.alignItems = 'flex-start';
|
|
289
272
|
optionsWrapper.style.gap = '8px';
|
|
290
273
|
|
|
291
|
-
// Clone and fill option element for each option
|
|
292
274
|
options.forEach((option, index) => {
|
|
293
275
|
const optionName = option.name || option;
|
|
294
276
|
const optionValue = option.value !== undefined ? option.value : option;
|
|
295
277
|
const valueStr = typeof optionValue === 'object' ?
|
|
296
278
|
JSON.stringify(optionValue) : String(optionValue);
|
|
297
279
|
|
|
298
|
-
// Clone existing option element
|
|
299
280
|
const clone = existingOption.cloneNode(true);
|
|
300
|
-
|
|
301
|
-
// Make clone visible (remove display:none from template)
|
|
302
281
|
clone.style.display = '';
|
|
303
282
|
|
|
304
|
-
// Check if this option should be pre-selected
|
|
305
283
|
let shouldBeChecked = false;
|
|
306
284
|
if (isSingleSelect) {
|
|
307
|
-
// For single-select, check if value matches existing data
|
|
308
285
|
shouldBeChecked = existingData !== undefined &&
|
|
309
286
|
JSON.stringify(existingData) === JSON.stringify(optionValue);
|
|
310
287
|
} else {
|
|
311
|
-
// For multi-select, check if value is in array
|
|
312
288
|
shouldBeChecked = Array.isArray(existingData) &&
|
|
313
289
|
existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
|
|
314
290
|
}
|
|
315
291
|
|
|
316
|
-
// Set checked state
|
|
317
292
|
if (shouldBeChecked) {
|
|
318
293
|
clone.classList.add('cf-checked');
|
|
319
294
|
clone.style.backgroundColor = config.selectedBackground;
|
|
@@ -323,56 +298,46 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
323
298
|
clone.style.backgroundColor = 'transparent';
|
|
324
299
|
}
|
|
325
300
|
|
|
326
|
-
// Set data attributes on container
|
|
327
301
|
clone.setAttribute('data-chat-element', inputTypeAttr);
|
|
328
302
|
clone.setAttribute('data-field', field);
|
|
329
303
|
clone.setAttribute('data-value', valueStr);
|
|
330
304
|
clone.setAttribute('data-name', optionName);
|
|
331
305
|
clone.setAttribute('data-index', index);
|
|
332
306
|
|
|
333
|
-
// Find and set input
|
|
334
307
|
const input = clone.querySelector('[data-chat-input-element="input"]');
|
|
335
308
|
if (input) {
|
|
336
309
|
input.setAttribute('data-chat-element', inputTypeAttr);
|
|
337
310
|
input.name = field;
|
|
338
311
|
input.value = valueStr;
|
|
339
|
-
input.checked = shouldBeChecked;
|
|
340
|
-
input.onclick = (e) => e.stopPropagation();
|
|
312
|
+
input.checked = shouldBeChecked;
|
|
313
|
+
input.onclick = (e) => e.stopPropagation();
|
|
341
314
|
}
|
|
342
315
|
|
|
343
|
-
// Find and set tick icon visibility
|
|
344
316
|
const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
345
317
|
if (tickIcon) {
|
|
346
318
|
tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
|
|
347
319
|
}
|
|
348
320
|
|
|
349
|
-
// Find and set text
|
|
350
321
|
const textElement = clone.querySelector('[data-chat-input-element="text"]');
|
|
351
322
|
if (textElement) {
|
|
352
323
|
textElement.textContent = optionName;
|
|
353
|
-
textElement.style.display = '';
|
|
324
|
+
textElement.style.display = '';
|
|
354
325
|
} else {
|
|
355
326
|
console.error('Text element not found in option');
|
|
356
327
|
}
|
|
357
328
|
|
|
358
|
-
// Append to wrapper
|
|
359
329
|
optionsWrapper.appendChild(clone);
|
|
360
330
|
});
|
|
361
331
|
|
|
362
|
-
// Append wrapper to messages
|
|
363
332
|
elements.messages.appendChild(optionsWrapper);
|
|
364
333
|
scrollToBottom();
|
|
365
334
|
|
|
366
|
-
// Add click handlers with proper event handling
|
|
367
335
|
const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
|
|
368
336
|
optionElements.forEach(el => {
|
|
369
|
-
// Clear any existing onclick
|
|
370
337
|
el.onclick = null;
|
|
371
|
-
|
|
372
|
-
// Add new click handler with stopPropagation
|
|
373
338
|
el.onclick = (e) => {
|
|
374
|
-
e.stopPropagation();
|
|
375
|
-
e.preventDefault();
|
|
339
|
+
e.stopPropagation();
|
|
340
|
+
e.preventDefault();
|
|
376
341
|
handleOptionClick(el, field, isSingleSelect);
|
|
377
342
|
};
|
|
378
343
|
});
|
|
@@ -385,7 +350,6 @@ function renderOptions(options, field, isSingleSelect = true) {
|
|
|
385
350
|
function renderColorOptions(options, field) {
|
|
386
351
|
if (!elements.messages) return;
|
|
387
352
|
|
|
388
|
-
// Find existing color option element in HTML by data-chat-element
|
|
389
353
|
const optionSelector = '[data-chat-element="multi-select-color"]';
|
|
390
354
|
const existingOption = document.querySelector(optionSelector);
|
|
391
355
|
|
|
@@ -394,37 +358,29 @@ function renderColorOptions(options, field) {
|
|
|
394
358
|
return;
|
|
395
359
|
}
|
|
396
360
|
|
|
397
|
-
// Get existing data for this field (for pre-filling when editing)
|
|
398
361
|
const existingData = chatState.data[field];
|
|
399
362
|
console.log(`📝 Pre-filling ${field} (color):`, existingData);
|
|
400
363
|
|
|
401
|
-
// Create wrapper to hold all options
|
|
402
364
|
const optionsWrapper = document.createElement('div');
|
|
403
365
|
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
404
366
|
optionsWrapper.style.display = 'flex';
|
|
405
367
|
optionsWrapper.style.flexDirection = 'column';
|
|
406
|
-
optionsWrapper.style.alignItems = 'flex-start';
|
|
368
|
+
optionsWrapper.style.alignItems = 'flex-start';
|
|
407
369
|
optionsWrapper.style.gap = '8px';
|
|
408
370
|
|
|
409
|
-
// Clone and fill option element for each option
|
|
410
371
|
options.forEach((option, index) => {
|
|
411
372
|
const optionName = option.name || option;
|
|
412
373
|
const optionValue = option.value !== undefined ? option.value : option;
|
|
413
|
-
const optionColor = option.color || '#cccccc';
|
|
374
|
+
const optionColor = option.color || '#cccccc';
|
|
414
375
|
const valueStr = typeof optionValue === 'object' ?
|
|
415
376
|
JSON.stringify(optionValue) : String(optionValue);
|
|
416
377
|
|
|
417
|
-
// Clone existing option element
|
|
418
378
|
const clone = existingOption.cloneNode(true);
|
|
419
|
-
|
|
420
|
-
// Make clone visible (remove display:none from template)
|
|
421
379
|
clone.style.display = '';
|
|
422
380
|
|
|
423
|
-
// Check if this option should be pre-selected (multi-select behavior)
|
|
424
381
|
const shouldBeChecked = Array.isArray(existingData) &&
|
|
425
382
|
existingData.some(v => JSON.stringify(v) === JSON.stringify(optionValue));
|
|
426
383
|
|
|
427
|
-
// Set checked state
|
|
428
384
|
if (shouldBeChecked) {
|
|
429
385
|
clone.classList.add('cf-checked');
|
|
430
386
|
clone.style.backgroundColor = config.selectedBackground;
|
|
@@ -434,7 +390,6 @@ function renderColorOptions(options, field) {
|
|
|
434
390
|
clone.style.backgroundColor = 'transparent';
|
|
435
391
|
}
|
|
436
392
|
|
|
437
|
-
// Set data attributes on container
|
|
438
393
|
clone.setAttribute('data-chat-element', 'multi-select-color');
|
|
439
394
|
clone.setAttribute('data-field', field);
|
|
440
395
|
clone.setAttribute('data-value', valueStr);
|
|
@@ -442,64 +397,321 @@ function renderColorOptions(options, field) {
|
|
|
442
397
|
clone.setAttribute('data-index', index);
|
|
443
398
|
clone.setAttribute('data-color', optionColor);
|
|
444
399
|
|
|
445
|
-
// Find and set input
|
|
446
400
|
const input = clone.querySelector('[data-chat-input-element="input"]');
|
|
447
401
|
if (input) {
|
|
448
402
|
input.setAttribute('data-chat-element', 'multi-select-color');
|
|
449
403
|
input.name = field;
|
|
450
404
|
input.value = valueStr;
|
|
451
|
-
input.checked = shouldBeChecked;
|
|
452
|
-
input.onclick = (e) => e.stopPropagation();
|
|
405
|
+
input.checked = shouldBeChecked;
|
|
406
|
+
input.onclick = (e) => e.stopPropagation();
|
|
453
407
|
}
|
|
454
408
|
|
|
455
|
-
// Find and set tick icon visibility
|
|
456
409
|
const tickIcon = clone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
457
410
|
if (tickIcon) {
|
|
458
411
|
tickIcon.style.display = shouldBeChecked ? 'block' : 'none';
|
|
459
412
|
}
|
|
460
413
|
|
|
461
|
-
// Find and set color block
|
|
462
414
|
const colorBlock = clone.querySelector('[data-chat-input-element="color-block"]');
|
|
463
415
|
if (colorBlock) {
|
|
464
416
|
colorBlock.style.backgroundColor = optionColor;
|
|
465
|
-
colorBlock.style.display = '';
|
|
417
|
+
colorBlock.style.display = '';
|
|
466
418
|
} else {
|
|
467
419
|
console.warn('Color block element not found in multi-select-color template');
|
|
468
420
|
}
|
|
469
421
|
|
|
470
|
-
// Find and set text
|
|
471
422
|
const textElement = clone.querySelector('[data-chat-input-element="text"]');
|
|
472
423
|
if (textElement) {
|
|
473
424
|
textElement.textContent = optionName;
|
|
474
|
-
textElement.style.display = '';
|
|
425
|
+
textElement.style.display = '';
|
|
475
426
|
} else {
|
|
476
427
|
console.error('Text element not found in option');
|
|
477
428
|
}
|
|
478
429
|
|
|
479
|
-
// Append to wrapper
|
|
480
430
|
optionsWrapper.appendChild(clone);
|
|
481
431
|
});
|
|
482
432
|
|
|
483
|
-
// Append wrapper to messages
|
|
484
433
|
elements.messages.appendChild(optionsWrapper);
|
|
485
434
|
scrollToBottom();
|
|
486
435
|
|
|
487
|
-
// Add click handlers with proper event handling
|
|
488
436
|
const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
|
|
489
437
|
optionElements.forEach(el => {
|
|
490
|
-
// Clear any existing onclick
|
|
491
438
|
el.onclick = null;
|
|
492
|
-
|
|
493
|
-
// Add new click handler with stopPropagation
|
|
494
439
|
el.onclick = (e) => {
|
|
495
|
-
e.stopPropagation();
|
|
496
|
-
e.preventDefault();
|
|
497
|
-
// Use multi-select behavior (isSingleSelect = false)
|
|
440
|
+
e.stopPropagation();
|
|
441
|
+
e.preventDefault();
|
|
498
442
|
handleOptionClick(el, field, false);
|
|
499
443
|
};
|
|
500
444
|
});
|
|
501
445
|
}
|
|
502
446
|
|
|
447
|
+
// =============================================================================
|
|
448
|
+
// MULTI-SELECT DROPDOWN RENDERING
|
|
449
|
+
// =============================================================================
|
|
450
|
+
|
|
451
|
+
function renderMultiSelectDropdown(options, field) {
|
|
452
|
+
if (!elements.messages) return;
|
|
453
|
+
|
|
454
|
+
console.log(`\n🎨 renderMultiSelectDropdown for field: ${field}`);
|
|
455
|
+
console.log(`Options:`, options);
|
|
456
|
+
|
|
457
|
+
// Find template
|
|
458
|
+
const template = document.querySelector('[data-chat-element="multi-select-dropdown"]');
|
|
459
|
+
if (!template) {
|
|
460
|
+
console.error('multi-select-dropdown template not found');
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Clone template
|
|
465
|
+
const clone = template.cloneNode(true);
|
|
466
|
+
clone.style.display = '';
|
|
467
|
+
clone.setAttribute('data-field', field);
|
|
468
|
+
|
|
469
|
+
// Get all elements
|
|
470
|
+
const dropdown = clone.querySelector('[data-chat-input-element="dropdown"]');
|
|
471
|
+
const optionsWrapper = clone.querySelector('[data-chat-input-element="dropdown-options-wrapper"]');
|
|
472
|
+
const searchBar = clone.querySelector('[data-chat-input-element="search-bar"]');
|
|
473
|
+
const optionsContainer = clone.querySelector('[data-chat-input-element="dropdown-options-container"]');
|
|
474
|
+
const optionTemplate = clone.querySelector('[data-chat-input-element="dropdown-option-wrapper"]');
|
|
475
|
+
const tagsContainer = clone.querySelector('[data-chat-input-element="tags-container"]');
|
|
476
|
+
const tagTemplate = clone.querySelector('[data-chat-input-element="tag-wrapper"]');
|
|
477
|
+
|
|
478
|
+
if (!dropdown || !optionsWrapper || !searchBar || !optionsContainer || !optionTemplate || !tagsContainer || !tagTemplate) {
|
|
479
|
+
console.error('Multi-select dropdown: Missing required elements');
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Store original template option and tag, then remove from DOM
|
|
484
|
+
const optionTemplateClone = optionTemplate.cloneNode(true);
|
|
485
|
+
const tagTemplateClone = tagTemplate.cloneNode(true);
|
|
486
|
+
optionTemplate.remove();
|
|
487
|
+
tagTemplate.remove();
|
|
488
|
+
|
|
489
|
+
// Get existing selections for pre-filling
|
|
490
|
+
const existingData = chatState.data[field] || [];
|
|
491
|
+
const selectedValues = new Set(existingData);
|
|
492
|
+
console.log(`📝 Pre-filling selections:`, Array.from(selectedValues));
|
|
493
|
+
|
|
494
|
+
// Store all options for filtering
|
|
495
|
+
const allOptions = [];
|
|
496
|
+
|
|
497
|
+
// Render options
|
|
498
|
+
options.forEach(option => {
|
|
499
|
+
const optionEl = optionTemplateClone.cloneNode(true);
|
|
500
|
+
optionEl.style.display = '';
|
|
501
|
+
optionEl.setAttribute('data-value', JSON.stringify(option.value));
|
|
502
|
+
optionEl.setAttribute('data-name', option.name);
|
|
503
|
+
|
|
504
|
+
const nameEl = optionEl.querySelector('[data-chat-input-element="dropdown-option-name"]');
|
|
505
|
+
const tickIcon = optionEl.querySelector('[data-chat-input-element="dropdown-option-tick-icon"]');
|
|
506
|
+
|
|
507
|
+
if (nameEl) nameEl.textContent = option.name;
|
|
508
|
+
|
|
509
|
+
// Check if pre-selected
|
|
510
|
+
const isSelected = selectedValues.has(option.value);
|
|
511
|
+
if (isSelected) {
|
|
512
|
+
optionEl.style.backgroundColor = config.selectedBackground;
|
|
513
|
+
if (tickIcon) tickIcon.style.display = '';
|
|
514
|
+
console.log(` ✅ Pre-selected: ${option.name}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Click handler - prevents dropdown from closing
|
|
518
|
+
optionEl.onclick = (e) => {
|
|
519
|
+
e.stopPropagation();
|
|
520
|
+
toggleOption(option, optionEl, tickIcon, field);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
optionsContainer.appendChild(optionEl);
|
|
524
|
+
allOptions.push({ element: optionEl, option });
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Toggle dropdown visibility - click to open/close
|
|
528
|
+
dropdown.onclick = (e) => {
|
|
529
|
+
e.stopPropagation();
|
|
530
|
+
const isVisible = optionsWrapper.style.display !== 'none';
|
|
531
|
+
|
|
532
|
+
if (isVisible) {
|
|
533
|
+
optionsWrapper.style.display = 'none';
|
|
534
|
+
console.log(' 🙈 Dropdown closed');
|
|
535
|
+
} else {
|
|
536
|
+
optionsWrapper.style.display = '';
|
|
537
|
+
console.log(' 👁️ Dropdown opened');
|
|
538
|
+
|
|
539
|
+
setTimeout(() => {
|
|
540
|
+
searchBar.focus();
|
|
541
|
+
}, 100);
|
|
542
|
+
|
|
543
|
+
searchBar.value = '';
|
|
544
|
+
allOptions.forEach(({ element }) => {
|
|
545
|
+
element.style.display = '';
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// Close dropdown when clicking outside
|
|
551
|
+
const closeDropdownHandler = (e) => {
|
|
552
|
+
if (!clone.contains(e.target)) {
|
|
553
|
+
if (optionsWrapper.style.display !== 'none') {
|
|
554
|
+
optionsWrapper.style.display = 'none';
|
|
555
|
+
console.log(' 🙈 Dropdown closed (clicked outside)');
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
document.addEventListener('click', closeDropdownHandler);
|
|
561
|
+
clone.dataset.closeHandler = 'attached';
|
|
562
|
+
|
|
563
|
+
// Search functionality - filters options by name or value
|
|
564
|
+
searchBar.oninput = (e) => {
|
|
565
|
+
const searchTerm = e.target.value.toLowerCase();
|
|
566
|
+
console.log(`🔍 Searching for: "${searchTerm}"`);
|
|
567
|
+
|
|
568
|
+
allOptions.forEach(({ element, option }) => {
|
|
569
|
+
const matchesName = option.name.toLowerCase().includes(searchTerm);
|
|
570
|
+
const matchesValue = String(option.value).toLowerCase().includes(searchTerm);
|
|
571
|
+
const matches = matchesName || matchesValue;
|
|
572
|
+
|
|
573
|
+
element.style.display = matches ? '' : 'none';
|
|
574
|
+
});
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// Prevent search bar clicks from closing dropdown
|
|
578
|
+
searchBar.onclick = (e) => {
|
|
579
|
+
e.stopPropagation();
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// Toggle option selection
|
|
583
|
+
function toggleOption(option, optionEl, tickIcon, field) {
|
|
584
|
+
const currentSelections = chatState.data[field] || [];
|
|
585
|
+
const isCurrentlySelected = currentSelections.includes(option.value);
|
|
586
|
+
|
|
587
|
+
console.log(`🎯 Toggle option: ${option.name}`, { isCurrentlySelected });
|
|
588
|
+
|
|
589
|
+
if (isCurrentlySelected) {
|
|
590
|
+
// Deselect
|
|
591
|
+
chatState.data[field] = currentSelections.filter(v => v !== option.value);
|
|
592
|
+
optionEl.style.backgroundColor = '';
|
|
593
|
+
if (tickIcon) tickIcon.style.display = 'none';
|
|
594
|
+
console.log(` ❌ Deselected: ${option.name}`);
|
|
595
|
+
} else {
|
|
596
|
+
// Select
|
|
597
|
+
chatState.data[field] = [...currentSelections, option.value];
|
|
598
|
+
optionEl.style.backgroundColor = config.selectedBackground;
|
|
599
|
+
if (tickIcon) tickIcon.style.display = '';
|
|
600
|
+
console.log(` ✅ Selected: ${option.name}`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Update current selection for handleNext
|
|
604
|
+
chatState.currentSelection = {
|
|
605
|
+
field,
|
|
606
|
+
value: chatState.data[field],
|
|
607
|
+
name: chatState.data[field].map(v => {
|
|
608
|
+
const opt = options.find(o => o.value === v);
|
|
609
|
+
return opt ? opt.name : v;
|
|
610
|
+
}).join(', ')
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
console.log(` 📊 Current selections:`, chatState.data[field]);
|
|
614
|
+
|
|
615
|
+
// Re-render tags
|
|
616
|
+
renderTags(field, options, tagsContainer, tagTemplateClone, optionsContainer);
|
|
617
|
+
|
|
618
|
+
// Enable Next button if at least one selected
|
|
619
|
+
if (chatState.data[field].length > 0) {
|
|
620
|
+
enableNextButton();
|
|
621
|
+
} else {
|
|
622
|
+
disableNextButton();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Render tags - displays selected options as removable tags
|
|
627
|
+
function renderTags(field, options, tagsContainer, tagTemplate, optionsContainer) {
|
|
628
|
+
// Clear existing tags (except template)
|
|
629
|
+
const existingTags = tagsContainer.querySelectorAll('[data-chat-input-element="tag-wrapper"]');
|
|
630
|
+
existingTags.forEach(tag => {
|
|
631
|
+
if (tag.style.display !== 'none') {
|
|
632
|
+
tag.remove();
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
const selections = chatState.data[field] || [];
|
|
637
|
+
console.log(`🏷️ Rendering ${selections.length} tags`);
|
|
638
|
+
|
|
639
|
+
selections.forEach(value => {
|
|
640
|
+
const option = options.find(o => o.value === value);
|
|
641
|
+
if (!option) return;
|
|
642
|
+
|
|
643
|
+
const tagEl = tagTemplate.cloneNode(true);
|
|
644
|
+
tagEl.style.display = '';
|
|
645
|
+
tagEl.setAttribute('data-value', JSON.stringify(value));
|
|
646
|
+
|
|
647
|
+
const textEl = tagEl.querySelector('[data-chat-input-element="tag-text"]');
|
|
648
|
+
const crossIcon = tagEl.querySelector('[data-chat-input-element="tag-cross-icon"]');
|
|
649
|
+
|
|
650
|
+
if (textEl) {
|
|
651
|
+
textEl.textContent = option.name;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// ✅ CRITICAL: Set cross icon display to BLOCK and handle click
|
|
655
|
+
if (crossIcon) {
|
|
656
|
+
// Set display to block (as requested by user)
|
|
657
|
+
crossIcon.style.display = 'block';
|
|
658
|
+
console.log(` ✅ Cross icon visible for: ${option.name}`);
|
|
659
|
+
|
|
660
|
+
// Handle click to remove tag
|
|
661
|
+
crossIcon.onclick = (e) => {
|
|
662
|
+
e.stopPropagation();
|
|
663
|
+
console.log(`🗑️ Removing tag: ${option.name}`);
|
|
664
|
+
|
|
665
|
+
// Remove from selections array
|
|
666
|
+
chatState.data[field] = chatState.data[field].filter(v => v !== value);
|
|
667
|
+
|
|
668
|
+
// Update option UI in dropdown - remove background and tick
|
|
669
|
+
const optionEl = optionsContainer.querySelector(`[data-value='${JSON.stringify(value)}']`);
|
|
670
|
+
if (optionEl) {
|
|
671
|
+
optionEl.style.backgroundColor = '';
|
|
672
|
+
const tickIcon = optionEl.querySelector('[data-chat-input-element="dropdown-option-tick-icon"]');
|
|
673
|
+
if (tickIcon) tickIcon.style.display = 'none';
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Remove tag from DOM
|
|
677
|
+
tagEl.remove();
|
|
678
|
+
|
|
679
|
+
// Update current selection
|
|
680
|
+
chatState.currentSelection = {
|
|
681
|
+
field,
|
|
682
|
+
value: chatState.data[field],
|
|
683
|
+
name: chatState.data[field].map(v => {
|
|
684
|
+
const opt = options.find(o => o.value === v);
|
|
685
|
+
return opt ? opt.name : v;
|
|
686
|
+
}).join(', ')
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
console.log(` 📊 Remaining selections:`, chatState.data[field]);
|
|
690
|
+
|
|
691
|
+
// Disable Next button if no selections remain
|
|
692
|
+
if (chatState.data[field].length === 0) {
|
|
693
|
+
disableNextButton();
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
tagsContainer.appendChild(tagEl);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Initial tag render if pre-filled
|
|
703
|
+
if (selectedValues.size > 0) {
|
|
704
|
+
renderTags(field, options, tagsContainer, tagTemplateClone, optionsContainer);
|
|
705
|
+
enableNextButton();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Append to messages
|
|
709
|
+
elements.messages.appendChild(clone);
|
|
710
|
+
scrollToBottom();
|
|
711
|
+
|
|
712
|
+
console.log(`✅ Multi-select dropdown rendered for ${field}`);
|
|
713
|
+
}
|
|
714
|
+
|
|
503
715
|
// =============================================================================
|
|
504
716
|
// SINGLE-SELECT-CUSTOM (WITH MIN/MAX RANGE)
|
|
505
717
|
// =============================================================================
|
|
@@ -507,7 +719,6 @@ function renderColorOptions(options, field) {
|
|
|
507
719
|
function renderCustomSelectOptions(options, field, customConfig) {
|
|
508
720
|
if (!elements.messages) return;
|
|
509
721
|
|
|
510
|
-
// Find existing single-select option element
|
|
511
722
|
const optionSelector = '[data-chat-element="single-select-input"]';
|
|
512
723
|
const existingOption = document.querySelector(optionSelector);
|
|
513
724
|
|
|
@@ -516,16 +727,14 @@ function renderCustomSelectOptions(options, field, customConfig) {
|
|
|
516
727
|
return;
|
|
517
728
|
}
|
|
518
729
|
|
|
519
|
-
// Get existing data for pre-filling
|
|
520
730
|
const existingData = chatState.data[field];
|
|
521
731
|
console.log(`📝 Pre-filling ${field} (custom):`, existingData);
|
|
522
732
|
|
|
523
|
-
// Create wrapper to hold all options
|
|
524
733
|
const optionsWrapper = document.createElement('div');
|
|
525
734
|
optionsWrapper.setAttribute('data-chat-element', 'options-wrapper');
|
|
526
735
|
optionsWrapper.style.display = 'flex';
|
|
527
736
|
optionsWrapper.style.flexDirection = 'column';
|
|
528
|
-
optionsWrapper.style.alignItems = 'flex-start';
|
|
737
|
+
optionsWrapper.style.alignItems = 'flex-start';
|
|
529
738
|
optionsWrapper.style.gap = '8px';
|
|
530
739
|
|
|
531
740
|
// Render regular options
|
|
@@ -538,9 +747,8 @@ function renderCustomSelectOptions(options, field, customConfig) {
|
|
|
538
747
|
const clone = existingOption.cloneNode(true);
|
|
539
748
|
clone.style.display = '';
|
|
540
749
|
|
|
541
|
-
// Check if this option should be pre-selected
|
|
542
750
|
const shouldBeChecked = existingData !== undefined &&
|
|
543
|
-
!Array.isArray(existingData) &&
|
|
751
|
+
!Array.isArray(existingData) &&
|
|
544
752
|
JSON.stringify(existingData) === JSON.stringify(optionValue);
|
|
545
753
|
|
|
546
754
|
if (shouldBeChecked) {
|
|
@@ -584,7 +792,6 @@ function renderCustomSelectOptions(options, field, customConfig) {
|
|
|
584
792
|
const customClone = existingOption.cloneNode(true);
|
|
585
793
|
customClone.style.display = '';
|
|
586
794
|
|
|
587
|
-
// Check if custom was selected before (data is array [min, max])
|
|
588
795
|
const isCustomSelected = existingData !== undefined &&
|
|
589
796
|
Array.isArray(existingData) &&
|
|
590
797
|
existingData.length === 2;
|
|
@@ -624,15 +831,10 @@ function renderCustomSelectOptions(options, field, customConfig) {
|
|
|
624
831
|
optionsWrapper.appendChild(customClone);
|
|
625
832
|
}
|
|
626
833
|
|
|
627
|
-
// Append wrapper to messages
|
|
628
834
|
elements.messages.appendChild(optionsWrapper);
|
|
629
|
-
|
|
630
|
-
// Render min/max inputs
|
|
631
835
|
renderMinMaxInputs(field, customConfig, existingData);
|
|
632
|
-
|
|
633
836
|
scrollToBottom();
|
|
634
837
|
|
|
635
|
-
// Add click handlers for regular and custom options
|
|
636
838
|
const optionElements = optionsWrapper.querySelectorAll('[data-chat-input-element="container"]');
|
|
637
839
|
optionElements.forEach(el => {
|
|
638
840
|
el.onclick = (e) => {
|
|
@@ -646,7 +848,6 @@ function renderCustomSelectOptions(options, field, customConfig) {
|
|
|
646
848
|
function renderMinMaxInputs(field, customConfig, existingData) {
|
|
647
849
|
if (!elements.messages || !customConfig) return;
|
|
648
850
|
|
|
649
|
-
// Find min/max input templates
|
|
650
851
|
const minSelector = '[data-chat-element="single-select-custom-min"]';
|
|
651
852
|
const maxSelector = '[data-chat-element="single-select-custom-max"]';
|
|
652
853
|
const minTemplate = document.querySelector(minSelector);
|
|
@@ -657,37 +858,32 @@ function renderMinMaxInputs(field, customConfig, existingData) {
|
|
|
657
858
|
return;
|
|
658
859
|
}
|
|
659
860
|
|
|
660
|
-
// Remove existing rangeWrapper if it exists (for edit flow)
|
|
661
861
|
const existingRangeWrapper = elements.messages.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
662
862
|
if (existingRangeWrapper) {
|
|
663
863
|
console.log(' 🗑️ Removing existing rangeWrapper for re-render');
|
|
664
864
|
existingRangeWrapper.remove();
|
|
665
865
|
}
|
|
666
866
|
|
|
667
|
-
// Create wrapper for min/max
|
|
668
867
|
const rangeWrapper = document.createElement('div');
|
|
669
868
|
rangeWrapper.setAttribute('data-chat-element', 'range-wrapper');
|
|
670
869
|
rangeWrapper.setAttribute('data-field', field);
|
|
671
|
-
rangeWrapper.style.marginBottom = '16px';
|
|
870
|
+
rangeWrapper.style.marginBottom = '16px';
|
|
672
871
|
rangeWrapper.style.display = 'flex';
|
|
673
|
-
rangeWrapper.style.gap = '16px';
|
|
872
|
+
rangeWrapper.style.gap = '16px';
|
|
674
873
|
rangeWrapper.style.flexWrap = 'wrap';
|
|
675
874
|
|
|
676
|
-
// Check if should show min/max (custom option selected)
|
|
677
|
-
// Data is stored as array [min, max]
|
|
678
875
|
const showMinMax = existingData !== undefined &&
|
|
679
876
|
Array.isArray(existingData) &&
|
|
680
877
|
existingData.length === 2;
|
|
681
878
|
|
|
682
|
-
rangeWrapper.style.display = showMinMax ? 'flex' : 'none';
|
|
879
|
+
rangeWrapper.style.display = showMinMax ? 'flex' : 'none';
|
|
683
880
|
|
|
684
|
-
// Clone and setup min input
|
|
685
881
|
const minClone = minTemplate.cloneNode(true);
|
|
686
882
|
minClone.style.display = '';
|
|
687
|
-
minClone.style.marginRight = '16px';
|
|
688
|
-
minClone.style.marginBottom = '12px';
|
|
689
|
-
minClone.style.width = 'auto';
|
|
690
|
-
minClone.style.flex = 'none';
|
|
883
|
+
minClone.style.marginRight = '16px';
|
|
884
|
+
minClone.style.marginBottom = '12px';
|
|
885
|
+
minClone.style.width = 'auto';
|
|
886
|
+
minClone.style.flex = 'none';
|
|
691
887
|
minClone.setAttribute('data-field', field);
|
|
692
888
|
|
|
693
889
|
const minInput = minClone.querySelector('[data-chat-input-element="input"]');
|
|
@@ -698,19 +894,17 @@ function renderMinMaxInputs(field, customConfig, existingData) {
|
|
|
698
894
|
minInput.min = customConfig.min !== undefined ? customConfig.min : 0;
|
|
699
895
|
minInput.max = customConfig.max !== undefined ? customConfig.max : 100;
|
|
700
896
|
minInput.value = showMinMax && existingData[0] !== undefined ? existingData[0] : '';
|
|
701
|
-
// Removed placeholder
|
|
702
897
|
|
|
703
898
|
if (showMinMax) {
|
|
704
899
|
console.log(` ✅ Pre-filled min: ${existingData[0]}`);
|
|
705
900
|
}
|
|
706
901
|
}
|
|
707
902
|
|
|
708
|
-
// Clone and setup max input
|
|
709
903
|
const maxClone = maxTemplate.cloneNode(true);
|
|
710
904
|
maxClone.style.display = '';
|
|
711
|
-
maxClone.style.marginBottom = '12px';
|
|
712
|
-
maxClone.style.width = 'auto';
|
|
713
|
-
maxClone.style.flex = 'none';
|
|
905
|
+
maxClone.style.marginBottom = '12px';
|
|
906
|
+
maxClone.style.width = 'auto';
|
|
907
|
+
maxClone.style.flex = 'none';
|
|
714
908
|
maxClone.setAttribute('data-field', field);
|
|
715
909
|
|
|
716
910
|
const maxInput = maxClone.querySelector('[data-chat-input-element="input"]');
|
|
@@ -721,7 +915,6 @@ function renderMinMaxInputs(field, customConfig, existingData) {
|
|
|
721
915
|
maxInput.min = customConfig.min !== undefined ? customConfig.min : 0;
|
|
722
916
|
maxInput.max = customConfig.max !== undefined ? customConfig.max : 100;
|
|
723
917
|
maxInput.value = showMinMax && existingData[1] !== undefined ? existingData[1] : '';
|
|
724
|
-
// Removed placeholder
|
|
725
918
|
|
|
726
919
|
if (showMinMax) {
|
|
727
920
|
console.log(` ✅ Pre-filled max: ${existingData[1]}`);
|
|
@@ -731,7 +924,6 @@ function renderMinMaxInputs(field, customConfig, existingData) {
|
|
|
731
924
|
rangeWrapper.appendChild(minClone);
|
|
732
925
|
rangeWrapper.appendChild(maxClone);
|
|
733
926
|
|
|
734
|
-
// Add error message display
|
|
735
927
|
const errorDiv = document.createElement('div');
|
|
736
928
|
errorDiv.setAttribute('data-chat-element', 'range-error');
|
|
737
929
|
errorDiv.setAttribute('data-field', field);
|
|
@@ -743,18 +935,15 @@ function renderMinMaxInputs(field, customConfig, existingData) {
|
|
|
743
935
|
|
|
744
936
|
elements.messages.appendChild(rangeWrapper);
|
|
745
937
|
|
|
746
|
-
// Add handlers for both inputs
|
|
747
938
|
if (minInput && maxInput) {
|
|
748
939
|
const updateVisualFeedback = () => {
|
|
749
940
|
const minFilled = minInput.value.trim() !== '';
|
|
750
941
|
const maxFilled = maxInput.value.trim() !== '';
|
|
751
942
|
const anyFilled = minFilled || maxFilled;
|
|
752
943
|
|
|
753
|
-
// Get tick icons
|
|
754
944
|
const minTick = minClone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
755
945
|
const maxTick = maxClone.querySelector('[data-chat-input-element="tick-icon"]');
|
|
756
946
|
|
|
757
|
-
// Update background and tick for BOTH inputs
|
|
758
947
|
if (anyFilled) {
|
|
759
948
|
minClone.style.backgroundColor = config.selectedBackground;
|
|
760
949
|
maxClone.style.backgroundColor = config.selectedBackground;
|
|
@@ -768,10 +957,8 @@ function renderMinMaxInputs(field, customConfig, existingData) {
|
|
|
768
957
|
}
|
|
769
958
|
};
|
|
770
959
|
|
|
771
|
-
// Apply visual feedback immediately if pre-filled
|
|
772
960
|
if (showMinMax) {
|
|
773
961
|
updateVisualFeedback();
|
|
774
|
-
// Also run validation to set currentSelection
|
|
775
962
|
validateMinMax(field, customConfig);
|
|
776
963
|
console.log(' ✅ Applied visual feedback for pre-filled values');
|
|
777
964
|
}
|
|
@@ -797,18 +984,15 @@ function renderMinMaxInputs(field, customConfig, existingData) {
|
|
|
797
984
|
}
|
|
798
985
|
|
|
799
986
|
function selectCustomOption(field) {
|
|
800
|
-
// Deselect all regular options
|
|
801
987
|
const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
|
|
802
988
|
allOptions.forEach(opt => {
|
|
803
989
|
const isCustom = opt.getAttribute('data-is-custom') === 'true';
|
|
804
990
|
if (isCustom) {
|
|
805
|
-
// Select custom option
|
|
806
991
|
opt.classList.add('cf-checked');
|
|
807
992
|
opt.style.setProperty('background-color', config.selectedBackground, 'important');
|
|
808
993
|
const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
|
|
809
994
|
if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
|
|
810
995
|
} else {
|
|
811
|
-
// Deselect regular options
|
|
812
996
|
opt.classList.remove('cf-checked');
|
|
813
997
|
opt.style.setProperty('background-color', 'transparent', 'important');
|
|
814
998
|
const tickIcon = opt.querySelector('[data-chat-input-element="tick-icon"]');
|
|
@@ -816,10 +1000,9 @@ function selectCustomOption(field) {
|
|
|
816
1000
|
}
|
|
817
1001
|
});
|
|
818
1002
|
|
|
819
|
-
// Show min/max inputs
|
|
820
1003
|
const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
821
1004
|
if (rangeWrapper) {
|
|
822
|
-
rangeWrapper.style.display = 'flex';
|
|
1005
|
+
rangeWrapper.style.display = 'flex';
|
|
823
1006
|
}
|
|
824
1007
|
}
|
|
825
1008
|
|
|
@@ -830,7 +1013,6 @@ function handleCustomSelectClick(element, field, customConfig) {
|
|
|
830
1013
|
|
|
831
1014
|
console.log(`Custom-select clicked:`, { field, name: optionName, isCustom });
|
|
832
1015
|
|
|
833
|
-
// Deselect all options first
|
|
834
1016
|
const allOptions = document.querySelectorAll(`[data-field="${field}"][data-chat-element="single-select-input"]`);
|
|
835
1017
|
allOptions.forEach(opt => {
|
|
836
1018
|
opt.classList.remove('cf-checked');
|
|
@@ -839,37 +1021,25 @@ function handleCustomSelectClick(element, field, customConfig) {
|
|
|
839
1021
|
if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
|
|
840
1022
|
});
|
|
841
1023
|
|
|
842
|
-
// Select this option
|
|
843
1024
|
element.classList.add('cf-checked');
|
|
844
1025
|
element.style.setProperty('background-color', config.selectedBackground, 'important');
|
|
845
1026
|
const tickIcon = element.querySelector('[data-chat-input-element="tick-icon"]');
|
|
846
1027
|
if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
|
|
847
1028
|
|
|
848
1029
|
if (isCustom) {
|
|
849
|
-
// Show min/max inputs
|
|
850
1030
|
const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
851
|
-
console.log(' 📦 Looking for rangeWrapper:', `[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
852
|
-
console.log(' 📦 Found rangeWrapper:', rangeWrapper);
|
|
853
1031
|
|
|
854
1032
|
if (rangeWrapper) {
|
|
855
|
-
|
|
856
|
-
rangeWrapper.style.display = 'flex'; // Keep flex!
|
|
857
|
-
console.log(' ✅ Set rangeWrapper display to flex');
|
|
858
|
-
console.log(' 📦 New display:', rangeWrapper.style.display);
|
|
859
|
-
} else {
|
|
860
|
-
console.log(' ❌ rangeWrapper not found!');
|
|
1033
|
+
rangeWrapper.style.display = 'flex';
|
|
861
1034
|
}
|
|
862
1035
|
|
|
863
|
-
// Don't save data yet - wait for validation
|
|
864
1036
|
chatState.currentSelection = null;
|
|
865
1037
|
console.log(' ℹ️ Custom selected - waiting for min/max input');
|
|
866
1038
|
} else {
|
|
867
|
-
// Hide min/max inputs and clear them
|
|
868
1039
|
const rangeWrapper = document.querySelector(`[data-chat-element="range-wrapper"][data-field="${field}"]`);
|
|
869
1040
|
if (rangeWrapper) {
|
|
870
1041
|
rangeWrapper.style.display = 'none';
|
|
871
1042
|
|
|
872
|
-
// Clear input values - fixed selectors
|
|
873
1043
|
const minInput = document.querySelector(`[data-field="${field}"][data-input-type="min"][data-chat-input-element="input"]`);
|
|
874
1044
|
const maxInput = document.querySelector(`[data-field="${field}"][data-input-type="max"][data-chat-input-element="input"]`);
|
|
875
1045
|
|
|
@@ -882,7 +1052,6 @@ function handleCustomSelectClick(element, field, customConfig) {
|
|
|
882
1052
|
console.log(' 🧹 Cleared max input');
|
|
883
1053
|
}
|
|
884
1054
|
|
|
885
|
-
// Reset visual feedback (background and ticks)
|
|
886
1055
|
const minContainer = rangeWrapper.querySelector('[data-chat-element="single-select-custom-min"]');
|
|
887
1056
|
const maxContainer = rangeWrapper.querySelector('[data-chat-element="single-select-custom-max"]');
|
|
888
1057
|
|
|
@@ -898,14 +1067,12 @@ function handleCustomSelectClick(element, field, customConfig) {
|
|
|
898
1067
|
if (maxTick) maxTick.style.display = 'none';
|
|
899
1068
|
}
|
|
900
1069
|
|
|
901
|
-
// Hide error message
|
|
902
1070
|
const errorDiv = rangeWrapper.querySelector('[data-chat-element="range-error"]');
|
|
903
1071
|
if (errorDiv) errorDiv.style.display = 'none';
|
|
904
1072
|
|
|
905
1073
|
console.log(' 🧹 Min/max inputs cleared and hidden');
|
|
906
1074
|
}
|
|
907
1075
|
|
|
908
|
-
// Parse and save regular value
|
|
909
1076
|
let value;
|
|
910
1077
|
try {
|
|
911
1078
|
value = JSON.parse(valueStr);
|
|
@@ -923,21 +1090,14 @@ function handleCustomSelectClick(element, field, customConfig) {
|
|
|
923
1090
|
function validateMinMax(field, customConfig) {
|
|
924
1091
|
console.log(`\n🔍 === VALIDATING MIN/MAX for field: ${field} ===`);
|
|
925
1092
|
|
|
926
|
-
// Always use global config errors
|
|
927
1093
|
const errors = config.customRangeErrors;
|
|
928
1094
|
|
|
929
|
-
// Fixed selectors - input has all attributes on it directly
|
|
930
1095
|
const minInput = document.querySelector(`[data-field="${field}"][data-input-type="min"][data-chat-input-element="input"]`);
|
|
931
1096
|
const maxInput = document.querySelector(`[data-field="${field}"][data-input-type="max"][data-chat-input-element="input"]`);
|
|
932
1097
|
const errorDiv = document.querySelector(`[data-chat-element="range-error"][data-field="${field}"]`);
|
|
933
1098
|
|
|
934
1099
|
if (!minInput || !maxInput) {
|
|
935
1100
|
console.log('❌ Min or Max input not found');
|
|
936
|
-
console.log(' Tried selectors:');
|
|
937
|
-
console.log(` Min: [data-field="${field}"][data-input-type="min"][data-chat-input-element="input"]`);
|
|
938
|
-
console.log(` Max: [data-field="${field}"][data-input-type="max"][data-chat-input-element="input"]`);
|
|
939
|
-
console.log(' Found min:', minInput);
|
|
940
|
-
console.log(' Found max:', maxInput);
|
|
941
1101
|
return { valid: false, error: null };
|
|
942
1102
|
}
|
|
943
1103
|
|
|
@@ -957,28 +1117,22 @@ function validateMinMax(field, customConfig) {
|
|
|
957
1117
|
Min constraint: >= ${minConstraint}
|
|
958
1118
|
Max constraint: <= ${maxConstraint}`);
|
|
959
1119
|
|
|
960
|
-
// Helper to show error in div and clear data
|
|
961
1120
|
const showErrorDiv = (message) => {
|
|
962
1121
|
if (errorDiv) {
|
|
963
1122
|
errorDiv.textContent = message;
|
|
964
1123
|
errorDiv.style.display = 'block';
|
|
965
1124
|
}
|
|
966
|
-
// Clear the data when validation fails
|
|
967
1125
|
delete chatState.data[field];
|
|
968
1126
|
chatState.currentSelection = null;
|
|
969
1127
|
console.log(`🗑️ Data cleared for field: ${field}`);
|
|
970
1128
|
};
|
|
971
1129
|
|
|
972
|
-
// Helper to hide error div
|
|
973
1130
|
const hideErrorDiv = () => {
|
|
974
1131
|
if (errorDiv) {
|
|
975
1132
|
errorDiv.style.display = 'none';
|
|
976
1133
|
}
|
|
977
1134
|
};
|
|
978
|
-
|
|
979
|
-
// Validation rules - return error message if invalid
|
|
980
1135
|
|
|
981
|
-
// 1. Check if only one is filled
|
|
982
1136
|
if (minFilled && !maxFilled) {
|
|
983
1137
|
const error = errors.maxRequired;
|
|
984
1138
|
showErrorDiv(error);
|
|
@@ -995,7 +1149,6 @@ function validateMinMax(field, customConfig) {
|
|
|
995
1149
|
return { valid: false, error };
|
|
996
1150
|
}
|
|
997
1151
|
|
|
998
|
-
// 2. Check if both are empty
|
|
999
1152
|
if (!minFilled && !maxFilled) {
|
|
1000
1153
|
hideErrorDiv();
|
|
1001
1154
|
delete chatState.data[field];
|
|
@@ -1005,7 +1158,6 @@ function validateMinMax(field, customConfig) {
|
|
|
1005
1158
|
return { valid: false, error: null };
|
|
1006
1159
|
}
|
|
1007
1160
|
|
|
1008
|
-
// 3. Check if values are valid numbers
|
|
1009
1161
|
if (isNaN(minValue) || isNaN(maxValue)) {
|
|
1010
1162
|
const error = errors.bothRequired;
|
|
1011
1163
|
showErrorDiv(error);
|
|
@@ -1014,7 +1166,6 @@ function validateMinMax(field, customConfig) {
|
|
|
1014
1166
|
return { valid: false, error };
|
|
1015
1167
|
}
|
|
1016
1168
|
|
|
1017
|
-
// 4. Check min constraint
|
|
1018
1169
|
console.log(`🔍 Checking: ${minValue} < ${minConstraint}?`);
|
|
1019
1170
|
if (minValue < minConstraint) {
|
|
1020
1171
|
const error = errors.minBelowConstraint.replace('{min}', minConstraint);
|
|
@@ -1025,7 +1176,6 @@ function validateMinMax(field, customConfig) {
|
|
|
1025
1176
|
}
|
|
1026
1177
|
console.log(`✅ PASS: Min (${minValue}) >= constraint (${minConstraint})`);
|
|
1027
1178
|
|
|
1028
|
-
// 5. Check max constraint
|
|
1029
1179
|
console.log(`🔍 Checking: ${maxValue} > ${maxConstraint}?`);
|
|
1030
1180
|
if (maxValue > maxConstraint) {
|
|
1031
1181
|
const error = errors.maxAboveConstraint.replace('{max}', maxConstraint);
|
|
@@ -1036,7 +1186,6 @@ function validateMinMax(field, customConfig) {
|
|
|
1036
1186
|
}
|
|
1037
1187
|
console.log(`✅ PASS: Max (${maxValue}) <= constraint (${maxConstraint})`);
|
|
1038
1188
|
|
|
1039
|
-
// 6. Check min < max
|
|
1040
1189
|
console.log(`🔍 Checking: ${minValue} >= ${maxValue}?`);
|
|
1041
1190
|
if (minValue >= maxValue) {
|
|
1042
1191
|
const error = errors.minGreaterThanMax;
|
|
@@ -1047,20 +1196,16 @@ function validateMinMax(field, customConfig) {
|
|
|
1047
1196
|
}
|
|
1048
1197
|
console.log(`✅ PASS: Min (${minValue}) < Max (${maxValue})`);
|
|
1049
1198
|
|
|
1050
|
-
// All valid! Hide error and save data as array
|
|
1051
1199
|
hideErrorDiv();
|
|
1052
1200
|
|
|
1053
|
-
// Store as array [min, max]
|
|
1054
1201
|
chatState.data[field] = [minValue, maxValue];
|
|
1055
1202
|
|
|
1056
|
-
// Get the current step's input config for formatting
|
|
1057
1203
|
const currentStep = flowData.flow[chatState.step];
|
|
1058
1204
|
const inputConfig = currentStep?.input || {};
|
|
1059
1205
|
const valueType = inputConfig.selectedInputValueType;
|
|
1060
1206
|
const prefix = inputConfig.selectedInputPrefix || '';
|
|
1061
1207
|
const suffix = inputConfig.selectedInputSuffix || '';
|
|
1062
1208
|
|
|
1063
|
-
// Format display name
|
|
1064
1209
|
let displayName = `${minValue}-${maxValue}`;
|
|
1065
1210
|
if (valueType === 'arrayRange') {
|
|
1066
1211
|
const formattedMin = minValue.toLocaleString();
|
|
@@ -1068,7 +1213,6 @@ function validateMinMax(field, customConfig) {
|
|
|
1068
1213
|
displayName = `${formattedMin} - ${formattedMax}`;
|
|
1069
1214
|
}
|
|
1070
1215
|
|
|
1071
|
-
// Add prefix and suffix with spaces
|
|
1072
1216
|
if (prefix) displayName = `${prefix} ${displayName}`;
|
|
1073
1217
|
if (suffix) displayName = `${displayName} ${suffix}`;
|
|
1074
1218
|
|
|
@@ -1094,10 +1238,7 @@ function validateMinMax(field, customConfig) {
|
|
|
1094
1238
|
function renderTextInput(field, inputType = 'text', inputConfig = {}) {
|
|
1095
1239
|
if (!elements.messages) return;
|
|
1096
1240
|
|
|
1097
|
-
// Determine input type attribute value
|
|
1098
1241
|
const inputTypeAttr = inputType === 'number' ? 'number-input' : 'text-input';
|
|
1099
|
-
|
|
1100
|
-
// Find existing input element in HTML by data-chat-element
|
|
1101
1242
|
const inputSelector = `[data-chat-element="${inputTypeAttr}"]`;
|
|
1102
1243
|
const existingInput = document.querySelector(inputSelector);
|
|
1103
1244
|
|
|
@@ -1106,11 +1247,9 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
|
|
|
1106
1247
|
return;
|
|
1107
1248
|
}
|
|
1108
1249
|
|
|
1109
|
-
// Get existing data for this field (for pre-filling when editing)
|
|
1110
1250
|
const existingValue = chatState.data[field];
|
|
1111
1251
|
console.log(`📝 Pre-filling ${field}:`, existingValue);
|
|
1112
1252
|
|
|
1113
|
-
// Check if validation should be applied (only if input has min/max defined)
|
|
1114
1253
|
const hasValidation = inputType === 'number' &&
|
|
1115
1254
|
(inputConfig.min !== undefined || inputConfig.max !== undefined);
|
|
1116
1255
|
|
|
@@ -1121,22 +1260,15 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
|
|
|
1121
1260
|
});
|
|
1122
1261
|
}
|
|
1123
1262
|
|
|
1124
|
-
// Clone existing input element
|
|
1125
1263
|
const clone = existingInput.cloneNode(true);
|
|
1126
|
-
|
|
1127
|
-
// Make clone visible
|
|
1128
1264
|
clone.style.display = '';
|
|
1129
|
-
|
|
1130
|
-
// Set data attributes
|
|
1131
1265
|
clone.setAttribute('data-field', field);
|
|
1132
1266
|
|
|
1133
|
-
// Find the actual input element
|
|
1134
1267
|
const inputElement = clone.querySelector('[data-chat-input-element="input"]');
|
|
1135
1268
|
if (inputElement) {
|
|
1136
1269
|
inputElement.setAttribute('data-field', field);
|
|
1137
1270
|
inputElement.name = field;
|
|
1138
1271
|
|
|
1139
|
-
// Set min/max attributes if validation enabled
|
|
1140
1272
|
if (hasValidation) {
|
|
1141
1273
|
if (inputConfig.min !== undefined) {
|
|
1142
1274
|
inputElement.min = inputConfig.min;
|
|
@@ -1146,12 +1278,10 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
|
|
|
1146
1278
|
}
|
|
1147
1279
|
}
|
|
1148
1280
|
|
|
1149
|
-
// Pre-fill value if it exists
|
|
1150
1281
|
if (existingValue !== undefined && existingValue !== null) {
|
|
1151
1282
|
inputElement.value = existingValue;
|
|
1152
1283
|
console.log(` ✅ Pre-filled with: ${existingValue}`);
|
|
1153
1284
|
|
|
1154
|
-
// If validation enabled, validate the pre-filled value
|
|
1155
1285
|
if (hasValidation) {
|
|
1156
1286
|
const value = parseFloat(existingValue);
|
|
1157
1287
|
const min = inputConfig.min;
|
|
@@ -1175,20 +1305,16 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
|
|
|
1175
1305
|
|
|
1176
1306
|
inputElement.type = inputType === 'number' ? 'number' : 'text';
|
|
1177
1307
|
|
|
1178
|
-
// Add input event handler
|
|
1179
1308
|
inputElement.oninput = (e) => {
|
|
1180
1309
|
const value = inputType === 'number' ? parseFloat(e.target.value) : e.target.value;
|
|
1181
1310
|
|
|
1182
|
-
// Save value
|
|
1183
1311
|
chatState.data[field] = value;
|
|
1184
1312
|
chatState.currentSelection = { field, value, name: value };
|
|
1185
1313
|
|
|
1186
|
-
// Apply validation ONLY if min/max defined at input level
|
|
1187
1314
|
if (hasValidation) {
|
|
1188
1315
|
const min = inputConfig.min;
|
|
1189
1316
|
const max = inputConfig.max;
|
|
1190
1317
|
|
|
1191
|
-
// Check if value is valid
|
|
1192
1318
|
let isValid = true;
|
|
1193
1319
|
let errorMessage = '';
|
|
1194
1320
|
|
|
@@ -1211,17 +1337,14 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
|
|
|
1211
1337
|
console.log(` ❌ Number input invalid: ${errorMessage}`);
|
|
1212
1338
|
}
|
|
1213
1339
|
} else {
|
|
1214
|
-
// No validation - just log
|
|
1215
1340
|
console.log(` 📝 Input updated: ${value} (no validation)`);
|
|
1216
1341
|
}
|
|
1217
1342
|
};
|
|
1218
1343
|
}
|
|
1219
1344
|
|
|
1220
|
-
// Append to messages
|
|
1221
1345
|
elements.messages.appendChild(clone);
|
|
1222
1346
|
scrollToBottom();
|
|
1223
1347
|
|
|
1224
|
-
// Focus the input
|
|
1225
1348
|
if (inputElement) {
|
|
1226
1349
|
setTimeout(() => inputElement.focus(), 100);
|
|
1227
1350
|
}
|
|
@@ -1232,7 +1355,6 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
|
|
|
1232
1355
|
// =============================================================================
|
|
1233
1356
|
|
|
1234
1357
|
function handleOptionClick(element, field, isSingleSelect) {
|
|
1235
|
-
// Prevent rapid double-clicks
|
|
1236
1358
|
const now = Date.now();
|
|
1237
1359
|
const lastClick = element.getAttribute('data-last-click');
|
|
1238
1360
|
if (lastClick && (now - parseInt(lastClick)) < 300) {
|
|
@@ -1241,7 +1363,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1241
1363
|
}
|
|
1242
1364
|
element.setAttribute('data-last-click', now.toString());
|
|
1243
1365
|
|
|
1244
|
-
// Find elements using custom attributes
|
|
1245
1366
|
const tickIcon = element.querySelector('[data-chat-input-element="tick-icon"]');
|
|
1246
1367
|
const input = element.querySelector('[data-chat-input-element="input"]');
|
|
1247
1368
|
const textElement = element.querySelector('[data-chat-input-element="text"]');
|
|
@@ -1257,7 +1378,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1257
1378
|
name: optionName
|
|
1258
1379
|
});
|
|
1259
1380
|
|
|
1260
|
-
// Parse value
|
|
1261
1381
|
let value;
|
|
1262
1382
|
try {
|
|
1263
1383
|
value = JSON.parse(valueStr);
|
|
@@ -1266,7 +1386,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1266
1386
|
}
|
|
1267
1387
|
|
|
1268
1388
|
if (isSingleSelect) {
|
|
1269
|
-
// Uncheck all others with same field and input type
|
|
1270
1389
|
const selector = `[data-field="${field}"][data-chat-element="${inputType}"]`;
|
|
1271
1390
|
const allOptions = document.querySelectorAll(selector);
|
|
1272
1391
|
|
|
@@ -1281,18 +1400,15 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1281
1400
|
if (otherInput) otherInput.checked = false;
|
|
1282
1401
|
});
|
|
1283
1402
|
|
|
1284
|
-
// Check this one
|
|
1285
1403
|
element.classList.add('cf-checked');
|
|
1286
1404
|
element.style.setProperty('background-color', config.selectedBackground, 'important');
|
|
1287
1405
|
if (tickIcon) tickIcon.style.setProperty('display', 'block', 'important');
|
|
1288
1406
|
if (input) input.checked = true;
|
|
1289
1407
|
|
|
1290
|
-
// Save value
|
|
1291
1408
|
chatState.data[field] = value;
|
|
1292
1409
|
chatState.currentSelection = { field, value, name: optionName };
|
|
1293
1410
|
|
|
1294
1411
|
} else {
|
|
1295
|
-
// Multi-select
|
|
1296
1412
|
const isChecked = !element.classList.contains('cf-checked');
|
|
1297
1413
|
|
|
1298
1414
|
if (isChecked) {
|
|
@@ -1303,7 +1419,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1303
1419
|
}
|
|
1304
1420
|
if (input) input.checked = true;
|
|
1305
1421
|
|
|
1306
|
-
// Add to array
|
|
1307
1422
|
if (!Array.isArray(chatState.data[field])) {
|
|
1308
1423
|
chatState.data[field] = [];
|
|
1309
1424
|
}
|
|
@@ -1322,7 +1437,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1322
1437
|
if (tickIcon) tickIcon.style.setProperty('display', 'none', 'important');
|
|
1323
1438
|
if (input) input.checked = false;
|
|
1324
1439
|
|
|
1325
|
-
// Remove from array
|
|
1326
1440
|
if (Array.isArray(chatState.data[field])) {
|
|
1327
1441
|
chatState.data[field] = chatState.data[field].filter(v =>
|
|
1328
1442
|
JSON.stringify(v) !== JSON.stringify(value)
|
|
@@ -1332,7 +1446,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1332
1446
|
console.log(`❌ Removed "${optionName}" - Total selected:`, chatState.data[field]);
|
|
1333
1447
|
}
|
|
1334
1448
|
|
|
1335
|
-
// Get all selected names using custom attribute
|
|
1336
1449
|
const selector = `[data-field="${field}"][data-chat-element="${inputType}"].cf-checked`;
|
|
1337
1450
|
const selectedOptions = document.querySelectorAll(selector);
|
|
1338
1451
|
const selectedNames = Array.from(selectedOptions).map(opt =>
|
|
@@ -1346,7 +1459,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1346
1459
|
};
|
|
1347
1460
|
}
|
|
1348
1461
|
|
|
1349
|
-
// Enable Next button
|
|
1350
1462
|
enableNextButton();
|
|
1351
1463
|
}
|
|
1352
1464
|
|
|
@@ -1357,7 +1469,6 @@ function handleOptionClick(element, field, isSingleSelect) {
|
|
|
1357
1469
|
async function handleNext() {
|
|
1358
1470
|
const currentStep = flowData.flow[chatState.step];
|
|
1359
1471
|
|
|
1360
|
-
// Check if input is required (default false)
|
|
1361
1472
|
const inputRequired = currentStep.inputRequired === true;
|
|
1362
1473
|
|
|
1363
1474
|
if (inputRequired && !chatState.currentSelection) {
|
|
@@ -1374,8 +1485,6 @@ async function handleNext() {
|
|
|
1374
1485
|
console.log(`Field: ${field}`);
|
|
1375
1486
|
console.log(`Custom config:`, customConfig);
|
|
1376
1487
|
|
|
1377
|
-
// Check if the CUSTOM OPTION is actually selected (not just if value is array)
|
|
1378
|
-
// Regular options can also have array values!
|
|
1379
1488
|
const customOptionElement = document.querySelector(
|
|
1380
1489
|
`[data-field="${field}"][data-is-custom="true"][data-chat-element="single-select-input"]`
|
|
1381
1490
|
);
|
|
@@ -1387,7 +1496,6 @@ async function handleNext() {
|
|
|
1387
1496
|
console.log(`Is custom option selected? ${isCustomOptionSelected}`);
|
|
1388
1497
|
|
|
1389
1498
|
if (isCustomOptionSelected && customConfig) {
|
|
1390
|
-
// Validate the custom range one more time before proceeding
|
|
1391
1499
|
console.log(`🔄 Running final validation before proceeding...`);
|
|
1392
1500
|
|
|
1393
1501
|
const validation = validateMinMax(field, customConfig);
|
|
@@ -1395,11 +1503,9 @@ async function handleNext() {
|
|
|
1395
1503
|
console.log(`Validation result:`, validation);
|
|
1396
1504
|
|
|
1397
1505
|
if (!validation.valid) {
|
|
1398
|
-
// If there's an error message, show it
|
|
1399
1506
|
if (validation.error) {
|
|
1400
1507
|
console.log(' ⚠️ Validation failed in handleNext, showing error message');
|
|
1401
1508
|
|
|
1402
|
-
// Hide current inputs (options and range wrapper)
|
|
1403
1509
|
const optionsWrapper = document.querySelector('[data-chat-element="options-wrapper"]');
|
|
1404
1510
|
if (optionsWrapper) {
|
|
1405
1511
|
optionsWrapper.style.display = 'none';
|
|
@@ -1410,28 +1516,22 @@ async function handleNext() {
|
|
|
1410
1516
|
rangeWrapper.style.display = 'none';
|
|
1411
1517
|
}
|
|
1412
1518
|
|
|
1413
|
-
// Add error message as bot message
|
|
1414
1519
|
addMessage(validation.error, 'bot', false, null);
|
|
1415
|
-
|
|
1416
|
-
// Re-display the same step with fresh inputs
|
|
1417
1520
|
await showNextStep();
|
|
1418
1521
|
} else {
|
|
1419
|
-
// No error message, just incomplete data - do nothing
|
|
1420
1522
|
console.log(' ⚠️ Validation incomplete, waiting for user input');
|
|
1421
1523
|
}
|
|
1422
1524
|
|
|
1423
|
-
return;
|
|
1525
|
+
return;
|
|
1424
1526
|
}
|
|
1425
1527
|
|
|
1426
1528
|
console.log('✅ Validation passed, proceeding to next step');
|
|
1427
1529
|
} else if (customConfig && chatState.currentSelection && chatState.currentSelection.value === customConfig.value) {
|
|
1428
|
-
// User selected the custom option but hasn't entered valid min/max yet
|
|
1429
1530
|
console.log(' ⚠️ Custom option selected but no valid range entered');
|
|
1430
|
-
return;
|
|
1531
|
+
return;
|
|
1431
1532
|
}
|
|
1432
1533
|
}
|
|
1433
1534
|
|
|
1434
|
-
// Call onNext validation if exists
|
|
1435
1535
|
if (currentStep.onNext) {
|
|
1436
1536
|
try {
|
|
1437
1537
|
disableNextButton();
|
|
@@ -1452,9 +1552,7 @@ async function handleNext() {
|
|
|
1452
1552
|
}
|
|
1453
1553
|
}
|
|
1454
1554
|
|
|
1455
|
-
// Add user message (only if selection was made)
|
|
1456
1555
|
if (chatState.currentSelection) {
|
|
1457
|
-
// Get the step's input config
|
|
1458
1556
|
const inputConfig = currentStep.input || {};
|
|
1459
1557
|
const valueType = inputConfig.selectedInputValueType;
|
|
1460
1558
|
const prefix = inputConfig.selectedInputPrefix || '';
|
|
@@ -1462,7 +1560,6 @@ async function handleNext() {
|
|
|
1462
1560
|
|
|
1463
1561
|
let displayName = chatState.currentSelection.name;
|
|
1464
1562
|
|
|
1465
|
-
// Format based on valueType
|
|
1466
1563
|
if (valueType === 'arrayRange' && Array.isArray(chatState.currentSelection.value)) {
|
|
1467
1564
|
const [min, max] = chatState.currentSelection.value;
|
|
1468
1565
|
const formattedMin = min.toLocaleString();
|
|
@@ -1470,13 +1567,11 @@ async function handleNext() {
|
|
|
1470
1567
|
displayName = `${formattedMin} - ${formattedMax}`;
|
|
1471
1568
|
}
|
|
1472
1569
|
|
|
1473
|
-
// Add prefix and suffix with spaces
|
|
1474
1570
|
if (prefix) displayName = `${prefix} ${displayName}`;
|
|
1475
1571
|
if (suffix) displayName = `${displayName} ${suffix}`;
|
|
1476
1572
|
|
|
1477
1573
|
addMessage(displayName, 'user', false, chatState.step);
|
|
1478
1574
|
|
|
1479
|
-
// Save to history
|
|
1480
1575
|
chatState.history.push({
|
|
1481
1576
|
step: chatState.step,
|
|
1482
1577
|
field: chatState.currentSelection.field,
|
|
@@ -1485,32 +1580,25 @@ async function handleNext() {
|
|
|
1485
1580
|
});
|
|
1486
1581
|
}
|
|
1487
1582
|
|
|
1488
|
-
// Reset selection
|
|
1489
1583
|
chatState.currentSelection = null;
|
|
1490
1584
|
disableNextButton();
|
|
1491
1585
|
|
|
1492
|
-
// Check if we should return to a saved step (after editing)
|
|
1493
1586
|
if (chatState.returnToStep !== null) {
|
|
1494
1587
|
const targetStep = chatState.returnToStep;
|
|
1495
1588
|
console.log(`\n🔙 Returning to saved step ${targetStep} (edited step complete)`);
|
|
1496
|
-
chatState.returnToStep = null;
|
|
1589
|
+
chatState.returnToStep = null;
|
|
1497
1590
|
|
|
1498
|
-
// Check if target step is valid
|
|
1499
1591
|
if (targetStep >= 0 && targetStep < flowData.flow.length) {
|
|
1500
1592
|
chatState.step = targetStep;
|
|
1501
1593
|
console.log(` ✅ Jumped to step ${targetStep}`);
|
|
1502
1594
|
|
|
1503
|
-
// Hide all range-wrappers
|
|
1504
1595
|
const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
|
|
1505
1596
|
allRangeWrappers.forEach(wrapper => {
|
|
1506
1597
|
wrapper.style.display = 'none';
|
|
1507
1598
|
});
|
|
1508
1599
|
console.log(' 🙈 Hidden all range-wrappers');
|
|
1509
1600
|
|
|
1510
|
-
// Update edit icons for the new position
|
|
1511
1601
|
updateEditIcons();
|
|
1512
|
-
|
|
1513
|
-
// Show the step we returned to
|
|
1514
1602
|
await showNextStep();
|
|
1515
1603
|
return;
|
|
1516
1604
|
} else {
|
|
@@ -1518,33 +1606,27 @@ async function handleNext() {
|
|
|
1518
1606
|
}
|
|
1519
1607
|
}
|
|
1520
1608
|
|
|
1521
|
-
// Normal flow: Move to next step
|
|
1522
1609
|
chatState.step++;
|
|
1523
1610
|
|
|
1524
|
-
// Hide all range-wrappers (custom min/max inputs)
|
|
1525
1611
|
const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
|
|
1526
1612
|
allRangeWrappers.forEach(wrapper => {
|
|
1527
1613
|
wrapper.style.display = 'none';
|
|
1528
1614
|
});
|
|
1529
1615
|
console.log(' 🙈 Hidden all range-wrappers');
|
|
1530
1616
|
|
|
1531
|
-
// Update edit icons for all previous steps
|
|
1532
1617
|
updateEditIcons();
|
|
1533
1618
|
|
|
1534
|
-
// Check if finished
|
|
1535
1619
|
if (chatState.step >= flowData.flow.length) {
|
|
1536
1620
|
handleCompletion();
|
|
1537
1621
|
return;
|
|
1538
1622
|
}
|
|
1539
1623
|
|
|
1540
|
-
// Show next step
|
|
1541
1624
|
await showNextStep();
|
|
1542
1625
|
}
|
|
1543
1626
|
|
|
1544
1627
|
async function showNextStep() {
|
|
1545
1628
|
const nextStep = flowData.flow[chatState.step];
|
|
1546
1629
|
|
|
1547
|
-
// Call onStart if exists
|
|
1548
1630
|
if (nextStep.onStart) {
|
|
1549
1631
|
try {
|
|
1550
1632
|
const canStart = await nextStep.onStart(chatState.data);
|
|
@@ -1558,87 +1640,79 @@ async function showNextStep() {
|
|
|
1558
1640
|
}
|
|
1559
1641
|
}
|
|
1560
1642
|
|
|
1561
|
-
// Hide previous inputs (options, text inputs, number inputs)
|
|
1562
1643
|
const previousInputs = elements.messages.querySelectorAll(
|
|
1563
1644
|
'[data-chat-element="options-wrapper"], ' +
|
|
1564
1645
|
'[data-chat-element="text-input"]:not([style*="display: none"]), ' +
|
|
1565
|
-
'[data-chat-element="number-input"]:not([style*="display: none"])'
|
|
1646
|
+
'[data-chat-element="number-input"]:not([style*="display: none"]), ' +
|
|
1647
|
+
'[data-chat-element="multi-select-dropdown"]:not([style*="display: none"])'
|
|
1566
1648
|
);
|
|
1567
1649
|
previousInputs.forEach(input => {
|
|
1568
1650
|
input.style.display = 'none';
|
|
1569
1651
|
});
|
|
1570
1652
|
|
|
1571
|
-
// Add bot message with edit icon if has input
|
|
1572
1653
|
const hasInput = !!nextStep.input;
|
|
1573
1654
|
addMessage(nextStep.message, 'bot', hasInput, chatState.step);
|
|
1574
1655
|
|
|
1575
|
-
// Check if input is required (default false)
|
|
1576
1656
|
const inputRequired = nextStep.inputRequired === true;
|
|
1577
1657
|
|
|
1578
|
-
// Add options if input exists
|
|
1579
1658
|
if (nextStep.input) {
|
|
1580
|
-
const inputType = nextStep.inputType || 'single-select';
|
|
1659
|
+
const inputType = nextStep.inputType || 'single-select';
|
|
1581
1660
|
|
|
1582
1661
|
if (inputType === 'text' || inputType === 'number') {
|
|
1583
|
-
// Render text or number input
|
|
1584
1662
|
renderTextInput(nextStep.input.field, inputType, nextStep.input);
|
|
1585
1663
|
|
|
1586
|
-
// Check if validation is enabled (number input with min/max defined)
|
|
1587
1664
|
const hasValidation = inputType === 'number' &&
|
|
1588
1665
|
(nextStep.input.min !== undefined || nextStep.input.max !== undefined);
|
|
1589
1666
|
|
|
1590
|
-
// Initial Next button state
|
|
1591
1667
|
if (inputRequired || hasValidation) {
|
|
1592
|
-
// Disable if input is required OR has validation rules
|
|
1593
1668
|
disableNextButton();
|
|
1594
1669
|
console.log(' 🔒 Next button disabled initially (input required or validation enabled)');
|
|
1595
1670
|
} else {
|
|
1596
|
-
// Enable if optional and no validation
|
|
1597
1671
|
enableNextButton();
|
|
1598
1672
|
console.log(' 🔓 Next button enabled (optional input, no validation)');
|
|
1599
1673
|
}
|
|
1600
1674
|
} else if (inputType === 'multi-select-color') {
|
|
1601
|
-
// Render color options with color blocks
|
|
1602
1675
|
renderColorOptions(nextStep.input.options, nextStep.input.field);
|
|
1603
1676
|
|
|
1604
|
-
// Enable Next button if input not required (default behavior)
|
|
1605
1677
|
if (!inputRequired) {
|
|
1606
1678
|
enableNextButton();
|
|
1607
1679
|
}
|
|
1608
1680
|
} else if (inputType === 'single-select-custom') {
|
|
1609
|
-
// Render single-select with custom min/max option
|
|
1610
1681
|
renderCustomSelectOptions(
|
|
1611
1682
|
nextStep.input.options,
|
|
1612
1683
|
nextStep.input.field,
|
|
1613
1684
|
nextStep.input.custom
|
|
1614
1685
|
);
|
|
1615
1686
|
|
|
1616
|
-
|
|
1687
|
+
if (inputRequired) {
|
|
1688
|
+
disableNextButton();
|
|
1689
|
+
} else {
|
|
1690
|
+
enableNextButton();
|
|
1691
|
+
}
|
|
1692
|
+
} else if (inputType === 'multi-select-dropdown') {
|
|
1693
|
+
// Render multi-select dropdown
|
|
1694
|
+
renderMultiSelectDropdown(nextStep.input.options, nextStep.input.field);
|
|
1695
|
+
|
|
1696
|
+
// Disable Next button initially if required
|
|
1617
1697
|
if (inputRequired) {
|
|
1618
1698
|
disableNextButton();
|
|
1619
1699
|
} else {
|
|
1620
1700
|
enableNextButton();
|
|
1621
1701
|
}
|
|
1622
1702
|
} else {
|
|
1623
|
-
// Render options (single-select or multi-select)
|
|
1624
1703
|
const isSingleSelect = inputType === 'single-select';
|
|
1625
1704
|
renderOptions(nextStep.input.options, nextStep.input.field, isSingleSelect);
|
|
1626
1705
|
|
|
1627
|
-
// Enable Next button if input not required (default behavior)
|
|
1628
1706
|
if (!inputRequired) {
|
|
1629
1707
|
enableNextButton();
|
|
1630
1708
|
}
|
|
1631
1709
|
}
|
|
1632
1710
|
} else {
|
|
1633
|
-
// No input - always auto-advance (nextButtonDisplay only controls button visibility)
|
|
1634
|
-
// Use step-level autoAdvanceDelay if provided, otherwise use global config
|
|
1635
1711
|
const delay = nextStep.autoAdvanceDelay !== undefined ? nextStep.autoAdvanceDelay : config.autoAdvanceDelay;
|
|
1636
1712
|
console.log(` ⏱️ Auto-advance delay: ${delay}ms ${nextStep.autoAdvanceDelay !== undefined ? '(step-level)' : '(global)'}`);
|
|
1637
1713
|
|
|
1638
1714
|
setTimeout(() => {
|
|
1639
1715
|
chatState.step++;
|
|
1640
|
-
|
|
1641
|
-
// Update edit icons after auto-advance
|
|
1642
1716
|
updateEditIcons();
|
|
1643
1717
|
|
|
1644
1718
|
if (chatState.step < flowData.flow.length) {
|
|
@@ -1649,7 +1723,6 @@ async function showNextStep() {
|
|
|
1649
1723
|
}, delay);
|
|
1650
1724
|
}
|
|
1651
1725
|
|
|
1652
|
-
// Handle nextButtonDisplay - default is true
|
|
1653
1726
|
const showNextButton = nextStep.nextButtonDisplay !== false;
|
|
1654
1727
|
if (elements.nextBtn) {
|
|
1655
1728
|
if (showNextButton) {
|
|
@@ -1661,20 +1734,23 @@ async function showNextStep() {
|
|
|
1661
1734
|
}
|
|
1662
1735
|
}
|
|
1663
1736
|
|
|
1664
|
-
// Handle nextButtonText - update button text if specified at step level
|
|
1665
1737
|
if (elements.nextBtn) {
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1738
|
+
const nextBtnTextElement = elements.nextBtn.querySelector('[data-chat-element="next-button-text"]');
|
|
1739
|
+
|
|
1740
|
+
if (nextBtnTextElement) {
|
|
1741
|
+
if (nextStep.nextButtonText) {
|
|
1742
|
+
nextBtnTextElement.textContent = nextStep.nextButtonText;
|
|
1743
|
+
console.log(` 📝 Next button text: "${nextStep.nextButtonText}" (step-level)`);
|
|
1744
|
+
} else {
|
|
1745
|
+
nextBtnTextElement.textContent = elements.originalNextBtnText;
|
|
1746
|
+
console.log(` 📝 Next button text: "${elements.originalNextBtnText}" (original)`);
|
|
1747
|
+
}
|
|
1748
|
+
} else if (nextStep.nextButtonText) {
|
|
1749
|
+
console.warn(` ⚠️ nextButtonText specified ("${nextStep.nextButtonText}") but next-button-text element not found`);
|
|
1750
|
+
console.warn(' Add <span data-chat-element="next-button-text">Next</span> inside your next-button');
|
|
1673
1751
|
}
|
|
1674
1752
|
}
|
|
1675
1753
|
|
|
1676
|
-
// Always update edit icons at the end to ensure correct state
|
|
1677
|
-
// Use setTimeout to ensure it runs after any DOM updates
|
|
1678
1754
|
setTimeout(() => {
|
|
1679
1755
|
updateEditIcons();
|
|
1680
1756
|
}, 10);
|
|
@@ -1683,19 +1759,15 @@ async function showNextStep() {
|
|
|
1683
1759
|
function handleCompletion() {
|
|
1684
1760
|
console.log('✅ Flow completed. Data:', chatState.data);
|
|
1685
1761
|
|
|
1686
|
-
// Mark flow as completed
|
|
1687
1762
|
chatState.completed = true;
|
|
1688
1763
|
console.log(' 🏁 Flow marked as completed');
|
|
1689
1764
|
|
|
1690
|
-
// Hide all edit icons
|
|
1691
1765
|
updateEditIcons();
|
|
1692
1766
|
|
|
1693
|
-
// Hide Next button
|
|
1694
1767
|
if (elements.nextBtn) {
|
|
1695
1768
|
elements.nextBtn.style.display = 'none';
|
|
1696
1769
|
}
|
|
1697
1770
|
|
|
1698
|
-
// Trigger event
|
|
1699
1771
|
if (typeof window !== 'undefined') {
|
|
1700
1772
|
const event = new CustomEvent('conversationalFlowComplete', {
|
|
1701
1773
|
detail: {
|
|
@@ -1706,7 +1778,6 @@ function handleCompletion() {
|
|
|
1706
1778
|
window.dispatchEvent(event);
|
|
1707
1779
|
}
|
|
1708
1780
|
|
|
1709
|
-
// Call onComplete if provided
|
|
1710
1781
|
if (flowData.onComplete && typeof flowData.onComplete === 'function') {
|
|
1711
1782
|
flowData.onComplete(chatState.data);
|
|
1712
1783
|
}
|
|
@@ -1717,7 +1788,6 @@ function handleCompletion() {
|
|
|
1717
1788
|
// =============================================================================
|
|
1718
1789
|
|
|
1719
1790
|
function init(flowName, flowConfig, options = {}) {
|
|
1720
|
-
// Find container by data-chat-element="chat-wrapper"
|
|
1721
1791
|
elements.container = document.querySelector('[data-chat-element="chat-wrapper"]');
|
|
1722
1792
|
|
|
1723
1793
|
if (!elements.container) {
|
|
@@ -1726,19 +1796,15 @@ function init(flowName, flowConfig, options = {}) {
|
|
|
1726
1796
|
return null;
|
|
1727
1797
|
}
|
|
1728
1798
|
|
|
1729
|
-
// Merge config
|
|
1730
1799
|
if (flowConfig.config) {
|
|
1731
1800
|
Object.assign(config, flowConfig.config);
|
|
1732
1801
|
}
|
|
1733
1802
|
|
|
1734
|
-
// Merge customRangeErrors - priority: options param > flow object > defaults
|
|
1735
|
-
// 1. First, try flow object customRangeErrors
|
|
1736
1803
|
if (flowConfig.customRangeErrors) {
|
|
1737
1804
|
console.log('✅ Using custom range errors from flow object');
|
|
1738
1805
|
Object.assign(config.customRangeErrors, flowConfig.customRangeErrors);
|
|
1739
1806
|
}
|
|
1740
1807
|
|
|
1741
|
-
// 2. Then, options parameter (overrides flow object if both provided)
|
|
1742
1808
|
if (options.customRangeErrors) {
|
|
1743
1809
|
console.log('✅ Using custom range errors from options parameter');
|
|
1744
1810
|
Object.assign(config.customRangeErrors, options.customRangeErrors);
|
|
@@ -1751,20 +1817,16 @@ function init(flowName, flowConfig, options = {}) {
|
|
|
1751
1817
|
customRangeErrors: config.customRangeErrors
|
|
1752
1818
|
});
|
|
1753
1819
|
|
|
1754
|
-
// Store flow data
|
|
1755
1820
|
flowData = flowConfig;
|
|
1756
1821
|
|
|
1757
|
-
// Initialize state
|
|
1758
1822
|
chatState.step = 0;
|
|
1759
1823
|
chatState.data = flowConfig.initialData || {};
|
|
1760
1824
|
chatState.history = [];
|
|
1761
1825
|
chatState.currentSelection = null;
|
|
1762
1826
|
|
|
1763
|
-
// Find existing elements (don't create new ones)
|
|
1764
1827
|
elements.messages = elements.container.querySelector('[data-chat-element="messages-container"]');
|
|
1765
1828
|
elements.nextBtn = elements.container.querySelector('[data-chat-element="next-button"]');
|
|
1766
1829
|
|
|
1767
|
-
// Validate required elements exist
|
|
1768
1830
|
if (!elements.messages) {
|
|
1769
1831
|
console.error('messages-container not found. Please add <div data-chat-element="messages-container"></div>');
|
|
1770
1832
|
return null;
|
|
@@ -1775,27 +1837,28 @@ function init(flowName, flowConfig, options = {}) {
|
|
|
1775
1837
|
return null;
|
|
1776
1838
|
}
|
|
1777
1839
|
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1840
|
+
const nextBtnTextElement = elements.nextBtn.querySelector('[data-chat-element="next-button-text"]');
|
|
1841
|
+
if (nextBtnTextElement) {
|
|
1842
|
+
elements.originalNextBtnText = nextBtnTextElement.textContent || nextBtnTextElement.innerText || 'Next';
|
|
1843
|
+
console.log(`💾 Stored original button text: "${elements.originalNextBtnText}" (from next-button-text element)`);
|
|
1844
|
+
} else {
|
|
1845
|
+
console.warn('⚠️ Element with data-chat-element="next-button-text" not found inside next-button');
|
|
1846
|
+
console.warn(' Button text customization will not work. Please add <span data-chat-element="next-button-text">Next</span> inside your button');
|
|
1847
|
+
elements.originalNextBtnText = 'Next';
|
|
1848
|
+
}
|
|
1781
1849
|
|
|
1782
|
-
// Clear ONLY messages container (not entire chat-wrapper)
|
|
1783
1850
|
elements.messages.innerHTML = '';
|
|
1784
1851
|
|
|
1785
|
-
// Reset button state
|
|
1786
1852
|
elements.nextBtn.disabled = true;
|
|
1787
1853
|
elements.nextBtn.style.opacity = '0.5';
|
|
1788
1854
|
elements.nextBtn.style.cursor = 'not-allowed';
|
|
1789
1855
|
|
|
1790
|
-
// Add event listener
|
|
1791
1856
|
elements.nextBtn.onclick = handleNext;
|
|
1792
1857
|
|
|
1793
|
-
// Expose editStep globally for onclick handlers
|
|
1794
1858
|
if (typeof window !== 'undefined') {
|
|
1795
1859
|
window.editStep = editStep;
|
|
1796
1860
|
}
|
|
1797
1861
|
|
|
1798
|
-
// Start
|
|
1799
1862
|
showNextStep();
|
|
1800
1863
|
|
|
1801
1864
|
console.log(`✅ Flow "${flowName}" initialized`);
|
|
@@ -1847,8 +1910,6 @@ function goToStep(stepNumber) {
|
|
|
1847
1910
|
chatState.step = stepNumber;
|
|
1848
1911
|
chatState.currentSelection = null;
|
|
1849
1912
|
|
|
1850
|
-
// DON'T clear messages - keep chat history visible!
|
|
1851
|
-
// User can see their previous answers
|
|
1852
1913
|
console.log(` Keeping messages visible (not clearing)...`);
|
|
1853
1914
|
|
|
1854
1915
|
console.log(` Disabling next button and calling showNextStep()...`);
|
|
@@ -1865,27 +1926,21 @@ function editStep(stepNumber) {
|
|
|
1865
1926
|
return;
|
|
1866
1927
|
}
|
|
1867
1928
|
|
|
1868
|
-
// Save where we are now so we can return after editing
|
|
1869
1929
|
chatState.returnToStep = chatState.step;
|
|
1870
1930
|
console.log(` 💾 Saved return step: ${chatState.returnToStep}`);
|
|
1871
1931
|
|
|
1872
|
-
// Reset completed flag since we're editing
|
|
1873
1932
|
if (chatState.completed) {
|
|
1874
1933
|
chatState.completed = false;
|
|
1875
1934
|
console.log(` 🔓 Flow unmarked as completed (editing)`);
|
|
1876
1935
|
}
|
|
1877
1936
|
|
|
1878
1937
|
console.log(` Clearing history from step ${stepNumber} forward...`);
|
|
1879
|
-
// Clear history from this step forward
|
|
1880
1938
|
chatState.history = chatState.history.filter(h => h.step < stepNumber);
|
|
1881
1939
|
|
|
1882
1940
|
console.log(` ✅ Keeping ALL data intact for editing (no data deleted)`);
|
|
1883
1941
|
console.log(` Current data:`, chatState.data);
|
|
1884
|
-
// Don't delete any data - keep everything for pre-filling
|
|
1885
|
-
// Data will be replaced when user submits the edited step
|
|
1886
1942
|
|
|
1887
1943
|
console.log(` Calling goToStep(${stepNumber})...`);
|
|
1888
|
-
// Go to that step
|
|
1889
1944
|
goToStep(stepNumber);
|
|
1890
1945
|
}
|
|
1891
1946
|
|
|
@@ -1898,7 +1953,6 @@ function injectStyles() {
|
|
|
1898
1953
|
const style = document.createElement('style');
|
|
1899
1954
|
style.id = styleId;
|
|
1900
1955
|
style.textContent = `
|
|
1901
|
-
/* User message spacing and alignment */
|
|
1902
1956
|
[data-chat-element="user-message-wrapper"] {
|
|
1903
1957
|
margin-bottom: 16px;
|
|
1904
1958
|
display: inline-flex;
|
|
@@ -1906,21 +1960,18 @@ function injectStyles() {
|
|
|
1906
1960
|
align-self: flex-end;
|
|
1907
1961
|
}
|
|
1908
1962
|
|
|
1909
|
-
/* User message natural width from right */
|
|
1910
1963
|
[data-chat-element="user-message-text"] {
|
|
1911
1964
|
width: auto;
|
|
1912
1965
|
display: inline-block;
|
|
1913
1966
|
text-align: left;
|
|
1914
1967
|
}
|
|
1915
1968
|
|
|
1916
|
-
/* Messages container as flex column */
|
|
1917
1969
|
[data-chat-element="messages-container"] {
|
|
1918
1970
|
padding: 20px;
|
|
1919
1971
|
display: flex;
|
|
1920
1972
|
flex-direction: column;
|
|
1921
1973
|
}
|
|
1922
1974
|
|
|
1923
|
-
/* Options wrapper spacing and gap */
|
|
1924
1975
|
[data-chat-element="options-wrapper"] {
|
|
1925
1976
|
margin-bottom: 20px;
|
|
1926
1977
|
display: flex;
|
|
@@ -1928,17 +1979,14 @@ function injectStyles() {
|
|
|
1928
1979
|
gap: 12px;
|
|
1929
1980
|
}
|
|
1930
1981
|
|
|
1931
|
-
/* Text input spacing */
|
|
1932
1982
|
[data-chat-element="text-input"] {
|
|
1933
1983
|
margin-bottom: 20px;
|
|
1934
1984
|
}
|
|
1935
1985
|
|
|
1936
|
-
/* Number input spacing */
|
|
1937
1986
|
[data-chat-element="number-input"] {
|
|
1938
1987
|
margin-bottom: 20px;
|
|
1939
1988
|
}
|
|
1940
1989
|
|
|
1941
|
-
/* Bot edit icon styling */
|
|
1942
1990
|
[data-chat-element="bot-edit-icon"] {
|
|
1943
1991
|
display: none;
|
|
1944
1992
|
margin-left: 8px !important;
|
|
@@ -1955,7 +2003,6 @@ function injectStyles() {
|
|
|
1955
2003
|
document.head.appendChild(style);
|
|
1956
2004
|
}
|
|
1957
2005
|
|
|
1958
|
-
// Auto-inject styles when imported
|
|
1959
2006
|
if (typeof document !== 'undefined') {
|
|
1960
2007
|
if (document.readyState === 'loading') {
|
|
1961
2008
|
document.addEventListener('DOMContentLoaded', injectStyles);
|