@ruc-lib/tour-guide 2.1.8 → 3.1.0

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.
@@ -1,431 +1,417 @@
1
- import * as i0 from '@angular/core';
2
- import { EventEmitter, Component, Output, Input, ViewChild, NgModule } from '@angular/core';
3
- import * as i1 from '@angular/common';
4
- import { CommonModule } from '@angular/common';
5
- import * as i2 from '@angular/material/button';
1
+ import * as i3 from '@angular/material/button';
6
2
  import { MatButtonModule } from '@angular/material/button';
7
- import * as i3 from '@angular/material/card';
3
+ import * as i2 from '@angular/material/card';
8
4
  import { MatCardModule } from '@angular/material/card';
5
+ import * as i1 from '@angular/common';
6
+ import { CommonModule } from '@angular/common';
7
+ import * as i0 from '@angular/core';
8
+ import { EventEmitter, ViewChild, Input, Output, Component } from '@angular/core';
9
9
 
10
- const defaultTourGuideConfig = {
11
- type: "advance",
12
- showSkipButton: true,
13
- buttonsFontSize: "14px",
14
- titleFontSize: '16px',
15
- contentFontSize: '14px',
16
- buttonsAlignment: 'end',
17
- stepCountAlignment: 'start',
18
- stepCountStartText: 'Step',
19
- stepCountMiddleText: 'of',
20
- skipButtonText: 'Skip',
21
- prevButtonText: 'Back',
22
- nextButtonText: 'Next',
23
- finishButtonText: 'Finish',
24
- highlightBorderColor: '#007bff',
25
- highlightBorderWidth: '2px',
26
- defaultPopupWidth: '250px',
27
- };
28
- const DEFAULT_VALUES = {
29
- arrowRight: 'ArrowRight',
30
- arrowLeft: 'ArrowLeft',
31
- escape: 'Escape',
32
- popup: { width: 300, height: 150, top: 0, left: 0, padding: 10, safeMargin: 64 },
33
- startTourButtonLabel: 'Start Tour Guide'
10
+ const defaultTourGuideConfig = {
11
+ type: "advance",
12
+ showSkipButton: true,
13
+ buttonsFontSize: "14px",
14
+ titleFontSize: '16px',
15
+ contentFontSize: '14px',
16
+ buttonsAlignment: 'end',
17
+ stepCountAlignment: 'start',
18
+ stepCountStartText: 'Step',
19
+ stepCountMiddleText: 'of',
20
+ skipButtonText: 'Skip',
21
+ prevButtonText: 'Back',
22
+ nextButtonText: 'Next',
23
+ finishButtonText: 'Finish',
24
+ highlightBorderColor: '#007bff',
25
+ highlightBorderWidth: '2px',
26
+ defaultPopupWidth: '250px',
27
+ };
28
+ const DEFAULT_VALUES = {
29
+ arrowRight: 'ArrowRight',
30
+ arrowLeft: 'ArrowLeft',
31
+ escape: 'Escape',
32
+ popup: { width: 300, height: 150, top: 0, left: 0, padding: 10, safeMargin: 64 },
33
+ startTourButtonLabel: 'Start Tour Guide'
34
34
  };
35
35
 
36
- class RuclibTourGuideComponent {
37
- /**
38
- * class constructor
39
- * @param tourGuideService
40
- * @param renderer
41
- */
42
- constructor(renderer) {
43
- this.renderer = renderer;
44
- this.rucEvent = new EventEmitter();
45
- this.showStartButton = false;
46
- this.dataSource = [];
47
- this.currentStepIndex = 0;
48
- this.showTour = false;
49
- this.startTourButtonLabel = DEFAULT_VALUES?.startTourButtonLabel;
50
- this.config = defaultTourGuideConfig;
51
- /**
52
- * method to navigate between tour slides or skip tour using left, right and skip key
53
- * @param event
54
- * @returns
55
- */
56
- this.handleKey = (event) => {
57
- if (!this.showTour)
58
- return;
59
- if (event.key === DEFAULT_VALUES.arrowRight)
60
- this.nextStep();
61
- else if (event.key === DEFAULT_VALUES.arrowLeft)
62
- this.previousStep();
63
- else if (event.key === DEFAULT_VALUES.escape)
64
- this.skip();
65
- };
66
- }
67
- /**
68
- * handling input data changes
69
- * updating default config with user provided config
70
- * @param changes
71
- */
72
- ngOnChanges(changes) {
73
- if (changes && changes['rucInputData'] && changes['rucInputData'].currentValue) {
74
- this.config = { ...this.config, ...changes['rucInputData'].currentValue };
75
- }
76
- }
77
- /**
78
- * handling change on component initilization
79
- */
80
- ngOnInit() {
81
- document.addEventListener('keydown', this.handleKey);
82
- this.resizeListener = this.debounce(() => this.highlightElement(), 150);
83
- window.addEventListener('resize', this.resizeListener);
84
- if (!this.showStartButton) {
85
- this.startTour();
86
- }
87
- }
88
- /**
89
- * this method handle the start of tour guide fetaure
90
- */
91
- startTour() {
92
- this.showTour = true;
93
- this.lockScroll();
94
- setTimeout(() => {
95
- if (!this.originalParent) {
96
- this.originalParent = this.renderer.parentNode(this.overlayRef?.nativeElement);
97
- }
98
- if (this.overlayRef) {
99
- this.renderer.appendChild(document.body, this.overlayRef?.nativeElement);
100
- }
101
- this.currentStepIndex = 0;
102
- if (this.config.type === 'advance') {
103
- this.highlightElement();
104
- }
105
- }, 10);
106
- }
107
- /**
108
- * this method handle the navigation to next tour guide feature
109
- */
110
- nextStep() {
111
- this.removeHighlight();
112
- this.currentStepIndex++;
113
- if (this.currentStepIndex < this.dataSource.length) {
114
- if (this.config.type === 'advance')
115
- this.highlightElement();
116
- }
117
- else {
118
- this.currentStepIndex--;
119
- this.showTour = false;
120
- this.unlockScroll();
121
- this.rucEvent.emit({ eventName: 'onTourComplete', eventOutput: null });
122
- if (this.overlayRef) {
123
- this.renderer.removeChild(document.body, this.overlayRef.nativeElement);
124
- }
125
- }
126
- }
127
- /**
128
- * this method allow us to navigate back to previous tour guide feature
129
- */
130
- previousStep() {
131
- if (this.currentStepIndex > 0) {
132
- this.removeHighlight();
133
- this.currentStepIndex--;
134
- if (this.config.type === 'advance')
135
- this.highlightElement();
136
- }
137
- }
138
- /**
139
- * method to skip the tour guide thats in progress
140
- */
141
- skip() {
142
- this.removeHighlight();
143
- this.showTour = false;
144
- this.unlockScroll();
145
- this.rucEvent.emit({ eventName: 'onTourSkip', eventOutput: null });
146
- if (this.overlayRef) {
147
- this.renderer.removeChild(document.body, this.overlayRef.nativeElement);
148
- }
149
- }
150
- /**
151
- * method to highlight feature area and changin details popup based on provide config
152
- * @returns
153
- */
154
- async highlightElement() {
155
- const step = this.dataSource[this.currentStepIndex];
156
- if (this.config.type === 'simple' || !step?.selector) {
157
- return;
158
- }
159
- // 1. Find and scroll to the element
160
- this.currentElement = await this.findElementAndScroll(step.selector);
161
- if (!this.currentElement) {
162
- return;
163
- }
164
- // 2. Apply highlight styles
165
- this.applyHighlightStyles(this.currentElement);
166
- // 3. Calculate and apply popup position
167
- this.calculateAndApplyPopupPosition(this.currentElement, step.position);
168
- // 4. Create the visual hole in the overlay
169
- this.createTransparentHole();
170
- }
171
- /**
172
- * Finds an element by its selector, scrolls it into view, and waits for the scroll to complete.
173
- * @param selector The CSS selector for the target element.
174
- * @returns A promise that resolves to the HTMLElement or undefined if not found.
175
- */
176
- async findElementAndScroll(selector) {
177
- const element = document.querySelector(selector);
178
- if (!element) {
179
- return undefined;
180
- }
181
- element.scrollIntoView({ behavior: 'smooth', block: 'center' });
182
- await this.waitForScroll();
183
- return element;
184
- }
185
- /**
186
- * Applies CSS classes and styles to visually highlight the target element.
187
- * @param element The element to highlight.
188
- */
189
- applyHighlightStyles(element) {
190
- this.renderer.addClass(element, 'tour-highlight');
191
- this.renderer.setStyle(element, 'position', 'relative');
192
- this.renderer.setStyle(element, 'z-index', '10000');
193
- }
194
- /**
195
- * Calculates the optimal position for the tour popup and applies it.
196
- * @param element The highlighted element.
197
- * @param position The desired position ('auto', 'top', 'bottom', 'left', 'right').
198
- */
199
- calculateAndApplyPopupPosition(element, position = 'auto') {
200
- const rect = element.getBoundingClientRect();
201
- const offset = this.getAbsoluteOffset(element);
202
- // Determine the best position if set to 'auto'
203
- const finalPosition = this.determineAutoPosition(position, rect);
204
- // Get the top/left coordinates based on the position
205
- let coords = this.computePopupCoordinates(finalPosition, offset, element);
206
- // Ensure the coordinates are within the viewport
207
- coords = this.clampPopupCoordinates(coords);
208
- // Apply the final styles to the popup
209
- this.popupStyle = {
210
- top: `${coords.top}px`,
211
- left: `${coords.left}px`
212
- };
213
- // Ensure the popup itself is visible
214
- this.scrollPopupIntoView();
215
- }
216
- /**
217
- * Determines the best placement for the popup when position is 'auto'.
218
- * @param position The configured position.
219
- * @param rect The BoundingClientRect of the target element.
220
- * @returns The calculated final position.
221
- */
222
- determineAutoPosition(position, rect) {
223
- if (position !== 'auto') {
224
- return position;
225
- }
226
- const { padding, width: popupWidth, height: popupHeight, safeMargin } = DEFAULT_VALUES.popup;
227
- const fitsTop = rect.top >= popupHeight + padding;
228
- const fitsBottom = window.innerHeight - rect.bottom >= popupHeight + padding + safeMargin;
229
- const fitsRight = window.innerWidth - rect.right >= popupWidth + padding;
230
- const fitsLeft = rect.left >= popupWidth + padding;
231
- if (fitsRight)
232
- return 'right';
233
- if (fitsBottom)
234
- return 'bottom';
235
- if (fitsLeft)
236
- return 'left';
237
- if (fitsTop)
238
- return 'top';
239
- return 'bottom'; // fallback
240
- }
241
- /**
242
- * Computes the top and left coordinates for the popup based on the target element and desired position.
243
- * @param position The final position for the popup.
244
- * @param offset The absolute offset of the target element.
245
- * @param element The target element.
246
- * @returns An object with top and left coordinates.
247
- */
248
- computePopupCoordinates(position, offset, element) {
249
- const { padding, width: popupWidth, height: popupHeight } = DEFAULT_VALUES.popup;
250
- let top = DEFAULT_VALUES.popup.top;
251
- let left = DEFAULT_VALUES.popup.left;
252
- switch (position) {
253
- case 'top':
254
- top = offset.top - popupHeight - padding - 20;
255
- left = offset.left + element.offsetWidth / 2 - popupWidth / 2;
256
- break;
257
- case 'bottom':
258
- top = offset.top + element.offsetHeight + padding;
259
- left = offset.left + element.offsetWidth / 2 - popupWidth / 2;
260
- break;
261
- case 'left':
262
- top = offset.top + element.offsetHeight / 2 - popupHeight / 2;
263
- left = offset.left - popupWidth - padding + 50;
264
- break;
265
- case 'right':
266
- top = offset.top + element.offsetHeight / 2 - popupHeight / 2;
267
- left = offset.left + element.offsetWidth + padding;
268
- break;
269
- }
270
- return { top, left };
271
- }
272
- /**
273
- * Clamps the popup's coordinates to ensure it stays within the visible viewport.
274
- * @param coords The calculated top and left coordinates.
275
- * @returns The adjusted coordinates.
276
- */
277
- clampPopupCoordinates(coords) {
278
- const { padding, width: popupWidth, height: popupHeight, safeMargin } = DEFAULT_VALUES.popup;
279
- const scrollTop = window.scrollY;
280
- const scrollLeft = window.scrollX;
281
- const maxTop = scrollTop + window.innerHeight - popupHeight - safeMargin;
282
- const maxLeft = scrollLeft + window.innerWidth - popupWidth - padding;
283
- const top = Math.max(scrollTop + padding, Math.min(coords.top, maxTop));
284
- const left = Math.max(scrollLeft + padding, Math.min(coords.left, maxLeft));
285
- return { top, left };
286
- }
287
- /**
288
- * If the popup is rendered inside a scrollable container, this ensures it's scrolled into view.
289
- */
290
- scrollPopupIntoView() {
291
- setTimeout(() => {
292
- const popupEl = document.querySelector('.tour-popup');
293
- popupEl?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
294
- }, 50);
295
- }
296
- /**
297
- * method to execute highlight process after scroll
298
- * @returns
299
- */
300
- waitForScroll() {
301
- return new Promise(resolve => setTimeout(resolve, 100)); // ⏱️ Adjust delay if needed
302
- }
303
- /**
304
- * method to create transparent hole in overlay for visibility of highlighted area
305
- */
306
- createTransparentHole() {
307
- if (!this.currentElement)
308
- return;
309
- setTimeout(() => {
310
- const padding = 5;
311
- const rect = this.currentElement?.getBoundingClientRect();
312
- const top = Math.max(rect?.top - padding, 0);
313
- const left = Math.max(rect?.left - padding, 0);
314
- const width = rect?.width + padding * 2;
315
- const height = rect?.height + padding * 2;
316
- const holeElement = document.querySelector('.hole');
317
- this.renderer.setStyle(holeElement, 'left', `${left}px`);
318
- this.renderer.setStyle(holeElement, 'top', `${top}px`);
319
- this.renderer.setStyle(holeElement, 'width', `${width}px`);
320
- this.renderer.setStyle(holeElement, 'height', `${height}px`);
321
- }, 50);
322
- }
323
- /**
324
- * method to get absolute offset
325
- * @param element
326
- * @returns
327
- */
328
- getAbsoluteOffset(element) {
329
- let top = 0, left = 0;
330
- let el = element;
331
- while (el) {
332
- top += el.offsetTop - el.scrollTop + el.clientTop;
333
- left += el.offsetLeft - el.scrollLeft + el.clientLeft;
334
- el = el.offsetParent;
335
- }
336
- return { top, left };
337
- }
338
- /**
339
- * method to lock scroll when tour guide is active
340
- */
341
- lockScroll() {
342
- this.renderer.setStyle(document.body, 'overflow', 'hidden');
343
- this.renderer.setStyle(document.documentElement, 'overflow', 'hidden');
344
- document.documentElement.classList.add('tour-scroll-lock');
345
- }
346
- /**
347
- * method to unlock scroll when tour guide is not active
348
- */
349
- unlockScroll() {
350
- this.renderer.removeStyle(document.body, 'overflow');
351
- this.renderer.removeStyle(document.documentElement, 'overflow');
352
- document.documentElement.classList.remove('tour-scroll-lock');
353
- }
354
- /**
355
- * method to remove highlight when tour is skipped or done
356
- */
357
- removeHighlight() {
358
- if (this.currentElement) {
359
- this.renderer.removeClass(this.currentElement, 'tour-highlight');
360
- this.renderer.removeStyle(this.currentElement, 'z-index');
361
- this.renderer.removeStyle(this.currentElement, 'position');
362
- this.renderer.removeStyle(this.currentElement, 'box-shadow');
363
- }
364
- }
365
- /**
366
- * debounce method for smooth operation
367
- * @param fn
368
- * @param delay
369
- * @returns
370
- */
371
- debounce(fn, delay) {
372
- let timeout;
373
- return () => {
374
- clearTimeout(timeout);
375
- timeout = setTimeout(() => fn(), delay);
376
- };
377
- }
378
- /**
379
- * handling change on component destroy
380
- */
381
- ngOnDestroy() {
382
- document.removeEventListener('keydown', this.handleKey);
383
- // Clean up: move overlay back (optional)
384
- if (this.overlayRef && this.originalParent) {
385
- this.renderer.appendChild(this.originalParent, this.overlayRef.nativeElement);
386
- }
387
- if (this.resizeListener) {
388
- window.removeEventListener('resize', this.resizeListener);
389
- }
390
- }
391
- }
392
- RuclibTourGuideComponentfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RuclibTourGuideComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
393
- RuclibTourGuideComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: RuclibTourGuideComponent, selector: "uxp-ruclib-tour-guide", inputs: { rucInputData: "rucInputData", customTheme: "customTheme", showStartButton: "showStartButton", dataSource: "dataSource" }, outputs: { rucEvent: "rucEvent" }, viewQueries: [{ propertyName: "overlayRef", first: true, predicate: ["overlayRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<button color=\"primary\" *ngIf=\"showStartButton\" mat-raised-button (click)=\"startTour()\">\r\n {{startTourButtonLabel}}\r\n</button>\r\n\r\n<div id=\"tour-container\" class=\"{{ customTheme }}\" *ngIf=\"showTour\">\r\n <div #overlayRef class=\"tour-overlay\" *ngIf=\"currentStepIndex < dataSource.length\">\r\n <mat-card appearance=\"outlined\" class=\"tour-popup\" [style.minWidth]=\"config.defaultPopupWidth\"\r\n [style.minHeight]=\"dataSource[currentStepIndex].shape === 'circle' ? config.defaultPopupWidth : 'auto'\"\r\n [style.width]=\"dataSource[currentStepIndex].width || config.defaultPopupWidth\"\r\n [style.height]=\"dataSource[currentStepIndex].shape === 'circle' ? dataSource[currentStepIndex].width || config.defaultPopupWidth : 'auto'\"\r\n [ngClass]=\"dataSource[currentStepIndex].shape || 'rounded'\"\r\n [ngStyle]=\"config.type === 'advance' ? popupStyle : null\" class=\"{{config.type}}\">\r\n\r\n <!-- tour modal title -->\r\n <mat-card-header class=\"tour-guide-title\">\r\n <mat-card-title [style.fontSize]=\"config.titleFontSize\" *ngIf=\"dataSource[currentStepIndex].title\">\r\n {{ dataSource[currentStepIndex].title }}\r\n </mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content>\r\n <!-- tour content details -->\r\n <div class=\"tour-guide-content\" [style.fontSize]=\"config.contentFontSize\">\r\n <div [innerHTML]=\"dataSource[currentStepIndex].content\"></div>\r\n </div>\r\n\r\n <!-- tour step counter -->\r\n <div class=\"tour-guide-step-counter\" [style.justifyContent]=\"config.stepCountAlignment\">\r\n {{config.stepCountStartText}} {{ currentStepIndex + 1 }} {{config.stepCountMiddleText}} {{\r\n dataSource.length }}\r\n </div>\r\n </mat-card-content>\r\n\r\n <!-- tour action buttons -->\r\n <mat-card-actions class=\"tour-guide-action-buttons\" [style.justifyContent]=\"config.buttonsAlignment\">\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" *ngIf=\"config.showSkipButton\"\r\n (click)=\"skip()\">{{config.skipButtonText}}</button>\r\n\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"previousStep()\"\r\n [disabled]=\"currentStepIndex === 0\">{{config.prevButtonText}}</button>\r\n\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"nextStep()\">\r\n {{ currentStepIndex === dataSource.length - 1 ? config.finishButtonText : config.nextButtonText\r\n }}\r\n </button>\r\n </mat-card-actions>\r\n </mat-card>\r\n\r\n <!-- hole on overlayer for highlighted content -->\r\n <div class=\"hole\" [style.borderColor]=\"config.highlightBorderColor\"\r\n [style.borderWidth]=\"config.highlightBorderWidth\"></div>\r\n </div>\r\n\r\n</div>", styles: [".tour-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:99999;pointer-events:auto;display:flex;align-items:center;justify-content:center}::ng-deep .tour-scroll-lock *{overscroll-behavior:none;overflow:hidden!important;scrollbar-width:none;-ms-overflow-style:none}.tour-popup{position:absolute;padding:0;border-radius:4px;box-shadow:0 4px 12px #0000004d;z-index:10000;pointer-events:auto;display:flex;flex-direction:column;gap:.5rem;font-family:Arial,sans-serif;transition:all .3s ease;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;animation:fadeSlideIn .3s ease-out;-webkit-animation:fadeSlideIn .3s ease-out}.tour-guide-title{margin:5px 0}.tour-guide-action-buttons{display:flex;justify-content:end;gap:.5rem;min-height:45px}.tour-guide-action-buttons button{padding:0rem .5rem;border:none;border-radius:4px;font-size:.9rem;cursor:pointer;transition:background .2s ease;height:25px}.tour-guide-action-buttons button:focus-visible{outline:none!important}.tour-guide-action-buttons button[disabled]{cursor:not-allowed}.tour-guide-step-counter{font-size:.8rem;display:flex;justify-content:start;margin-top:20px}.tour-highlight{position:relative!important;z-index:10000!important;border-radius:4px;transition:box-shadow .3s ease}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.hole{position:absolute;width:0px;height:0px;z-index:3;top:0%;left:0%;box-shadow:0 0 0 9999px #000c;background:transparent;border:2px solid #57d914;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px}.tour-popup.rounded{border-radius:12px}.tour-popup.rectangle{border-radius:0}.tour-popup.circle{border-radius:50%;justify-content:center;padding:2rem;text-align:center;-webkit-border-radius:50%;-moz-border-radius:50%;-ms-border-radius:50%;-o-border-radius:50%}.tour-popup.circle .tour-guide-title{padding:0 1rem;justify-content:center}.tour-popup.circle .tour-guide-action-buttons,.tour-popup.circle .tour-guide-step-counter{justify-content:center!important}.mat-mdc-card-header,.mat-mdc-card-content{padding:0 10px!important}.mdc-button{min-width:50px}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i3.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i3.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i3.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i3.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }] });
394
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RuclibTourGuideComponent, decorators: [{
395
- type: Component,
396
- args: [{ selector: 'uxp-ruclib-tour-guide', template: "<button color=\"primary\" *ngIf=\"showStartButton\" mat-raised-button (click)=\"startTour()\">\r\n {{startTourButtonLabel}}\r\n</button>\r\n\r\n<div id=\"tour-container\" class=\"{{ customTheme }}\" *ngIf=\"showTour\">\r\n <div #overlayRef class=\"tour-overlay\" *ngIf=\"currentStepIndex < dataSource.length\">\r\n <mat-card appearance=\"outlined\" class=\"tour-popup\" [style.minWidth]=\"config.defaultPopupWidth\"\r\n [style.minHeight]=\"dataSource[currentStepIndex].shape === 'circle' ? config.defaultPopupWidth : 'auto'\"\r\n [style.width]=\"dataSource[currentStepIndex].width || config.defaultPopupWidth\"\r\n [style.height]=\"dataSource[currentStepIndex].shape === 'circle' ? dataSource[currentStepIndex].width || config.defaultPopupWidth : 'auto'\"\r\n [ngClass]=\"dataSource[currentStepIndex].shape || 'rounded'\"\r\n [ngStyle]=\"config.type === 'advance' ? popupStyle : null\" class=\"{{config.type}}\">\r\n\r\n <!-- tour modal title -->\r\n <mat-card-header class=\"tour-guide-title\">\r\n <mat-card-title [style.fontSize]=\"config.titleFontSize\" *ngIf=\"dataSource[currentStepIndex].title\">\r\n {{ dataSource[currentStepIndex].title }}\r\n </mat-card-title>\r\n </mat-card-header>\r\n\r\n <mat-card-content>\r\n <!-- tour content details -->\r\n <div class=\"tour-guide-content\" [style.fontSize]=\"config.contentFontSize\">\r\n <div [innerHTML]=\"dataSource[currentStepIndex].content\"></div>\r\n </div>\r\n\r\n <!-- tour step counter -->\r\n <div class=\"tour-guide-step-counter\" [style.justifyContent]=\"config.stepCountAlignment\">\r\n {{config.stepCountStartText}} {{ currentStepIndex + 1 }} {{config.stepCountMiddleText}} {{\r\n dataSource.length }}\r\n </div>\r\n </mat-card-content>\r\n\r\n <!-- tour action buttons -->\r\n <mat-card-actions class=\"tour-guide-action-buttons\" [style.justifyContent]=\"config.buttonsAlignment\">\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" *ngIf=\"config.showSkipButton\"\r\n (click)=\"skip()\">{{config.skipButtonText}}</button>\r\n\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"previousStep()\"\r\n [disabled]=\"currentStepIndex === 0\">{{config.prevButtonText}}</button>\r\n\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"nextStep()\">\r\n {{ currentStepIndex === dataSource.length - 1 ? config.finishButtonText : config.nextButtonText\r\n }}\r\n </button>\r\n </mat-card-actions>\r\n </mat-card>\r\n\r\n <!-- hole on overlayer for highlighted content -->\r\n <div class=\"hole\" [style.borderColor]=\"config.highlightBorderColor\"\r\n [style.borderWidth]=\"config.highlightBorderWidth\"></div>\r\n </div>\r\n\r\n</div>", styles: [".tour-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:99999;pointer-events:auto;display:flex;align-items:center;justify-content:center}::ng-deep .tour-scroll-lock *{overscroll-behavior:none;overflow:hidden!important;scrollbar-width:none;-ms-overflow-style:none}.tour-popup{position:absolute;padding:0;border-radius:4px;box-shadow:0 4px 12px #0000004d;z-index:10000;pointer-events:auto;display:flex;flex-direction:column;gap:.5rem;font-family:Arial,sans-serif;transition:all .3s ease;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;animation:fadeSlideIn .3s ease-out;-webkit-animation:fadeSlideIn .3s ease-out}.tour-guide-title{margin:5px 0}.tour-guide-action-buttons{display:flex;justify-content:end;gap:.5rem;min-height:45px}.tour-guide-action-buttons button{padding:0rem .5rem;border:none;border-radius:4px;font-size:.9rem;cursor:pointer;transition:background .2s ease;height:25px}.tour-guide-action-buttons button:focus-visible{outline:none!important}.tour-guide-action-buttons button[disabled]{cursor:not-allowed}.tour-guide-step-counter{font-size:.8rem;display:flex;justify-content:start;margin-top:20px}.tour-highlight{position:relative!important;z-index:10000!important;border-radius:4px;transition:box-shadow .3s ease}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.hole{position:absolute;width:0px;height:0px;z-index:3;top:0%;left:0%;box-shadow:0 0 0 9999px #000c;background:transparent;border:2px solid #57d914;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px}.tour-popup.rounded{border-radius:12px}.tour-popup.rectangle{border-radius:0}.tour-popup.circle{border-radius:50%;justify-content:center;padding:2rem;text-align:center;-webkit-border-radius:50%;-moz-border-radius:50%;-ms-border-radius:50%;-o-border-radius:50%}.tour-popup.circle .tour-guide-title{padding:0 1rem;justify-content:center}.tour-popup.circle .tour-guide-action-buttons,.tour-popup.circle .tour-guide-step-counter{justify-content:center!important}.mat-mdc-card-header,.mat-mdc-card-content{padding:0 10px!important}.mdc-button{min-width:50px}\n"] }]
397
- }], ctorParameters: function () { return [{ type: i0.Renderer2 }]; }, propDecorators: { rucEvent: [{
398
- type: Output
399
- }], rucInputData: [{
400
- type: Input
401
- }], customTheme: [{
402
- type: Input
403
- }], showStartButton: [{
404
- type: Input
405
- }], dataSource: [{
406
- type: Input
407
- }], overlayRef: [{
408
- type: ViewChild,
409
- args: ['overlayRef']
36
+ class RuclibTourGuideComponent {
37
+ /**
38
+ * class constructor
39
+ * @param tourGuideService
40
+ * @param renderer
41
+ */
42
+ constructor(renderer) {
43
+ this.renderer = renderer;
44
+ this.rucEvent = new EventEmitter();
45
+ this.showStartButton = false;
46
+ this.dataSource = [];
47
+ this.currentStepIndex = 0;
48
+ this.showTour = false;
49
+ this.startTourButtonLabel = DEFAULT_VALUES?.startTourButtonLabel;
50
+ this.config = defaultTourGuideConfig;
51
+ /**
52
+ * method to navigate between tour slides or skip tour using left, right and skip key
53
+ * @param event
54
+ * @returns
55
+ */
56
+ this.handleKey = (event) => {
57
+ if (!this.showTour)
58
+ return;
59
+ if (event.key === DEFAULT_VALUES.arrowRight)
60
+ this.nextStep();
61
+ else if (event.key === DEFAULT_VALUES.arrowLeft)
62
+ this.previousStep();
63
+ else if (event.key === DEFAULT_VALUES.escape)
64
+ this.skip();
65
+ };
66
+ }
67
+ /**
68
+ * handling input data changes
69
+ * updating default config with user provided config
70
+ * @param changes
71
+ */
72
+ ngOnChanges(changes) {
73
+ if (changes && changes['rucInputData'] && changes['rucInputData'].currentValue) {
74
+ this.config = { ...this.config, ...changes['rucInputData'].currentValue };
75
+ }
76
+ }
77
+ /**
78
+ * handling change on component initilization
79
+ */
80
+ ngOnInit() {
81
+ document.addEventListener('keydown', this.handleKey);
82
+ this.resizeListener = this.debounce(() => this.highlightElement(), 150);
83
+ window.addEventListener('resize', this.resizeListener);
84
+ if (!this.showStartButton) {
85
+ this.startTour();
86
+ }
87
+ }
88
+ /**
89
+ * this method handle the start of tour guide fetaure
90
+ */
91
+ startTour() {
92
+ this.showTour = true;
93
+ this.lockScroll();
94
+ setTimeout(() => {
95
+ if (!this.originalParent) {
96
+ this.originalParent = this.renderer.parentNode(this.overlayRef?.nativeElement);
97
+ }
98
+ if (this.overlayRef) {
99
+ this.renderer.appendChild(document.body, this.overlayRef?.nativeElement);
100
+ }
101
+ this.currentStepIndex = 0;
102
+ if (this.config.type === 'advance') {
103
+ this.highlightElement();
104
+ }
105
+ }, 10);
106
+ }
107
+ /**
108
+ * this method handle the navigation to next tour guide feature
109
+ */
110
+ nextStep() {
111
+ this.removeHighlight();
112
+ this.currentStepIndex++;
113
+ if (this.currentStepIndex < this.dataSource.length) {
114
+ if (this.config.type === 'advance')
115
+ this.highlightElement();
116
+ }
117
+ else {
118
+ this.currentStepIndex--;
119
+ this.showTour = false;
120
+ this.unlockScroll();
121
+ this.rucEvent.emit({ eventName: 'onTourComplete', eventOutput: null });
122
+ if (this.overlayRef) {
123
+ this.renderer.removeChild(document.body, this.overlayRef.nativeElement);
124
+ }
125
+ }
126
+ }
127
+ /**
128
+ * this method allow us to navigate back to previous tour guide feature
129
+ */
130
+ previousStep() {
131
+ if (this.currentStepIndex > 0) {
132
+ this.removeHighlight();
133
+ this.currentStepIndex--;
134
+ if (this.config.type === 'advance')
135
+ this.highlightElement();
136
+ }
137
+ }
138
+ /**
139
+ * method to skip the tour guide thats in progress
140
+ */
141
+ skip() {
142
+ this.removeHighlight();
143
+ this.showTour = false;
144
+ this.unlockScroll();
145
+ this.rucEvent.emit({ eventName: 'onTourSkip', eventOutput: null });
146
+ if (this.overlayRef) {
147
+ this.renderer.removeChild(document.body, this.overlayRef.nativeElement);
148
+ }
149
+ }
150
+ /**
151
+ * method to highlight feature area and changin details popup based on provide config
152
+ * @returns
153
+ */
154
+ async highlightElement() {
155
+ const step = this.dataSource[this.currentStepIndex];
156
+ if (this.config.type === 'simple' || !step?.selector) {
157
+ return;
158
+ }
159
+ // 1. Find and scroll to the element
160
+ this.currentElement = await this.findElementAndScroll(step.selector);
161
+ if (!this.currentElement) {
162
+ return;
163
+ }
164
+ // 2. Apply highlight styles
165
+ this.applyHighlightStyles(this.currentElement);
166
+ // 3. Calculate and apply popup position
167
+ this.calculateAndApplyPopupPosition(this.currentElement, step.position);
168
+ // 4. Create the visual hole in the overlay
169
+ this.createTransparentHole();
170
+ }
171
+ /**
172
+ * Finds an element by its selector, scrolls it into view, and waits for the scroll to complete.
173
+ * @param selector The CSS selector for the target element.
174
+ * @returns A promise that resolves to the HTMLElement or undefined if not found.
175
+ */
176
+ async findElementAndScroll(selector) {
177
+ const element = document.querySelector(selector);
178
+ if (!element) {
179
+ return undefined;
180
+ }
181
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
182
+ await this.waitForScroll();
183
+ return element;
184
+ }
185
+ /**
186
+ * Applies CSS classes and styles to visually highlight the target element.
187
+ * @param element The element to highlight.
188
+ */
189
+ applyHighlightStyles(element) {
190
+ this.renderer.addClass(element, 'tour-highlight');
191
+ this.renderer.setStyle(element, 'position', 'relative');
192
+ this.renderer.setStyle(element, 'z-index', '10000');
193
+ }
194
+ /**
195
+ * Calculates the optimal position for the tour popup and applies it.
196
+ * @param element The highlighted element.
197
+ * @param position The desired position ('auto', 'top', 'bottom', 'left', 'right').
198
+ */
199
+ calculateAndApplyPopupPosition(element, position = 'auto') {
200
+ const rect = element.getBoundingClientRect();
201
+ const offset = this.getAbsoluteOffset(element);
202
+ // Determine the best position if set to 'auto'
203
+ const finalPosition = this.determineAutoPosition(position, rect);
204
+ // Get the top/left coordinates based on the position
205
+ let coords = this.computePopupCoordinates(finalPosition, offset, element);
206
+ // Ensure the coordinates are within the viewport
207
+ coords = this.clampPopupCoordinates(coords);
208
+ // Apply the final styles to the popup
209
+ this.popupStyle = {
210
+ top: `${coords.top}px`,
211
+ left: `${coords.left}px`
212
+ };
213
+ // Ensure the popup itself is visible
214
+ this.scrollPopupIntoView();
215
+ }
216
+ /**
217
+ * Determines the best placement for the popup when position is 'auto'.
218
+ * @param position The configured position.
219
+ * @param rect The BoundingClientRect of the target element.
220
+ * @returns The calculated final position.
221
+ */
222
+ determineAutoPosition(position, rect) {
223
+ if (position !== 'auto') {
224
+ return position;
225
+ }
226
+ const { padding, width: popupWidth, height: popupHeight, safeMargin } = DEFAULT_VALUES.popup;
227
+ const fitsTop = rect.top >= popupHeight + padding;
228
+ const fitsBottom = window.innerHeight - rect.bottom >= popupHeight + padding + safeMargin;
229
+ const fitsRight = window.innerWidth - rect.right >= popupWidth + padding;
230
+ const fitsLeft = rect.left >= popupWidth + padding;
231
+ if (fitsRight)
232
+ return 'right';
233
+ if (fitsBottom)
234
+ return 'bottom';
235
+ if (fitsLeft)
236
+ return 'left';
237
+ if (fitsTop)
238
+ return 'top';
239
+ return 'bottom'; // fallback
240
+ }
241
+ /**
242
+ * Computes the top and left coordinates for the popup based on the target element and desired position.
243
+ * @param position The final position for the popup.
244
+ * @param offset The absolute offset of the target element.
245
+ * @param element The target element.
246
+ * @returns An object with top and left coordinates.
247
+ */
248
+ computePopupCoordinates(position, offset, element) {
249
+ const { padding, width: popupWidth, height: popupHeight } = DEFAULT_VALUES.popup;
250
+ let top = DEFAULT_VALUES.popup.top;
251
+ let left = DEFAULT_VALUES.popup.left;
252
+ switch (position) {
253
+ case 'top':
254
+ top = offset.top - popupHeight - padding - 20;
255
+ left = offset.left + element.offsetWidth / 2 - popupWidth / 2;
256
+ break;
257
+ case 'bottom':
258
+ top = offset.top + element.offsetHeight + padding;
259
+ left = offset.left + element.offsetWidth / 2 - popupWidth / 2;
260
+ break;
261
+ case 'left':
262
+ top = offset.top + element.offsetHeight / 2 - popupHeight / 2;
263
+ left = offset.left - popupWidth - padding + 50;
264
+ break;
265
+ case 'right':
266
+ top = offset.top + element.offsetHeight / 2 - popupHeight / 2;
267
+ left = offset.left + element.offsetWidth + padding;
268
+ break;
269
+ }
270
+ return { top, left };
271
+ }
272
+ /**
273
+ * Clamps the popup's coordinates to ensure it stays within the visible viewport.
274
+ * @param coords The calculated top and left coordinates.
275
+ * @returns The adjusted coordinates.
276
+ */
277
+ clampPopupCoordinates(coords) {
278
+ const { padding, width: popupWidth, height: popupHeight, safeMargin } = DEFAULT_VALUES.popup;
279
+ const scrollTop = window.scrollY;
280
+ const scrollLeft = window.scrollX;
281
+ const maxTop = scrollTop + window.innerHeight - popupHeight - safeMargin;
282
+ const maxLeft = scrollLeft + window.innerWidth - popupWidth - padding;
283
+ const top = Math.max(scrollTop + padding, Math.min(coords.top, maxTop));
284
+ const left = Math.max(scrollLeft + padding, Math.min(coords.left, maxLeft));
285
+ return { top, left };
286
+ }
287
+ /**
288
+ * If the popup is rendered inside a scrollable container, this ensures it's scrolled into view.
289
+ */
290
+ scrollPopupIntoView() {
291
+ setTimeout(() => {
292
+ const popupEl = document.querySelector('.tour-popup');
293
+ popupEl?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
294
+ }, 50);
295
+ }
296
+ /**
297
+ * method to execute highlight process after scroll
298
+ * @returns
299
+ */
300
+ waitForScroll() {
301
+ return new Promise(resolve => setTimeout(resolve, 100)); // ⏱️ Adjust delay if needed
302
+ }
303
+ /**
304
+ * method to create transparent hole in overlay for visibility of highlighted area
305
+ */
306
+ createTransparentHole() {
307
+ if (!this.currentElement)
308
+ return;
309
+ setTimeout(() => {
310
+ const padding = 5;
311
+ const rect = this.currentElement?.getBoundingClientRect();
312
+ const top = Math.max(rect?.top - padding, 0);
313
+ const left = Math.max(rect?.left - padding, 0);
314
+ const width = rect?.width + padding * 2;
315
+ const height = rect?.height + padding * 2;
316
+ const holeElement = document.querySelector('.hole');
317
+ this.renderer.setStyle(holeElement, 'left', `${left}px`);
318
+ this.renderer.setStyle(holeElement, 'top', `${top}px`);
319
+ this.renderer.setStyle(holeElement, 'width', `${width}px`);
320
+ this.renderer.setStyle(holeElement, 'height', `${height}px`);
321
+ }, 50);
322
+ }
323
+ /**
324
+ * method to get absolute offset
325
+ * @param element
326
+ * @returns
327
+ */
328
+ getAbsoluteOffset(element) {
329
+ let top = 0, left = 0;
330
+ let el = element;
331
+ while (el) {
332
+ top += el.offsetTop - el.scrollTop + el.clientTop;
333
+ left += el.offsetLeft - el.scrollLeft + el.clientLeft;
334
+ el = el.offsetParent;
335
+ }
336
+ return { top, left };
337
+ }
338
+ /**
339
+ * method to lock scroll when tour guide is active
340
+ */
341
+ lockScroll() {
342
+ this.renderer.setStyle(document.body, 'overflow', 'hidden');
343
+ this.renderer.setStyle(document.documentElement, 'overflow', 'hidden');
344
+ document.documentElement.classList.add('tour-scroll-lock');
345
+ }
346
+ /**
347
+ * method to unlock scroll when tour guide is not active
348
+ */
349
+ unlockScroll() {
350
+ this.renderer.removeStyle(document.body, 'overflow');
351
+ this.renderer.removeStyle(document.documentElement, 'overflow');
352
+ document.documentElement.classList.remove('tour-scroll-lock');
353
+ }
354
+ /**
355
+ * method to remove highlight when tour is skipped or done
356
+ */
357
+ removeHighlight() {
358
+ if (this.currentElement) {
359
+ this.renderer.removeClass(this.currentElement, 'tour-highlight');
360
+ this.renderer.removeStyle(this.currentElement, 'z-index');
361
+ this.renderer.removeStyle(this.currentElement, 'position');
362
+ this.renderer.removeStyle(this.currentElement, 'box-shadow');
363
+ }
364
+ }
365
+ /**
366
+ * debounce method for smooth operation
367
+ * @param fn
368
+ * @param delay
369
+ * @returns
370
+ */
371
+ debounce(fn, delay) {
372
+ let timeout;
373
+ return () => {
374
+ clearTimeout(timeout);
375
+ timeout = setTimeout(() => fn(), delay);
376
+ };
377
+ }
378
+ /**
379
+ * handling change on component destroy
380
+ */
381
+ ngOnDestroy() {
382
+ document.removeEventListener('keydown', this.handleKey);
383
+ // Clean up: move overlay back (optional)
384
+ if (this.overlayRef && this.originalParent) {
385
+ this.renderer.appendChild(this.originalParent, this.overlayRef.nativeElement);
386
+ }
387
+ if (this.resizeListener) {
388
+ window.removeEventListener('resize', this.resizeListener);
389
+ }
390
+ }
391
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RuclibTourGuideComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); }
392
+ static { thiscmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: RuclibTourGuideComponent, isStandalone: true, selector: "uxp-ruclib-tour-guide", inputs: { rucInputData: "rucInputData", customTheme: "customTheme", showStartButton: "showStartButton", dataSource: "dataSource" }, outputs: { rucEvent: "rucEvent" }, viewQueries: [{ propertyName: "overlayRef", first: true, predicate: ["overlayRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (showStartButton) {\r\n <button color=\"primary\" mat-raised-button (click)=\"startTour()\">\r\n {{startTourButtonLabel}}\r\n </button>\r\n}\r\n\r\n@if (showTour) {\r\n <div id=\"tour-container\" class=\"{{ customTheme }}\">\r\n @if (currentStepIndex < dataSource.length) {\r\n <div #overlayRef class=\"tour-overlay\">\r\n <mat-card appearance=\"outlined\" class=\"tour-popup\" [style.minWidth]=\"config.defaultPopupWidth\"\r\n [style.minHeight]=\"dataSource[currentStepIndex].shape === 'circle' ? config.defaultPopupWidth : 'auto'\"\r\n [style.width]=\"dataSource[currentStepIndex].width || config.defaultPopupWidth\"\r\n [style.height]=\"dataSource[currentStepIndex].shape === 'circle' ? dataSource[currentStepIndex].width || config.defaultPopupWidth : 'auto'\"\r\n [ngClass]=\"dataSource[currentStepIndex].shape || 'rounded'\"\r\n [ngStyle]=\"config.type === 'advance' ? popupStyle : null\" class=\"{{config.type}}\">\r\n <!-- tour modal title -->\r\n <mat-card-header class=\"tour-guide-title\">\r\n @if (dataSource[currentStepIndex].title) {\r\n <mat-card-title [style.fontSize]=\"config.titleFontSize\">\r\n {{ dataSource[currentStepIndex].title }}\r\n </mat-card-title>\r\n }\r\n </mat-card-header>\r\n <mat-card-content>\r\n <!-- tour content details -->\r\n <div class=\"tour-guide-content\" [style.fontSize]=\"config.contentFontSize\">\r\n <div [innerHTML]=\"dataSource[currentStepIndex].content\"></div>\r\n </div>\r\n <!-- tour step counter -->\r\n <div class=\"tour-guide-step-counter\" [style.justifyContent]=\"config.stepCountAlignment\">\r\n {{config.stepCountStartText}} {{ currentStepIndex + 1 }} {{config.stepCountMiddleText}} {{\r\n dataSource.length }}\r\n </div>\r\n </mat-card-content>\r\n <!-- tour action buttons -->\r\n <mat-card-actions class=\"tour-guide-action-buttons\" [style.justifyContent]=\"config.buttonsAlignment\">\r\n @if (config.showSkipButton) {\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\"\r\n (click)=\"skip()\">{{config.skipButtonText}}</button>\r\n }\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"previousStep()\"\r\n [disabled]=\"currentStepIndex === 0\">{{config.prevButtonText}}</button>\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"nextStep()\">\r\n {{ currentStepIndex === dataSource.length - 1 ? config.finishButtonText : config.nextButtonText\r\n }}\r\n </button>\r\n </mat-card-actions>\r\n </mat-card>\r\n <!-- hole on overlayer for highlighted content -->\r\n <div class=\"hole\" [style.borderColor]=\"config.highlightBorderColor\"\r\n [style.borderWidth]=\"config.highlightBorderWidth\"></div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".tour-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:99999;pointer-events:auto;display:flex;align-items:center;justify-content:center}::ng-deep .tour-scroll-lock *{overscroll-behavior:none;overflow:hidden!important;scrollbar-width:none;-ms-overflow-style:none}.tour-popup{position:absolute;padding:0;border-radius:4px;box-shadow:0 4px 12px #0000004d;z-index:10000;pointer-events:auto;display:flex;flex-direction:column;gap:.5rem;font-family:Arial,sans-serif;transition:all .3s ease;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;animation:fadeSlideIn .3s ease-out;-webkit-animation:fadeSlideIn .3s ease-out}.tour-guide-title{margin:5px 0}.tour-guide-action-buttons{display:flex;justify-content:end;gap:.5rem;min-height:45px}.tour-guide-action-buttons button{padding:0rem .5rem;border:none;border-radius:4px;font-size:.9rem;cursor:pointer;transition:background .2s ease;height:25px}.tour-guide-action-buttons button:focus-visible{outline:none!important}.tour-guide-action-buttons button[disabled]{cursor:not-allowed}.tour-guide-step-counter{font-size:.8rem;display:flex;justify-content:start;margin-top:20px}.tour-highlight{position:relative!important;z-index:10000!important;border-radius:4px;transition:box-shadow .3s ease}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.hole{position:absolute;width:0px;height:0px;z-index:3;top:0%;left:0%;box-shadow:0 0 0 9999px #000c;background:transparent;border:2px solid #57d914;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px}.tour-popup.rounded{border-radius:12px}.tour-popup.rectangle{border-radius:0}.tour-popup.circle{border-radius:50%;justify-content:center;padding:2rem;text-align:center;-webkit-border-radius:50%;-moz-border-radius:50%;-ms-border-radius:50%;-o-border-radius:50%}.tour-popup.circle .tour-guide-title{padding:0 1rem;justify-content:center}.tour-popup.circle .tour-guide-action-buttons,.tour-popup.circle .tour-guide-step-counter{justify-content:center!important}.mat-mdc-card-header,.mat-mdc-card-content{padding:0 10px!important}.mdc-button{min-width:50px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] }); }
393
+ }
394
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RuclibTourGuideComponent, decorators: [{
395
+ type: Component,
396
+ args: [{ selector: 'uxp-ruclib-tour-guide', imports: [CommonModule, MatCardModule, MatButtonModule], template: "@if (showStartButton) {\r\n <button color=\"primary\" mat-raised-button (click)=\"startTour()\">\r\n {{startTourButtonLabel}}\r\n </button>\r\n}\r\n\r\n@if (showTour) {\r\n <div id=\"tour-container\" class=\"{{ customTheme }}\">\r\n @if (currentStepIndex < dataSource.length) {\r\n <div #overlayRef class=\"tour-overlay\">\r\n <mat-card appearance=\"outlined\" class=\"tour-popup\" [style.minWidth]=\"config.defaultPopupWidth\"\r\n [style.minHeight]=\"dataSource[currentStepIndex].shape === 'circle' ? config.defaultPopupWidth : 'auto'\"\r\n [style.width]=\"dataSource[currentStepIndex].width || config.defaultPopupWidth\"\r\n [style.height]=\"dataSource[currentStepIndex].shape === 'circle' ? dataSource[currentStepIndex].width || config.defaultPopupWidth : 'auto'\"\r\n [ngClass]=\"dataSource[currentStepIndex].shape || 'rounded'\"\r\n [ngStyle]=\"config.type === 'advance' ? popupStyle : null\" class=\"{{config.type}}\">\r\n <!-- tour modal title -->\r\n <mat-card-header class=\"tour-guide-title\">\r\n @if (dataSource[currentStepIndex].title) {\r\n <mat-card-title [style.fontSize]=\"config.titleFontSize\">\r\n {{ dataSource[currentStepIndex].title }}\r\n </mat-card-title>\r\n }\r\n </mat-card-header>\r\n <mat-card-content>\r\n <!-- tour content details -->\r\n <div class=\"tour-guide-content\" [style.fontSize]=\"config.contentFontSize\">\r\n <div [innerHTML]=\"dataSource[currentStepIndex].content\"></div>\r\n </div>\r\n <!-- tour step counter -->\r\n <div class=\"tour-guide-step-counter\" [style.justifyContent]=\"config.stepCountAlignment\">\r\n {{config.stepCountStartText}} {{ currentStepIndex + 1 }} {{config.stepCountMiddleText}} {{\r\n dataSource.length }}\r\n </div>\r\n </mat-card-content>\r\n <!-- tour action buttons -->\r\n <mat-card-actions class=\"tour-guide-action-buttons\" [style.justifyContent]=\"config.buttonsAlignment\">\r\n @if (config.showSkipButton) {\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\"\r\n (click)=\"skip()\">{{config.skipButtonText}}</button>\r\n }\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"previousStep()\"\r\n [disabled]=\"currentStepIndex === 0\">{{config.prevButtonText}}</button>\r\n <button mat-raised-button [style.fontSize]=\"config.buttonsFontSize\" (click)=\"nextStep()\">\r\n {{ currentStepIndex === dataSource.length - 1 ? config.finishButtonText : config.nextButtonText\r\n }}\r\n </button>\r\n </mat-card-actions>\r\n </mat-card>\r\n <!-- hole on overlayer for highlighted content -->\r\n <div class=\"hole\" [style.borderColor]=\"config.highlightBorderColor\"\r\n [style.borderWidth]=\"config.highlightBorderWidth\"></div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".tour-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:99999;pointer-events:auto;display:flex;align-items:center;justify-content:center}::ng-deep .tour-scroll-lock *{overscroll-behavior:none;overflow:hidden!important;scrollbar-width:none;-ms-overflow-style:none}.tour-popup{position:absolute;padding:0;border-radius:4px;box-shadow:0 4px 12px #0000004d;z-index:10000;pointer-events:auto;display:flex;flex-direction:column;gap:.5rem;font-family:Arial,sans-serif;transition:all .3s ease;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;animation:fadeSlideIn .3s ease-out;-webkit-animation:fadeSlideIn .3s ease-out}.tour-guide-title{margin:5px 0}.tour-guide-action-buttons{display:flex;justify-content:end;gap:.5rem;min-height:45px}.tour-guide-action-buttons button{padding:0rem .5rem;border:none;border-radius:4px;font-size:.9rem;cursor:pointer;transition:background .2s ease;height:25px}.tour-guide-action-buttons button:focus-visible{outline:none!important}.tour-guide-action-buttons button[disabled]{cursor:not-allowed}.tour-guide-step-counter{font-size:.8rem;display:flex;justify-content:start;margin-top:20px}.tour-highlight{position:relative!important;z-index:10000!important;border-radius:4px;transition:box-shadow .3s ease}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.hole{position:absolute;width:0px;height:0px;z-index:3;top:0%;left:0%;box-shadow:0 0 0 9999px #000c;background:transparent;border:2px solid #57d914;border-radius:5px;-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px}.tour-popup.rounded{border-radius:12px}.tour-popup.rectangle{border-radius:0}.tour-popup.circle{border-radius:50%;justify-content:center;padding:2rem;text-align:center;-webkit-border-radius:50%;-moz-border-radius:50%;-ms-border-radius:50%;-o-border-radius:50%}.tour-popup.circle .tour-guide-title{padding:0 1rem;justify-content:center}.tour-popup.circle .tour-guide-action-buttons,.tour-popup.circle .tour-guide-step-counter{justify-content:center!important}.mat-mdc-card-header,.mat-mdc-card-content{padding:0 10px!important}.mdc-button{min-width:50px}\n"] }]
397
+ }], ctorParameters: () => [{ type: i0.Renderer2 }], propDecorators: { rucEvent: [{
398
+ type: Output
399
+ }], rucInputData: [{
400
+ type: Input
401
+ }], customTheme: [{
402
+ type: Input
403
+ }], showStartButton: [{
404
+ type: Input
405
+ }], dataSource: [{
406
+ type: Input
407
+ }], overlayRef: [{
408
+ type: ViewChild,
409
+ args: ['overlayRef']
410
410
  }] } });
411
411
 
412
- class RuclibTourGuideModule {
413
- }
414
- RuclibTourGuideModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RuclibTourGuideModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
415
- RuclibTourGuideModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: RuclibTourGuideModule, declarations: [RuclibTourGuideComponent], imports: [CommonModule, MatButtonModule, MatCardModule], exports: [RuclibTourGuideComponent] });
416
- RuclibTourGuideModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RuclibTourGuideModule, imports: [CommonModule, MatButtonModule, MatCardModule] });
417
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RuclibTourGuideModule, decorators: [{
418
- type: NgModule,
419
- args: [{
420
- imports: [CommonModule, MatButtonModule, MatCardModule],
421
- declarations: [RuclibTourGuideComponent],
422
- exports: [RuclibTourGuideComponent],
423
- }]
424
- }] });
425
-
426
- /**
427
- * Generated bundle index. Do not edit.
412
+ /**
413
+ * Generated bundle index. Do not edit.
428
414
  */
429
415
 
430
- export { RuclibTourGuideComponent, RuclibTourGuideModule };
416
+ export { RuclibTourGuideComponent };
431
417
  //# sourceMappingURL=ruc-lib-tour-guide.mjs.map