torchlit 0.1.0 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"tour-overlay.js","sources":["../src/tour-overlay.ts"],"sourcesContent":["import { LitElement, html, css } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport type { TourService } from './tour-service.js';\nimport type { TourSnapshot, TourPlacement } from './types.js';\n\n/**\n * `<torchlit-overlay>` — Full-screen overlay that renders a spotlight cutout\n * around the current tour target, a tooltip with title / message / progress,\n * and navigation controls.\n *\n * Wire it to a `TourService` instance via the `service` property:\n *\n * ```html\n * <torchlit-overlay></torchlit-overlay>\n * ```\n * ```js\n * document.querySelector('torchlit-overlay').service = myTourService;\n * ```\n *\n * @fires tour-route-change - When a step has a `route` property, dispatched\n * with `{ route: string }` so the host app can switch views.\n *\n * @csspart backdrop - The semi-transparent overlay behind the spotlight.\n * @csspart spotlight - The cutout highlight around the target element.\n * @csspart tooltip - The floating tooltip card.\n * @csspart center-card - The centered card shown when there is no target.\n */\n@customElement('torchlit-overlay')\nexport class TorchlitOverlay extends LitElement {\n /* ── Styles ─────────────────────────────────────── */\n\n static override styles = css`\n :host {\n display: block;\n }\n\n /* ── Backdrop ──────────────────────────────────── */\n\n .tour-backdrop {\n position: fixed;\n inset: 0;\n z-index: 9998;\n pointer-events: auto;\n opacity: 0;\n transition: opacity 0.3s ease;\n }\n\n .tour-backdrop.visible {\n opacity: 1;\n }\n\n /* ── Spotlight (box-shadow cutout) ─────────────── */\n\n .tour-spotlight {\n position: fixed;\n z-index: 9999;\n border-radius: var(--tour-spotlight-radius, var(--radius-lg, 0.75rem));\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);\n transition: top 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n left 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n width 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n height 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n /* Subtle pulsing ring around spotlight */\n .tour-spotlight::after {\n content: '';\n position: absolute;\n inset: -4px;\n border-radius: inherit;\n border: 2px solid var(--tour-primary, var(--primary, oklch(0.65 0.17 220)));\n opacity: 0.5;\n animation: spotlightPulse 2s ease-in-out infinite;\n }\n\n @keyframes spotlightPulse {\n 0%, 100% { opacity: 0.3; transform: scale(1); }\n 50% { opacity: 0.7; transform: scale(1.01); }\n }\n\n /* ── Tooltip ───────────────────────────────────── */\n\n .tour-tooltip {\n position: fixed;\n z-index: 10000;\n width: 320px;\n background: var(--tour-card, var(--card, #fff));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n border-radius: var(--tour-tooltip-radius, var(--radius-lg, 0.75rem));\n box-shadow: 0 20px 40px -8px rgba(0, 0, 0, 0.2),\n 0 8px 16px -4px rgba(0, 0, 0, 0.1);\n padding: 1.25rem;\n pointer-events: auto;\n opacity: 0;\n transform: translateY(8px) scale(0.96);\n transition: opacity 0.25s ease, transform 0.25s ease,\n top 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n left 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .tour-tooltip.visible {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n\n /* Arrow */\n .tour-arrow {\n position: absolute;\n width: 12px;\n height: 12px;\n background: var(--tour-card, var(--card, #fff));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n transform: rotate(45deg);\n }\n\n .tour-arrow.arrow-top {\n bottom: -7px;\n left: 50%;\n margin-left: -6px;\n border-top: none;\n border-left: none;\n }\n\n .tour-arrow.arrow-bottom {\n top: -7px;\n left: 50%;\n margin-left: -6px;\n border-bottom: none;\n border-right: none;\n }\n\n .tour-arrow.arrow-left {\n right: -7px;\n top: 50%;\n margin-top: -6px;\n border-bottom: none;\n border-left: none;\n }\n\n .tour-arrow.arrow-right {\n left: -7px;\n top: 50%;\n margin-top: -6px;\n border-top: none;\n border-right: none;\n }\n\n /* ── Tooltip content ──────────────────────────── */\n\n .tour-step-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--tour-primary, var(--primary, oklch(0.65 0.17 220)));\n margin-bottom: 0.5rem;\n }\n\n .tour-title {\n margin: 0 0 0.375rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--tour-foreground, var(--foreground, #1a1a1a));\n line-height: 1.3;\n }\n\n .tour-message {\n margin: 0 0 1rem;\n font-size: 0.8125rem;\n color: var(--tour-muted-foreground, var(--muted-foreground, #737373));\n line-height: 1.55;\n }\n\n /* ── Progress dots ────────────────────────────── */\n\n .tour-progress {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n margin-bottom: 1rem;\n }\n\n .tour-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--tour-muted, var(--muted, #e5e5e5));\n transition: background 0.2s, transform 0.2s;\n }\n\n .tour-dot.active {\n background: var(--tour-primary, var(--primary, oklch(0.65 0.17 220)));\n transform: scale(1.3);\n }\n\n .tour-dot.completed {\n background: var(--tour-primary, var(--primary, oklch(0.65 0.17 220)));\n opacity: 0.5;\n }\n\n /* ── Footer buttons ───────────────────────────── */\n\n .tour-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .tour-skip {\n font-size: 0.75rem;\n color: var(--tour-muted-foreground, var(--muted-foreground, #737373));\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.25rem 0;\n transition: color 0.15s;\n }\n\n .tour-skip:hover {\n color: var(--tour-foreground, var(--foreground, #1a1a1a));\n }\n\n .tour-nav {\n display: flex;\n gap: 0.5rem;\n }\n\n .tour-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.4rem 0.875rem;\n font-size: 0.8125rem;\n font-weight: 500;\n border-radius: var(--tour-btn-radius, var(--radius-md, 0.5rem));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n background: var(--tour-background, var(--background, #fff));\n color: var(--tour-foreground, var(--foreground, #1a1a1a));\n cursor: pointer;\n transition: all 0.15s;\n }\n\n .tour-btn:hover {\n background: var(--tour-muted, var(--muted, #f5f5f5));\n }\n\n .tour-btn.primary {\n background: var(--tour-primary, var(--primary, oklch(0.65 0.17 220)));\n color: var(--tour-primary-foreground, var(--primary-foreground, #fff));\n border-color: var(--tour-primary, var(--primary, oklch(0.65 0.17 220)));\n }\n\n .tour-btn.primary:hover {\n opacity: 0.9;\n }\n\n .tour-btn svg {\n width: 14px;\n height: 14px;\n }\n\n /* ── Welcome / no-target step ─────────────────── */\n\n .tour-center-card {\n position: fixed;\n z-index: 10000;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.96);\n width: 400px;\n max-width: calc(100vw - 2rem);\n background: var(--tour-card, var(--card, #fff));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n border-radius: var(--tour-card-radius, var(--radius-xl, 1rem));\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n padding: 2rem;\n text-align: center;\n pointer-events: auto;\n opacity: 0;\n transition: opacity 0.3s ease, transform 0.3s ease;\n }\n\n .tour-center-card.visible {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n\n .tour-center-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 1rem;\n background: var(--tour-primary, var(--primary, oklch(0.65 0.17 220)));\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--tour-primary-foreground, var(--primary-foreground, #fff));\n }\n `;\n\n /* ── Properties ──────────────────────────────────── */\n\n /**\n * The `TourService` instance this overlay subscribes to.\n * Must be set before the overlay will render anything.\n */\n @property({ attribute: false })\n service!: TourService;\n\n @state() private snapshot: TourSnapshot | null = null;\n @state() private visible = false;\n\n private unsubscribe?: () => void;\n\n /* ── Lifecycle ──────────────────────────────────── */\n\n override connectedCallback() {\n super.connectedCallback();\n if (this.service) {\n this.attachService();\n }\n window.addEventListener('resize', this.handleResize);\n window.addEventListener('keydown', this.handleKeydown);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.unsubscribe?.();\n window.removeEventListener('resize', this.handleResize);\n window.removeEventListener('keydown', this.handleKeydown);\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('service') && this.service) {\n this.unsubscribe?.();\n this.attachService();\n }\n }\n\n private attachService() {\n this.unsubscribe = this.service.subscribe(snap => this.handleTourChange(snap));\n }\n\n /* ── Tour state handler ─────────────────────────── */\n\n private async handleTourChange(snapshot: TourSnapshot | null) {\n if (!snapshot) {\n // Tour ended — fade out\n this.visible = false;\n setTimeout(() => { this.snapshot = null; }, 300);\n return;\n }\n\n // Run beforeShow hook if present\n if (snapshot.step.beforeShow) {\n try {\n await snapshot.step.beforeShow();\n } catch (err) {\n console.error('[torchlit] beforeShow hook failed:', err);\n }\n }\n\n // Emit route-change event if the step has a route\n if (snapshot.step.route) {\n this.dispatchEvent(new CustomEvent('tour-route-change', {\n detail: { route: snapshot.step.route },\n bubbles: true,\n composed: true,\n }));\n // Give the view transition time to render, then re-resolve the target\n await new Promise(r => setTimeout(r, 350));\n this.snapshot = this.service.getSnapshot();\n } else {\n this.snapshot = snapshot;\n }\n\n this.scrollTargetIntoView();\n requestAnimationFrame(() => { this.visible = true; });\n }\n\n private scrollTargetIntoView() {\n if (this.snapshot?.targetElement) {\n this.snapshot.targetElement.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n inline: 'nearest',\n });\n // Recalculate rect after scroll settles\n setTimeout(() => {\n if (this.service) {\n this.snapshot = this.service.getSnapshot();\n }\n }, 400);\n }\n }\n\n /* ── Event handlers ─────────────────────────────── */\n\n private handleResize = () => {\n if (this.snapshot && this.service) {\n this.snapshot = this.service.getSnapshot();\n }\n };\n\n private handleKeydown = (e: KeyboardEvent) => {\n if (!this.snapshot || !this.service) return;\n if (e.key === 'Escape') {\n e.preventDefault();\n this.service.skipTour();\n } else if (e.key === 'ArrowRight' || e.key === 'Enter') {\n e.preventDefault();\n this.service.nextStep();\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n this.service.prevStep();\n }\n };\n\n private handleBackdropClick = () => {\n this.service?.skipTour();\n };\n\n /* ── Tooltip positioning ────────────────────────── */\n\n private getTooltipPosition(rect: DOMRect, placement: TourPlacement): { top: number; left: number } {\n const PADDING = this.service?.spotlightPadding ?? 10;\n const GAP = 16;\n const TOOLTIP_W = 320;\n\n switch (placement) {\n case 'right':\n return {\n top: rect.top + rect.height / 2 - 80,\n left: rect.right + PADDING + GAP,\n };\n case 'left':\n return {\n top: rect.top + rect.height / 2 - 80,\n left: rect.left - PADDING - GAP - TOOLTIP_W,\n };\n case 'bottom':\n return {\n top: rect.bottom + PADDING + GAP,\n left: rect.left + rect.width / 2 - TOOLTIP_W / 2,\n };\n case 'top':\n return {\n top: rect.top - PADDING - GAP - 180,\n left: rect.left + rect.width / 2 - TOOLTIP_W / 2,\n };\n default:\n return { top: rect.bottom + GAP, left: rect.left };\n }\n }\n\n private clampToViewport(pos: { top: number; left: number }): { top: number; left: number } {\n const MARGIN = 16;\n const TOOLTIP_W = 320;\n return {\n top: Math.max(MARGIN, Math.min(pos.top, window.innerHeight - 250)),\n left: Math.max(MARGIN, Math.min(pos.left, window.innerWidth - TOOLTIP_W - MARGIN)),\n };\n }\n\n private getArrowClass(placement: TourPlacement): string {\n switch (placement) {\n case 'right': return 'arrow-right';\n case 'left': return 'arrow-left';\n case 'bottom': return 'arrow-bottom';\n case 'top': return 'arrow-top';\n default: return 'arrow-bottom';\n }\n }\n\n /* ── Render ─────────────────────────────────────── */\n\n override render() {\n if (!this.snapshot) return html``;\n\n const { step, stepIndex, totalSteps, targetRect } = this.snapshot;\n\n // No target found — show centered card\n if (!targetRect) {\n return this.renderCenteredStep(step, stepIndex, totalSteps);\n }\n\n const PADDING = this.service?.spotlightPadding ?? 10;\n const spotlightStyle = `\n top: ${targetRect.top - PADDING}px;\n left: ${targetRect.left - PADDING}px;\n width: ${targetRect.width + PADDING * 2}px;\n height: ${targetRect.height + PADDING * 2}px;\n `;\n\n const tooltipPos = this.clampToViewport(\n this.getTooltipPosition(targetRect, step.placement),\n );\n const tooltipStyle = `top: ${tooltipPos.top}px; left: ${tooltipPos.left}px;`;\n\n return html`\n <div\n class=\"tour-backdrop ${this.visible ? 'visible' : ''}\"\n part=\"backdrop\"\n @click=${this.handleBackdropClick}\n ></div>\n\n <div class=\"tour-spotlight\" part=\"spotlight\" style=${spotlightStyle}></div>\n\n <div class=\"tour-tooltip ${this.visible ? 'visible' : ''}\" part=\"tooltip\" style=${tooltipStyle}>\n <div class=\"tour-arrow ${this.getArrowClass(step.placement)}\"></div>\n\n <div class=\"tour-step-badge\">\n <svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"></path>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"></line>\n </svg>\n Step ${stepIndex + 1} of ${totalSteps}\n </div>\n\n <h3 class=\"tour-title\">${step.title}</h3>\n <p class=\"tour-message\">${step.message}</p>\n\n ${this.renderProgressDots(stepIndex, totalSteps)}\n\n <div class=\"tour-footer\">\n <button class=\"tour-skip\" @click=${() => this.service.skipTour()}>\n Skip tour\n </button>\n <div class=\"tour-nav\">\n ${stepIndex > 0 ? html`\n <button class=\"tour-btn\" @click=${() => this.service.prevStep()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"15 18 9 12 15 6\"></polyline>\n </svg>\n Back\n </button>\n ` : ''}\n <button class=\"tour-btn primary\" @click=${() => this.service.nextStep()}>\n ${stepIndex === totalSteps - 1 ? 'Finish' : 'Next'}\n ${stepIndex < totalSteps - 1 ? html`\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 18 15 12 9 6\"></polyline>\n </svg>\n ` : html`\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n `}\n </button>\n </div>\n </div>\n </div>\n `;\n }\n\n private renderProgressDots(current: number, total: number) {\n if (total <= 1) return html``;\n return html`\n <div class=\"tour-progress\">\n ${Array.from({ length: total }, (_, i) => html`\n <div class=\"tour-dot ${i === current ? 'active' : i < current ? 'completed' : ''}\"></div>\n `)}\n </div>\n `;\n }\n\n private renderCenteredStep(step: { title: string; message: string }, stepIndex: number, totalSteps: number) {\n return html`\n <div\n class=\"tour-backdrop ${this.visible ? 'visible' : ''}\"\n part=\"backdrop\"\n @click=${this.handleBackdropClick}\n ></div>\n\n <div class=\"tour-center-card ${this.visible ? 'visible' : ''}\" part=\"center-card\">\n <div class=\"tour-center-icon\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"></path>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"></line>\n </svg>\n </div>\n\n <h3 class=\"tour-title\">${step.title}</h3>\n <p class=\"tour-message\">${step.message}</p>\n\n ${this.renderProgressDots(stepIndex, totalSteps)}\n\n <div class=\"tour-footer\">\n <button class=\"tour-skip\" @click=${() => this.service.skipTour()}>\n Skip tour\n </button>\n <div class=\"tour-nav\">\n ${stepIndex > 0 ? html`\n <button class=\"tour-btn\" @click=${() => this.service.prevStep()}>Back</button>\n ` : ''}\n <button class=\"tour-btn primary\" @click=${() => this.service.nextStep()}>\n ${stepIndex === totalSteps - 1 ? \"Let's go!\" : 'Next'}\n ${stepIndex < totalSteps - 1 ? html`\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"9 18 15 12 9 6\"></polyline>\n </svg>\n ` : ''}\n </button>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'torchlit-overlay': TorchlitOverlay;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tour-overlay.js","sources":["../src/tour-overlay.ts"],"sourcesContent":["import { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { keyed } from 'lit/directives/keyed.js';\nimport { deepQuery } from './utils/deep-query.js';\nimport type { TourService } from './tour-service.js';\nimport type { TourStep, TourSnapshot, TourPlacement } from './types.js';\n\n// ── Constants ────────────────────────────────────────────────────────────────\n\nconst TOOLTIP_W = 320;\nconst TOOLTIP_H_MAX = 270; // conservative max height for clamp & flip checks\nconst GAP = 16;\nconst VIEWPORT_MARGIN = 24;\nconst MUTATION_TIMEOUT = 3000;\n\n/**\n * `<torchlit-overlay>` — Full-screen overlay that renders a spotlight cutout\n * around the current tour target, a tooltip with title / message / progress,\n * and navigation controls.\n *\n * Wire it to a `TourService` instance via the `service` property:\n *\n * ```html\n * <torchlit-overlay></torchlit-overlay>\n * ```\n * ```js\n * document.querySelector('torchlit-overlay').service = myTourService;\n * ```\n *\n * @fires tour-route-change - When a step has a `route` property, dispatched\n * with `{ route: string }` so the host app can switch views.\n *\n * @csspart backdrop - The semi-transparent overlay behind the spotlight.\n * @csspart spotlight - The cutout highlight around the target element.\n * @csspart tooltip - The floating tooltip card.\n * @csspart center-card - The centered card shown when there is no target.\n */\n@customElement('torchlit-overlay')\nexport class TorchlitOverlay extends LitElement {\n /* ── Styles ─────────────────────────────────────── */\n\n static override styles = css`\n :host {\n display: block;\n }\n\n /* ── Visually hidden (sr-only) ─────────────────── */\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* ── Backdrop ──────────────────────────────────── */\n\n .tour-backdrop {\n position: fixed;\n inset: 0;\n z-index: 9998;\n pointer-events: auto;\n opacity: 0;\n transition: opacity 0.3s ease;\n }\n\n .tour-backdrop.visible {\n opacity: 1;\n }\n\n /* ── Spotlight (box-shadow cutout) ─────────────── */\n\n .tour-spotlight {\n position: fixed;\n z-index: 9999;\n border-radius: var(--tour-spotlight-radius, var(--radius-lg, 0.75rem));\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);\n transition: top 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n left 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n width 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n height 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n /* Subtle pulsing ring around spotlight */\n .tour-spotlight::after {\n content: '';\n position: absolute;\n inset: -4px;\n border-radius: inherit;\n border: 2px solid var(--tour-primary, var(--primary, #F26122));\n opacity: 0.5;\n animation: spotlightPulse 2s ease-in-out infinite;\n }\n\n @keyframes spotlightPulse {\n 0%, 100% { opacity: 0.3; transform: scale(1); }\n 50% { opacity: 0.7; transform: scale(1.01); }\n }\n\n /* ── Tooltip ───────────────────────────────────── */\n\n .tour-tooltip {\n position: fixed;\n z-index: 10000;\n box-sizing: border-box;\n width: 320px;\n background: var(--tour-card, var(--card, #fff));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n border-radius: var(--tour-tooltip-radius, var(--radius-lg, 0.75rem));\n box-shadow: 0 20px 40px -8px rgba(0, 0, 0, 0.2),\n 0 8px 16px -4px rgba(0, 0, 0, 0.1);\n padding: 1.25rem;\n pointer-events: auto;\n opacity: 0;\n transform: translateY(8px) scale(0.96);\n transition: opacity 0.25s ease, transform 0.25s ease,\n top 0.35s cubic-bezier(0.4, 0, 0.2, 1),\n left 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .tour-tooltip:focus {\n outline: none;\n }\n\n .tour-tooltip.visible {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n\n /* Arrow — position along edge is set via inline --arrow-offset */\n .tour-arrow {\n position: absolute;\n width: 12px;\n height: 12px;\n background: var(--tour-card, var(--card, #fff));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n transform: rotate(45deg);\n }\n\n /* tooltip is above target → arrow at bottom of tooltip pointing down */\n .tour-arrow.arrow-top {\n bottom: -7px;\n left: var(--arrow-offset, 50%);\n margin-left: -6px;\n border-top: none;\n border-left: none;\n }\n\n /* tooltip is below target → arrow at top of tooltip pointing up */\n .tour-arrow.arrow-bottom {\n top: -7px;\n left: var(--arrow-offset, 50%);\n margin-left: -6px;\n border-bottom: none;\n border-right: none;\n }\n\n /* tooltip is right of target → arrow on left edge pointing left */\n .tour-arrow.arrow-left {\n right: -7px;\n top: var(--arrow-offset, 50%);\n margin-top: -6px;\n border-bottom: none;\n border-left: none;\n }\n\n /* tooltip is left of target → arrow on right edge pointing right */\n .tour-arrow.arrow-right {\n left: -7px;\n top: var(--arrow-offset, 50%);\n margin-top: -6px;\n border-top: none;\n border-right: none;\n }\n\n /* ── Tooltip content ──────────────────────────── */\n\n .tour-step-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--tour-primary, var(--primary, #F26122));\n margin-bottom: 0.5rem;\n }\n\n .tour-title {\n margin: 0 0 0.375rem;\n font-size: 1rem;\n font-weight: 600;\n color: var(--tour-foreground, var(--foreground, #1a1a1a));\n line-height: 1.3;\n }\n\n .tour-message {\n margin: 0 0 1rem;\n font-size: 0.8125rem;\n color: var(--tour-muted-foreground, var(--muted-foreground, #737373));\n line-height: 1.55;\n }\n\n /* ── Progress dots ────────────────────────────── */\n\n .tour-progress {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n margin-bottom: 1rem;\n }\n\n .tour-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--tour-muted, var(--muted, #e5e5e5));\n transition: background 0.2s, transform 0.2s;\n }\n\n .tour-dot.active {\n background: var(--tour-primary, var(--primary, #F26122));\n transform: scale(1.3);\n }\n\n .tour-dot.completed {\n background: var(--tour-primary, var(--primary, #F26122));\n opacity: 0.5;\n }\n\n /* ── Auto-advance progress bar ────────────────── */\n\n .tour-auto-progress {\n position: absolute;\n bottom: 0;\n left: 0;\n max-width: 100%;\n height: 3px;\n background: var(--tour-primary, var(--primary, #F26122));\n opacity: 0.7;\n border-radius: 0 0 var(--tour-tooltip-radius, var(--radius-lg, 0.75rem)) var(--tour-tooltip-radius, var(--radius-lg, 0.75rem));\n }\n\n @keyframes autoAdvanceFill {\n from { width: 0%; }\n to { width: 100%; }\n }\n\n /* ── Footer buttons ───────────────────────────── */\n\n .tour-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .tour-skip {\n font-size: 0.75rem;\n color: var(--tour-muted-foreground, var(--muted-foreground, #737373));\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.25rem 0;\n transition: color 0.15s;\n }\n\n .tour-skip:hover {\n color: var(--tour-foreground, var(--foreground, #1a1a1a));\n }\n\n .tour-nav {\n display: flex;\n gap: 0.5rem;\n }\n\n .tour-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.4rem 0.875rem;\n font-size: 0.8125rem;\n font-weight: 500;\n border-radius: var(--tour-btn-radius, var(--radius-md, 0.5rem));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n background: var(--tour-background, var(--background, #fff));\n color: var(--tour-foreground, var(--foreground, #1a1a1a));\n cursor: pointer;\n transition: all 0.15s;\n }\n\n .tour-btn:hover {\n background: var(--tour-muted, var(--muted, #f5f5f5));\n }\n\n .tour-btn:focus-visible {\n outline: 2px solid var(--tour-primary, var(--primary, #F26122));\n outline-offset: 2px;\n }\n\n .tour-btn.primary {\n background: var(--tour-primary, var(--primary, #F26122));\n color: var(--tour-primary-foreground, var(--primary-foreground, #fff));\n border-color: var(--tour-primary, var(--primary, #F26122));\n }\n\n .tour-btn.primary:hover {\n opacity: 0.9;\n }\n\n .tour-btn svg {\n width: 14px;\n height: 14px;\n }\n\n /* ── Welcome / no-target step ─────────────────── */\n\n .tour-center-card {\n position: fixed;\n z-index: 10000;\n box-sizing: border-box;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) scale(0.96);\n width: 400px;\n max-width: calc(100vw - 2rem);\n background: var(--tour-card, var(--card, #fff));\n border: 1px solid var(--tour-border, var(--border, #e5e5e5));\n border-radius: var(--tour-card-radius, var(--radius-xl, 1rem));\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n padding: 2rem;\n text-align: center;\n pointer-events: auto;\n opacity: 0;\n transition: opacity 0.3s ease, transform 0.3s ease;\n }\n\n .tour-center-card:focus {\n outline: none;\n }\n\n .tour-center-card.visible {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n\n .tour-center-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 1rem;\n background: var(--tour-primary, var(--primary, #F26122));\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--tour-primary-foreground, var(--primary-foreground, #fff));\n }\n `;\n\n /* ── Properties ──────────────────────────────────── */\n\n /**\n * The `TourService` instance this overlay subscribes to.\n * Must be set before the overlay will render anything.\n */\n @property({ attribute: false })\n service!: TourService;\n\n @state() private snapshot: TourSnapshot | null = null;\n @state() private visible = false;\n\n private unsubscribe?: () => void;\n private previouslyFocused: HTMLElement | null = null;\n private autoAdvanceTimer: ReturnType<typeof setTimeout> | null = null;\n private lastResolvedPlacement: TourPlacement = 'bottom';\n private scrollRafId = 0;\n private savedScrollY = 0;\n private activeTourId: string | null = null;\n\n /* ── Lifecycle ──────────────────────────────────── */\n\n override connectedCallback() {\n super.connectedCallback();\n if (this.service) {\n this.attachService();\n }\n window.addEventListener('resize', this.handleResize);\n window.addEventListener('scroll', this.handleScroll, true);\n window.addEventListener('keydown', this.handleKeydown);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this.unsubscribe?.();\n this.clearAutoAdvance();\n if (this.scrollRafId) cancelAnimationFrame(this.scrollRafId);\n window.removeEventListener('resize', this.handleResize);\n window.removeEventListener('scroll', this.handleScroll, true);\n window.removeEventListener('keydown', this.handleKeydown);\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('service') && this.service) {\n this.unsubscribe?.();\n this.attachService();\n }\n\n if (this.visible && this.snapshot) {\n // Measure the actual tooltip and correct position for 'top' placement\n this.adjustTooltipPosition();\n\n // Focus the dialog container\n this.updateComplete.then(() => {\n const dialog = this.shadowRoot?.querySelector<HTMLElement>(\n '.tour-tooltip, .tour-center-card',\n );\n dialog?.focus();\n });\n }\n }\n\n /**\n * After rendering, measure the tooltip's actual height and correct\n * its position for 'top' placement (the only one that depends on\n * tooltip height). This eliminates hardcoded height estimates.\n */\n private adjustTooltipPosition() {\n if (this.lastResolvedPlacement !== 'top') return;\n\n const tooltip = this.shadowRoot?.querySelector<HTMLElement>('.tour-tooltip');\n const targetRect = this.snapshot?.targetRect;\n if (!tooltip || !targetRect) return;\n\n const PADDING = this.service?.spotlightPadding ?? 10;\n const actualHeight = tooltip.getBoundingClientRect().height;\n const correctTop = targetRect.top - PADDING - GAP - actualHeight;\n const clampedTop = Math.max(VIEWPORT_MARGIN, correctTop);\n\n tooltip.style.top = `${clampedTop}px`;\n }\n\n private attachService() {\n this.unsubscribe = this.service.subscribe(snap => this.handleTourChange(snap));\n }\n\n /* ── Auto-advance ───────────────────────────────── */\n\n private clearAutoAdvance() {\n if (this.autoAdvanceTimer !== null) {\n clearTimeout(this.autoAdvanceTimer);\n this.autoAdvanceTimer = null;\n }\n }\n\n private startAutoAdvance(ms: number) {\n this.clearAutoAdvance();\n this.autoAdvanceTimer = setTimeout(() => {\n this.autoAdvanceTimer = null;\n this.service?.nextStep();\n }, ms);\n }\n\n /* ── MutationObserver target resolution ─────────── */\n\n /**\n * Wait for a target element to appear in the DOM using a MutationObserver.\n * Resolves as soon as `deepQuery` finds the target, or after `timeout` ms.\n */\n private waitForTarget(\n targetId: string,\n timeout = MUTATION_TIMEOUT,\n ): Promise<Element | null> {\n const attr = this.service?.targetAttribute ?? 'data-tour-id';\n const selector = `[${attr}=\"${targetId}\"]`;\n\n // Fast path — already in the DOM\n const existing = deepQuery(selector, document.body);\n if (existing) return Promise.resolve(existing);\n\n return new Promise<Element | null>(resolve => {\n let resolved = false;\n const observer = new MutationObserver(() => {\n const el = deepQuery(selector, document.body);\n if (el) {\n resolved = true;\n observer.disconnect();\n resolve(el);\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n setTimeout(() => {\n if (!resolved) {\n observer.disconnect();\n resolve(deepQuery(selector, document.body));\n }\n }, timeout);\n });\n }\n\n /* ── Tour state handler ─────────────────────────── */\n\n private async handleTourChange(snapshot: TourSnapshot | null) {\n this.clearAutoAdvance();\n\n if (!snapshot) {\n // Tour ended — fade out, restore focus, and restore scroll\n const endingTourId = this.activeTourId;\n this.visible = false;\n this.activeTourId = null;\n setTimeout(() => {\n this.snapshot = null;\n if (this.previouslyFocused) {\n this.previouslyFocused.focus();\n this.previouslyFocused = null;\n }\n // Scroll restore\n const tour = endingTourId ? this.service?.getTour(endingTourId) : null;\n const scrollMode = tour?.onEndScroll ?? 'restore';\n if (scrollMode === 'restore') {\n window.scrollTo({ top: this.savedScrollY, behavior: 'smooth' });\n } else if (scrollMode === 'top') {\n window.scrollTo({ top: 0, behavior: 'smooth' });\n }\n }, 300);\n return;\n }\n\n // Save the element that had focus and scroll position before the tour started\n if (!this.snapshot) {\n if (document.activeElement instanceof HTMLElement) {\n this.previouslyFocused = document.activeElement;\n }\n this.savedScrollY = window.scrollY;\n this.activeTourId = snapshot.tourId;\n }\n\n // Run beforeShow hook if present\n if (snapshot.step.beforeShow) {\n try {\n await snapshot.step.beforeShow();\n } catch (err) {\n console.error('[torchlit] beforeShow hook failed:', err);\n }\n }\n\n // Emit route-change event if the step has a route\n if (snapshot.step.route) {\n this.dispatchEvent(new CustomEvent('tour-route-change', {\n detail: { route: snapshot.step.route },\n bubbles: true,\n composed: true,\n }));\n }\n\n // Wait for the target element to appear (handles lazy rendering / route transitions)\n if (snapshot.step.target && snapshot.step.target !== '_none_') {\n await this.waitForTarget(snapshot.step.target);\n this.snapshot = this.service.getSnapshot();\n } else {\n this.snapshot = snapshot;\n }\n\n // Scroll into view if needed, then show\n if (this.snapshot?.targetElement) {\n const rect = this.snapshot.targetElement.getBoundingClientRect();\n const vh = window.innerHeight;\n const isTall = rect.height > vh * 0.6;\n // For tall targets, only require the top to be visible (user can scroll further).\n // For normal targets, require the whole element in view.\n const inView = isTall\n ? rect.top >= 0 && rect.top < vh * 0.5\n : rect.top >= 0 && rect.bottom <= vh && rect.left >= 0 && rect.right <= window.innerWidth;\n\n if (!inView) {\n await this.scrollAndSettle(this.snapshot.targetElement);\n // Recalculate rect at the post-scroll position\n this.snapshot = this.service.getSnapshot();\n }\n }\n\n requestAnimationFrame(() => {\n this.visible = true;\n // Start auto-advance timer if configured\n if (this.snapshot?.step.autoAdvance) {\n this.startAutoAdvance(this.snapshot.step.autoAdvance);\n }\n });\n }\n\n /**\n * Scroll an element into view and wait for the scroll to finish.\n * For tall elements (> 60% of viewport), scrolls to the top so the user\n * sees the start of the element plus the tooltip. For smaller elements,\n * centers them in the viewport.\n */\n private scrollAndSettle(el: Element): Promise<void> {\n const vh = window.innerHeight;\n const isTall = el.getBoundingClientRect().height > vh * 0.6;\n el.scrollIntoView({ behavior: 'smooth', block: isTall ? 'start' : 'center', inline: 'nearest' });\n\n return new Promise(resolve => {\n let lastTop = el.getBoundingClientRect().top;\n let stableFrames = 0;\n let rafId = 0;\n const maxWait = setTimeout(() => { cancelAnimationFrame(rafId); resolve(); }, 1500);\n\n const poll = () => {\n const top = el.getBoundingClientRect().top;\n if (Math.abs(top - lastTop) < 1) {\n stableFrames++;\n } else {\n stableFrames = 0;\n }\n lastTop = top;\n\n // Consider settled after 3 consecutive stable frames (~50ms)\n if (stableFrames >= 3) {\n clearTimeout(maxWait);\n resolve();\n } else {\n rafId = requestAnimationFrame(poll);\n }\n };\n\n rafId = requestAnimationFrame(poll);\n });\n }\n\n /* ── Event handlers ─────────────────────────────── */\n\n private handleResize = () => {\n if (this.snapshot && this.service) {\n this.snapshot = this.service.getSnapshot();\n }\n };\n\n /** Throttled scroll handler — refreshes the snapshot once per frame. */\n private handleScroll = () => {\n if (!this.snapshot || !this.service || this.scrollRafId) return;\n this.scrollRafId = requestAnimationFrame(() => {\n this.scrollRafId = 0;\n if (this.snapshot && this.service) {\n this.snapshot = this.service.getSnapshot();\n }\n });\n };\n\n private handleKeydown = (e: KeyboardEvent) => {\n if (!this.snapshot || !this.service) return;\n\n if (e.key === 'Escape') {\n e.preventDefault();\n this.clearAutoAdvance();\n this.service.skipTour();\n } else if (e.key === 'ArrowRight' || e.key === 'Enter') {\n e.preventDefault();\n this.clearAutoAdvance();\n this.service.nextStep();\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n this.clearAutoAdvance();\n this.service.prevStep();\n } else if (e.key === 'Tab') {\n // Focus trap — keep Tab within the tooltip\n this.trapFocus(e);\n }\n };\n\n private handleBackdropClick = () => {\n this.clearAutoAdvance();\n this.service?.skipTour();\n };\n\n /* ── Focus trap ─────────────────────────────────── */\n\n private trapFocus(e: KeyboardEvent) {\n const container = this.shadowRoot?.querySelector<HTMLElement>(\n '.tour-tooltip, .tour-center-card',\n );\n if (!container) return;\n\n const focusable = container.querySelectorAll<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])',\n );\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (this.shadowRoot?.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (this.shadowRoot?.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n\n /* ── Smart auto-positioning ─────────────────────── */\n\n /**\n * Determine the best placement for the tooltip, flipping when the preferred\n * placement would clip the viewport. Tries: preferred → opposite → perpendicular.\n */\n private bestPlacement(rect: DOMRect, preferred: TourPlacement): TourPlacement {\n const PADDING = this.service?.spotlightPadding ?? 10;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n const fits = (p: TourPlacement): boolean => {\n switch (p) {\n case 'bottom':\n return rect.bottom + PADDING + GAP + TOOLTIP_H_MAX < vh;\n case 'top':\n return rect.top - PADDING - GAP - TOOLTIP_H_MAX > 0;\n case 'right':\n return rect.right + PADDING + GAP + TOOLTIP_W < vw;\n case 'left':\n return rect.left - PADDING - GAP - TOOLTIP_W > 0;\n }\n };\n\n const opposite: Record<TourPlacement, TourPlacement> = {\n top: 'bottom', bottom: 'top', left: 'right', right: 'left',\n };\n\n const perpendicular: Record<TourPlacement, [TourPlacement, TourPlacement]> = {\n top: ['left', 'right'], bottom: ['left', 'right'],\n left: ['top', 'bottom'], right: ['top', 'bottom'],\n };\n\n if (fits(preferred)) return preferred;\n if (fits(opposite[preferred])) return opposite[preferred];\n for (const p of perpendicular[preferred]) {\n if (fits(p)) return p;\n }\n // Nothing fits perfectly — keep preferred, clampToViewport will save us\n return preferred;\n }\n\n /* ── Tooltip positioning ────────────────────────── */\n\n private getTooltipPosition(rect: DOMRect, placement: TourPlacement): { top: number; left: number } {\n const PADDING = this.service?.spotlightPadding ?? 10;\n const vh = window.innerHeight;\n\n // For tall targets, use the visible center rather than the absolute center.\n // This keeps the tooltip near the portion of the target the user can actually see.\n const visibleTop = Math.max(0, rect.top);\n const visibleBottom = Math.min(vh, rect.bottom);\n const visibleCenterY = (visibleTop + visibleBottom) / 2;\n\n switch (placement) {\n case 'right':\n return {\n top: visibleCenterY - 80,\n left: rect.right + PADDING + GAP,\n };\n case 'left':\n return {\n top: visibleCenterY - 80,\n left: rect.left - PADDING - GAP - TOOLTIP_W,\n };\n case 'bottom':\n return {\n top: rect.bottom + PADDING + GAP,\n left: rect.left + rect.width / 2 - TOOLTIP_W / 2,\n };\n case 'top':\n // Initial estimate — corrected after render in adjustTooltipPosition()\n return {\n top: rect.top - PADDING - GAP,\n left: rect.left + rect.width / 2 - TOOLTIP_W / 2,\n };\n default:\n return { top: rect.bottom + GAP, left: rect.left };\n }\n }\n\n private clampToViewport(pos: { top: number; left: number }): { top: number; left: number } {\n return {\n top: Math.max(VIEWPORT_MARGIN, Math.min(pos.top, window.innerHeight - TOOLTIP_H_MAX - VIEWPORT_MARGIN)),\n left: Math.max(VIEWPORT_MARGIN, Math.min(pos.left, window.innerWidth - TOOLTIP_W - VIEWPORT_MARGIN)),\n };\n }\n\n private getArrowClass(placement: TourPlacement): string {\n switch (placement) {\n case 'right': return 'arrow-right';\n case 'left': return 'arrow-left';\n case 'bottom': return 'arrow-bottom';\n case 'top': return 'arrow-top';\n default: return 'arrow-bottom';\n }\n }\n\n /**\n * Compute the arrow's offset along the tooltip edge so it points at\n * the center of the target element, clamped to stay within the tooltip.\n */\n private getArrowOffset(\n targetRect: DOMRect,\n tooltipPos: { top: number; left: number },\n placement: TourPlacement,\n ): string {\n const ARROW_SIZE = 12;\n const MIN = ARROW_SIZE + 8;\n\n if (placement === 'top' || placement === 'bottom') {\n // Horizontal offset\n const targetCenterX = targetRect.left + targetRect.width / 2;\n const offset = targetCenterX - tooltipPos.left;\n const clamped = Math.max(MIN, Math.min(offset, TOOLTIP_W - MIN));\n return `${clamped}px`;\n }\n\n // Vertical offset (left / right placement) — use visible center for tall targets\n const visibleTop = Math.max(0, targetRect.top);\n const visibleBottom = Math.min(window.innerHeight, targetRect.bottom);\n const targetCenterY = (visibleTop + visibleBottom) / 2;\n const offset = targetCenterY - tooltipPos.top;\n const clamped = Math.max(MIN, Math.min(offset, TOOLTIP_H_MAX - MIN));\n return `${clamped}px`;\n }\n\n /* ── Render ─────────────────────────────────────── */\n\n override render() {\n if (!this.snapshot) return html``;\n\n const { step, stepIndex, totalSteps, targetRect } = this.snapshot;\n\n // No target found — show centered card\n if (!targetRect) {\n return this.renderCenteredStep(step, stepIndex, totalSteps);\n }\n\n const PADDING = this.service?.spotlightPadding ?? 10;\n\n // Per-step spotlight border-radius override\n const spotlightRadius = step.spotlightBorderRadius\n ? `border-radius: ${step.spotlightBorderRadius};`\n : '';\n\n const spotlightStyle = `\n top: ${targetRect.top - PADDING}px;\n left: ${targetRect.left - PADDING}px;\n width: ${targetRect.width + PADDING * 2}px;\n height: ${targetRect.height + PADDING * 2}px;\n ${spotlightRadius}\n `;\n\n // Smart placement — flip if the preferred side clips\n const resolved = this.bestPlacement(targetRect, step.placement);\n this.lastResolvedPlacement = resolved;\n\n const tooltipPos = this.clampToViewport(\n this.getTooltipPosition(targetRect, resolved),\n );\n const arrowOffset = this.getArrowOffset(targetRect, tooltipPos, resolved);\n const tooltipStyle = `top: ${tooltipPos.top}px; left: ${tooltipPos.left}px;`;\n const stepLabel = `Step ${stepIndex + 1} of ${totalSteps}: ${step.title}`;\n\n return html`\n <!-- Screen reader announcement -->\n <div class=\"sr-only\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n ${stepLabel}\n </div>\n\n <div\n class=\"tour-backdrop ${this.visible ? 'visible' : ''}\"\n part=\"backdrop\"\n @click=${this.handleBackdropClick}\n ></div>\n\n <div class=\"tour-spotlight\" part=\"spotlight\" style=${spotlightStyle}></div>\n\n <div\n class=\"tour-tooltip ${this.visible ? 'visible' : ''}\"\n part=\"tooltip\"\n style=${tooltipStyle}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"${step.title}\"\n aria-describedby=\"tour-desc\"\n tabindex=\"-1\"\n >\n <div class=\"tour-arrow ${this.getArrowClass(resolved)}\" style=\"--arrow-offset: ${arrowOffset}\"></div>\n\n <div class=\"tour-step-badge\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"></path>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"></line>\n </svg>\n Step ${stepIndex + 1} of ${totalSteps}\n </div>\n\n <h3 class=\"tour-title\">${step.title}</h3>\n <div class=\"tour-message\" id=\"tour-desc\">${step.message}</div>\n\n ${this.renderProgressDots(stepIndex, totalSteps)}\n\n <div class=\"tour-footer\">\n <button\n class=\"tour-skip\"\n aria-label=\"Skip tour\"\n @click=${() => { this.clearAutoAdvance(); this.service.skipTour(); }}\n >\n Skip tour\n </button>\n <div class=\"tour-nav\">\n ${stepIndex > 0 ? html`\n <button\n class=\"tour-btn\"\n aria-label=\"Go to previous step\"\n @click=${() => { this.clearAutoAdvance(); this.service.prevStep(); }}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <polyline points=\"15 18 9 12 15 6\"></polyline>\n </svg>\n Back\n </button>\n ` : nothing}\n <button\n class=\"tour-btn primary\"\n aria-label=\"${stepIndex === totalSteps - 1 ? 'Finish tour' : 'Go to next step'}\"\n @click=${() => { this.clearAutoAdvance(); this.service.nextStep(); }}\n >\n ${stepIndex === totalSteps - 1 ? 'Finish' : 'Next'}\n ${stepIndex < totalSteps - 1 ? html`\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <polyline points=\"9 18 15 12 9 6\"></polyline>\n </svg>\n ` : html`\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n `}\n </button>\n </div>\n </div>\n\n ${step.autoAdvance ? keyed(stepIndex, html`\n <div\n class=\"tour-auto-progress\"\n style=\"animation: autoAdvanceFill ${step.autoAdvance}ms linear forwards;\"\n aria-hidden=\"true\"\n ></div>\n `) : nothing}\n </div>\n `;\n }\n\n private renderProgressDots(current: number, total: number) {\n if (total <= 1) return nothing;\n return html`\n <div class=\"tour-progress\" role=\"group\" aria-label=\"Tour progress\">\n ${Array.from({ length: total }, (_, i) => html`\n <div\n class=\"tour-dot ${i === current ? 'active' : i < current ? 'completed' : ''}\"\n role=\"presentation\"\n ></div>\n `)}\n </div>\n `;\n }\n\n private renderCenteredStep(step: TourStep, stepIndex: number, totalSteps: number) {\n const stepLabel = `Step ${stepIndex + 1} of ${totalSteps}: ${step.title}`;\n\n return html`\n <!-- Screen reader announcement -->\n <div class=\"sr-only\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n ${stepLabel}\n </div>\n\n <div\n class=\"tour-backdrop ${this.visible ? 'visible' : ''}\"\n part=\"backdrop\"\n @click=${this.handleBackdropClick}\n ></div>\n\n <div\n class=\"tour-center-card ${this.visible ? 'visible' : ''}\"\n part=\"center-card\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"${step.title}\"\n aria-describedby=\"tour-desc-center\"\n tabindex=\"-1\"\n >\n <div class=\"tour-center-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"></path>\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"></line>\n </svg>\n </div>\n\n <h3 class=\"tour-title\">${step.title}</h3>\n <div class=\"tour-message\" id=\"tour-desc-center\">${step.message}</div>\n\n ${this.renderProgressDots(stepIndex, totalSteps)}\n\n <div class=\"tour-footer\">\n <button\n class=\"tour-skip\"\n aria-label=\"Skip tour\"\n @click=${() => { this.clearAutoAdvance(); this.service.skipTour(); }}\n >\n Skip tour\n </button>\n <div class=\"tour-nav\">\n ${stepIndex > 0 ? html`\n <button\n class=\"tour-btn\"\n aria-label=\"Go to previous step\"\n @click=${() => { this.clearAutoAdvance(); this.service.prevStep(); }}\n >Back</button>\n ` : nothing}\n <button\n class=\"tour-btn primary\"\n aria-label=\"${stepIndex === totalSteps - 1 ? 'Start the tour' : 'Go to next step'}\"\n @click=${() => { this.clearAutoAdvance(); this.service.nextStep(); }}\n >\n ${stepIndex === totalSteps - 1 ? \"Let's go!\" : 'Next'}\n ${stepIndex < totalSteps - 1 ? html`\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\">\n <polyline points=\"9 18 15 12 9 6\"></polyline>\n </svg>\n ` : nothing}\n </button>\n </div>\n </div>\n\n ${step.autoAdvance ? keyed(stepIndex, html`\n <div\n class=\"tour-auto-progress\"\n style=\"animation: autoAdvanceFill ${step.autoAdvance}ms linear forwards;\"\n aria-hidden=\"true\"\n ></div>\n `) : nothing}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'torchlit-overlay': TorchlitOverlay;\n }\n}\n"],"names":["offset","clamped"],"mappings":"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"}
@@ -7,7 +7,7 @@ export declare class TourService {
7
7
  private listeners;
8
8
  private readonly storageKey;
9
9
  private readonly storage;
10
- private readonly targetAttribute;
10
+ readonly targetAttribute: string;
11
11
  readonly spotlightPadding: number;
12
12
  constructor(config?: TourConfig);
13
13
  private loadState;
@@ -28,7 +28,7 @@ export declare class TourService {
28
28
  isActive(): boolean;
29
29
  /** Start a tour by ID. No-op if the tour doesn't exist or has no steps. */
30
30
  start(tourId: string): void;
31
- /** Advance to the next step, or complete the tour if on the last step. */
31
+ /** Advance to the next step, loop if enabled, or complete the tour. */
32
32
  nextStep(): void;
33
33
  /** Go back to the previous step. No-op if already on step 0. */
34
34
  prevStep(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"tour-service.d.ts","sourceRoot":"","sources":["../src/tour-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EAGb,MAAM,YAAY,CAAC;AA6BpB,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,SAAS,CAAgC;IAGjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;gBAEtB,MAAM,GAAE,UAAe;IAUnC,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,SAAS;IAUjB,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,IAAI;IACvC,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IAWpC,sCAAsC;IACtC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI/C,mCAAmC;IACnC,iBAAiB,IAAI,cAAc,EAAE;IAIrC;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IASxC,4CAA4C;IAC5C,QAAQ,IAAI,OAAO;IAMnB,2EAA2E;IAC3E,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS3B,0EAA0E;IAC1E,QAAQ,IAAI,IAAI;IAYhB,gEAAgE;IAChE,QAAQ,IAAI,IAAI;IAQhB,mEAAmE;IACnE,QAAQ,IAAI,IAAI;IAiBhB,OAAO,CAAC,YAAY;IAmBpB,0EAA0E;IAC1E,WAAW,IAAI,YAAY,GAAG,IAAI;IAwBlC,sEAAsE;IACtE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAM5C,sEAAsE;IACtE,SAAS,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,IAAI;IAK7C,OAAO,CAAC,MAAM;IAOd,0DAA0D;IAC1D,QAAQ,IAAI,IAAI;CAQjB;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAElE"}
1
+ {"version":3,"file":"tour-service.d.ts","sourceRoot":"","sources":["../src/tour-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EAGb,MAAM,YAAY,CAAC;AA6BpB,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,SAAS,CAAgC;IAGjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;gBAEtB,MAAM,GAAE,UAAe;IAUnC,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,SAAS;IAUjB,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,IAAI;IACvC,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IAWpC,sCAAsC;IACtC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI/C,mCAAmC;IACnC,iBAAiB,IAAI,cAAc,EAAE;IAIrC;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IASxC,4CAA4C;IAC5C,QAAQ,IAAI,OAAO;IAMnB,2EAA2E;IAC3E,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS3B,uEAAuE;IACvE,QAAQ,IAAI,IAAI;IAehB,gEAAgE;IAChE,QAAQ,IAAI,IAAI;IAQhB,mEAAmE;IACnE,QAAQ,IAAI,IAAI;IAiBhB,OAAO,CAAC,YAAY;IAmBpB,0EAA0E;IAC1E,WAAW,IAAI,YAAY,GAAG,IAAI;IAwBlC,sEAAsE;IACtE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAM5C,sEAAsE;IACtE,SAAS,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,IAAI;IAK7C,OAAO,CAAC,MAAM;IAOd,0DAA0D;IAC1D,QAAQ,IAAI,IAAI;CAQjB;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAElE"}
@@ -1,6 +1,195 @@
1
- import { T, c } from "./tour-service-BKz7eeWb.js";
1
+ import { d as deepQuery } from "./deep-query-vkmcq1Dw.js";
2
+ const DEFAULT_STORAGE_KEY = "torchlit-state";
3
+ const DEFAULT_TARGET_ATTR = "data-tour-id";
4
+ const DEFAULT_SPOTLIGHT_PADDING = 10;
5
+ const noopStorage = {
6
+ getItem: () => null,
7
+ setItem: () => {
8
+ }
9
+ };
10
+ function defaultStorage() {
11
+ try {
12
+ const test = "__torchlit_test__";
13
+ localStorage.setItem(test, test);
14
+ localStorage.removeItem(test);
15
+ return localStorage;
16
+ } catch {
17
+ return noopStorage;
18
+ }
19
+ }
20
+ class TourService {
21
+ constructor(config = {}) {
22
+ this.tours = /* @__PURE__ */ new Map();
23
+ this.activeTourId = null;
24
+ this.currentStepIndex = 0;
25
+ this.listeners = /* @__PURE__ */ new Set();
26
+ this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
27
+ this.storage = config.storage ?? defaultStorage();
28
+ this.targetAttribute = config.targetAttribute ?? DEFAULT_TARGET_ATTR;
29
+ this.spotlightPadding = config.spotlightPadding ?? DEFAULT_SPOTLIGHT_PADDING;
30
+ this.persistedState = this.loadState();
31
+ }
32
+ /* ── Persistence ──────────────────────────────── */
33
+ loadState() {
34
+ try {
35
+ const stored = this.storage.getItem(this.storageKey);
36
+ if (stored) {
37
+ const parsed = JSON.parse(stored);
38
+ return {
39
+ completed: Array.isArray(parsed.completed) ? parsed.completed : [],
40
+ dismissed: Array.isArray(parsed.dismissed) ? parsed.dismissed : []
41
+ };
42
+ }
43
+ } catch (error) {
44
+ console.error("[torchlit] Failed to load state:", error);
45
+ }
46
+ return { completed: [], dismissed: [] };
47
+ }
48
+ saveState() {
49
+ try {
50
+ this.storage.setItem(this.storageKey, JSON.stringify(this.persistedState));
51
+ } catch (error) {
52
+ console.error("[torchlit] Failed to save state:", error);
53
+ }
54
+ }
55
+ register(input) {
56
+ if (Array.isArray(input)) {
57
+ input.forEach((t) => this.tours.set(t.id, t));
58
+ } else {
59
+ this.tours.set(input.id, input);
60
+ }
61
+ }
62
+ /* ── Queries ──────────────────────────────────── */
63
+ /** Return a registered tour by ID. */
64
+ getTour(id) {
65
+ return this.tours.get(id);
66
+ }
67
+ /** Return all registered tours. */
68
+ getAvailableTours() {
69
+ return Array.from(this.tours.values());
70
+ }
71
+ /**
72
+ * Whether a `first-visit` tour should auto-start.
73
+ * Returns `false` if the tour is manual, already completed, or dismissed.
74
+ */
75
+ shouldAutoStart(tourId) {
76
+ const tour = this.tours.get(tourId);
77
+ if (!tour || tour.trigger !== "first-visit") return false;
78
+ return !this.persistedState.completed.includes(tourId) && !this.persistedState.dismissed.includes(tourId);
79
+ }
80
+ /** Whether any tour is currently active. */
81
+ isActive() {
82
+ return this.activeTourId !== null;
83
+ }
84
+ /* ── Tour control ─────────────────────────────── */
85
+ /** Start a tour by ID. No-op if the tour doesn't exist or has no steps. */
86
+ start(tourId) {
87
+ const tour = this.tours.get(tourId);
88
+ if (!tour || tour.steps.length === 0) return;
89
+ this.activeTourId = tourId;
90
+ this.currentStepIndex = 0;
91
+ this.notify();
92
+ }
93
+ /** Advance to the next step, loop if enabled, or complete the tour. */
94
+ nextStep() {
95
+ if (!this.activeTourId) return;
96
+ const tour = this.tours.get(this.activeTourId);
97
+ if (this.currentStepIndex < tour.steps.length - 1) {
98
+ this.currentStepIndex++;
99
+ this.notify();
100
+ } else if (tour.loop) {
101
+ this.currentStepIndex = 0;
102
+ this.notify();
103
+ } else {
104
+ this.completeTour();
105
+ }
106
+ }
107
+ /** Go back to the previous step. No-op if already on step 0. */
108
+ prevStep() {
109
+ if (!this.activeTourId) return;
110
+ if (this.currentStepIndex > 0) {
111
+ this.currentStepIndex--;
112
+ this.notify();
113
+ }
114
+ }
115
+ /** Skip / dismiss the current tour. Persists "dismissed" state. */
116
+ skipTour() {
117
+ if (!this.activeTourId) return;
118
+ const id = this.activeTourId;
119
+ const tour = this.tours.get(id);
120
+ if (!this.persistedState.dismissed.includes(id)) {
121
+ this.persistedState.dismissed.push(id);
122
+ this.saveState();
123
+ }
124
+ this.activeTourId = null;
125
+ this.currentStepIndex = 0;
126
+ this.notify();
127
+ tour?.onSkip?.();
128
+ }
129
+ completeTour() {
130
+ if (!this.activeTourId) return;
131
+ const id = this.activeTourId;
132
+ const tour = this.tours.get(id);
133
+ if (!this.persistedState.completed.includes(id)) {
134
+ this.persistedState.completed.push(id);
135
+ this.saveState();
136
+ }
137
+ this.activeTourId = null;
138
+ this.currentStepIndex = 0;
139
+ this.notify();
140
+ tour?.onComplete?.();
141
+ }
142
+ /* ── Snapshot (current state for overlay) ─────── */
143
+ /** Return a snapshot of the current tour state, or `null` if inactive. */
144
+ getSnapshot() {
145
+ if (!this.activeTourId) return null;
146
+ const tour = this.tours.get(this.activeTourId);
147
+ if (!tour) return null;
148
+ const step = tour.steps[this.currentStepIndex];
149
+ if (!step) return null;
150
+ const targetElement = this.findTarget(step.target);
151
+ const targetRect = targetElement?.getBoundingClientRect() ?? null;
152
+ return {
153
+ tourId: this.activeTourId,
154
+ tourName: tour.name,
155
+ step,
156
+ stepIndex: this.currentStepIndex,
157
+ totalSteps: tour.steps.length,
158
+ targetRect,
159
+ targetElement
160
+ };
161
+ }
162
+ /* ── Shadow DOM target resolution ─────────────── */
163
+ /** Find a DOM element by its `data-tour-id` (or custom attribute). */
164
+ findTarget(targetId) {
165
+ return deepQuery(`[${this.targetAttribute}="${targetId}"]`, document.body);
166
+ }
167
+ /* ── Observer pattern ─────────────────────────── */
168
+ /** Subscribe to snapshot changes. Returns an unsubscribe function. */
169
+ subscribe(listener) {
170
+ this.listeners.add(listener);
171
+ return () => this.listeners.delete(listener);
172
+ }
173
+ notify() {
174
+ const snapshot = this.getSnapshot();
175
+ this.listeners.forEach((listener) => listener(snapshot));
176
+ }
177
+ /* ── Reset (for testing & demos) ──────────────── */
178
+ /** Clear all persisted state and stop any active tour. */
179
+ resetAll() {
180
+ this.persistedState = { completed: [], dismissed: [] };
181
+ this.activeTourId = null;
182
+ this.currentStepIndex = 0;
183
+ this.tours.clear();
184
+ this.saveState();
185
+ this.notify();
186
+ }
187
+ }
188
+ function createTourService(config) {
189
+ return new TourService(config);
190
+ }
2
191
  export {
3
- T as TourService,
4
- c as createTourService
192
+ TourService,
193
+ createTourService
5
194
  };
6
195
  //# sourceMappingURL=tour-service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tour-service.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"tour-service.js","sources":["../src/tour-service.ts"],"sourcesContent":["import { deepQuery } from './utils/deep-query.js';\nimport type {\n TourConfig,\n TourDefinition,\n TourListener,\n TourSnapshot,\n TourState,\n StorageAdapter,\n} from './types.js';\n\n// ── Defaults ─────────────────────────────────────────────────────────────────\n\nconst DEFAULT_STORAGE_KEY = 'torchlit-state';\nconst DEFAULT_TARGET_ATTR = 'data-tour-id';\nconst DEFAULT_SPOTLIGHT_PADDING = 10;\n\n/** A no-op storage adapter for SSR / environments without localStorage. */\nconst noopStorage: StorageAdapter = {\n getItem: () => null,\n setItem: () => {},\n};\n\n/** Safely wrap localStorage — falls back to noop if unavailable. */\nfunction defaultStorage(): StorageAdapter {\n try {\n // Guard against SSR or restricted environments\n const test = '__torchlit_test__';\n localStorage.setItem(test, test);\n localStorage.removeItem(test);\n return localStorage;\n } catch {\n return noopStorage;\n }\n}\n\n// ── TourService ──────────────────────────────────────────────────────────────\n\nexport class TourService {\n private tours: Map<string, TourDefinition> = new Map();\n private persistedState: TourState;\n private activeTourId: string | null = null;\n private currentStepIndex = 0;\n private listeners: Set<TourListener> = new Set();\n\n // Resolved config\n private readonly storageKey: string;\n private readonly storage: StorageAdapter;\n readonly targetAttribute: string;\n readonly spotlightPadding: number;\n\n constructor(config: TourConfig = {}) {\n this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;\n this.storage = config.storage ?? defaultStorage();\n this.targetAttribute = config.targetAttribute ?? DEFAULT_TARGET_ATTR;\n this.spotlightPadding = config.spotlightPadding ?? DEFAULT_SPOTLIGHT_PADDING;\n this.persistedState = this.loadState();\n }\n\n /* ── Persistence ──────────────────────────────── */\n\n private loadState(): TourState {\n try {\n const stored = this.storage.getItem(this.storageKey);\n if (stored) {\n const parsed = JSON.parse(stored);\n return {\n completed: Array.isArray(parsed.completed) ? parsed.completed : [],\n dismissed: Array.isArray(parsed.dismissed) ? parsed.dismissed : [],\n };\n }\n } catch (error) {\n console.error('[torchlit] Failed to load state:', error);\n }\n return { completed: [], dismissed: [] };\n }\n\n private saveState(): void {\n try {\n this.storage.setItem(this.storageKey, JSON.stringify(this.persistedState));\n } catch (error) {\n console.error('[torchlit] Failed to save state:', error);\n }\n }\n\n /* ── Registration ─────────────────────────────── */\n\n /** Register a single tour definition. */\n register(tours: TourDefinition[]): void;\n register(tour: TourDefinition): void;\n register(input: TourDefinition | TourDefinition[]): void {\n if (Array.isArray(input)) {\n input.forEach(t => this.tours.set(t.id, t));\n } else {\n this.tours.set(input.id, input);\n }\n }\n\n /* ── Queries ──────────────────────────────────── */\n\n /** Return a registered tour by ID. */\n getTour(id: string): TourDefinition | undefined {\n return this.tours.get(id);\n }\n\n /** Return all registered tours. */\n getAvailableTours(): TourDefinition[] {\n return Array.from(this.tours.values());\n }\n\n /**\n * Whether a `first-visit` tour should auto-start.\n * Returns `false` if the tour is manual, already completed, or dismissed.\n */\n shouldAutoStart(tourId: string): boolean {\n const tour = this.tours.get(tourId);\n if (!tour || tour.trigger !== 'first-visit') return false;\n return (\n !this.persistedState.completed.includes(tourId) &&\n !this.persistedState.dismissed.includes(tourId)\n );\n }\n\n /** Whether any tour is currently active. */\n isActive(): boolean {\n return this.activeTourId !== null;\n }\n\n /* ── Tour control ─────────────────────────────── */\n\n /** Start a tour by ID. No-op if the tour doesn't exist or has no steps. */\n start(tourId: string): void {\n const tour = this.tours.get(tourId);\n if (!tour || tour.steps.length === 0) return;\n\n this.activeTourId = tourId;\n this.currentStepIndex = 0;\n this.notify();\n }\n\n /** Advance to the next step, loop if enabled, or complete the tour. */\n nextStep(): void {\n if (!this.activeTourId) return;\n const tour = this.tours.get(this.activeTourId)!;\n\n if (this.currentStepIndex < tour.steps.length - 1) {\n this.currentStepIndex++;\n this.notify();\n } else if (tour.loop) {\n this.currentStepIndex = 0;\n this.notify();\n } else {\n this.completeTour();\n }\n }\n\n /** Go back to the previous step. No-op if already on step 0. */\n prevStep(): void {\n if (!this.activeTourId) return;\n if (this.currentStepIndex > 0) {\n this.currentStepIndex--;\n this.notify();\n }\n }\n\n /** Skip / dismiss the current tour. Persists \"dismissed\" state. */\n skipTour(): void {\n if (!this.activeTourId) return;\n const id = this.activeTourId;\n const tour = this.tours.get(id);\n\n if (!this.persistedState.dismissed.includes(id)) {\n this.persistedState.dismissed.push(id);\n this.saveState();\n }\n\n this.activeTourId = null;\n this.currentStepIndex = 0;\n this.notify();\n\n tour?.onSkip?.();\n }\n\n private completeTour(): void {\n if (!this.activeTourId) return;\n const id = this.activeTourId;\n const tour = this.tours.get(id);\n\n if (!this.persistedState.completed.includes(id)) {\n this.persistedState.completed.push(id);\n this.saveState();\n }\n\n this.activeTourId = null;\n this.currentStepIndex = 0;\n this.notify();\n\n tour?.onComplete?.();\n }\n\n /* ── Snapshot (current state for overlay) ─────── */\n\n /** Return a snapshot of the current tour state, or `null` if inactive. */\n getSnapshot(): TourSnapshot | null {\n if (!this.activeTourId) return null;\n const tour = this.tours.get(this.activeTourId);\n if (!tour) return null;\n\n const step = tour.steps[this.currentStepIndex];\n if (!step) return null;\n\n const targetElement = this.findTarget(step.target);\n const targetRect = targetElement?.getBoundingClientRect() ?? null;\n\n return {\n tourId: this.activeTourId,\n tourName: tour.name,\n step,\n stepIndex: this.currentStepIndex,\n totalSteps: tour.steps.length,\n targetRect,\n targetElement,\n };\n }\n\n /* ── Shadow DOM target resolution ─────────────── */\n\n /** Find a DOM element by its `data-tour-id` (or custom attribute). */\n findTarget(targetId: string): Element | null {\n return deepQuery(`[${this.targetAttribute}=\"${targetId}\"]`, document.body);\n }\n\n /* ── Observer pattern ─────────────────────────── */\n\n /** Subscribe to snapshot changes. Returns an unsubscribe function. */\n subscribe(listener: TourListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(): void {\n const snapshot = this.getSnapshot();\n this.listeners.forEach(listener => listener(snapshot));\n }\n\n /* ── Reset (for testing & demos) ──────────────── */\n\n /** Clear all persisted state and stop any active tour. */\n resetAll(): void {\n this.persistedState = { completed: [], dismissed: [] };\n this.activeTourId = null;\n this.currentStepIndex = 0;\n this.tours.clear();\n this.saveState();\n this.notify();\n }\n}\n\n// ── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Create a new `TourService` instance.\n *\n * @example\n * ```ts\n * import { createTourService } from 'torchlit';\n *\n * const tours = createTourService({ storageKey: 'my-app-tours' });\n * tours.register([...]);\n * tours.start('onboarding');\n * ```\n */\nexport function createTourService(config?: TourConfig): TourService {\n return new TourService(config);\n}\n"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { TemplateResult } from 'lit';
1
2
  /** Where to position the tooltip relative to the spotlight target. */
2
3
  export type TourPlacement = 'top' | 'bottom' | 'left' | 'right';
3
4
  export interface TourStep {
@@ -9,10 +10,29 @@ export interface TourStep {
9
10
  target: string;
10
11
  /** Bold title shown in the tooltip. */
11
12
  title: string;
12
- /** Descriptive message shown below the title. */
13
- message: string;
13
+ /**
14
+ * Descriptive message shown below the title.
15
+ *
16
+ * Accepts a plain string **or** a Lit `TemplateResult` for rich HTML content:
17
+ * ```ts
18
+ * message: html`Click <strong>here</strong> to continue.`
19
+ * ```
20
+ */
21
+ message: string | TemplateResult;
14
22
  /** Where to position the tooltip relative to the target. */
15
23
  placement: TourPlacement;
24
+ /**
25
+ * Override the spotlight border-radius for this step.
26
+ * Use `'50%'` for a circle, `'9999px'` for a pill, `'0'` for sharp corners.
27
+ * Falls back to the `--tour-spotlight-radius` CSS custom property.
28
+ */
29
+ spotlightBorderRadius?: string;
30
+ /**
31
+ * Automatically advance to the next step after this many milliseconds.
32
+ * Useful for demo / kiosk modes. A progress bar is rendered at the bottom
33
+ * of the tooltip. Manual interaction (Next / Back / Skip) cancels the timer.
34
+ */
35
+ autoAdvance?: number;
16
36
  /**
17
37
  * Arbitrary route / view hint.
18
38
  * When set, a `tour-route-change` event is dispatched with `{ route }` detail
@@ -37,6 +57,22 @@ export interface TourDefinition {
37
57
  trigger: 'first-visit' | 'manual';
38
58
  /** Ordered list of tour steps. */
39
59
  steps: TourStep[];
60
+ /**
61
+ * When `true`, advancing past the last step restarts at step 0
62
+ * instead of completing. Combine with `autoAdvance` for kiosk / demo modes.
63
+ * The user can still exit via Skip or Escape.
64
+ */
65
+ loop?: boolean;
66
+ /**
67
+ * Scroll behaviour when the tour ends (completes or is skipped).
68
+ *
69
+ * - `'restore'` — scroll back to where the user was before the tour started (default)
70
+ * - `'top'` — scroll to the top of the page
71
+ * - `'none'` — leave the scroll position as-is
72
+ *
73
+ * @default `'restore'`
74
+ */
75
+ onEndScroll?: 'restore' | 'top' | 'none';
40
76
  /** Called when the user completes every step in the tour. */
41
77
  onComplete?: () => void;
42
78
  /** Called when the user skips / dismisses the tour. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAIhE,MAAM,WAAW,QAAQ;IACvB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAEhB,4DAA4D;IAC5D,SAAS,EAAE,aAAa,CAAC;IAEzB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAID,MAAM,WAAW,cAAc;IAC7B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IAEX,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,EAAE,aAAa,GAAG,QAAQ,CAAC;IAElC,kCAAkC;IAClC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IAExB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,wDAAwD;IACxD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAID,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B;AAID;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAI1C,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAIhE,MAAM,WAAW,QAAQ;IACvB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC;IAEjC,4DAA4D;IAC5D,SAAS,EAAE,aAAa,CAAC;IAEzB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAID,MAAM,WAAW,cAAc;IAC7B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IAEX,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,EAAE,aAAa,GAAG,QAAQ,CAAC;IAElC,kCAAkC;IAClC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC;IAEzC,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IAExB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,wDAAwD;IACxD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAID,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B;AAID;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torchlit",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Lightweight guided-tour & onboarding library built on Lit web components. Shadow DOM aware, framework-agnostic, tiny footprint.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,14 +26,6 @@
26
26
  "sideEffects": [
27
27
  "./dist/tour-overlay.js"
28
28
  ],
29
- "scripts": {
30
- "dev": "vite serve examples",
31
- "build": "vite build && tsc --emitDeclarationOnly --outDir dist",
32
- "test": "vitest run",
33
- "test:watch": "vitest",
34
- "preview": "vite preview",
35
- "prepublishOnly": "npm test && npm run build"
36
- },
37
29
  "keywords": [
38
30
  "tour",
39
31
  "onboarding",
@@ -50,10 +42,12 @@
50
42
  "license": "MIT",
51
43
  "repository": {
52
44
  "type": "git",
53
- "url": "https://github.com/barryking/torchlit.git"
45
+ "url": "git+https://github.com/barryking/torchlit.git"
54
46
  },
55
47
  "homepage": "https://github.com/barryking/torchlit",
56
- "bugs": "https://github.com/barryking/torchlit/issues",
48
+ "bugs": {
49
+ "url": "https://github.com/barryking/torchlit/issues"
50
+ },
57
51
  "peerDependencies": {
58
52
  "lit": "^3.0.0"
59
53
  },
@@ -63,5 +57,14 @@
63
57
  "typescript": "^5.7.0",
64
58
  "vite": "^6.1.0",
65
59
  "vitest": "^3.0.0"
60
+ },
61
+ "scripts": {
62
+ "dev": "vite serve site",
63
+ "dev:examples": "vite serve examples",
64
+ "build": "vite build && tsc --emitDeclarationOnly --outDir dist",
65
+ "test": "vitest run",
66
+ "test:watch": "vitest",
67
+ "preview": "vite preview",
68
+ "build:demo": "vite build --config vite.config.demo.ts"
66
69
  }
67
- }
70
+ }