lisichatbot 2.0.9 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +224 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisichatbot",
3
- "version": "2.0.9",
3
+ "version": "2.1.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "exports": {
package/src/index.js CHANGED
@@ -19,7 +19,11 @@ let chatState = {
19
19
  returnToStep: null,
20
20
  completed: false,
21
21
  editPath: [], // ✅ NEW: Array of step names/numbers to edit in sequence
22
- chatMode: 'create' // ✅ NEW: Track if in 'create' or 'edit' mode
22
+ chatMode: 'create', // ✅ NEW: Track if in 'create' or 'edit' mode
23
+ editSteps: null, // ✅ Array of step indices to walk through in edit-steps mode
24
+ editStepsIndex: 0, // ✅ Current position within editSteps array
25
+ onEditComplete: null, // ✅ Callback when editSteps flow finishes
26
+ editFinalButtonText: null // ✅ Button text for the last step in editSteps
23
27
  };
24
28
 
25
29
  let elements = {
@@ -197,45 +201,74 @@ function updateEditIcons() {
197
201
  const allBotMessages = elements.messages.querySelectorAll('[data-chat-element="bot-message-wrapper"]');
198
202
 
199
203
  console.log(`\n🔄 Updating edit icons - Current step: ${chatState.step}, Completed: ${chatState.completed}`);
200
-
204
+
201
205
  allBotMessages.forEach(wrapper => {
202
206
  const stepNumber = parseInt(wrapper.getAttribute('data-chat-step'));
203
207
  const editIcon = wrapper.querySelector('[data-chat-element="bot-edit-icon"]');
204
208
  const isLatest = wrapper.hasAttribute('data-chat-latest');
205
-
209
+
206
210
  if (editIcon && !isNaN(stepNumber)) {
207
211
  const stepData = flowData.flow[stepNumber];
208
212
  const hasInput = stepData && !!stepData.input;
209
-
213
+
210
214
  // ✅ NEW: Check if edit is disabled for this step
211
215
  if (stepData && stepData.disableEdit === true) {
212
216
  editIcon.style.setProperty('display', 'none', 'important');
213
217
  console.log(` 🚫 Step ${stepNumber}: Icon HIDDEN (edit disabled)`);
214
218
  return;
215
219
  }
216
-
220
+
217
221
  // ✅ FIX: In edit mode, keep edit icons visible even after completion
218
222
  if (chatState.completed && chatState.chatMode !== 'edit') {
219
223
  editIcon.style.setProperty('display', 'none', 'important');
220
224
  console.log(` 🏁 Step ${stepNumber}: Icon HIDDEN (flow completed in create mode)`);
221
225
  return;
222
226
  }
223
-
227
+
224
228
  // ✅ NEW: Check if edit icon should be forced to show (even without input)
225
229
  const forceShowEdit = stepData && stepData.showEditIcon === true;
226
-
230
+
227
231
  // ✅ FIX: In edit mode, show edit icons for all completed steps (not just previous)
228
232
  const shouldShowInEditMode = chatState.chatMode === 'edit' && chatState.completed && isLatest;
229
233
  const shouldShowNormally = stepNumber < chatState.step && isLatest;
230
-
231
- if ((hasInput || forceShowEdit) && (shouldShowInEditMode || shouldShowNormally)) {
234
+ // ✅ In editSteps mode, show edit icons for steps already visited in the editSteps list
235
+ const shouldShowInEditSteps = chatState.editSteps && chatState.editSteps.length > 0 &&
236
+ chatState.editSteps.includes(stepNumber) &&
237
+ chatState.editSteps.indexOf(stepNumber) < chatState.editStepsIndex && isLatest;
238
+
239
+ if ((hasInput || forceShowEdit) && (shouldShowInEditMode || shouldShowNormally || shouldShowInEditSteps)) {
232
240
  editIcon.onclick = (e) => {
233
241
  e.stopPropagation();
234
242
  e.preventDefault();
235
243
  console.log(`\n🖱️ EDIT ICON CLICKED (from updateEditIcons) - Step ${stepNumber}`);
236
244
  console.log(` Current step: ${chatState.step}`);
237
-
238
- // ✅ NEW: Check if this step has an editSteps path
245
+
246
+ // ✅ In editSteps mode, go back to that step within the editSteps flow
247
+ if (chatState.editSteps && chatState.editSteps.length > 0) {
248
+ const editStepIdx = chatState.editSteps.indexOf(stepNumber);
249
+ if (editStepIdx !== -1) {
250
+ console.log(` 🛤️ editSteps: jumping back to editSteps[${editStepIdx}] (step ${stepNumber})`);
251
+ chatState.editStepsIndex = editStepIdx;
252
+ chatState.step = stepNumber;
253
+ chatState.currentSelection = null;
254
+
255
+ // Remove history from this editStep onward
256
+ const stepsToRemove = chatState.editSteps.slice(editStepIdx);
257
+ chatState.history = chatState.history.filter(h => !stepsToRemove.includes(h.step));
258
+
259
+ // Set prefill override
260
+ const targetStepData = flowData.flow[stepNumber];
261
+ if (targetStepData?.input?.field && chatState.data[targetStepData.input.field] !== undefined) {
262
+ chatState.prefillOverrideFields = [targetStepData.input.field];
263
+ }
264
+
265
+ disableNextButton();
266
+ showNextStep();
267
+ return;
268
+ }
269
+ }
270
+
271
+ // ✅ Check if this step has an editSteps path (normal mode)
239
272
  if (stepData.editSteps && Array.isArray(stepData.editSteps) && stepData.editSteps.length > 0) {
240
273
  console.log(` 🛤️ Step has editSteps path:`, stepData.editSteps);
241
274
  startEditPath(stepData.editSteps);
@@ -320,7 +353,7 @@ function renderMultiSelectDropdown(options, field) {
320
353
  // ✅ NEW: Check if prefill is disabled for this step
321
354
  const currentStep = flowData.flow[chatState.step];
322
355
  const hasOverride = chatState.prefillOverrideFields && chatState.prefillOverrideFields.includes(field);
323
- const isEditing = chatState.returnToStep !== null && chatState.returnToStep !== undefined;
356
+ const isEditing = (chatState.returnToStep !== null && chatState.returnToStep !== undefined) || (chatState.editSteps && chatState.editSteps.length > 0);
324
357
  const disablePrefill = currentStep?.disableInputValuePrefill === true && !hasOverride && !isEditing;
325
358
 
326
359
  const existingData = disablePrefill ? [] : (chatState.data[field] || []);
@@ -622,7 +655,7 @@ function renderOptions(options, field, isSingleSelect = true) {
622
655
  // ✅ NEW: Check if prefill is disabled for this step
623
656
  const currentStep = flowData.flow[chatState.step];
624
657
  const hasOverride = chatState.prefillOverrideFields && chatState.prefillOverrideFields.includes(field);
625
- const isEditing = chatState.returnToStep !== null && chatState.returnToStep !== undefined;
658
+ const isEditing = (chatState.returnToStep !== null && chatState.returnToStep !== undefined) || (chatState.editSteps && chatState.editSteps.length > 0);
626
659
  const disablePrefill = currentStep?.disableInputValuePrefill === true && !hasOverride && !isEditing;
627
660
 
628
661
  const existingData = disablePrefill ? (isSingleSelect ? null : []) : chatState.data[field];
@@ -763,7 +796,7 @@ function renderColorOptions(options, field) {
763
796
  // ✅ NEW: Check if prefill is disabled for this step
764
797
  const currentStep = flowData.flow[chatState.step];
765
798
  const hasOverride = chatState.prefillOverrideFields && chatState.prefillOverrideFields.includes(field);
766
- const isEditing = chatState.returnToStep !== null && chatState.returnToStep !== undefined;
799
+ const isEditing = (chatState.returnToStep !== null && chatState.returnToStep !== undefined) || (chatState.editSteps && chatState.editSteps.length > 0);
767
800
  const disablePrefill = currentStep?.disableInputValuePrefill === true && !hasOverride && !isEditing;
768
801
 
769
802
  const existingData = disablePrefill ? [] : chatState.data[field];
@@ -901,7 +934,7 @@ function renderCustomSelectOptions(options, field, customConfig) {
901
934
  // ✅ NEW: Check if prefill is disabled for this step
902
935
  const currentStep = flowData.flow[chatState.step];
903
936
  const hasOverride = chatState.prefillOverrideFields && chatState.prefillOverrideFields.includes(field);
904
- const isEditing = chatState.returnToStep !== null && chatState.returnToStep !== undefined;
937
+ const isEditing = (chatState.returnToStep !== null && chatState.returnToStep !== undefined) || (chatState.editSteps && chatState.editSteps.length > 0);
905
938
  const disablePrefill = currentStep?.disableInputValuePrefill === true && !hasOverride && !isEditing;
906
939
 
907
940
  console.log(`\n🔍 === PREFILL DEBUG for field: ${field} ===`);
@@ -1692,7 +1725,7 @@ function renderTextInput(field, inputType = 'text', inputConfig = {}) {
1692
1725
  // ✅ NEW: Check if prefill is disabled for this step
1693
1726
  const currentStep = flowData.flow[chatState.step];
1694
1727
  const hasOverride = chatState.prefillOverrideFields && chatState.prefillOverrideFields.includes(field);
1695
- const isEditing = chatState.returnToStep !== null && chatState.returnToStep !== undefined;
1728
+ const isEditing = (chatState.returnToStep !== null && chatState.returnToStep !== undefined) || (chatState.editSteps && chatState.editSteps.length > 0);
1696
1729
  const disablePrefill = currentStep?.disableInputValuePrefill === true && !hasOverride && !isEditing;
1697
1730
 
1698
1731
  const existingValue = disablePrefill ? null : chatState.data[field];
@@ -2541,6 +2574,100 @@ async function handleNext() {
2541
2574
  chatState.currentSelection = null;
2542
2575
  disableNextButton();
2543
2576
 
2577
+ // ✅ Edit-steps mode: jump to next step in the list or finish
2578
+ if (chatState.editSteps && chatState.editSteps.length > 0) {
2579
+ const currentEditIndex = chatState.editStepsIndex;
2580
+ const nextEditIndex = currentEditIndex + 1;
2581
+
2582
+ if (nextEditIndex < chatState.editSteps.length) {
2583
+ // More steps to edit
2584
+ chatState.editStepsIndex = nextEditIndex;
2585
+ const nextStepIndex = chatState.editSteps[nextEditIndex];
2586
+ console.log(`\n🛤️ Edit-steps: moving to step ${nextEditIndex + 1}/${chatState.editSteps.length} (flow index ${nextStepIndex})`);
2587
+
2588
+ chatState.step = nextStepIndex;
2589
+
2590
+ // Set prefillOverrideFields for the next step
2591
+ const targetStep = flowData.flow[nextStepIndex];
2592
+ if (targetStep?.input?.field && chatState.data[targetStep.input.field] !== undefined) {
2593
+ chatState.prefillOverrideFields = [targetStep.input.field];
2594
+ }
2595
+
2596
+ const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
2597
+ allRangeWrappers.forEach(wrapper => {
2598
+ wrapper.style.display = 'none';
2599
+ });
2600
+
2601
+ updateEditIcons();
2602
+ await showNextStep();
2603
+ scrollToBottom();
2604
+ return;
2605
+ } else {
2606
+ // All edit steps done - call onEditComplete
2607
+ console.log(`\n✅ Edit-steps complete! All ${chatState.editSteps.length} steps finished.`);
2608
+
2609
+ const dataCopy = { ...chatState.data };
2610
+ const historyCopy = [...chatState.history];
2611
+ const callback = chatState.onEditComplete;
2612
+
2613
+ // ✅ Show loader on next button while onEditComplete executes
2614
+ const nextBtnText = elements.nextBtn ? elements.nextBtn.querySelector('[data-chat-element="next-button-text"]') : null;
2615
+ const nextBtnLoader = elements.nextBtn ? elements.nextBtn.querySelector('[data-chat-element="next-button-loader"]') : null;
2616
+
2617
+ if (nextBtnText) nextBtnText.style.display = 'none';
2618
+ if (nextBtnLoader) nextBtnLoader.style.display = 'block';
2619
+
2620
+ // Call onEditComplete callback (await if async)
2621
+ if (callback) {
2622
+ console.log('📞 Calling onEditComplete with data:', dataCopy);
2623
+ try {
2624
+ await callback(dataCopy);
2625
+ } catch (error) {
2626
+ console.error('Error in onEditComplete callback:', error);
2627
+ }
2628
+ }
2629
+
2630
+ // ✅ Hide loader and restore button text after callback completes
2631
+ if (nextBtnText) nextBtnText.style.display = '';
2632
+ if (nextBtnLoader) nextBtnLoader.style.display = 'none';
2633
+
2634
+ // Clean up edit-steps state
2635
+ chatState.editSteps = null;
2636
+ chatState.editStepsIndex = 0;
2637
+ chatState.onEditComplete = null;
2638
+ chatState.editFinalButtonText = null;
2639
+ chatState.completed = true;
2640
+
2641
+ // Hide inputs and next button
2642
+ const allRangeWrappers = document.querySelectorAll('[data-chat-element="range-wrapper"]');
2643
+ allRangeWrappers.forEach(wrapper => { wrapper.style.display = 'none'; });
2644
+
2645
+ const previousInputs = elements.messages.querySelectorAll(
2646
+ '[data-chat-element="options-wrapper"], ' +
2647
+ '[data-chat-element="text-input"]:not([style*="display: none"]), ' +
2648
+ '[data-chat-element="number-input"]:not([style*="display: none"]), ' +
2649
+ '[data-chat-element="multi-select-dropdown"]:not([style*="display: none"])'
2650
+ );
2651
+ previousInputs.forEach(input => { input.style.display = 'none'; });
2652
+
2653
+ if (elements.nextBtn) {
2654
+ elements.nextBtn.style.display = 'none';
2655
+ }
2656
+
2657
+ updateEditIcons();
2658
+
2659
+ // Dispatch event
2660
+ if (typeof window !== 'undefined') {
2661
+ const event = new CustomEvent('conversationalFlowEditStepsComplete', {
2662
+ detail: { data: dataCopy, history: historyCopy }
2663
+ });
2664
+ window.dispatchEvent(event);
2665
+ }
2666
+
2667
+ return;
2668
+ }
2669
+ }
2670
+
2544
2671
  if (chatState.returnToStep !== null) {
2545
2672
  const targetStep = chatState.returnToStep;
2546
2673
  console.log(`\n🔙 Returning to saved step ${targetStep} (edited step complete)`);
@@ -2740,7 +2867,12 @@ async function showNextStep() {
2740
2867
  }
2741
2868
 
2742
2869
  // ✅ NEW: Check if step should be displayed based on condition
2743
- if (nextStep.shouldDisplay) {
2870
+ // ✅ Skip shouldDisplay check entirely if this step is explicitly in editSteps
2871
+ const isInEditSteps = chatState.editSteps && chatState.editSteps.includes(chatState.step);
2872
+ if (isInEditSteps) {
2873
+ console.log(` 🔓 Step ${chatState.step} is in editSteps - overriding shouldDisplay, always showing`);
2874
+ }
2875
+ if (nextStep.shouldDisplay && !isInEditSteps) {
2744
2876
  let shouldShow = false;
2745
2877
 
2746
2878
  if (typeof nextStep.shouldDisplay === 'function') {
@@ -2762,13 +2894,31 @@ async function showNextStep() {
2762
2894
 
2763
2895
  if (!shouldShow) {
2764
2896
  console.log(` ⏭️ Skipping step ${chatState.step} (shouldDisplay returned false)`);
2897
+
2898
+ // ✅ In editSteps mode, skip to the next step in the editSteps list
2899
+ if (chatState.editSteps && chatState.editSteps.length > 0) {
2900
+ const nextEditIndex = chatState.editStepsIndex + 1;
2901
+ if (nextEditIndex < chatState.editSteps.length) {
2902
+ chatState.editStepsIndex = nextEditIndex;
2903
+ chatState.step = chatState.editSteps[nextEditIndex];
2904
+ console.log(` 🛤️ editSteps: skipping to next edit step ${chatState.step}`);
2905
+ await showNextStep();
2906
+ return;
2907
+ } else {
2908
+ // All remaining edit steps should be skipped - trigger edit completion
2909
+ console.log(` 🛤️ editSteps: no more steps - completing`);
2910
+ handleCompletion();
2911
+ return;
2912
+ }
2913
+ }
2914
+
2765
2915
  chatState.step++;
2766
-
2916
+
2767
2917
  if (chatState.step >= flowData.flow.length) {
2768
2918
  handleCompletion();
2769
2919
  return;
2770
2920
  }
2771
-
2921
+
2772
2922
  // Recursively check next step
2773
2923
  await showNextStep();
2774
2924
  return;
@@ -3348,12 +3498,20 @@ async function showNextStep() {
3348
3498
 
3349
3499
  if (elements.nextBtn) {
3350
3500
  const nextBtnTextElement = elements.nextBtn.querySelector('[data-chat-element="next-button-text"]');
3351
-
3501
+
3352
3502
  if (nextBtnTextElement) {
3353
- // ✅ Priority: step-level > global config > original
3503
+ // ✅ Priority: editFinalButtonText (last edit step) > step-level > global config > original
3354
3504
  let buttonText;
3355
-
3356
- if (nextStep.nextButtonText) {
3505
+
3506
+ // Check if this is the last step in editSteps mode
3507
+ const isLastEditStep = chatState.editSteps &&
3508
+ chatState.editStepsIndex === chatState.editSteps.length - 1 &&
3509
+ chatState.editFinalButtonText;
3510
+
3511
+ if (isLastEditStep) {
3512
+ buttonText = chatState.editFinalButtonText;
3513
+ console.log(` 📝 Next button text: "${buttonText}" (editFinalButtonText - last edit step)`);
3514
+ } else if (nextStep.nextButtonText) {
3357
3515
  // Step-level override takes highest priority
3358
3516
  buttonText = nextStep.nextButtonText;
3359
3517
  console.log(` 📝 Next button text: "${buttonText}" (step-level)`);
@@ -3366,7 +3524,7 @@ async function showNextStep() {
3366
3524
  buttonText = elements.originalNextBtnText;
3367
3525
  console.log(` 📝 Next button text: "${buttonText}" (original)`);
3368
3526
  }
3369
-
3527
+
3370
3528
  nextBtnTextElement.textContent = buttonText;
3371
3529
  } else if (nextStep.nextButtonText || config.nextButtonText) {
3372
3530
  console.warn(` ⚠️ nextButtonText specified but next-button-text element not found`);
@@ -3514,6 +3672,43 @@ function init(flowName, flowConfig, options = {}) {
3514
3672
  chatState.history = [];
3515
3673
  chatState.currentSelection = null;
3516
3674
 
3675
+ // ✅ NEW: Edit-steps mode - only walk through specific steps by name
3676
+ if (options.editSteps && Array.isArray(options.editSteps) && options.editSteps.length > 0) {
3677
+ // Resolve step names to indices
3678
+ const resolvedSteps = [];
3679
+ options.editSteps.forEach(stepName => {
3680
+ const index = flowConfig.flow.findIndex(s => s.name === stepName);
3681
+ if (index !== -1) {
3682
+ resolvedSteps.push(index);
3683
+ console.log(` 🔍 editSteps: "${stepName}" → step index ${index}`);
3684
+ } else {
3685
+ console.warn(` ⚠️ editSteps: step name "${stepName}" not found in flow`);
3686
+ }
3687
+ });
3688
+
3689
+ if (resolvedSteps.length > 0) {
3690
+ chatState.editSteps = resolvedSteps;
3691
+ chatState.editStepsIndex = 0;
3692
+ chatState.onEditComplete = typeof options.onEditComplete === 'function' ? options.onEditComplete : null;
3693
+ chatState.editFinalButtonText = options.editFinalButtonText || null;
3694
+ chatState.step = resolvedSteps[0]; // Start at the first edit step
3695
+ console.log(`🛤️ Edit-steps mode: ${resolvedSteps.length} steps to edit`, resolvedSteps);
3696
+ console.log(` onEditComplete: ${chatState.onEditComplete ? 'provided' : 'not provided'}`);
3697
+ console.log(` editFinalButtonText: ${chatState.editFinalButtonText || 'not set'}`);
3698
+ } else {
3699
+ console.warn('⚠️ editSteps provided but no valid step names found - using normal flow');
3700
+ chatState.editSteps = null;
3701
+ chatState.editStepsIndex = 0;
3702
+ chatState.onEditComplete = null;
3703
+ chatState.editFinalButtonText = null;
3704
+ }
3705
+ } else {
3706
+ chatState.editSteps = null;
3707
+ chatState.editStepsIndex = 0;
3708
+ chatState.onEditComplete = null;
3709
+ chatState.editFinalButtonText = null;
3710
+ }
3711
+
3517
3712
  elements.messages = elements.container.querySelector('[data-chat-element="messages-container"]');
3518
3713
  elements.nextBtn = elements.container.querySelector('[data-chat-element="next-button"]');
3519
3714
  elements.cancelBtn = elements.container.querySelector('[data-chat-element="cancel-button"]');
@@ -3617,7 +3812,11 @@ function reset() {
3617
3812
  chatState.history = [];
3618
3813
  chatState.currentSelection = null;
3619
3814
  chatState.chatMode = 'create'; // ✅ Reset to create mode
3620
-
3815
+ chatState.editSteps = null;
3816
+ chatState.editStepsIndex = 0;
3817
+ chatState.onEditComplete = null;
3818
+ chatState.editFinalButtonText = null;
3819
+
3621
3820
  // ✅ Move back any injected elements before clearing
3622
3821
  if (elements.messages) {
3623
3822
  const injectedElements = elements.messages.querySelectorAll('[data-chat-injected="true"]');