javascriptgantt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1095 @@
1
+ /* =========================================================
2
+ * Created by Sunil Solanki
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ * ========================================================= */
16
+
17
+ (function (global) {
18
+ class ztTour {
19
+ constructor(options, templates) {
20
+ this.initializeOptions(options);
21
+ this.initializeTemplate(templates);
22
+
23
+ this.popup = null;
24
+ this.overlaySvg = null;
25
+ this.currentStep = 0;
26
+ this.activeStagePosition = null;
27
+
28
+ this.onKeyUp = this.onKeyUp.bind(this);
29
+ this.refreshStep = this.refreshStep.bind(this);
30
+
31
+ this.initEvents();
32
+
33
+ this.onNextClick = options.onNextClick || null;
34
+ this.onClose = options.onClose || null;
35
+ this.onPreviousClick = options.onPreviousClick || null;
36
+ }
37
+
38
+ // initialize Options
39
+ initializeOptions(opt) {
40
+ if (opt == null) {
41
+ opt = {};
42
+ }
43
+
44
+ this.options = {
45
+ steps: opt.steps || [],
46
+ overlayOpacity: opt.overlayOpacity || 0.7,
47
+ stagePadding: opt.stagePadding || 10,
48
+ popupOffset: opt.popupOffset || 10,
49
+ stageRadius: opt.stageRadius || 5,
50
+ overlayColor: opt.overlayColor || "#000",
51
+ animate: opt.animate !== undefined ? opt.animate : true,
52
+ smoothScroll: opt.smoothScroll || false,
53
+ visibleButtons: opt.visibleButtons || ["next", "previous", "close"],
54
+ disableButtons: opt.disableButtons || [],
55
+ showProgress: opt.showProgress || false,
56
+ nextBtnText: opt.nextBtnText || "Next →",
57
+ prevBtnText: opt.prevBtnText || "← Previous",
58
+ doneBtnText: opt.doneBtnText || "Done",
59
+ allowBackdropClose: opt.allowBackdropClose !== undefined ? opt.allowBackdropClose : true,
60
+ popupClass: opt.popupClass || false,
61
+ keyboardControl: opt.keyboardControl !== undefined ? opt.keyboardControl : true,
62
+ animationDuration: opt.animationDuration || 400,
63
+ };
64
+ }
65
+
66
+ initializeTemplate(templ) {
67
+ if (templ == null) {
68
+ templ = {};
69
+ }
70
+ this.templates = {
71
+ progressText:
72
+ templ.progressText || ((current, total) => `${current} of ${total}`),
73
+ };
74
+ }
75
+
76
+ initEvents() {
77
+ window.addEventListener("keyup", this.onKeyUp, false);
78
+ window.addEventListener("resize", this.refreshStep, false);
79
+ window.addEventListener("scroll", this.refreshStep, false);
80
+ }
81
+
82
+ onKeyUp(e) {
83
+ const keyboardControl = this.getOption("keyboardControl");
84
+ if (keyboardControl) {
85
+ if (e.key === "Escape") {
86
+ this.destroyTour();
87
+ } else if (e.key === "ArrowRight") {
88
+ this.highlightStep(this.getOption("currentStep") + 1);
89
+ } else if (e.key === "ArrowLeft") {
90
+ this.highlightStep(this.getOption("currentStep") - 1);
91
+ }
92
+ }
93
+ }
94
+
95
+ destroyEvents() {
96
+ window.removeEventListener("keyup", this.onKeyUp);
97
+ window.removeEventListener("resize", this.refreshStep);
98
+ window.removeEventListener("scroll", this.refreshStep);
99
+ }
100
+
101
+ getOption(key) {
102
+ if (!key) {
103
+ return this.options;
104
+ }
105
+ return this.options[key];
106
+ }
107
+
108
+ setOption(key, val) {
109
+ this.options[key] = val;
110
+ }
111
+
112
+ throwError(error) {
113
+ throw new Error(error);
114
+ }
115
+
116
+ start() {
117
+ this.highlightStep(0);
118
+ }
119
+
120
+ showHint(hint, isAnnouncement = false){
121
+ this.removeHint(isAnnouncement);
122
+
123
+ let popupType = isAnnouncement ? "announcement" : "hint";
124
+
125
+ let hintEle = document.createElement("div");
126
+ let hintBackdrop = document.createElement("div");
127
+ hintBackdrop.classList.add(`zt-tour-${popupType}-backdrop`);
128
+ hintBackdrop.addEventListener('click',()=>{
129
+ hintEle.remove();
130
+ hintBackdrop.remove();
131
+ })
132
+ hintEle.innerHTML = hint.innerHTML;
133
+ hintEle.classList.add(`zt-tour-${popupType}`);
134
+
135
+ let dimentions;
136
+ if(!isAnnouncement){
137
+ dimentions = document.querySelector(hint.element).getBoundingClientRect();
138
+ }
139
+
140
+ document.body.appendChild(hintBackdrop);
141
+ document.body.appendChild(hintEle);
142
+
143
+
144
+ hintEle.style.top = isAnnouncement ? "50%" : dimentions.top + dimentions.height + 10 + "px";
145
+ hintEle.style.left = isAnnouncement ? "50%" : dimentions.left + 10 + "px";
146
+ hintEle.style.transform = isAnnouncement ? "translate(-50%, -50%)" : "";
147
+ }
148
+
149
+ showAnnouncement(announcement){
150
+ announcement = {innerHTML: announcement};
151
+ this.showHint(announcement, true);
152
+ }
153
+
154
+ removeHint(isAnnouncement = false){
155
+ let popupType = isAnnouncement ? "announcement" : "hint";
156
+ let oldHint = document.querySelectorAll(`.zt-tour-${popupType}`);
157
+ let oldHintBackdrop = document.querySelectorAll(`.zt-tour-${popupType}-backdrop`);
158
+
159
+ Array.from(oldHint).forEach((hint)=>hint.remove())
160
+ Array.from(oldHintBackdrop).forEach((hintBackdrop)=>hintBackdrop.remove())
161
+ }
162
+
163
+ removeAnnouncement(){
164
+ this.removeHint(true);
165
+ }
166
+
167
+ highlightStep(currentStep) {
168
+ if (0 > currentStep || currentStep > this.getOption("steps").length - 1) {
169
+ this.destroyTour();
170
+ return;
171
+ }
172
+
173
+ let step = this.options.steps[currentStep];
174
+ let element = document.querySelector(step?.element);
175
+
176
+ if (!element) {
177
+ element = this.addDummyElement();
178
+ }
179
+
180
+ this.changeHighlight(element, step, currentStep);
181
+ }
182
+
183
+ createPopup() {
184
+ const wrapper = document.createElement("div");
185
+ wrapper.classList.add("zt-tour-popup");
186
+ if (this.getOption("popupClass")) {
187
+ wrapper.classList.add(this.getOption("popupClass").trim());
188
+ }
189
+
190
+ const arrow = document.createElement("div");
191
+ arrow.classList.add("zt-tour-popup-arrow");
192
+
193
+ const title = document.createElement("header");
194
+ title.id = "zt-tour-popup-title";
195
+ title.classList.add("zt-tour-popup-title");
196
+ title.style.display = "none";
197
+ title.innerText = "Popup Title";
198
+
199
+ const description = document.createElement("div");
200
+ description.id = "zt-tour-popup-description";
201
+ description.classList.add("zt-tour-popup-description");
202
+ description.style.display = "none";
203
+ description.innerText = "Popup description is here";
204
+
205
+ const closeButton = document.createElement("button");
206
+ closeButton.type = "button";
207
+ closeButton.classList.add("zt-tour-popup-close-btn");
208
+ closeButton.setAttribute("aria-label", "Close");
209
+ closeButton.innerHTML = "×";
210
+
211
+ const footer = document.createElement("footer");
212
+ footer.classList.add("zt-tour-popup-footer");
213
+
214
+ const progress = document.createElement("span");
215
+ progress.classList.add("zt-tour-popup-progress-text");
216
+ progress.innerText = "";
217
+
218
+ const footerButtons = document.createElement("span");
219
+ footerButtons.classList.add("zt-tour-popup-navigation-btns");
220
+
221
+ const previousButton = document.createElement("button");
222
+ previousButton.type = "button";
223
+ previousButton.classList.add("zt-tour-popup-prev-btn");
224
+ previousButton.innerHTML = "← Previous";
225
+
226
+ const nextButton = document.createElement("button");
227
+ nextButton.type = "button";
228
+ nextButton.classList.add("zt-tour-popup-next-btn");
229
+ nextButton.innerHTML = "Next →";
230
+
231
+ footerButtons.appendChild(previousButton);
232
+ footerButtons.appendChild(nextButton);
233
+ footer.appendChild(progress);
234
+ footer.appendChild(footerButtons);
235
+
236
+ wrapper.appendChild(closeButton);
237
+ wrapper.appendChild(arrow);
238
+ wrapper.appendChild(title);
239
+ wrapper.appendChild(description);
240
+ wrapper.appendChild(footer);
241
+
242
+ return {
243
+ wrapper,
244
+ arrow,
245
+ title,
246
+ description,
247
+ footer,
248
+ previousButton,
249
+ nextButton,
250
+ closeButton,
251
+ footerButtons,
252
+ progress,
253
+ };
254
+ }
255
+
256
+ renderPopup(element, step) {
257
+ let popup = this.createPopup();
258
+ this.options.popup = popup;
259
+
260
+ let oldPopups = document.querySelectorAll(".zt-tour-popup");
261
+
262
+ // remove all old popups
263
+ Array.from(oldPopups).forEach((ele) => {
264
+ ele.remove();
265
+ });
266
+
267
+ document.body.appendChild(popup.wrapper);
268
+
269
+ let {
270
+ title,
271
+ description,
272
+ visibleButtons,
273
+ disableButtons,
274
+ showProgress,
275
+ nextBtnText = this.options.nextBtnText,
276
+ prevBtnText = this.options.prevBtnText,
277
+ progressText = this.templates.progressText(
278
+ this.options.currentStep + 1,
279
+ this.options.steps.length
280
+ ),
281
+ } = step.popup || {};
282
+
283
+ if (this.options.currentStep + 1 === this.options.steps.length) {
284
+ nextBtnText = this.options.doneBtnText;
285
+ }
286
+
287
+ if (this.options.currentStep === 0) {
288
+ popup.previousButton.disabled = true;
289
+ }
290
+
291
+ popup.nextButton.innerHTML = nextBtnText;
292
+ popup.previousButton.innerHTML = prevBtnText;
293
+ popup.progress.innerHTML = progressText;
294
+
295
+ if (title) {
296
+ popup.title.innerHTML = title;
297
+ popup.title.style.display = "block";
298
+ } else {
299
+ popup.title.style.display = "none";
300
+ }
301
+
302
+ if (description) {
303
+ popup.description.innerHTML = description;
304
+ popup.description.style.display = "block";
305
+ } else {
306
+ popup.description.style.display = "none";
307
+ }
308
+
309
+ const showButtonsOption =
310
+ visibleButtons || this.getOption("visibleButtons");
311
+ const isShowProgress = showProgress || this.getOption("showProgress");
312
+
313
+ const isShowFooter =
314
+ showButtonsOption.includes("next") ||
315
+ showButtonsOption?.includes("previous") ||
316
+ isShowProgress;
317
+
318
+ popup.closeButton.style.display = showButtonsOption.includes("close")
319
+ ? "block"
320
+ : "none";
321
+
322
+ if (isShowFooter) {
323
+ popup.footer.style.display = "flex";
324
+
325
+ popup.progress.style.display = isShowProgress ? "block" : "none";
326
+ popup.nextButton.style.display = showButtonsOption.includes("next")
327
+ ? "block"
328
+ : "none";
329
+ popup.previousButton.style.display = showButtonsOption.includes(
330
+ "previous"
331
+ )
332
+ ? "block"
333
+ : "none";
334
+ } else {
335
+ popup.footer.style.display = "none";
336
+ }
337
+
338
+ const disabledButtonsOption =
339
+ disableButtons || this.getOption("disableButtons");
340
+ if (disabledButtonsOption?.includes("next")) {
341
+ popup.nextButton.disabled = true;
342
+ }
343
+
344
+ if (disabledButtonsOption?.includes("previous")) {
345
+ popup.previousButton.disabled = true;
346
+ }
347
+
348
+ if (disabledButtonsOption?.includes("close")) {
349
+ popup.closeButton.disabled = true;
350
+ }
351
+
352
+ popup.nextButton.addEventListener("click", () => {
353
+ let isOutOfIndex =
354
+ this.options.currentStep + 1 < 0 ||
355
+ this.options.currentStep + 1 >= this.getOption("steps").length;
356
+ this.highlightStep(this.options.currentStep + 1);
357
+ if (typeof this.onNextClick === "function" && !isOutOfIndex) {
358
+ this.onNextClick(this.options.currentStep);
359
+ }
360
+ });
361
+
362
+ popup.previousButton.addEventListener("click", () => {
363
+ this.highlightStep(this.options.currentStep - 1);
364
+ if (typeof this.onPreviousClick === "function") {
365
+ this.onPreviousClick(this.options.currentStep);
366
+ }
367
+ });
368
+
369
+ popup.closeButton.addEventListener("click", () => {
370
+ this.destroyTour();
371
+ if (typeof this.onClose === "function") {
372
+ this.onClose();
373
+ }
374
+ });
375
+
376
+ this.repositionPopup(element, step);
377
+ }
378
+
379
+ hidePopup() {
380
+ const popup = this.getOption("popup");
381
+ if (!popup) {
382
+ return;
383
+ }
384
+
385
+ popup.wrapper.style.display = "none";
386
+ }
387
+
388
+ renderPopupArrow(alignment, side, element) {
389
+ const elementDimensions = element.getBoundingClientRect();
390
+ const popupDimensions = this.getPopupDimensions();
391
+ const popupArrow = this.options.popup.arrow;
392
+
393
+ const popupWidth = popupDimensions.width;
394
+ const windowWidth = window.innerWidth;
395
+ const elementWidth = elementDimensions.width;
396
+ const elementLeft = elementDimensions.left;
397
+
398
+ const popupHeight = popupDimensions.height;
399
+ const windowHeight = window.innerHeight;
400
+ const elementTop = elementDimensions.top;
401
+ const elementHeight = elementDimensions.height;
402
+
403
+ // Remove all arrow classes
404
+ popupArrow.className = "zt-tour-popup-arrow";
405
+
406
+ let arrowSide = side;
407
+ let arrowAlignment = alignment;
408
+
409
+ if (side === "top") {
410
+ if (elementLeft + elementWidth <= 0) {
411
+ arrowSide = "right";
412
+ arrowAlignment = "end";
413
+ } else if (elementLeft + elementWidth - popupWidth <= 0) {
414
+ arrowSide = "top";
415
+ arrowAlignment = "start";
416
+ }
417
+ if (elementLeft >= windowWidth) {
418
+ arrowSide = "left";
419
+ arrowAlignment = "end";
420
+ } else if (elementLeft + popupWidth >= windowWidth) {
421
+ arrowSide = "top";
422
+ arrowAlignment = "end";
423
+ }
424
+ } else if (side === "bottom") {
425
+ if (elementLeft + elementWidth <= 0) {
426
+ arrowSide = "right";
427
+ arrowAlignment = "start";
428
+ } else if (elementLeft + elementWidth - popupWidth <= 0) {
429
+ arrowSide = "bottom";
430
+ arrowAlignment = "start";
431
+ }
432
+ if (elementLeft >= windowWidth) {
433
+ arrowSide = "left";
434
+ arrowAlignment = "start";
435
+ } else if (elementLeft + popupWidth >= windowWidth) {
436
+ arrowSide = "bottom";
437
+ arrowAlignment = "end";
438
+ }
439
+ } else if (side === "left") {
440
+ if (elementTop + elementHeight <= 0) {
441
+ arrowSide = "bottom";
442
+ arrowAlignment = "end";
443
+ } else if (elementTop + elementHeight - popupHeight <= 0) {
444
+ arrowSide = "left";
445
+ arrowAlignment = "start";
446
+ }
447
+
448
+ if (elementTop >= windowHeight) {
449
+ arrowSide = "top";
450
+ arrowAlignment = "end";
451
+ } else if (elementTop + popupHeight >= windowHeight) {
452
+ arrowSide = "left";
453
+ arrowAlignment = "end";
454
+ }
455
+ } else if (side === "right") {
456
+ if (elementTop + elementHeight <= 0) {
457
+ arrowSide = "bottom";
458
+ arrowAlignment = "start";
459
+ } else if (elementTop + elementHeight - popupHeight <= 0) {
460
+ arrowSide = "right";
461
+ arrowAlignment = "start";
462
+ }
463
+
464
+ if (elementTop >= windowHeight) {
465
+ arrowSide = "top";
466
+ arrowAlignment = "start";
467
+ } else if (elementTop + popupHeight >= windowHeight) {
468
+ arrowSide = "right";
469
+ arrowAlignment = "end";
470
+ }
471
+ }
472
+
473
+ if (!arrowSide) {
474
+ popupArrow.classList.add("zt-tour-d-none");
475
+ } else {
476
+ popupArrow.classList.add(`zt-tour-popup-arrow-side-${arrowSide}`);
477
+ popupArrow.classList.add(`zt-tour-popup-arrow-align-${arrowAlignment}`);
478
+ }
479
+ }
480
+
481
+ getPopupDimensions() {
482
+ const boundingClientRect =
483
+ this.options.popup.wrapper.getBoundingClientRect();
484
+
485
+ const stagePadding = this.options.stagePadding || 0;
486
+ const popupOffset = this.options.popupOffset || 0;
487
+
488
+ return {
489
+ width: boundingClientRect.width + stagePadding + popupOffset,
490
+ height: boundingClientRect.height + stagePadding + popupOffset,
491
+
492
+ realWidth: boundingClientRect.width,
493
+ realHeight: boundingClientRect.height,
494
+ };
495
+ }
496
+
497
+ // create svg
498
+ createOverlaySvg(stage) {
499
+ const windowX = window.innerWidth;
500
+ const windowY = window.innerHeight;
501
+
502
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
503
+ svg.classList.add("zt-tour-overlay", "zt-tour-overlay-animated");
504
+
505
+ svg.setAttribute("viewBox", `0 0 ${windowX} ${windowY}`);
506
+ svg.setAttribute("xmlSpace", "preserve");
507
+ svg.setAttribute("xmlnsXlink", "http://www.w3.org/1999/xlink");
508
+ svg.setAttribute("version", "1.1");
509
+ svg.setAttribute("preserveAspectRatio", "xMinYMin slice");
510
+
511
+ svg.style.fillRule = "evenodd";
512
+ svg.style.clipRule = "evenodd";
513
+ svg.style.strokeLinejoin = "round";
514
+ svg.style.strokeMiterlimit = "2";
515
+ svg.style.zIndex = "10000";
516
+ svg.style.position = "fixed";
517
+ svg.style.top = "0";
518
+ svg.style.left = "0";
519
+ svg.style.width = "100%";
520
+ svg.style.height = "100%";
521
+
522
+ const stagePath = document.createElementNS(
523
+ "http://www.w3.org/2000/svg",
524
+ "path"
525
+ );
526
+
527
+ stagePath.setAttribute("d", this.generateStageSvgPathString(stage));
528
+
529
+ stagePath.style.fill = this.options.overlayColor || "rgb(0,0,0)";
530
+ stagePath.style.opacity = `${this.options.overlayOpacity}`;
531
+ stagePath.style.pointerEvents = "auto";
532
+ stagePath.style.cursor = "auto";
533
+
534
+ svg.addEventListener("click", () => {
535
+ if (this.getOption("allowBackdropClose")) {
536
+ this.destroyTour();
537
+ if (typeof this.onClose === "function") {
538
+ this.onClose();
539
+ }
540
+ }
541
+ });
542
+
543
+ svg.appendChild(stagePath);
544
+
545
+ return svg;
546
+ }
547
+
548
+ // generate svg path
549
+ generateStageSvgPathString(stage) {
550
+ const windowX = window.innerWidth;
551
+ const windowY = window.innerHeight;
552
+
553
+ const stagePadding = this.options.stagePadding || 0;
554
+ const stageRadius = this.options.stageRadius || 0;
555
+
556
+ const stageWidth = stage.width + stagePadding * 2;
557
+ const stageHeight = stage.height + stagePadding * 2;
558
+
559
+ // prevent glitches when stage is too small for radius
560
+ const limitedRadius = Math.min(
561
+ stageRadius,
562
+ stageWidth / 2,
563
+ stageHeight / 2
564
+ );
565
+
566
+ // no value below 0 allowed + round down
567
+ const normalizedRadius = Math.floor(Math.max(limitedRadius, 0));
568
+
569
+ const highlightBoxX = stage.x - stagePadding + normalizedRadius;
570
+ const highlightBoxY = stage.y - stagePadding;
571
+ const highlightBoxWidth = stageWidth - normalizedRadius * 2;
572
+ const highlightBoxHeight = stageHeight - normalizedRadius * 2;
573
+
574
+ return `M${windowX},0L0,0L0,${windowY}L${windowX},${windowY}L${windowX},0Z
575
+ M${highlightBoxX},${highlightBoxY} h${highlightBoxWidth} a${normalizedRadius},${normalizedRadius} 0 0 1 ${normalizedRadius},${normalizedRadius} v${highlightBoxHeight} a${normalizedRadius},${normalizedRadius} 0 0 1 -${normalizedRadius},${normalizedRadius} h-${highlightBoxWidth} a${normalizedRadius},${normalizedRadius} 0 0 1 -${normalizedRadius},-${normalizedRadius} v-${highlightBoxHeight} a${normalizedRadius},${normalizedRadius} 0 0 1 ${normalizedRadius},-${normalizedRadius} z`;
576
+ }
577
+
578
+ repositionPopup(element, step) {
579
+ const popup = this.options.popup;
580
+ let { align = "start", side = "left" } = step?.popup || {};
581
+
582
+ align = step.element ? align : "over";
583
+ side = step.element ? side : "over";
584
+
585
+ // Configure the popup positioning
586
+ const requiredAlignment = align;
587
+ const requiredSide = side;
588
+ const popupPadding = this.options.stagePadding;
589
+
590
+ const popupDimensions = this.getPopupDimensions();
591
+ const popupArrowDimensions = popup.arrow.getBoundingClientRect();
592
+ const elementDimensions = element.getBoundingClientRect();
593
+
594
+ const topValue = elementDimensions.top - popupDimensions.height;
595
+ let isTopOptimal = topValue >= 0;
596
+
597
+ const bottomValue =
598
+ window.innerHeight -
599
+ (elementDimensions.bottom + popupDimensions.height);
600
+ let isBottomOptimal = bottomValue >= 0;
601
+
602
+ const leftValue = elementDimensions.left - popupDimensions.width;
603
+ let isLeftOptimal = leftValue >= 0;
604
+
605
+ const rightValue =
606
+ window.innerWidth - (elementDimensions.right + popupDimensions.width);
607
+ let isRightOptimal = rightValue >= 0;
608
+
609
+ const noneOptimal =
610
+ !isTopOptimal && !isBottomOptimal && !isLeftOptimal && !isRightOptimal;
611
+ let popupRenderedSide = requiredSide;
612
+
613
+ if (requiredSide === "top" && isTopOptimal) {
614
+ isRightOptimal = isLeftOptimal = isBottomOptimal = false;
615
+ } else if (requiredSide === "bottom" && isBottomOptimal) {
616
+ isRightOptimal = isLeftOptimal = isTopOptimal = false;
617
+ } else if (requiredSide === "left" && isLeftOptimal) {
618
+ isRightOptimal = isTopOptimal = isBottomOptimal = false;
619
+ } else if (requiredSide === "right" && isRightOptimal) {
620
+ isLeftOptimal = isTopOptimal = isBottomOptimal = false;
621
+ }
622
+
623
+ if (requiredSide === "over") {
624
+ const leftToSet = window.innerWidth / 2 - popupDimensions.realWidth / 2;
625
+ const topToSet =
626
+ window.innerHeight / 2 - popupDimensions.realHeight / 2;
627
+
628
+ popup.wrapper.style.left = `${leftToSet}px`;
629
+ popup.wrapper.style.right = `auto`;
630
+ popup.wrapper.style.top = `${topToSet}px`;
631
+ popup.wrapper.style.bottom = `auto`;
632
+ } else if (noneOptimal) {
633
+ const leftValue =
634
+ window.innerWidth / 2 - popupDimensions?.realWidth / 2;
635
+ const bottomValue = 10;
636
+
637
+ popup.wrapper.style.left = `${leftValue}px`;
638
+ popup.wrapper.style.right = `auto`;
639
+ popup.wrapper.style.bottom = `${bottomValue}px`;
640
+ popup.wrapper.style.top = `auto`;
641
+ } else if (isLeftOptimal) {
642
+ const leftToSet = Math.min(
643
+ leftValue,
644
+ window.innerWidth -
645
+ popupDimensions?.realWidth -
646
+ popupArrowDimensions.width
647
+ );
648
+
649
+ const topToSet = this.calculateTopForLeftRight(requiredAlignment, {
650
+ elementDimensions,
651
+ popupDimensions,
652
+ popupPadding,
653
+ popupArrowDimensions,
654
+ });
655
+
656
+ popup.wrapper.style.left = `${leftToSet}px`;
657
+ popup.wrapper.style.top = `${topToSet}px`;
658
+ popup.wrapper.style.bottom = `auto`;
659
+ popup.wrapper.style.right = "auto";
660
+
661
+ popupRenderedSide = "left";
662
+ } else if (isRightOptimal) {
663
+ const rightToSet = Math.min(
664
+ rightValue,
665
+ window.innerWidth -
666
+ popupDimensions?.realWidth -
667
+ popupArrowDimensions.width
668
+ );
669
+ const topToSet = this.calculateTopForLeftRight(requiredAlignment, {
670
+ elementDimensions,
671
+ popupDimensions,
672
+ popupPadding,
673
+ popupArrowDimensions,
674
+ });
675
+
676
+ popup.wrapper.style.right = `${rightToSet}px`;
677
+ popup.wrapper.style.top = `${topToSet}px`;
678
+ popup.wrapper.style.bottom = `auto`;
679
+ popup.wrapper.style.left = "auto";
680
+
681
+ popupRenderedSide = "right";
682
+ } else if (isTopOptimal) {
683
+ const topToSet = Math.min(
684
+ topValue,
685
+ window.innerHeight -
686
+ popupDimensions.realHeight -
687
+ popupArrowDimensions.width
688
+ );
689
+ let leftToSet = this.calculateLeftForTopBottom(requiredAlignment, {
690
+ elementDimensions,
691
+ popupDimensions,
692
+ popupPadding,
693
+ popupArrowDimensions,
694
+ });
695
+
696
+ popup.wrapper.style.top = `${topToSet}px`;
697
+ popup.wrapper.style.left = `${leftToSet}px`;
698
+ popup.wrapper.style.bottom = `auto`;
699
+ popup.wrapper.style.right = "auto";
700
+
701
+ popupRenderedSide = "top";
702
+ } else if (isBottomOptimal) {
703
+ const bottomToSet = Math.min(
704
+ bottomValue,
705
+ window.innerHeight -
706
+ popupDimensions?.realHeight -
707
+ popupArrowDimensions.width
708
+ );
709
+
710
+ let leftToSet = this.calculateLeftForTopBottom(requiredAlignment, {
711
+ elementDimensions,
712
+ popupDimensions,
713
+ popupPadding,
714
+ popupArrowDimensions,
715
+ });
716
+
717
+ popup.wrapper.style.left = `${leftToSet}px`;
718
+ popup.wrapper.style.bottom = `${bottomToSet}px`;
719
+ popup.wrapper.style.top = `auto`;
720
+ popup.wrapper.style.right = "auto";
721
+
722
+ popupRenderedSide = "bottom";
723
+ }
724
+
725
+ if (!noneOptimal) {
726
+ this.renderPopupArrow(requiredAlignment, popupRenderedSide, element);
727
+ } else {
728
+ popup.arrow.classList.add("zt-tour-d-none");
729
+ }
730
+ }
731
+
732
+ calculateLeftForTopBottom(alignment, config) {
733
+ const {
734
+ elementDimensions,
735
+ popupDimensions,
736
+ popupPadding,
737
+ popupArrowDimensions,
738
+ } = config;
739
+
740
+ if (alignment === "start") {
741
+ return Math.max(
742
+ Math.min(
743
+ elementDimensions.left - popupPadding,
744
+ window.innerWidth -
745
+ popupDimensions.realWidth -
746
+ popupArrowDimensions.width
747
+ ),
748
+ popupArrowDimensions.width
749
+ );
750
+ }
751
+
752
+ if (alignment === "end") {
753
+ return Math.max(
754
+ Math.min(
755
+ elementDimensions.left -
756
+ popupDimensions?.realWidth +
757
+ elementDimensions.width +
758
+ popupPadding,
759
+ window.innerWidth -
760
+ popupDimensions?.realWidth -
761
+ popupArrowDimensions.width
762
+ ),
763
+ popupArrowDimensions.width
764
+ );
765
+ }
766
+
767
+ if (alignment === "center") {
768
+ return Math.max(
769
+ Math.min(
770
+ elementDimensions.left +
771
+ elementDimensions.width / 2 -
772
+ popupDimensions?.realWidth / 2,
773
+ window.innerWidth -
774
+ popupDimensions?.realWidth -
775
+ popupArrowDimensions.width
776
+ ),
777
+ popupArrowDimensions.width
778
+ );
779
+ }
780
+
781
+ return 0;
782
+ }
783
+
784
+ calculateTopForLeftRight(alignment, config) {
785
+ const {
786
+ elementDimensions,
787
+ popupDimensions,
788
+ popupPadding,
789
+ popupArrowDimensions,
790
+ } = config;
791
+
792
+ if (alignment === "start") {
793
+ return Math.max(
794
+ Math.min(
795
+ elementDimensions.top - popupPadding,
796
+ window.innerHeight -
797
+ popupDimensions.realHeight -
798
+ popupArrowDimensions.width
799
+ ),
800
+ popupArrowDimensions.width
801
+ );
802
+ }
803
+
804
+ if (alignment === "end") {
805
+ return Math.max(
806
+ Math.min(
807
+ elementDimensions.top -
808
+ popupDimensions?.realHeight +
809
+ elementDimensions.height +
810
+ popupPadding,
811
+ window.innerHeight -
812
+ popupDimensions?.realHeight -
813
+ popupArrowDimensions.width
814
+ ),
815
+ popupArrowDimensions.width
816
+ );
817
+ }
818
+
819
+ if (alignment === "center") {
820
+ return Math.max(
821
+ Math.min(
822
+ elementDimensions.top +
823
+ elementDimensions.height / 2 -
824
+ popupDimensions?.realHeight / 2,
825
+ window.innerHeight -
826
+ popupDimensions?.realHeight -
827
+ popupArrowDimensions.width
828
+ ),
829
+ popupArrowDimensions.width
830
+ );
831
+ }
832
+
833
+ return 0;
834
+ }
835
+
836
+ destroyTour() {
837
+ let popup = this.options.popup;
838
+ const overlaySvg = document.querySelector(".zt-tour-overlay");
839
+
840
+ if (popup.wrapper) {
841
+ popup.wrapper.remove();
842
+ }
843
+ if (overlaySvg) {
844
+ overlaySvg.remove();
845
+ }
846
+
847
+ this.options.activeStagePosition = null;
848
+ this.options.overlaySvg = null;
849
+ this.destroyEvents();
850
+ }
851
+
852
+ transitionStage(timeDiff, duration, from, to) {
853
+ let activeStagePosition = this.getOption("activeStagePosition");
854
+
855
+ const fromDefinition = activeStagePosition
856
+ ? activeStagePosition
857
+ : from.getBoundingClientRect();
858
+ const toDefinition = to.getBoundingClientRect();
859
+
860
+ const x = this.easeInOutQuad(
861
+ timeDiff,
862
+ fromDefinition.x,
863
+ toDefinition.x - fromDefinition.x,
864
+ duration
865
+ );
866
+ const y = this.easeInOutQuad(
867
+ timeDiff,
868
+ fromDefinition.y,
869
+ toDefinition.y - fromDefinition.y,
870
+ duration
871
+ );
872
+ const width = this.easeInOutQuad(
873
+ timeDiff,
874
+ fromDefinition.width,
875
+ toDefinition.width - fromDefinition.width,
876
+ duration
877
+ );
878
+ const height = this.easeInOutQuad(
879
+ timeDiff,
880
+ fromDefinition.height,
881
+ toDefinition.height - fromDefinition.height,
882
+ duration
883
+ );
884
+
885
+ activeStagePosition = {
886
+ x,
887
+ y,
888
+ width,
889
+ height,
890
+ };
891
+
892
+ this.renderOverlay(activeStagePosition);
893
+ this.setOption("activeStagePosition", activeStagePosition);
894
+ }
895
+
896
+ renderOverlay(stagePosition) {
897
+ const overlaySvg = this.getOption("overlaySvg");
898
+ if (!overlaySvg) {
899
+ this.addOverlay(stagePosition);
900
+ return;
901
+ }
902
+
903
+ const pathElement = overlaySvg.firstElementChild;
904
+ if (pathElement?.tagName !== "path") {
905
+ throw new Error("no path element found in stage svg");
906
+ }
907
+
908
+ pathElement.setAttribute(
909
+ "d",
910
+ this.generateStageSvgPathString(stagePosition)
911
+ );
912
+ }
913
+
914
+ changeHighlight(toElement, toStep, toStepIndex) {
915
+ const duration = this.getOption("animationDuration");
916
+ let currentStepEle =
917
+ this.options.steps[this.options.currentStep]?.element;
918
+
919
+ const start = Date.now();
920
+
921
+ const fromElement = currentStepEle
922
+ ? document.querySelector(currentStepEle)
923
+ : toElement;
924
+
925
+ const isFirstHighlight = !fromElement || fromElement === toElement;
926
+
927
+ const isAnimatedTour = this.getOption("animate");
928
+
929
+ const hasDelayedPopup = !isFirstHighlight && isAnimatedTour;
930
+ let isPopupRendered = false;
931
+
932
+ this.hidePopup();
933
+
934
+ this.setOption("currentStep", toStepIndex);
935
+
936
+ const animate = () => {
937
+ const timeDiff = Date.now() - start;
938
+ const timeRemaining = duration - timeDiff;
939
+ const isHalfwayThrough = timeRemaining <= duration / 2;
940
+ if (
941
+ toStep.popup &&
942
+ isHalfwayThrough &&
943
+ !isPopupRendered &&
944
+ hasDelayedPopup
945
+ ) {
946
+ this.renderPopup(toElement, toStep);
947
+ isPopupRendered = true;
948
+ }
949
+
950
+ if (this.getOption("animate") && timeDiff < duration) {
951
+ this.transitionStage(timeDiff, duration, fromElement, toElement);
952
+ } else {
953
+ this.trackActiveElement(toElement);
954
+ }
955
+ if (timeRemaining >= 0 && this.getOption("animate")) {
956
+ window.requestAnimationFrame(animate);
957
+ }
958
+ };
959
+
960
+ window.requestAnimationFrame(animate);
961
+
962
+ this.bringInView(toElement);
963
+ if (!hasDelayedPopup && toStep.popup) {
964
+ this.renderPopup(toElement, toStep);
965
+ }
966
+ }
967
+
968
+ bringInView(element) {
969
+ if (!element || this.isElementInView(element)) {
970
+ return;
971
+ }
972
+
973
+ const shouldSmoothScroll = this.getOption("smoothScroll");
974
+
975
+ element.scrollIntoView({
976
+ behavior:
977
+ !shouldSmoothScroll || this.isScrollableParent(element)
978
+ ? "auto"
979
+ : "smooth",
980
+ inline: "center",
981
+ block: "center",
982
+ });
983
+ }
984
+
985
+ isElementInView(element) {
986
+ const rect = element.getBoundingClientRect();
987
+
988
+ return (
989
+ rect.top >= 0 &&
990
+ rect.left >= 0 &&
991
+ rect.bottom <=
992
+ (window.innerHeight || document.documentElement.clientHeight) &&
993
+ rect.right <=
994
+ (window.innerWidth || document.documentElement.clientWidth)
995
+ );
996
+ }
997
+
998
+ isScrollableParent(e) {
999
+ if (!e || !e.parentElement) {
1000
+ return;
1001
+ }
1002
+ const parent = e.parentElement;
1003
+ return parent.scrollHeight > parent.clientHeight;
1004
+ }
1005
+
1006
+ trackActiveElement(element) {
1007
+ if (!element) {
1008
+ return;
1009
+ }
1010
+
1011
+ const definition = element.getBoundingClientRect();
1012
+
1013
+ const activeStagePosition = {
1014
+ x: definition.x,
1015
+ y: definition.y,
1016
+ width: definition.width,
1017
+ height: definition.height,
1018
+ };
1019
+
1020
+ this.setOption("activeStagePosition", activeStagePosition);
1021
+
1022
+ this.renderOverlay(activeStagePosition);
1023
+ }
1024
+
1025
+ addOverlay(stagePosition) {
1026
+ const overlaySvg = this.createOverlaySvg(stagePosition);
1027
+ document.body.appendChild(overlaySvg);
1028
+
1029
+ this.setOption("overlaySvg", overlaySvg);
1030
+ }
1031
+
1032
+ easeInOutQuad(timeDiff, initialValue, amountOfChange, duration) {
1033
+ if ((timeDiff /= duration / 2) < 1) {
1034
+ return (amountOfChange / 2) * timeDiff * timeDiff + initialValue;
1035
+ }
1036
+ return (
1037
+ (-amountOfChange / 2) * (--timeDiff * (timeDiff - 2) - 1) + initialValue
1038
+ );
1039
+ }
1040
+
1041
+ refreshOverlay() {
1042
+ const activeStagePosition = this.getOption("activeStagePosition");
1043
+ const overlaySvg = this.getOption("overlaySvg");
1044
+
1045
+ if (!activeStagePosition) {
1046
+ return;
1047
+ }
1048
+
1049
+ if (!overlaySvg) {
1050
+ console.warn("No svg found.");
1051
+ return;
1052
+ }
1053
+
1054
+ const windowX = window.innerWidth;
1055
+ const windowY = window.innerHeight;
1056
+
1057
+ overlaySvg.setAttribute("viewBox", `0 0 ${windowX} ${windowY}`);
1058
+ }
1059
+
1060
+ refreshStep() {
1061
+ const currentStep = this.getOption("currentStep");
1062
+ if(!currentStep) return;
1063
+ const step = this.getOption("steps")[currentStep];
1064
+ const element = document.querySelector(step.element);
1065
+ this.trackActiveElement(element);
1066
+ this.refreshOverlay();
1067
+ this.repositionPopup(element, step);
1068
+ }
1069
+
1070
+ addDummyElement() {
1071
+ const isDummyElement = document.getElementById("zt-popup-dummy-element");
1072
+ if (isDummyElement) {
1073
+ return isDummyElement;
1074
+ }
1075
+
1076
+ let element = document.createElement("div");
1077
+
1078
+ element.id = "zt-popup-dummy-element";
1079
+ element.style.width = "0";
1080
+ element.style.height = "0";
1081
+ element.style.pointerEvents = "none";
1082
+ element.style.opacity = "0";
1083
+ element.style.position = "fixed";
1084
+ element.style.top = "50%";
1085
+ element.style.left = "50%";
1086
+
1087
+ document.body.appendChild(element);
1088
+
1089
+ return element;
1090
+ }
1091
+ //
1092
+ }
1093
+
1094
+ global.ztTour = ztTour;
1095
+ })(this);