@styloviz/product-tour 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 StyloViz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @styloviz/product-tour
2
+
3
+ > 8 onboarding variants: modal (free) plus slideshow, tooltip, spotlight, beacon, checklist, progress steps and announcement (Pro). Portal-based overlay with keyboard navigation.
4
+
5
+ Part of the **StyloViz UI Kit** — a premium Angular 21 + Tailwind CSS 4 dashboard component library. Standalone, `OnPush`, strongly typed, dark-mode ready.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @styloviz/core @styloviz/product-tour
11
+ ```
12
+
13
+ Requires `@angular/core` and `@angular/common` >= 21. `@styloviz/core` is a peer dependency shared by every component. Prefer everything at once? `@styloviz/all` installs the whole free tier in one command.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { SvProductTourComponent } from '@styloviz/product-tour';
19
+
20
+ @Component({
21
+ standalone: true,
22
+ imports: [SvProductTourComponent],
23
+ template: `
24
+ <sv-product-tour [steps]="steps" [active]="touring" (complete)="finish()" (skip)="finish()" />
25
+ `,
26
+ })
27
+ export class DemoComponent {}
28
+ ```
29
+
30
+ ## Inputs
31
+
32
+ | Input | Type | Default | Description |
33
+ | --- | --- | --- | --- |
34
+ | `steps` | `TourStep[]` | `[]` | Step definitions. |
35
+ | `variant` | `TourVariant` | `'modal'` | Visual variant. Free tier supports `'modal'` only. |
36
+ | `active` | `boolean (two-way)` | `false` | Two-way: controls tour visibility. Set to `true` to launch the tour. |
37
+ | `stepIndex` | `number (two-way)` | `0` | Two-way: current step index. |
38
+ | `nextLabel` | `string` | `'Next'` | — |
39
+ | `prevLabel` | `string` | `'Back'` | — |
40
+ | `skipLabel` | `string` | `'Skip tour'` | — |
41
+ | `doneLabel` | `string` | `'Done'` | — |
42
+ | `allowSkip` | `boolean` | `true` | Show the skip / close controls. |
43
+ | `showStepCount` | `boolean` | `true` | Show the "step X of N" counter. |
44
+ | `showProgress` | `boolean` | `true` | Show the dot progress indicator. |
45
+
46
+ ## Outputs
47
+
48
+ | Output | Type | Description |
49
+ | --- | --- | --- |
50
+ | `stepChange` | `number` | Fires with the new step index whenever navigation occurs. |
51
+ | `complete` | `void` | Fires when the last step is confirmed (Next on final step). |
52
+ | `skip` | `void` | Fires when the user explicitly skips the tour. |
53
+
54
+ ## Documentation
55
+
56
+ Full API reference and live demos: https://styloviz.dev/docs/product-tour
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,236 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, model, output, inject, ApplicationRef, computed, effect, HostListener, ViewChild, ChangeDetectionStrategy, Component } from '@angular/core';
3
+
4
+ /**
5
+ * SvProductTourComponent — Onboarding & product-tour overlay (Free tier).
6
+ *
7
+ * Ships the **modal** variant: a centered lightbox dialog with an optional
8
+ * image, icon, tag badge, dot pagination, skip control and CTA link. The
9
+ * card is rendered into `document.body` via an `ApplicationRef` portal so it
10
+ * escapes every `overflow` / `transform` / `z-index` stacking context, and it
11
+ * behaves like a dialog: focus moves inside on open, Tab is trapped, and focus
12
+ * is restored on close.
13
+ *
14
+ * The Pro package (`@styloviz-pro/product-tour`) adds seven further variants:
15
+ * slideshow, tooltip, spotlight, beacon, checklist, progress-steps and
16
+ * announcement.
17
+ *
18
+ * @example
19
+ * <sv-product-tour
20
+ * [(active)]="tourOpen"
21
+ * [(stepIndex)]="tourStep"
22
+ * [steps]="tourSteps"
23
+ * (complete)="onTourDone()"
24
+ * />
25
+ */
26
+ class SvProductTourComponent {
27
+ // ── Inputs ─────────────────────────────────────────────────────────────────
28
+ /** Step definitions. */
29
+ steps = input([], ...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
30
+ /** Visual variant. Free tier supports `'modal'` only. */
31
+ variant = input('modal', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
32
+ /** Two-way: controls tour visibility. Set to `true` to launch the tour. */
33
+ active = model(false, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
34
+ /** Two-way: current step index. */
35
+ stepIndex = model(0, ...(ngDevMode ? [{ debugName: "stepIndex" }] : /* istanbul ignore next */ []));
36
+ // ── Labels (override for i18n) ─────────────────────────────────────────────
37
+ nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : /* istanbul ignore next */ []));
38
+ prevLabel = input('Back', ...(ngDevMode ? [{ debugName: "prevLabel" }] : /* istanbul ignore next */ []));
39
+ skipLabel = input('Skip tour', ...(ngDevMode ? [{ debugName: "skipLabel" }] : /* istanbul ignore next */ []));
40
+ doneLabel = input('Done', ...(ngDevMode ? [{ debugName: "doneLabel" }] : /* istanbul ignore next */ []));
41
+ // ── Behaviour ──────────────────────────────────────────────────────────────
42
+ /** Show the skip / close controls. */
43
+ allowSkip = input(true, ...(ngDevMode ? [{ debugName: "allowSkip" }] : /* istanbul ignore next */ []));
44
+ /** Show the "step X of N" counter. */
45
+ showStepCount = input(true, ...(ngDevMode ? [{ debugName: "showStepCount" }] : /* istanbul ignore next */ []));
46
+ /** Show the dot progress indicator. */
47
+ showProgress = input(true, ...(ngDevMode ? [{ debugName: "showProgress" }] : /* istanbul ignore next */ []));
48
+ // ── Outputs ────────────────────────────────────────────────────────────────
49
+ /** Fires with the new step index whenever navigation occurs. */
50
+ stepChange = output();
51
+ /** Fires when the last step is confirmed (Next on final step). */
52
+ complete = output();
53
+ /** Fires when the user explicitly skips the tour. */
54
+ skip = output();
55
+ // ── Template refs ──────────────────────────────────────────────────────────
56
+ tourTpl;
57
+ // ── Services ───────────────────────────────────────────────────────────────
58
+ appRef = inject(ApplicationRef);
59
+ // ── Portal state ───────────────────────────────────────────────────────────
60
+ portalEl = null;
61
+ portalView = null;
62
+ /** Element focused before the tour opened, restored when it closes. */
63
+ _previouslyFocused = null;
64
+ // ── Computed ───────────────────────────────────────────────────────────────
65
+ currentStep = computed(() => this.steps()[this.stepIndex()] ?? null, ...(ngDevMode ? [{ debugName: "currentStep" }] : /* istanbul ignore next */ []));
66
+ isFirstStep = computed(() => this.stepIndex() === 0, ...(ngDevMode ? [{ debugName: "isFirstStep" }] : /* istanbul ignore next */ []));
67
+ isLastStep = computed(() => this.stepIndex() >= this.steps().length - 1, ...(ngDevMode ? [{ debugName: "isLastStep" }] : /* istanbul ignore next */ []));
68
+ tagColorClass = computed(() => {
69
+ const map = {
70
+ blue: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
71
+ green: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300',
72
+ purple: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300',
73
+ amber: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
74
+ rose: 'bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300',
75
+ };
76
+ return (color) => map[color ?? 'blue'];
77
+ }, ...(ngDevMode ? [{ debugName: "tagColorClass" }] : /* istanbul ignore next */ []));
78
+ // ── Constructor ────────────────────────────────────────────────────────────
79
+ constructor() {
80
+ // Attach / detach portal as `active` changes (+ focus management).
81
+ effect(() => {
82
+ if (this.active()) {
83
+ if (typeof document !== 'undefined') {
84
+ this._previouslyFocused = document.activeElement;
85
+ }
86
+ Promise.resolve().then(() => this._attachPortal());
87
+ }
88
+ else {
89
+ this._detachPortal();
90
+ this._previouslyFocused?.focus?.();
91
+ this._previouslyFocused = null;
92
+ }
93
+ });
94
+ }
95
+ // ── Navigation ─────────────────────────────────────────────────────────────
96
+ goNext() {
97
+ if (this.isLastStep()) {
98
+ this._finish();
99
+ }
100
+ else {
101
+ this._setStep(this.stepIndex() + 1);
102
+ }
103
+ }
104
+ goPrev() {
105
+ if (!this.isFirstStep())
106
+ this._setStep(this.stepIndex() - 1);
107
+ }
108
+ goToStep(idx) {
109
+ this._setStep(Math.max(0, Math.min(idx, this.steps().length - 1)));
110
+ }
111
+ skipTour() {
112
+ this.active.set(false);
113
+ this.skip.emit();
114
+ }
115
+ dismiss() {
116
+ this.active.set(false);
117
+ }
118
+ // ── Keyboard ───────────────────────────────────────────────────────────────
119
+ onKeydown(e) {
120
+ if (!this.active())
121
+ return;
122
+ // Trap Tab focus within the portalled dialog card.
123
+ if (e.key === 'Tab') {
124
+ const focusable = this._focusablesIn(this.portalEl);
125
+ if (focusable.length === 0) {
126
+ e.preventDefault();
127
+ return;
128
+ }
129
+ const first = focusable[0];
130
+ const last = focusable[focusable.length - 1];
131
+ const activeEl = this.portalEl?.getRootNode()?.activeElement
132
+ ?? document.activeElement;
133
+ if (e.shiftKey && activeEl === first) {
134
+ e.preventDefault();
135
+ last.focus();
136
+ }
137
+ else if (!e.shiftKey && activeEl === last) {
138
+ e.preventDefault();
139
+ first.focus();
140
+ }
141
+ return;
142
+ }
143
+ switch (e.key) {
144
+ case 'ArrowRight':
145
+ case 'ArrowDown':
146
+ e.preventDefault();
147
+ this.goNext();
148
+ break;
149
+ case 'ArrowLeft':
150
+ case 'ArrowUp':
151
+ e.preventDefault();
152
+ this.goPrev();
153
+ break;
154
+ case 'Escape':
155
+ if (this.allowSkip())
156
+ this.skipTour();
157
+ break;
158
+ }
159
+ }
160
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
161
+ ngOnDestroy() {
162
+ this._detachPortal();
163
+ }
164
+ // ── Private — navigation ───────────────────────────────────────────────────
165
+ _setStep(idx) {
166
+ this.stepIndex.set(idx);
167
+ this.stepChange.emit(idx);
168
+ this.portalView?.detectChanges();
169
+ }
170
+ _finish() {
171
+ this.active.set(false);
172
+ this.stepIndex.set(0);
173
+ this.complete.emit();
174
+ }
175
+ // ── Private — portal ──────────────────────────────────────────────────────
176
+ _attachPortal() {
177
+ if (this.portalEl) {
178
+ this.portalView?.detectChanges();
179
+ return;
180
+ }
181
+ const el = document.createElement('div');
182
+ document.body.appendChild(el);
183
+ this.portalEl = el;
184
+ const view = this.tourTpl.createEmbeddedView({});
185
+ this.appRef.attachView(view);
186
+ view.rootNodes.forEach(n => el.appendChild(n));
187
+ this.portalView = view;
188
+ view.detectChanges();
189
+ // Move focus into the dialog card so keyboard / SR users land inside.
190
+ setTimeout(() => {
191
+ const first = this._focusableIn(this.portalEl);
192
+ first?.focus();
193
+ }, 30);
194
+ }
195
+ /** First focusable element inside the given container, if any. */
196
+ _focusableIn(container) {
197
+ if (!container)
198
+ return null;
199
+ return container.querySelector('a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])');
200
+ }
201
+ _focusablesIn(container) {
202
+ if (!container)
203
+ return [];
204
+ return Array.from(container.querySelectorAll('a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'));
205
+ }
206
+ _detachPortal() {
207
+ if (this.portalView) {
208
+ this.appRef.detachView(this.portalView);
209
+ this.portalView.destroy();
210
+ this.portalView = null;
211
+ }
212
+ if (this.portalEl) {
213
+ this.portalEl.remove();
214
+ this.portalEl = null;
215
+ }
216
+ }
217
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvProductTourComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
218
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvProductTourComponent, isStandalone: true, selector: "sv-product-tour", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, stepIndex: { classPropertyName: "stepIndex", publicName: "stepIndex", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, prevLabel: { classPropertyName: "prevLabel", publicName: "prevLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null }, allowSkip: { classPropertyName: "allowSkip", publicName: "allowSkip", isSignal: true, isRequired: false, transformFunction: null }, showStepCount: { classPropertyName: "showStepCount", publicName: "showStepCount", isSignal: true, isRequired: false, transformFunction: null }, showProgress: { classPropertyName: "showProgress", publicName: "showProgress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { active: "activeChange", stepIndex: "stepIndexChange", stepChange: "stepChange", complete: "complete", skip: "skip" }, host: { listeners: { "document:keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "tourTpl", first: true, predicate: ["tourTpl"], descendants: true }], ngImport: i0, template: "<!--\n SvProductTourComponent (Free) \u2014 all visual output lives inside #tourTpl.\n The template is appended to document.body via ApplicationRef portal,\n escaping every overflow / transform / z-index stacking context.\n\n Free tier ships the MODAL variant only. The full set of eight variants\n lives in @styloviz-pro/product-tour.\n-->\n\n<ng-template #tourTpl>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n MODAL \u2014 centered lightbox dialog\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n\n <!-- Backdrop -->\n <div\n class=\"fixed inset-0 z-[9990] bg-black/50 backdrop-blur-sm\"\n (click)=\"allowSkip() && skipTour()\"\n ></div>\n\n <!-- Dialog -->\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"currentStep()?.title\"\n class=\"fixed inset-0 z-[9991] flex items-center justify-center p-4 pointer-events-none\"\n >\n <div class=\"pointer-events-auto w-full max-w-lg rounded-2xl overflow-hidden\n bg-white shadow-2xl ring-1 ring-black/10\n dark:bg-gray-900 dark:ring-white/10\">\n\n <!-- Optional step image -->\n @if (currentStep()?.imageUrl) {\n <div class=\"relative h-52 overflow-hidden bg-gray-100 dark:bg-gray-800\">\n <img\n [src]=\"currentStep()!.imageUrl\"\n [alt]=\"currentStep()!.title\"\n class=\"w-full h-full object-cover\"\n />\n <!-- Step count badge over image -->\n @if (showStepCount() && steps().length > 1) {\n <span class=\"absolute top-3 right-3 rounded-full bg-black/50 px-2.5 py-1\n text-[11px] font-medium text-white backdrop-blur-sm\">\n {{ stepIndex() + 1 }} / {{ steps().length }}\n </span>\n }\n </div>\n }\n\n <div class=\"px-7 pt-6 pb-7 flex flex-col gap-4\">\n <!-- Tag + step count (no image case) -->\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-2\">\n @if (currentStep()?.tag) {\n <span class=\"rounded-full px-2.5 py-0.5 text-[11px] font-semibold\n {{ tagColorClass()(currentStep()!.tagColor) }}\">\n {{ currentStep()!.tag }}\n </span>\n }\n </div>\n @if (showStepCount() && steps().length > 1 && !currentStep()?.imageUrl) {\n <span class=\"text-xs text-gray-500 dark:text-gray-400\">\n {{ stepIndex() + 1 }} / {{ steps().length }}\n </span>\n }\n <!-- Close button -->\n @if (allowSkip()) {\n <button\n type=\"button\"\n aria-label=\"Skip tour\"\n (click)=\"skipTour()\"\n class=\"flex items-center justify-center w-7 h-7 rounded-full\n text-gray-400 hover:text-gray-600 hover:bg-gray-100\n dark:hover:bg-gray-800 dark:hover:text-gray-300 transition\"\n >\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" class=\"w-4 h-4\">\n <path d=\"M3 3l10 10M13 3L3 13\"/>\n </svg>\n </button>\n }\n </div>\n\n <!-- Icon (no image) -->\n @if (currentStep()?.icon && !currentStep()?.imageUrl) {\n <div class=\"flex h-11 w-11 items-center justify-center rounded-xl\n bg-blue-50 dark:bg-blue-900/30\">\n <span class=\"text-2xl\">{{ currentStep()!.icon }}</span>\n </div>\n }\n\n <!-- Title + content -->\n <div class=\"flex flex-col gap-2\">\n <h2 class=\"text-xl font-bold text-gray-900 dark:text-gray-50 leading-snug\">\n {{ currentStep()?.title }}\n </h2>\n <p class=\"text-sm text-gray-500 dark:text-gray-400 leading-relaxed whitespace-pre-line\">\n {{ currentStep()?.content }}\n </p>\n </div>\n\n <!-- Optional CTA link -->\n @if (currentStep()?.actionLabel && currentStep()?.actionUrl) {\n <a\n [href]=\"currentStep()!.actionUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center gap-1.5 text-sm font-medium text-blue-600\n dark:text-blue-400 hover:underline\"\n >\n {{ currentStep()!.actionLabel }}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" class=\"w-3.5 h-3.5\">\n <path d=\"M7 3H3v10h10V9M9 3h4v4M13 3L8 8\"/>\n </svg>\n </a>\n }\n\n <!-- Progress dots -->\n @if (showProgress() && steps().length > 1) {\n <div class=\"flex items-center gap-1.5\">\n @for (s of steps(); track s.id; let i = $index) {\n <button\n type=\"button\"\n [attr.aria-label]=\"'Go to step ' + (i + 1)\"\n (click)=\"goToStep(i)\"\n class=\"rounded-full transition-all duration-200\n {{ i === stepIndex()\n ? 'w-5 h-2 bg-blue-500'\n : 'w-2 h-2 bg-gray-200 dark:bg-gray-700 hover:bg-blue-300' }}\"\n ></button>\n }\n </div>\n }\n\n <!-- Actions -->\n <div class=\"flex items-center justify-between pt-1\">\n <div>\n @if (allowSkip() && !isLastStep()) {\n <button\n type=\"button\"\n (click)=\"skipTour()\"\n class=\"text-sm text-gray-400 hover:text-gray-600\n dark:text-gray-500 dark:hover:text-gray-300 transition\"\n >{{ skipLabel() }}</button>\n }\n </div>\n <div class=\"flex items-center gap-2\">\n @if (!isFirstStep()) {\n <button\n type=\"button\"\n (click)=\"goPrev()\"\n class=\"px-4 py-2 rounded-lg text-sm font-medium text-gray-600\n hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition\"\n >{{ prevLabel() }}</button>\n }\n <button\n type=\"button\"\n (click)=\"goNext()\"\n class=\"inline-flex items-center gap-1.5 px-5 py-2 rounded-lg text-sm font-semibold\n bg-blue-600 text-white hover:bg-blue-700 active:scale-95 transition\n shadow-sm shadow-blue-500/30\"\n >\n {{ isLastStep() ? doneLabel() : nextLabel() }}\n @if (!isLastStep()) {\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.5\" stroke-linecap=\"round\" class=\"w-3.5 h-3.5\">\n <path d=\"M3 8h10M9 4l4 4-4 4\"/>\n </svg>\n }\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n</ng-template>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
219
+ }
220
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvProductTourComponent, decorators: [{
221
+ type: Component,
222
+ args: [{ selector: 'sv-product-tour', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n SvProductTourComponent (Free) \u2014 all visual output lives inside #tourTpl.\n The template is appended to document.body via ApplicationRef portal,\n escaping every overflow / transform / z-index stacking context.\n\n Free tier ships the MODAL variant only. The full set of eight variants\n lives in @styloviz-pro/product-tour.\n-->\n\n<ng-template #tourTpl>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n MODAL \u2014 centered lightbox dialog\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n\n <!-- Backdrop -->\n <div\n class=\"fixed inset-0 z-[9990] bg-black/50 backdrop-blur-sm\"\n (click)=\"allowSkip() && skipTour()\"\n ></div>\n\n <!-- Dialog -->\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"currentStep()?.title\"\n class=\"fixed inset-0 z-[9991] flex items-center justify-center p-4 pointer-events-none\"\n >\n <div class=\"pointer-events-auto w-full max-w-lg rounded-2xl overflow-hidden\n bg-white shadow-2xl ring-1 ring-black/10\n dark:bg-gray-900 dark:ring-white/10\">\n\n <!-- Optional step image -->\n @if (currentStep()?.imageUrl) {\n <div class=\"relative h-52 overflow-hidden bg-gray-100 dark:bg-gray-800\">\n <img\n [src]=\"currentStep()!.imageUrl\"\n [alt]=\"currentStep()!.title\"\n class=\"w-full h-full object-cover\"\n />\n <!-- Step count badge over image -->\n @if (showStepCount() && steps().length > 1) {\n <span class=\"absolute top-3 right-3 rounded-full bg-black/50 px-2.5 py-1\n text-[11px] font-medium text-white backdrop-blur-sm\">\n {{ stepIndex() + 1 }} / {{ steps().length }}\n </span>\n }\n </div>\n }\n\n <div class=\"px-7 pt-6 pb-7 flex flex-col gap-4\">\n <!-- Tag + step count (no image case) -->\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-2\">\n @if (currentStep()?.tag) {\n <span class=\"rounded-full px-2.5 py-0.5 text-[11px] font-semibold\n {{ tagColorClass()(currentStep()!.tagColor) }}\">\n {{ currentStep()!.tag }}\n </span>\n }\n </div>\n @if (showStepCount() && steps().length > 1 && !currentStep()?.imageUrl) {\n <span class=\"text-xs text-gray-500 dark:text-gray-400\">\n {{ stepIndex() + 1 }} / {{ steps().length }}\n </span>\n }\n <!-- Close button -->\n @if (allowSkip()) {\n <button\n type=\"button\"\n aria-label=\"Skip tour\"\n (click)=\"skipTour()\"\n class=\"flex items-center justify-center w-7 h-7 rounded-full\n text-gray-400 hover:text-gray-600 hover:bg-gray-100\n dark:hover:bg-gray-800 dark:hover:text-gray-300 transition\"\n >\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" class=\"w-4 h-4\">\n <path d=\"M3 3l10 10M13 3L3 13\"/>\n </svg>\n </button>\n }\n </div>\n\n <!-- Icon (no image) -->\n @if (currentStep()?.icon && !currentStep()?.imageUrl) {\n <div class=\"flex h-11 w-11 items-center justify-center rounded-xl\n bg-blue-50 dark:bg-blue-900/30\">\n <span class=\"text-2xl\">{{ currentStep()!.icon }}</span>\n </div>\n }\n\n <!-- Title + content -->\n <div class=\"flex flex-col gap-2\">\n <h2 class=\"text-xl font-bold text-gray-900 dark:text-gray-50 leading-snug\">\n {{ currentStep()?.title }}\n </h2>\n <p class=\"text-sm text-gray-500 dark:text-gray-400 leading-relaxed whitespace-pre-line\">\n {{ currentStep()?.content }}\n </p>\n </div>\n\n <!-- Optional CTA link -->\n @if (currentStep()?.actionLabel && currentStep()?.actionUrl) {\n <a\n [href]=\"currentStep()!.actionUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center gap-1.5 text-sm font-medium text-blue-600\n dark:text-blue-400 hover:underline\"\n >\n {{ currentStep()!.actionLabel }}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" class=\"w-3.5 h-3.5\">\n <path d=\"M7 3H3v10h10V9M9 3h4v4M13 3L8 8\"/>\n </svg>\n </a>\n }\n\n <!-- Progress dots -->\n @if (showProgress() && steps().length > 1) {\n <div class=\"flex items-center gap-1.5\">\n @for (s of steps(); track s.id; let i = $index) {\n <button\n type=\"button\"\n [attr.aria-label]=\"'Go to step ' + (i + 1)\"\n (click)=\"goToStep(i)\"\n class=\"rounded-full transition-all duration-200\n {{ i === stepIndex()\n ? 'w-5 h-2 bg-blue-500'\n : 'w-2 h-2 bg-gray-200 dark:bg-gray-700 hover:bg-blue-300' }}\"\n ></button>\n }\n </div>\n }\n\n <!-- Actions -->\n <div class=\"flex items-center justify-between pt-1\">\n <div>\n @if (allowSkip() && !isLastStep()) {\n <button\n type=\"button\"\n (click)=\"skipTour()\"\n class=\"text-sm text-gray-400 hover:text-gray-600\n dark:text-gray-500 dark:hover:text-gray-300 transition\"\n >{{ skipLabel() }}</button>\n }\n </div>\n <div class=\"flex items-center gap-2\">\n @if (!isFirstStep()) {\n <button\n type=\"button\"\n (click)=\"goPrev()\"\n class=\"px-4 py-2 rounded-lg text-sm font-medium text-gray-600\n hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition\"\n >{{ prevLabel() }}</button>\n }\n <button\n type=\"button\"\n (click)=\"goNext()\"\n class=\"inline-flex items-center gap-1.5 px-5 py-2 rounded-lg text-sm font-semibold\n bg-blue-600 text-white hover:bg-blue-700 active:scale-95 transition\n shadow-sm shadow-blue-500/30\"\n >\n {{ isLastStep() ? doneLabel() : nextLabel() }}\n @if (!isLastStep()) {\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.5\" stroke-linecap=\"round\" class=\"w-3.5 h-3.5\">\n <path d=\"M3 8h10M9 4l4 4-4 4\"/>\n </svg>\n }\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n</ng-template>\n" }]
223
+ }], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }, { type: i0.Output, args: ["activeChange"] }], stepIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepIndex", required: false }] }, { type: i0.Output, args: ["stepIndexChange"] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], prevLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "prevLabel", required: false }] }], skipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], allowSkip: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSkip", required: false }] }], showStepCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showStepCount", required: false }] }], showProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "showProgress", required: false }] }], stepChange: [{ type: i0.Output, args: ["stepChange"] }], complete: [{ type: i0.Output, args: ["complete"] }], skip: [{ type: i0.Output, args: ["skip"] }], tourTpl: [{
224
+ type: ViewChild,
225
+ args: ['tourTpl']
226
+ }], onKeydown: [{
227
+ type: HostListener,
228
+ args: ['document:keydown', ['$event']]
229
+ }] } });
230
+
231
+ /**
232
+ * Generated bundle index. Do not edit.
233
+ */
234
+
235
+ export { SvProductTourComponent };
236
+ //# sourceMappingURL=styloviz-product-tour.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styloviz-product-tour.mjs","sources":["../../../../projects/product-tour-free/src/lib/product-tour.component.ts","../../../../projects/product-tour-free/src/lib/product-tour.component.html","../../../../projects/product-tour-free/src/styloviz-product-tour.ts"],"sourcesContent":["import {\n ApplicationRef,\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n EmbeddedViewRef,\n HostListener,\n inject,\n input,\n model,\n OnDestroy,\n output,\n TemplateRef,\n ViewChild,\n} from '@angular/core';\n\nimport type { TourVariant } from './product-tour.types';\nimport { type TourStep } from './product-tour.types';\n\n// Re-export types so consumers can import from the component file if desired.\nexport type { TourVariant, TourPlacement, TourStep } from './product-tour.types';\n\n/**\n * SvProductTourComponent — Onboarding & product-tour overlay (Free tier).\n *\n * Ships the **modal** variant: a centered lightbox dialog with an optional\n * image, icon, tag badge, dot pagination, skip control and CTA link. The\n * card is rendered into `document.body` via an `ApplicationRef` portal so it\n * escapes every `overflow` / `transform` / `z-index` stacking context, and it\n * behaves like a dialog: focus moves inside on open, Tab is trapped, and focus\n * is restored on close.\n *\n * The Pro package (`@styloviz-pro/product-tour`) adds seven further variants:\n * slideshow, tooltip, spotlight, beacon, checklist, progress-steps and\n * announcement.\n *\n * @example\n * <sv-product-tour\n * [(active)]=\"tourOpen\"\n * [(stepIndex)]=\"tourStep\"\n * [steps]=\"tourSteps\"\n * (complete)=\"onTourDone()\"\n * />\n */\n@Component({\n selector: 'sv-product-tour',\n standalone: true,\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './product-tour.component.html',\n})\nexport class SvProductTourComponent implements OnDestroy {\n\n // ── Inputs ─────────────────────────────────────────────────────────────────\n\n /** Step definitions. */\n steps = input<TourStep[]>([]);\n /** Visual variant. Free tier supports `'modal'` only. */\n variant = input<TourVariant>('modal');\n /** Two-way: controls tour visibility. Set to `true` to launch the tour. */\n active = model<boolean>(false);\n /** Two-way: current step index. */\n stepIndex = model<number>(0);\n\n // ── Labels (override for i18n) ─────────────────────────────────────────────\n\n nextLabel = input('Next');\n prevLabel = input('Back');\n skipLabel = input('Skip tour');\n doneLabel = input('Done');\n\n // ── Behaviour ──────────────────────────────────────────────────────────────\n\n /** Show the skip / close controls. */\n allowSkip = input(true);\n /** Show the \"step X of N\" counter. */\n showStepCount = input(true);\n /** Show the dot progress indicator. */\n showProgress = input(true);\n\n // ── Outputs ────────────────────────────────────────────────────────────────\n\n /** Fires with the new step index whenever navigation occurs. */\n readonly stepChange = output<number>();\n /** Fires when the last step is confirmed (Next on final step). */\n readonly complete = output<void>();\n /** Fires when the user explicitly skips the tour. */\n readonly skip = output<void>();\n\n // ── Template refs ──────────────────────────────────────────────────────────\n\n @ViewChild('tourTpl') private tourTpl!: TemplateRef<unknown>;\n\n // ── Services ───────────────────────────────────────────────────────────────\n\n private readonly appRef = inject(ApplicationRef);\n\n // ── Portal state ───────────────────────────────────────────────────────────\n\n private portalEl: HTMLDivElement | null = null;\n protected portalView: EmbeddedViewRef<unknown> | null = null;\n\n /** Element focused before the tour opened, restored when it closes. */\n private _previouslyFocused: HTMLElement | null = null;\n\n // ── Computed ───────────────────────────────────────────────────────────────\n\n readonly currentStep = computed((): TourStep | null => this.steps()[this.stepIndex()] ?? null);\n readonly isFirstStep = computed(() => this.stepIndex() === 0);\n readonly isLastStep = computed(() => this.stepIndex() >= this.steps().length - 1);\n\n readonly tagColorClass = computed(() => {\n const map: Record<NonNullable<TourStep['tagColor']>, string> = {\n blue: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',\n green: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300',\n purple: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300',\n amber: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',\n rose: 'bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300',\n };\n return (color: TourStep['tagColor']) => map[color ?? 'blue'];\n });\n\n // ── Constructor ────────────────────────────────────────────────────────────\n\n constructor() {\n // Attach / detach portal as `active` changes (+ focus management).\n effect(() => {\n if (this.active()) {\n if (typeof document !== 'undefined') {\n this._previouslyFocused = document.activeElement as HTMLElement | null;\n }\n Promise.resolve().then(() => this._attachPortal());\n } else {\n this._detachPortal();\n this._previouslyFocused?.focus?.();\n this._previouslyFocused = null;\n }\n });\n }\n\n // ── Navigation ─────────────────────────────────────────────────────────────\n\n goNext(): void {\n if (this.isLastStep()) {\n this._finish();\n } else {\n this._setStep(this.stepIndex() + 1);\n }\n }\n\n goPrev(): void {\n if (!this.isFirstStep()) this._setStep(this.stepIndex() - 1);\n }\n\n goToStep(idx: number): void {\n this._setStep(Math.max(0, Math.min(idx, this.steps().length - 1)));\n }\n\n skipTour(): void {\n this.active.set(false);\n this.skip.emit();\n }\n\n dismiss(): void {\n this.active.set(false);\n }\n\n // ── Keyboard ───────────────────────────────────────────────────────────────\n\n @HostListener('document:keydown', ['$event'])\n onKeydown(e: KeyboardEvent): void {\n if (!this.active()) return;\n\n // Trap Tab focus within the portalled dialog card.\n if (e.key === 'Tab') {\n const focusable = this._focusablesIn(this.portalEl);\n if (focusable.length === 0) { e.preventDefault(); return; }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const activeEl = (this.portalEl?.getRootNode() as Document | ShadowRoot)?.activeElement\n ?? document.activeElement;\n if (e.shiftKey && activeEl === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && activeEl === last) {\n e.preventDefault();\n first.focus();\n }\n return;\n }\n\n switch (e.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n e.preventDefault();\n this.goNext();\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n e.preventDefault();\n this.goPrev();\n break;\n case 'Escape':\n if (this.allowSkip()) this.skipTour();\n break;\n }\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────────────\n\n ngOnDestroy(): void {\n this._detachPortal();\n }\n\n // ── Private — navigation ───────────────────────────────────────────────────\n\n private _setStep(idx: number): void {\n this.stepIndex.set(idx);\n this.stepChange.emit(idx);\n this.portalView?.detectChanges();\n }\n\n private _finish(): void {\n this.active.set(false);\n this.stepIndex.set(0);\n this.complete.emit();\n }\n\n // ── Private — portal ──────────────────────────────────────────────────────\n\n private _attachPortal(): void {\n if (this.portalEl) {\n this.portalView?.detectChanges();\n return;\n }\n const el = document.createElement('div');\n document.body.appendChild(el);\n this.portalEl = el;\n const view = this.tourTpl.createEmbeddedView({});\n this.appRef.attachView(view);\n (view.rootNodes as Node[]).forEach(n => el.appendChild(n));\n this.portalView = view;\n\n view.detectChanges();\n\n // Move focus into the dialog card so keyboard / SR users land inside.\n setTimeout(() => {\n const first = this._focusableIn(this.portalEl);\n first?.focus();\n }, 30);\n }\n\n /** First focusable element inside the given container, if any. */\n private _focusableIn(container: HTMLElement | null): HTMLElement | null {\n if (!container) return null;\n return container.querySelector<HTMLElement>(\n 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex=\"-1\"])'\n );\n }\n\n private _focusablesIn(container: HTMLElement | null): HTMLElement[] {\n if (!container) return [];\n return Array.from(\n container.querySelectorAll<HTMLElement>(\n 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex=\"-1\"])'\n )\n );\n }\n\n private _detachPortal(): void {\n if (this.portalView) {\n this.appRef.detachView(this.portalView);\n this.portalView.destroy();\n this.portalView = null;\n }\n if (this.portalEl) {\n this.portalEl.remove();\n this.portalEl = null;\n }\n }\n}\n","<!--\n SvProductTourComponent (Free) — all visual output lives inside #tourTpl.\n The template is appended to document.body via ApplicationRef portal,\n escaping every overflow / transform / z-index stacking context.\n\n Free tier ships the MODAL variant only. The full set of eight variants\n lives in @styloviz-pro/product-tour.\n-->\n\n<ng-template #tourTpl>\n\n <!-- ══════════════════════════════════════════════════════════════════════\n MODAL — centered lightbox dialog\n ════════════════════════════════════════════════════════════════════ -->\n\n <!-- Backdrop -->\n <div\n class=\"fixed inset-0 z-[9990] bg-black/50 backdrop-blur-sm\"\n (click)=\"allowSkip() && skipTour()\"\n ></div>\n\n <!-- Dialog -->\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"currentStep()?.title\"\n class=\"fixed inset-0 z-[9991] flex items-center justify-center p-4 pointer-events-none\"\n >\n <div class=\"pointer-events-auto w-full max-w-lg rounded-2xl overflow-hidden\n bg-white shadow-2xl ring-1 ring-black/10\n dark:bg-gray-900 dark:ring-white/10\">\n\n <!-- Optional step image -->\n @if (currentStep()?.imageUrl) {\n <div class=\"relative h-52 overflow-hidden bg-gray-100 dark:bg-gray-800\">\n <img\n [src]=\"currentStep()!.imageUrl\"\n [alt]=\"currentStep()!.title\"\n class=\"w-full h-full object-cover\"\n />\n <!-- Step count badge over image -->\n @if (showStepCount() && steps().length > 1) {\n <span class=\"absolute top-3 right-3 rounded-full bg-black/50 px-2.5 py-1\n text-[11px] font-medium text-white backdrop-blur-sm\">\n {{ stepIndex() + 1 }} / {{ steps().length }}\n </span>\n }\n </div>\n }\n\n <div class=\"px-7 pt-6 pb-7 flex flex-col gap-4\">\n <!-- Tag + step count (no image case) -->\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-2\">\n @if (currentStep()?.tag) {\n <span class=\"rounded-full px-2.5 py-0.5 text-[11px] font-semibold\n {{ tagColorClass()(currentStep()!.tagColor) }}\">\n {{ currentStep()!.tag }}\n </span>\n }\n </div>\n @if (showStepCount() && steps().length > 1 && !currentStep()?.imageUrl) {\n <span class=\"text-xs text-gray-500 dark:text-gray-400\">\n {{ stepIndex() + 1 }} / {{ steps().length }}\n </span>\n }\n <!-- Close button -->\n @if (allowSkip()) {\n <button\n type=\"button\"\n aria-label=\"Skip tour\"\n (click)=\"skipTour()\"\n class=\"flex items-center justify-center w-7 h-7 rounded-full\n text-gray-400 hover:text-gray-600 hover:bg-gray-100\n dark:hover:bg-gray-800 dark:hover:text-gray-300 transition\"\n >\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" class=\"w-4 h-4\">\n <path d=\"M3 3l10 10M13 3L3 13\"/>\n </svg>\n </button>\n }\n </div>\n\n <!-- Icon (no image) -->\n @if (currentStep()?.icon && !currentStep()?.imageUrl) {\n <div class=\"flex h-11 w-11 items-center justify-center rounded-xl\n bg-blue-50 dark:bg-blue-900/30\">\n <span class=\"text-2xl\">{{ currentStep()!.icon }}</span>\n </div>\n }\n\n <!-- Title + content -->\n <div class=\"flex flex-col gap-2\">\n <h2 class=\"text-xl font-bold text-gray-900 dark:text-gray-50 leading-snug\">\n {{ currentStep()?.title }}\n </h2>\n <p class=\"text-sm text-gray-500 dark:text-gray-400 leading-relaxed whitespace-pre-line\">\n {{ currentStep()?.content }}\n </p>\n </div>\n\n <!-- Optional CTA link -->\n @if (currentStep()?.actionLabel && currentStep()?.actionUrl) {\n <a\n [href]=\"currentStep()!.actionUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center gap-1.5 text-sm font-medium text-blue-600\n dark:text-blue-400 hover:underline\"\n >\n {{ currentStep()!.actionLabel }}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" class=\"w-3.5 h-3.5\">\n <path d=\"M7 3H3v10h10V9M9 3h4v4M13 3L8 8\"/>\n </svg>\n </a>\n }\n\n <!-- Progress dots -->\n @if (showProgress() && steps().length > 1) {\n <div class=\"flex items-center gap-1.5\">\n @for (s of steps(); track s.id; let i = $index) {\n <button\n type=\"button\"\n [attr.aria-label]=\"'Go to step ' + (i + 1)\"\n (click)=\"goToStep(i)\"\n class=\"rounded-full transition-all duration-200\n {{ i === stepIndex()\n ? 'w-5 h-2 bg-blue-500'\n : 'w-2 h-2 bg-gray-200 dark:bg-gray-700 hover:bg-blue-300' }}\"\n ></button>\n }\n </div>\n }\n\n <!-- Actions -->\n <div class=\"flex items-center justify-between pt-1\">\n <div>\n @if (allowSkip() && !isLastStep()) {\n <button\n type=\"button\"\n (click)=\"skipTour()\"\n class=\"text-sm text-gray-400 hover:text-gray-600\n dark:text-gray-500 dark:hover:text-gray-300 transition\"\n >{{ skipLabel() }}</button>\n }\n </div>\n <div class=\"flex items-center gap-2\">\n @if (!isFirstStep()) {\n <button\n type=\"button\"\n (click)=\"goPrev()\"\n class=\"px-4 py-2 rounded-lg text-sm font-medium text-gray-600\n hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition\"\n >{{ prevLabel() }}</button>\n }\n <button\n type=\"button\"\n (click)=\"goNext()\"\n class=\"inline-flex items-center gap-1.5 px-5 py-2 rounded-lg text-sm font-semibold\n bg-blue-600 text-white hover:bg-blue-700 active:scale-95 transition\n shadow-sm shadow-blue-500/30\"\n >\n {{ isLastStep() ? doneLabel() : nextLabel() }}\n @if (!isLastStep()) {\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.5\" stroke-linecap=\"round\" class=\"w-3.5 h-3.5\">\n <path d=\"M3 8h10M9 4l4 4-4 4\"/>\n </svg>\n }\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;AAuBA;;;;;;;;;;;;;;;;;;;;;AAqBG;MAQU,sBAAsB,CAAA;;;AAKjC,IAAA,KAAK,GAAG,KAAK,CAAa,EAAE,4EAAC;;AAE7B,IAAA,OAAO,GAAG,KAAK,CAAc,OAAO,8EAAC;;AAErC,IAAA,MAAM,GAAG,KAAK,CAAU,KAAK,6EAAC;;AAE9B,IAAA,SAAS,GAAG,KAAK,CAAS,CAAC,gFAAC;;AAI5B,IAAA,SAAS,GAAG,KAAK,CAAC,MAAM,gFAAC;AACzB,IAAA,SAAS,GAAG,KAAK,CAAC,MAAM,gFAAC;AACzB,IAAA,SAAS,GAAG,KAAK,CAAC,WAAW,gFAAC;AAC9B,IAAA,SAAS,GAAG,KAAK,CAAC,MAAM,gFAAC;;;AAKzB,IAAA,SAAS,GAAO,KAAK,CAAC,IAAI,gFAAC;;AAE3B,IAAA,aAAa,GAAG,KAAK,CAAC,IAAI,oFAAC;;AAE3B,IAAA,YAAY,GAAI,KAAK,CAAC,IAAI,mFAAC;;;IAKlB,UAAU,GAAG,MAAM,EAAU;;IAE7B,QAAQ,GAAK,MAAM,EAAQ;;IAE3B,IAAI,GAAS,MAAM,EAAQ;;AAIN,IAAA,OAAO;;AAIpB,IAAA,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;;IAIxC,QAAQ,GAAwC,IAAI;IAClD,UAAU,GAAoC,IAAI;;IAGpD,kBAAkB,GAAuB,IAAI;;AAI5C,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAuB,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,IAAI,kFAAC;AACrF,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,kFAAC;IACpD,UAAU,GAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,iFAAC;AAEzE,IAAA,aAAa,GAAG,QAAQ,CAAC,MAAK;AACrC,QAAA,MAAM,GAAG,GAAsD;AAC7D,YAAA,IAAI,EAAI,kEAAkE;AAC1E,YAAA,KAAK,EAAG,8EAA8E;AACtF,YAAA,MAAM,EAAE,0EAA0E;AAClF,YAAA,KAAK,EAAG,sEAAsE;AAC9E,YAAA,IAAI,EAAI,kEAAkE;SAC3E;QACD,OAAO,CAAC,KAA2B,KAAK,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC;AAC9D,IAAA,CAAC,oFAAC;;AAIF,IAAA,WAAA,GAAA;;QAEE,MAAM,CAAC,MAAK;AACV,YAAA,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE;AACjB,gBAAA,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE;AACnC,oBAAA,IAAI,CAAC,kBAAkB,GAAG,QAAQ,CAAC,aAAmC;gBACxE;AACA,gBAAA,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YACpD;iBAAO;gBACL,IAAI,CAAC,aAAa,EAAE;AACpB,gBAAA,IAAI,CAAC,kBAAkB,EAAE,KAAK,IAAI;AAClC,gBAAA,IAAI,CAAC,kBAAkB,GAAG,IAAI;YAChC;AACF,QAAA,CAAC,CAAC;IACJ;;IAIA,MAAM,GAAA;AACJ,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE;YACrB,IAAI,CAAC,OAAO,EAAE;QAChB;aAAO;YACL,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACrC;IACF;IAEA,MAAM,GAAA;AACJ,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAC9D;AAEA,IAAA,QAAQ,CAAC,GAAW,EAAA;QAClB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACpE;IAEA,QAAQ,GAAA;AACN,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;AACtB,QAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;IAClB;IAEA,OAAO,GAAA;AACL,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;IACxB;;AAKA,IAAA,SAAS,CAAC,CAAgB,EAAA;AACxB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE;;AAGpB,QAAA,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK,EAAE;YACnB,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;AACnD,YAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE;gBAAE,CAAC,CAAC,cAAc,EAAE;gBAAE;YAAQ;AAC1D,YAAA,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC;YAC1B,MAAM,IAAI,GAAI,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7C,MAAM,QAAQ,GAAI,IAAI,CAAC,QAAQ,EAAE,WAAW,EAA4B,EAAE;mBACtD,QAAQ,CAAC,aAAa;YAC1C,IAAI,CAAC,CAAC,QAAQ,IAAI,QAAQ,KAAK,KAAK,EAAE;gBACpC,CAAC,CAAC,cAAc,EAAE;gBAClB,IAAI,CAAC,KAAK,EAAE;YACd;iBAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE;gBAC3C,CAAC,CAAC,cAAc,EAAE;gBAClB,KAAK,CAAC,KAAK,EAAE;YACf;YACA;QACF;AAEA,QAAA,QAAQ,CAAC,CAAC,GAAG;AACX,YAAA,KAAK,YAAY;AACjB,YAAA,KAAK,WAAW;gBACd,CAAC,CAAC,cAAc,EAAE;gBAClB,IAAI,CAAC,MAAM,EAAE;gBACb;AACF,YAAA,KAAK,WAAW;AAChB,YAAA,KAAK,SAAS;gBACZ,CAAC,CAAC,cAAc,EAAE;gBAClB,IAAI,CAAC,MAAM,EAAE;gBACb;AACF,YAAA,KAAK,QAAQ;gBACX,IAAI,IAAI,CAAC,SAAS,EAAE;oBAAE,IAAI,CAAC,QAAQ,EAAE;gBACrC;;IAEN;;IAIA,WAAW,GAAA;QACT,IAAI,CAAC,aAAa,EAAE;IACtB;;AAIQ,IAAA,QAAQ,CAAC,GAAW,EAAA;AAC1B,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;AACvB,QAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;AACzB,QAAA,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE;IAClC;IAEQ,OAAO,GAAA;AACb,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;AACtB,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AACrB,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;IACtB;;IAIQ,aAAa,GAAA;AACnB,QAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AACjB,YAAA,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE;YAChC;QACF;QACA,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;AACxC,QAAA,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;AAC7B,QAAA,IAAI,CAAC,QAAQ,GAAG,EAAE;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;AAChD,QAAA,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;AAC3B,QAAA,IAAI,CAAC,SAAoB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;AAC1D,QAAA,IAAI,CAAC,UAAU,GAAG,IAAI;QAEtB,IAAI,CAAC,aAAa,EAAE;;QAGpB,UAAU,CAAC,MAAK;YACd,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC9C,KAAK,EAAE,KAAK,EAAE;QAChB,CAAC,EAAE,EAAE,CAAC;IACR;;AAGQ,IAAA,YAAY,CAAC,SAA6B,EAAA;AAChD,QAAA,IAAI,CAAC,SAAS;AAAE,YAAA,OAAO,IAAI;AAC3B,QAAA,OAAO,SAAS,CAAC,aAAa,CAC5B,2FAA2F,CAC5F;IACH;AAEQ,IAAA,aAAa,CAAC,SAA6B,EAAA;AACjD,QAAA,IAAI,CAAC,SAAS;AAAE,YAAA,OAAO,EAAE;QACzB,OAAO,KAAK,CAAC,IAAI,CACf,SAAS,CAAC,gBAAgB,CACxB,2FAA2F,CAC5F,CACF;IACH;IAEQ,aAAa,GAAA;AACnB,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE;YACnB,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;AACvC,YAAA,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE;AACzB,YAAA,IAAI,CAAC,UAAU,GAAG,IAAI;QACxB;AACA,QAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AACjB,YAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;AACtB,YAAA,IAAI,CAAC,QAAQ,GAAG,IAAI;QACtB;IACF;wGApOW,sBAAsB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAtB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,sBAAsB,oyDCpDnC,0vPAmLA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;4FD/Ha,sBAAsB,EAAA,UAAA,EAAA,CAAA;kBAPlC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,cACf,IAAI,EAAA,OAAA,EACP,EAAE,EAAA,eAAA,EACM,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,0vPAAA,EAAA;;sBA2C9C,SAAS;uBAAC,SAAS;;sBA8EnB,YAAY;uBAAC,kBAAkB,EAAE,CAAC,QAAQ,CAAC;;;AE1K9C;;AAEG;;;;"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@styloviz/product-tour",
3
+ "version": "0.1.1",
4
+ "description": "8 onboarding variants: modal (free) plus slideshow, tooltip, spotlight, beacon, checklist, progress steps and announcement (Pro). Portal-based overlay with keyboard navigation.",
5
+ "keywords": [
6
+ "angular",
7
+ "tailwind",
8
+ "product-tour",
9
+ "onboarding"
10
+ ],
11
+ "license": "MIT",
12
+ "author": "sazzad-bs23",
13
+ "homepage": "https://styloviz.dev",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/rhossain/angular-tailwind-dashboard-components.git",
17
+ "directory": "projects/product-tour-free"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/rhossain/angular-tailwind-dashboard-components/issues"
21
+ },
22
+ "peerDependencies": {
23
+ "@angular/common": ">=21.0.0",
24
+ "@angular/core": ">=21.0.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "engines": {
30
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
31
+ },
32
+ "module": "fesm2022/styloviz-product-tour.mjs",
33
+ "typings": "types/styloviz-product-tour.d.ts",
34
+ "exports": {
35
+ "./package.json": {
36
+ "default": "./package.json"
37
+ },
38
+ ".": {
39
+ "types": "./types/styloviz-product-tour.d.ts",
40
+ "default": "./fesm2022/styloviz-product-tour.mjs"
41
+ }
42
+ },
43
+ "sideEffects": false,
44
+ "type": "module",
45
+ "dependencies": {
46
+ "tslib": "^2.3.0"
47
+ }
48
+ }
@@ -0,0 +1,141 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { OnDestroy, EmbeddedViewRef } from '@angular/core';
3
+
4
+ /**
5
+ * Visual variant of the product tour.
6
+ *
7
+ * The **free** tier ships the `modal` variant only. The full set of eight
8
+ * variants — slideshow, tooltip, spotlight, beacon, checklist, progress-steps
9
+ * and announcement — is available in `@styloviz-pro/product-tour`.
10
+ *
11
+ * | Variant | Description |
12
+ * |---------|-------------------------------------------------------------|
13
+ * | modal | Centered lightbox dialog — image, rich text, dot pagination |
14
+ */
15
+ type TourVariant = 'modal';
16
+ /** Preferred tooltip / popover placement relative to the target element. */
17
+ type TourPlacement = 'top' | 'bottom' | 'left' | 'right' | 'auto';
18
+ /**
19
+ * A single step in a tour.
20
+ *
21
+ * The free `modal` variant uses `title`, `content`, `imageUrl`, `icon`, `tag`,
22
+ * `tagColor`, `actionLabel` and `actionUrl`. The positioning fields
23
+ * (`targetSelector`, `placement`) and behaviour flags (`completable`,
24
+ * `initiallyCompleted`, `accentColor`) are consumed by the Pro-only variants
25
+ * and are kept here so step data stays interchangeable when upgrading.
26
+ */
27
+ interface TourStep {
28
+ /** Unique identifier — used as a key and for checklist completion tracking. */
29
+ readonly id: string;
30
+ /** Short heading shown in all variants. */
31
+ readonly title: string;
32
+ /** Body copy — HTML is NOT supported (XSS safe). Use `\n` for line breaks. */
33
+ readonly content: string;
34
+ /** CSS selector (`#id`, `.class`, `[data-attr]`) that identifies the target element. */
35
+ readonly targetSelector?: string;
36
+ /** Preferred tooltip placement. `'auto'` picks the side with the most space. */
37
+ readonly placement?: TourPlacement;
38
+ /** URL of an illustration or screenshot — shown in the `modal` variant. */
39
+ readonly imageUrl?: string;
40
+ /**
41
+ * SVG path data OR an emoji for the step icon.
42
+ * Used in `modal` steps without images.
43
+ */
44
+ readonly icon?: string;
45
+ /**
46
+ * Accent badge text, e.g. `"New"`, `"Pro"`, `"Step 1"`.
47
+ * Shown in the `modal` variant.
48
+ */
49
+ readonly tag?: string;
50
+ /** Tag colour. Defaults to `'blue'`. */
51
+ readonly tagColor?: 'blue' | 'green' | 'purple' | 'amber' | 'rose';
52
+ /** Checklist: whether this item can be manually checked off by the user. */
53
+ readonly completable?: boolean;
54
+ /** Checklist: pre-populate as already completed. */
55
+ readonly initiallyCompleted?: boolean;
56
+ /** `announcement` variant: accent colour of the banner strip. */
57
+ readonly accentColor?: 'blue' | 'indigo' | 'emerald' | 'amber' | 'rose';
58
+ /** Optional primary CTA button label in the `modal` variant. */
59
+ readonly actionLabel?: string;
60
+ /** Optional URL the CTA opens (blank tab). */
61
+ readonly actionUrl?: string;
62
+ }
63
+
64
+ /**
65
+ * SvProductTourComponent — Onboarding & product-tour overlay (Free tier).
66
+ *
67
+ * Ships the **modal** variant: a centered lightbox dialog with an optional
68
+ * image, icon, tag badge, dot pagination, skip control and CTA link. The
69
+ * card is rendered into `document.body` via an `ApplicationRef` portal so it
70
+ * escapes every `overflow` / `transform` / `z-index` stacking context, and it
71
+ * behaves like a dialog: focus moves inside on open, Tab is trapped, and focus
72
+ * is restored on close.
73
+ *
74
+ * The Pro package (`@styloviz-pro/product-tour`) adds seven further variants:
75
+ * slideshow, tooltip, spotlight, beacon, checklist, progress-steps and
76
+ * announcement.
77
+ *
78
+ * @example
79
+ * <sv-product-tour
80
+ * [(active)]="tourOpen"
81
+ * [(stepIndex)]="tourStep"
82
+ * [steps]="tourSteps"
83
+ * (complete)="onTourDone()"
84
+ * />
85
+ */
86
+ declare class SvProductTourComponent implements OnDestroy {
87
+ /** Step definitions. */
88
+ steps: _angular_core.InputSignal<TourStep[]>;
89
+ /** Visual variant. Free tier supports `'modal'` only. */
90
+ variant: _angular_core.InputSignal<"modal">;
91
+ /** Two-way: controls tour visibility. Set to `true` to launch the tour. */
92
+ active: _angular_core.ModelSignal<boolean>;
93
+ /** Two-way: current step index. */
94
+ stepIndex: _angular_core.ModelSignal<number>;
95
+ nextLabel: _angular_core.InputSignal<string>;
96
+ prevLabel: _angular_core.InputSignal<string>;
97
+ skipLabel: _angular_core.InputSignal<string>;
98
+ doneLabel: _angular_core.InputSignal<string>;
99
+ /** Show the skip / close controls. */
100
+ allowSkip: _angular_core.InputSignal<boolean>;
101
+ /** Show the "step X of N" counter. */
102
+ showStepCount: _angular_core.InputSignal<boolean>;
103
+ /** Show the dot progress indicator. */
104
+ showProgress: _angular_core.InputSignal<boolean>;
105
+ /** Fires with the new step index whenever navigation occurs. */
106
+ readonly stepChange: _angular_core.OutputEmitterRef<number>;
107
+ /** Fires when the last step is confirmed (Next on final step). */
108
+ readonly complete: _angular_core.OutputEmitterRef<void>;
109
+ /** Fires when the user explicitly skips the tour. */
110
+ readonly skip: _angular_core.OutputEmitterRef<void>;
111
+ private tourTpl;
112
+ private readonly appRef;
113
+ private portalEl;
114
+ protected portalView: EmbeddedViewRef<unknown> | null;
115
+ /** Element focused before the tour opened, restored when it closes. */
116
+ private _previouslyFocused;
117
+ readonly currentStep: _angular_core.Signal<TourStep | null>;
118
+ readonly isFirstStep: _angular_core.Signal<boolean>;
119
+ readonly isLastStep: _angular_core.Signal<boolean>;
120
+ readonly tagColorClass: _angular_core.Signal<(color: TourStep["tagColor"]) => string>;
121
+ constructor();
122
+ goNext(): void;
123
+ goPrev(): void;
124
+ goToStep(idx: number): void;
125
+ skipTour(): void;
126
+ dismiss(): void;
127
+ onKeydown(e: KeyboardEvent): void;
128
+ ngOnDestroy(): void;
129
+ private _setStep;
130
+ private _finish;
131
+ private _attachPortal;
132
+ /** First focusable element inside the given container, if any. */
133
+ private _focusableIn;
134
+ private _focusablesIn;
135
+ private _detachPortal;
136
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvProductTourComponent, never>;
137
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvProductTourComponent, "sv-product-tour", never, { "steps": { "alias": "steps"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "stepIndex": { "alias": "stepIndex"; "required": false; "isSignal": true; }; "nextLabel": { "alias": "nextLabel"; "required": false; "isSignal": true; }; "prevLabel": { "alias": "prevLabel"; "required": false; "isSignal": true; }; "skipLabel": { "alias": "skipLabel"; "required": false; "isSignal": true; }; "doneLabel": { "alias": "doneLabel"; "required": false; "isSignal": true; }; "allowSkip": { "alias": "allowSkip"; "required": false; "isSignal": true; }; "showStepCount": { "alias": "showStepCount"; "required": false; "isSignal": true; }; "showProgress": { "alias": "showProgress"; "required": false; "isSignal": true; }; }, { "active": "activeChange"; "stepIndex": "stepIndexChange"; "stepChange": "stepChange"; "complete": "complete"; "skip": "skip"; }, never, never, true, never>;
138
+ }
139
+
140
+ export { SvProductTourComponent };
141
+ export type { TourPlacement, TourStep, TourVariant };