torchlit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Barry King
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,396 @@
1
+ <p align="center">
2
+ <br />
3
+ <img src="https://em-content.zobj.net/source/apple/391/fire_1f525.png" width="80" />
4
+ <br />
5
+ </p>
6
+
7
+ <h1 align="center">Torchlit</h1>
8
+
9
+ <p align="center">
10
+ Guided tours & onboarding for <strong>any</strong> web app.<br />
11
+ Shadow DOM aware. Framework agnostic. Tiny footprint.
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/torchlit"><img src="https://img.shields.io/npm/v/torchlit?color=blue&label=npm" alt="npm version" /></a>
16
+ <img src="https://img.shields.io/bundlephobia/minzip/torchlit?label=gzip" alt="bundle size" />
17
+ <img src="https://img.shields.io/badge/lit-%5E3.0-blue" alt="lit peer" />
18
+ <img src="https://img.shields.io/badge/license-MIT-green" alt="license" />
19
+ </p>
20
+
21
+ ---
22
+
23
+ ## Why Torchlit?
24
+
25
+ Most tour libraries break the moment your UI uses Shadow DOM. Torchlit was built for modern web component architectures from day one.
26
+
27
+ | Feature | Torchlit | Shepherd.js | Intro.js | React Joyride |
28
+ |---|:---:|:---:|:---:|:---:|
29
+ | Shadow DOM traversal | **Yes** | No | No | No |
30
+ | Framework agnostic | **Yes** | Yes | Yes | React only |
31
+ | Bundle size (gzip) | **~6 KB** | ~50 KB | ~15 KB | ~40 KB |
32
+ | Web Component | **Yes** | No | No | No |
33
+ | CSS custom property theming | **Yes** | No | Partial | No |
34
+ | Async `beforeShow` hooks | **Yes** | Yes | No | Yes |
35
+ | Zero runtime dependencies | **Yes** | Popper.js | No | React |
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ npm install torchlit lit
41
+ ```
42
+
43
+ > **Lit** is a peer dependency (~7 KB gzip). If your app already uses Lit, you're set.
44
+
45
+ ## Quick Start
46
+
47
+ ```html
48
+ <!-- Drop the overlay anywhere in your HTML -->
49
+ <torchlit-overlay></torchlit-overlay>
50
+ ```
51
+
52
+ ```typescript
53
+ import { createTourService } from 'torchlit';
54
+ import 'torchlit/overlay'; // registers <torchlit-overlay>
55
+
56
+ // 1. Create a service
57
+ const tours = createTourService();
58
+
59
+ // 2. Register a tour
60
+ tours.register({
61
+ id: 'welcome',
62
+ name: 'Welcome',
63
+ trigger: 'first-visit',
64
+ steps: [
65
+ {
66
+ target: '_none_',
67
+ title: 'Welcome!',
68
+ message: 'Let us show you around.',
69
+ placement: 'bottom',
70
+ },
71
+ {
72
+ target: 'sidebar-nav',
73
+ title: 'Navigation',
74
+ message: 'Use the sidebar to move between pages.',
75
+ placement: 'right',
76
+ },
77
+ {
78
+ target: 'search-bar',
79
+ title: 'Search',
80
+ message: 'Find anything instantly.',
81
+ placement: 'bottom',
82
+ beforeShow: async () => {
83
+ // Navigate, load data, or do any async prep work
84
+ await router.push('/search');
85
+ },
86
+ },
87
+ ],
88
+ onComplete: () => analytics.track('tour_finished'),
89
+ onSkip: () => analytics.track('tour_skipped'),
90
+ });
91
+
92
+ // 3. Wire the overlay
93
+ const overlay = document.querySelector('torchlit-overlay');
94
+ overlay.service = tours;
95
+
96
+ // 4. Auto-start on first visit
97
+ if (tours.shouldAutoStart('welcome')) {
98
+ setTimeout(() => tours.start('welcome'), 800);
99
+ }
100
+ ```
101
+
102
+ Mark your target elements with `data-tour-id`:
103
+
104
+ ```html
105
+ <nav data-tour-id="sidebar-nav">...</nav>
106
+ <input data-tour-id="search-bar" placeholder="Search..." />
107
+ ```
108
+
109
+ That's it. The spotlight finds elements even inside shadow roots.
110
+
111
+ ## Tree-Shakeable Imports
112
+
113
+ Import only what you need:
114
+
115
+ ```typescript
116
+ // Full library (service + overlay + types + deepQuery)
117
+ import { createTourService, TorchlitOverlay, deepQuery } from 'torchlit';
118
+
119
+ // Headless service only — zero Lit dependency
120
+ import { createTourService } from 'torchlit/service';
121
+
122
+ // Overlay component only
123
+ import { TorchlitOverlay } from 'torchlit/overlay';
124
+ ```
125
+
126
+ The `torchlit/service` entry point has **zero dependencies** and can be used with any rendering layer.
127
+
128
+ ## API Reference
129
+
130
+ ### `createTourService(config?)`
131
+
132
+ Creates a new `TourService` instance.
133
+
134
+ ```typescript
135
+ const tours = createTourService({
136
+ storageKey: 'my-app-tours', // localStorage key (default: 'torchlit-state')
137
+ storage: sessionStorage, // any { getItem, setItem } adapter
138
+ targetAttribute: 'data-tour-id', // attribute for target lookup (default)
139
+ spotlightPadding: 10, // px around spotlight cutout (default: 10)
140
+ });
141
+ ```
142
+
143
+ #### `TourConfig`
144
+
145
+ | Property | Type | Default | Description |
146
+ |---|---|---|---|
147
+ | `storageKey` | `string` | `'torchlit-state'` | Key for persisting completed/dismissed state |
148
+ | `storage` | `StorageAdapter` | `localStorage` | Any object with `getItem` / `setItem` |
149
+ | `targetAttribute` | `string` | `'data-tour-id'` | The data attribute used to locate targets |
150
+ | `spotlightPadding` | `number` | `10` | Padding in pixels around the spotlight |
151
+
152
+ ### `TourService`
153
+
154
+ | Method | Description |
155
+ |---|---|
156
+ | `register(tour)` | Register a single `TourDefinition` |
157
+ | `register(tours[])` | Register multiple tours at once |
158
+ | `start(tourId)` | Start a tour by ID |
159
+ | `nextStep()` | Advance to the next step |
160
+ | `prevStep()` | Go back to the previous step |
161
+ | `skipTour()` | Dismiss the current tour |
162
+ | `isActive()` | Whether a tour is currently running |
163
+ | `shouldAutoStart(tourId)` | Whether a first-visit tour should start |
164
+ | `getTour(tourId)` | Retrieve a registered tour |
165
+ | `getAvailableTours()` | Get all registered tours |
166
+ | `getSnapshot()` | Get the current step snapshot (or `null`) |
167
+ | `findTarget(targetId)` | Find a DOM element by tour target ID |
168
+ | `subscribe(listener)` | Subscribe to state changes; returns unsubscribe fn |
169
+ | `resetAll()` | Clear all state (useful for testing and demos) |
170
+
171
+ ### `TourDefinition`
172
+
173
+ ```typescript
174
+ interface TourDefinition {
175
+ id: string;
176
+ name: string;
177
+ trigger: 'first-visit' | 'manual';
178
+ steps: TourStep[];
179
+ onComplete?: () => void;
180
+ onSkip?: () => void;
181
+ }
182
+ ```
183
+
184
+ ### `TourStep`
185
+
186
+ ```typescript
187
+ interface TourStep {
188
+ target: string; // data-tour-id value, or '_none_' for centered card
189
+ title: string;
190
+ message: string;
191
+ placement: 'top' | 'bottom' | 'left' | 'right';
192
+ route?: string; // emits 'tour-route-change' event
193
+ beforeShow?: () => void | Promise<void>;
194
+ }
195
+ ```
196
+
197
+ ### `<torchlit-overlay>`
198
+
199
+ | Property | Type | Description |
200
+ |---|---|---|
201
+ | `service` | `TourService` | The service instance to subscribe to (required) |
202
+
203
+ | Event | Detail | Description |
204
+ |---|---|---|
205
+ | `tour-route-change` | `{ route: string }` | Fired when a step has a `route` property |
206
+
207
+ | CSS Part | Description |
208
+ |---|---|
209
+ | `backdrop` | The semi-transparent overlay |
210
+ | `spotlight` | The cutout highlight |
211
+ | `tooltip` | The floating tooltip card |
212
+ | `center-card` | The centered card (no-target steps) |
213
+
214
+ ### `deepQuery(selector, root?)`
215
+
216
+ Recursively searches the DOM including shadow roots. This is the utility that powers Torchlit's Shadow DOM support, exported for standalone use.
217
+
218
+ ```typescript
219
+ import { deepQuery } from 'torchlit';
220
+
221
+ const el = deepQuery('[data-tour-id="my-element"]');
222
+ // Finds the element even if it's buried inside nested shadow DOMs
223
+ ```
224
+
225
+ ## Theming
226
+
227
+ Torchlit adapts to your app's theme via CSS custom properties. Set them on `:root` or any ancestor:
228
+
229
+ ```css
230
+ :root {
231
+ --primary: #6366f1;
232
+ --primary-foreground: #ffffff;
233
+ --card: #ffffff;
234
+ --border: #e5e5e5;
235
+ --foreground: #1a1a1a;
236
+ --muted-foreground: #737373;
237
+ --muted: #f5f5f5;
238
+ --background: #ffffff;
239
+ --radius-lg: 0.75rem;
240
+ --radius-md: 0.5rem;
241
+ --radius-xl: 1rem;
242
+ }
243
+ ```
244
+
245
+ For isolated theming (without affecting your app), use the `--tour-*` prefix:
246
+
247
+ ```css
248
+ torchlit-overlay {
249
+ --tour-primary: #6366f1;
250
+ --tour-card: #1a1a2e;
251
+ --tour-foreground: #e5e5e5;
252
+ --tour-border: #333;
253
+ --tour-spotlight-radius: 1rem;
254
+ --tour-tooltip-radius: 0.75rem;
255
+ --tour-btn-radius: 0.5rem;
256
+ }
257
+ ```
258
+
259
+ ### Dark Mode
260
+
261
+ If your app already toggles CSS variables for dark mode, the tour overlay adapts automatically -- zero additional configuration.
262
+
263
+ ## Keyboard Navigation
264
+
265
+ | Key | Action |
266
+ |---|---|
267
+ | `Arrow Right` / `Enter` | Next step |
268
+ | `Arrow Left` | Previous step |
269
+ | `Escape` | Skip / dismiss tour |
270
+
271
+ ## Advanced Patterns
272
+
273
+ ### Route Changes During Tours
274
+
275
+ Use `beforeShow` to navigate before a step renders:
276
+
277
+ ```typescript
278
+ {
279
+ target: 'settings-panel',
280
+ title: 'Settings',
281
+ message: 'Configure your preferences here.',
282
+ placement: 'right',
283
+ beforeShow: async () => {
284
+ await router.push('/settings');
285
+ // Wait for the route transition to complete
286
+ await new Promise(r => setTimeout(r, 300));
287
+ },
288
+ }
289
+ ```
290
+
291
+ Or use the `route` property to emit a `tour-route-change` event that your app handles:
292
+
293
+ ```typescript
294
+ { target: 'dashboard-widget', title: '...', message: '...', placement: 'top', route: 'dashboard' }
295
+ ```
296
+
297
+ ```javascript
298
+ overlay.addEventListener('tour-route-change', (e) => {
299
+ router.push(`/${e.detail.route}`);
300
+ });
301
+ ```
302
+
303
+ ### Multiple Service Instances
304
+
305
+ Each call to `createTourService()` creates an independent instance. This is useful for micro-frontends:
306
+
307
+ ```typescript
308
+ const appTours = createTourService({ storageKey: 'app-tours' });
309
+ const widgetTours = createTourService({ storageKey: 'widget-tours' });
310
+ ```
311
+
312
+ ### Custom Storage
313
+
314
+ Persist tour state to an API instead of localStorage:
315
+
316
+ ```typescript
317
+ const tours = createTourService({
318
+ storage: {
319
+ getItem: (key) => fetchFromAPI(key),
320
+ setItem: (key, value) => postToAPI(key, value),
321
+ },
322
+ });
323
+ ```
324
+
325
+ ### Headless Mode (Custom UI)
326
+
327
+ Use the service without the built-in overlay:
328
+
329
+ ```typescript
330
+ import { createTourService } from 'torchlit/service';
331
+
332
+ const tours = createTourService();
333
+ tours.register([...]);
334
+
335
+ tours.subscribe((snapshot) => {
336
+ if (!snapshot) {
337
+ // Tour ended
338
+ hideMyCustomUI();
339
+ return;
340
+ }
341
+ // Render your own tooltip using snapshot.step, snapshot.targetRect, etc.
342
+ renderMyCustomTooltip(snapshot);
343
+ });
344
+
345
+ tours.start('onboarding');
346
+ ```
347
+
348
+ ## Running the Demo
349
+
350
+ ## Project Structure
351
+
352
+ ```
353
+ torchlit/
354
+ src/
355
+ index.ts # Public API barrel export
356
+ types.ts # All TypeScript interfaces
357
+ tour-service.ts # Framework-agnostic state engine
358
+ tour-overlay.ts # Lit web component (rendering)
359
+ utils/
360
+ deep-query.ts # Shadow DOM traversal utility
361
+ test/
362
+ tour-service.test.ts # Service unit tests (Vitest)
363
+ deep-query.test.ts # Deep query unit tests
364
+ examples/
365
+ index.html # Interactive demo (no build step)
366
+ ```
367
+
368
+ ## Running the Demo
369
+
370
+ ```bash
371
+ git clone https://github.com/barryking/torchlit.git
372
+ cd torchlit
373
+ npm install
374
+ npm run dev
375
+ ```
376
+
377
+ Open `http://localhost:5173` to see the interactive demo with an onboarding tour, contextual help, and theme switching.
378
+
379
+ ## Contributing
380
+
381
+ Contributions are welcome! Please open an issue first to discuss what you'd like to change.
382
+
383
+ ```bash
384
+ npm install # install dependencies
385
+ npm test # run tests
386
+ npm run build # build the library
387
+ npm run dev # start the demo dev server
388
+ ```
389
+
390
+ ## License
391
+
392
+ [MIT](LICENSE) -- use it anywhere, in any project.
393
+
394
+ ---
395
+
396
+ Created by [Barry King](https://github.com/barryking).
@@ -0,0 +1,5 @@
1
+ export type { TourPlacement, TourStep, TourDefinition, TourState, TourSnapshot, TourConfig, StorageAdapter, TourListener, } from './types.js';
2
+ export { TourService, createTourService } from './tour-service.js';
3
+ export { TorchlitOverlay } from './tour-overlay.js';
4
+ export { deepQuery } from './utils/deep-query.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,aAAa,EACb,QAAQ,EACR,cAAc,EACd,SAAS,EACT,YAAY,EACZ,UAAU,EACV,cAAc,EACd,YAAY,GACb,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAInE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { T, c, d } from "./tour-service-BKz7eeWb.js";
2
+ import { TorchlitOverlay } from "./tour-overlay.js";
3
+ export {
4
+ TorchlitOverlay,
5
+ T as TourService,
6
+ c as createTourService,
7
+ d as deepQuery
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -0,0 +1,56 @@
1
+ import { LitElement } from 'lit';
2
+ import type { TourService } from './tour-service.js';
3
+ /**
4
+ * `<torchlit-overlay>` — Full-screen overlay that renders a spotlight cutout
5
+ * around the current tour target, a tooltip with title / message / progress,
6
+ * and navigation controls.
7
+ *
8
+ * Wire it to a `TourService` instance via the `service` property:
9
+ *
10
+ * ```html
11
+ * <torchlit-overlay></torchlit-overlay>
12
+ * ```
13
+ * ```js
14
+ * document.querySelector('torchlit-overlay').service = myTourService;
15
+ * ```
16
+ *
17
+ * @fires tour-route-change - When a step has a `route` property, dispatched
18
+ * with `{ route: string }` so the host app can switch views.
19
+ *
20
+ * @csspart backdrop - The semi-transparent overlay behind the spotlight.
21
+ * @csspart spotlight - The cutout highlight around the target element.
22
+ * @csspart tooltip - The floating tooltip card.
23
+ * @csspart center-card - The centered card shown when there is no target.
24
+ */
25
+ export declare class TorchlitOverlay extends LitElement {
26
+ static styles: import("lit").CSSResult;
27
+ /**
28
+ * The `TourService` instance this overlay subscribes to.
29
+ * Must be set before the overlay will render anything.
30
+ */
31
+ service: TourService;
32
+ private snapshot;
33
+ private visible;
34
+ private unsubscribe?;
35
+ connectedCallback(): void;
36
+ disconnectedCallback(): void;
37
+ updated(changed: Map<string, unknown>): void;
38
+ private attachService;
39
+ private handleTourChange;
40
+ private scrollTargetIntoView;
41
+ private handleResize;
42
+ private handleKeydown;
43
+ private handleBackdropClick;
44
+ private getTooltipPosition;
45
+ private clampToViewport;
46
+ private getArrowClass;
47
+ render(): import("lit-html").TemplateResult<1>;
48
+ private renderProgressDots;
49
+ private renderCenteredStep;
50
+ }
51
+ declare global {
52
+ interface HTMLElementTagNameMap {
53
+ 'torchlit-overlay': TorchlitOverlay;
54
+ }
55
+ }
56
+ //# sourceMappingURL=tour-overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tour-overlay.d.ts","sourceRoot":"","sources":["../src/tour-overlay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAG7C,OAAgB,MAAM,0BA+QpB;IAIF;;;OAGG;IAEH,OAAO,EAAG,WAAW,CAAC;IAEb,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,WAAW,CAAC,CAAa;IAIxB,iBAAiB;IASjB,oBAAoB;IAOpB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAO9C,OAAO,CAAC,aAAa;YAMP,gBAAgB;IAmC9B,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,YAAY,CAIlB;IAEF,OAAO,CAAC,aAAa,CAYnB;IAEF,OAAO,CAAC,mBAAmB,CAEzB;IAIF,OAAO,CAAC,kBAAkB;IA+B1B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,aAAa;IAYZ,MAAM;IAgFf,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,kBAAkB;CA2C3B;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,eAAe,CAAC;KACrC;CACF"}