1+ class lcwTourGuide {
2+ constructor ( options = { } ) {
3+ this . steps = Array . from ( document . querySelectorAll ( '[data-lcw-tour-step]' ) ) . sort ( ( a , b ) => a . dataset . tourStep - b . dataset . tourStep ) ;
4+
5+ this . current = 0 ;
6+ this . tooltip = null ;
7+ this . overlay = null ;
8+
9+ // callbacks
10+ this . onStart = options . onStart || ( ( ) => { } ) ;
11+ this . onEnd = options . onEnd || ( ( ) => { } ) ;
12+ this . onStep = options . onStep || ( ( ) => { } ) ;
13+ }
14+
15+ start ( ) {
16+ if ( ! this . steps . length ) return ;
17+ this . _createOverlay ( ) ;
18+ this . _showStep ( this . current ) ;
19+
20+ // fire callback
21+ this . onStart ( ) ;
22+ }
23+
24+ _createOverlay ( ) {
25+ this . overlay = document . createElement ( 'div' ) ;
26+ this . overlay . className = 'lcw-tour-overlay' ;
27+ document . body . appendChild ( this . overlay ) ;
28+ }
29+
30+ _showStep ( index ) {
31+ const step = this . steps [ index ] ;
32+
33+ // scroll so both element + space for tooltip is visible
34+ const rect = step . getBoundingClientRect ( ) ;
35+ const tooltipHeight = 120 ; // estimated
36+ const scrollY = window . scrollY + rect . top - ( window . innerHeight / 2 - rect . height / 2 ) ;
37+
38+ let targetScroll = scrollY ;
39+ if ( rect . bottom + tooltipHeight > window . innerHeight ) {
40+ targetScroll = window . scrollY + rect . top - tooltipHeight - 20 ;
41+ }
42+ window . scrollTo ( { top : targetScroll , behavior : 'smooth' } ) ;
43+
44+ // Remove highlight from all
45+ this . steps . forEach ( el => el . classList . remove ( 'lcw-tour-highlight' ) ) ;
46+
47+ setTimeout ( ( ) => {
48+ const rect = step . getBoundingClientRect ( ) ;
49+ if ( this . tooltip ) this . tooltip . remove ( ) ;
50+
51+ // Highlight the current step
52+ step . classList . add ( 'lcw-tour-highlight' ) ;
53+
54+ this . tooltip = document . createElement ( 'div' ) ;
55+ this . tooltip . className = 'lcw-tour-tooltip' ;
56+ this . tooltip . innerHTML = `
57+ <div class="lcw-tour-close" id="close-tour">×</div>
58+ <div style="margin-top: 10px;">${ step . dataset . lcwTourText } </div>
59+ <div style="margin-top: 12px;">
60+ ${ index > 0 ? '<button id="prev-step">Previous</button>' : '' }
61+ ${ index < this . steps . length - 1
62+ ? '<button id="next-step">Next</button>'
63+ : '<button id="end-tour">Done</button>' }
64+ </div>` ;
65+ document . body . appendChild ( this . tooltip ) ;
66+
67+ // Decide tooltip position
68+ const tooltipRect = this . tooltip . getBoundingClientRect ( ) ;
69+ let top = rect . bottom + window . scrollY + 10 ; // default below
70+ if ( rect . bottom + tooltipRect . height + 20 > window . innerHeight ) {
71+ // place above if not enough space
72+ top = rect . top + window . scrollY - tooltipRect . height - 10 ;
73+ }
74+
75+ let left = rect . left + window . scrollX ;
76+ if ( left + tooltipRect . width > window . innerWidth ) {
77+ left = window . innerWidth - tooltipRect . width - 10 ;
78+ }
79+ if ( left < 0 ) left = 10 ;
80+
81+ this . tooltip . style . top = `${ top } px` ;
82+ this . tooltip . style . left = `${ left } px` ;
83+
84+ // Button Events
85+ document . getElementById ( 'next-step' ) ?. addEventListener ( 'click' , ( ) => this . _next ( ) ) ;
86+ document . getElementById ( 'prev-step' ) ?. addEventListener ( 'click' , ( ) => this . _prev ( ) ) ;
87+ document . getElementById ( 'end-tour' ) ?. addEventListener ( 'click' , ( ) => this . _end ( ) ) ;
88+ document . getElementById ( 'close-tour' ) ?. addEventListener ( 'click' , ( ) => this . _end ( ) ) ;
89+
90+ // fire step callback
91+ this . onStep ( index , step ) ;
92+ } , 400 ) ;
93+ }
94+
95+ _next ( ) {
96+ if ( this . current < this . steps . length - 1 ) {
97+ this . current ++ ;
98+ this . _showStep ( this . current ) ;
99+ } else {
100+ this . _end ( ) ;
101+ }
102+ }
103+
104+ _prev ( ) {
105+ if ( this . current > 0 ) {
106+ this . current -- ;
107+ this . _showStep ( this . current ) ;
108+ }
109+ }
110+
111+ _end ( ) {
112+ this . tooltip ?. remove ( ) ;
113+ this . overlay ?. remove ( ) ;
114+ this . steps . forEach ( el => el . classList . remove ( 'lcw-tour-highlight' ) ) ;
115+ this . tooltip = null ;
116+ this . overlay = null ;
117+ this . current = 0 ;
118+
119+ // fire callback
120+ this . onEnd ( ) ;
121+ }
122+ }
123+ window . lcwTourGuide = lcwTourGuide ;
0 commit comments