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