1+ import { GleapTranslationManager } from "./Gleap" ;
12import { loadIcon } from "./UI" ;
23
34const localStorageKey = "gleap-tour-data" ;
@@ -6,6 +7,8 @@ const styleId = "copilot-tour-styles";
67const copilotJoinedContainerId = "copilot-joined-container" ;
78const copilotInfoBubbleId = "copilot-info-bubble" ;
89
10+ const arrowRightIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>` ;
11+
912function estimateReadTime ( text ) {
1013 const wordsPerSecond = 3.6 ; // Average reading speed
1114 const wordCount = text . split ( / \s + / ) . filter ( ( word ) => word . length > 0 ) . length ;
@@ -138,7 +141,8 @@ export default class GleapCopilotTours {
138141 ) {
139142 // Wait for the element to be rendered.
140143 self . updatePointerPosition (
141- document . querySelector ( currentStep . selector )
144+ document . querySelector ( currentStep . selector ) ,
145+ currentStep
142146 ) ;
143147 }
144148 }
@@ -186,7 +190,7 @@ export default class GleapCopilotTours {
186190 }
187191 }
188192
189- updatePointerPosition ( anchor ) {
193+ updatePointerPosition ( anchor , currentStep ) {
190194 try {
191195 const container = document . getElementById ( pointerContainerId ) ;
192196 if ( ! container ) {
@@ -221,6 +225,11 @@ export default class GleapCopilotTours {
221225 let anchorCenterY =
222226 anchorRect . top + anchorRect . height / 2 + window . scrollY ;
223227
228+ if ( currentStep ?. mode === "INPUT" ) {
229+ anchorCenterX -= anchorRect . width / 2 - 10 ;
230+ anchorCenterY += anchorRect . height / 2 - 5 ;
231+ }
232+
224233 let containerWidthSpace = 350 ;
225234 if ( containerWidthSpace > window . innerWidth - 40 ) {
226235 containerWidthSpace = window . innerWidth - 40 ;
@@ -286,8 +295,21 @@ export default class GleapCopilotTours {
286295 display: flex;
287296 align-items: flex-start;
288297 pointer-events: none;
289- z-index: 9999;
290- transition: all 0.5s ease;;
298+ z-index: 2147483610;
299+ transition: all 0.5s ease;
300+ }
301+
302+ .${ pointerContainerId } -clickmode {
303+ cursor: pointer;
304+ pointer-events: all !important;
305+ }
306+
307+ .${ pointerContainerId } -clickmode #${ copilotInfoBubbleId } -content {
308+ display: flex !important;
309+ }
310+
311+ .${ pointerContainerId } -clickmode svg {
312+ display: none !important;
291313 }
292314
293315 #${ pointerContainerId } svg {
@@ -315,6 +337,13 @@ export default class GleapCopilotTours {
315337 align-items: flex-start;
316338 }
317339
340+ #${ copilotInfoBubbleId } -content svg {
341+ width: 16px;
342+ height: 16px;
343+ display: inline-block !important;
344+ margin-left: 5px;
345+ }
346+
318347 #${ copilotInfoBubbleId } -content {
319348 margin-top: 18px;
320349 margin-left: 5px;
@@ -605,6 +634,7 @@ export default class GleapCopilotTours {
605634 return ;
606635 }
607636
637+ const self = this ;
608638 const config = this . productTourData ;
609639 const steps = config . steps ;
610640
@@ -619,13 +649,69 @@ export default class GleapCopilotTours {
619649 const currentStep = steps [ this . currentActiveIndex ] ;
620650
621651 const handleStep = ( element ) => {
652+ document . getElementById ( pointerContainerId ) . style . display = "flex" ;
653+
622654 // If we have a selector but the element was null, close the tour.
623655 if ( currentStep . selector && currentStep . selector . length > 0 && ! element ) {
624656 this . completeTour ( false ) ;
625657 return ;
626658 }
627659
628660 const gotToNextStep = ( ) => {
661+ if ( currentStep . mode === "INPUT" && element ) {
662+ // Wait for text to be entered. Continue tour on enter. element is the input.
663+ function handleClick ( ) {
664+ document
665+ . querySelector ( `#${ pointerContainerId } ` )
666+ . classList . remove ( "copilot-pointer-container-clickmode" ) ;
667+
668+ // Remove the highlight from the input fields.
669+ element . classList . remove ( "gleap-input-highlight" ) ;
670+
671+ // Hide the info bubble.
672+ document . getElementById ( pointerContainerId ) . style . display = "none" ;
673+
674+ self . currentActiveIndex ++ ;
675+ self . storeUncompletedTour ( ) ;
676+ self . renderNextStep ( ) ;
677+ }
678+
679+ function handleInputEvent ( e ) {
680+ if ( e . target . value . length === 0 ) return ;
681+
682+ const cursor = document . getElementById (
683+ `${ copilotInfoBubbleId } -content`
684+ ) ;
685+ if ( ! cursor ) return ;
686+
687+ cursor . innerHTML = `${ GleapTranslationManager . translateText (
688+ `next`
689+ ) } ${ arrowRightIcon } `;
690+ cursor . addEventListener ( "click" , handleClick , { once : true } ) ;
691+
692+ // Add highlight to the input fields. red shadow glow.
693+ element . classList . add ( "gleap-input-highlight" ) ;
694+
695+ document
696+ . querySelector ( `#${ pointerContainerId } ` )
697+ . classList . add ( "copilot-pointer-container-clickmode" ) ;
698+
699+ // Remove the input event listener after execution
700+ element . removeEventListener ( "input" , handleInputEvent ) ;
701+ }
702+
703+ element . addEventListener ( "input" , handleInputEvent ) ;
704+
705+ // Focus on the input.
706+ element . addEventListener ( "blur" , ( ) => {
707+ element . focus ( ) ;
708+ } ) ;
709+
710+ element . focus ( ) ;
711+
712+ return ;
713+ }
714+
629715 this . currentActiveIndex ++ ;
630716 this . storeUncompletedTour ( ) ;
631717
@@ -648,7 +734,7 @@ export default class GleapCopilotTours {
648734 } ;
649735
650736 // Update pointer position, even if element is null.
651- this . updatePointerPosition ( element ) ;
737+ this . updatePointerPosition ( element , currentStep ) ;
652738
653739 const message = currentStep ?. message
654740 ? htmlToPlainText ( currentStep . message )
0 commit comments