lumen-tour 0.2.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 lumen-tour contributors
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,173 @@
1
+ # lumen-tour
2
+
3
+ 轻量级、零依赖的产品引导(Product Tour / Onboarding Guide)库。TypeScript + 原生 CSS,不依赖任何框架,可在 Vanilla / React / Vue 等任意项目中使用。
4
+
5
+ ## 特性
6
+
7
+ - 🪶 **零依赖**:无运行时依赖,打包体积小
8
+ - 🎯 **自动定位**:卡片自动选择目标下方/上方,放不下自动翻转,支持手动指定方向
9
+ - ⌨️ **键盘导航**:`→` 下一步、`←` 上一步、`Esc` 跳过
10
+ - 🎨 **可定制**:按钮文案、遮罩颜色、挖孔边距均可配置;支持完全自定义卡片内容
11
+ - 🔄 **跟随重定位**:resize / scroll(含嵌套滚动容器)时自动跟随目标
12
+ - 🧩 **步骤自动跳过**:target 无法解析的步骤自动跳过,不中断引导
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ npm install lumen-tour
18
+ ```
19
+
20
+ 引入样式(引导卡片的默认样式):
21
+
22
+ ```ts
23
+ import 'lumen-tour/style.css';
24
+ ```
25
+
26
+ ## 快速开始
27
+
28
+ ```ts
29
+ import { createTour } from 'lumen-tour';
30
+ import 'lumen-tour/style.css';
31
+
32
+ const tour = createTour({
33
+ steps: [
34
+ {
35
+ target: '#logo', // CSS 选择器或 HTMLElement
36
+ title: '欢迎来到 Lumen',
37
+ description: '这里是 logo,点击可返回首页。',
38
+ media: '/img/step1.png' // 可选插图
39
+ },
40
+ {
41
+ target: document.querySelector('#menu')!,
42
+ title: '导航菜单',
43
+ description: '在这里切换功能模块。',
44
+ placement: 'right' // 可选:手动指定卡片方向
45
+ }
46
+ ],
47
+ onNext: async (ctx) => {
48
+ // 异步校验示例:返回 false 阻止前进
49
+ const ok = await validateStep(ctx.index);
50
+ return ok;
51
+ },
52
+ onFinish: () => {
53
+ console.log('引导完成');
54
+ }
55
+ });
56
+
57
+ tour.start();
58
+ ```
59
+
60
+ ## API
61
+
62
+ ### `createTour(options: TourOptions): TourInstance`
63
+
64
+ 创建引导实例。
65
+
66
+ ### `TourOptions`
67
+
68
+ | 属性 | 类型 | 默认值 | 说明 |
69
+ | --- | --- | --- | --- |
70
+ | `steps` | `TourStep[]` | (必填) | 引导步骤列表 |
71
+ | `texts` | `Partial<ButtonTexts>` | 见下 | 覆盖按钮文案 |
72
+ | `overlayColor` | `string` | `rgba(0,0,0,0.5)` | 遮罩颜色 |
73
+ | `padding` | `number \| [number, number]` | `8` | 挖孔相对目标元素的内边距(px);传 `number` 四周等距,传 `[x, y]` 则水平 `x`、垂直 `y` 分别控制 |
74
+ | `hideSkipOnLast` | `boolean` | `false` | 到达最后一步时是否隐藏"跳过"按钮 |
75
+ | `keyboard` | `boolean` | `true` | 是否启用键盘导航(→ 下一步、← 上一步、Esc 跳过) |
76
+ | `onNext` | `(ctx) => void \| boolean \| Promise<void \| boolean>` | — | 点击"下一步"时触发;返回 `false` 可阻止前进(支持异步校验) |
77
+ | `onPrev` | `(ctx) => void` | — | 点击"上一步"时触发 |
78
+ | `onSkip` | `(ctx) => void` | — | 点击"跳过"或按 Esc 时触发 |
79
+ | `onFinish` | `(ctx) => void` | — | 引导完成(最后一步点"完成")或所有步骤无效结束时触发 |
80
+ | `onStepChange` | `(ctx) => void` | — | 每次切换到新步骤后触发 |
81
+
82
+ ### `TourStep`
83
+
84
+ | 属性 | 类型 | 默认值 | 说明 |
85
+ | --- | --- | --- | --- |
86
+ | `target` | `string \| HTMLElement` | (必填) | 目标元素,CSS 选择器或元素本身 |
87
+ | `title` | `string` | — | 卡片标题,支持受限 HTML(经白名单清洗,见下) |
88
+ | `description` | `string` | — | 卡片描述文本,支持受限 HTML(经白名单清洗,见下) |
89
+ | `media` | `string` | — | 卡片顶部插图 URL |
90
+ | `placement` | `Placement` | 自动 | 卡片相对目标的方向,共 12 个:基础方向 `'bottom' \| 'top' \| 'left' \| 'right'`(贴边 + 交叉轴居中);组合方向 `'topLeft' \| 'topRight' \| 'bottomLeft' \| 'bottomRight'`(贴上/下边,左/右缘对齐目标)与 `'leftTop' \| 'leftBottom' \| 'rightTop' \| 'rightBottom'`(贴左/右边,上/下缘对齐目标)。不传则自动(下方优先,放不下翻转上方);指定方向主轴放不下时回退自动策略 |
91
+ | `renderMedia` | `(container, ctx) => void` | — | 传入则完全接管卡片插图区(替代 `media` 图片),可渲染视频等任意 HTML |
92
+ | `renderContent` | `(container, ctx) => void` | — | 传入则完全接管卡片内容区(按钮区除外),可渲染任意 HTML |
93
+
94
+ > 注意:`media` / `title` / `description` 仅在未传 `renderContent` 时生效;`renderContent` 优先级最高;`renderMedia` 优先于 `media`,且不受 `renderContent` 影响(需接管整个卡片请用 `renderContent`)。
95
+
96
+ ### `title` / `description` 的富文本与 XSS 防护
97
+
98
+ `title` 与 `description` 支持直接渲染 HTML(可加粗、插图、超链接等),但在渲染前会经过**白名单消毒**,剥离脚本与攻击向量:
99
+
100
+ - **允许的标签**:`a b strong i em u ins s del strike code kbd pre span div p br hr ul ol li blockquote q cite h1-h6 img sub sup mark small abbr figure figcaption` 等;其余标签解包保留文本,`script`/`iframe`/`object`/`embed`/`svg`/`template`/`form` 等危险标签连同内容一并删除。
101
+ - **允许的属性**:`href src alt title class style target rel width height` 等;所有 `on*` 事件处理器属性一律移除。
102
+ - **协议白名单**:`href`/`src` 仅放行 `http(s)`、相对路径、锚点、`mailto`/`tel`;`javascript:`/`vbscript:`/`file:`、`data:text/`、`data:image/svg` 等危险协议清空(`img` 的 `data:image/png|jpeg|gif|webp` 放行)。
103
+ - **内联 style 清洗**:阻断 `expression()`、`-moz-binding`、`behavior`、`url(javascript:)` 等 CSS 注入;外链 `<a target="_blank">` 自动补 `noopener noreferrer`。
104
+
105
+ 若需渲染不受限的 HTML,请改用 `renderContent` 自行控制(注意自行处理 XSS)。
106
+
107
+ ### `ButtonTexts`
108
+
109
+ | 属性 | 默认值 |
110
+ | --- | --- |
111
+ | `prev` | `'上一步'` |
112
+ | `next` | `'下一步'` |
113
+ | `skip` | `'跳过'` |
114
+ | `finish` | `'完成'` |
115
+
116
+ ### `StepContext`
117
+
118
+ 传给所有回调与 `renderContent` 的上下文:
119
+
120
+ | 属性 | 类型 | 说明 |
121
+ | --- | --- | --- |
122
+ | `index` | `number` | 当前步索引(从 0 开始) |
123
+ | `total` | `number` | 总步数 |
124
+ | `step` | `TourStep` | 当前步骤配置 |
125
+
126
+ ### `TourInstance` 方法
127
+
128
+ | 方法 | 说明 |
129
+ | --- | --- |
130
+ | `start()` | 启动引导(从第一个有效步骤开始;可重复调用,已启动则忽略) |
131
+ | `next()` | 前进到下一步(受 `onNext` 校验约束) |
132
+ | `prev()` | 返回上一步 |
133
+ | `skip()` | 跳过引导并触发 `onSkip` |
134
+ | `finish()` | 结束引导并触发 `onFinish` |
135
+ | `goTo(index)` | 跳转到指定步骤(越界忽略) |
136
+ | `destroy()` | 销毁实例并移除 DOM;之后不可再用,需重新 `createTour()` |
137
+
138
+ ## 事件与回调说明
139
+
140
+ - **`onNext` 拦截**:返回 `false`(或 `Promise<false>`)时停留在当前步。等待异步结果期间重复点击会被防重入,且用户若已 skip/finish/导航离开则不再前进。
141
+ - **步骤自动跳过**:某步 `target` 在文档中找不到时会跳到下一个有效步骤;所有步骤都无效时直接结束并触发 `onFinish`。
142
+ - **destroy 后调用**:任何实例方法都会抛错,提示重新 `createTour()`。
143
+
144
+ ## 自定义卡片内容
145
+
146
+ ```ts
147
+ const tour = createTour({
148
+ steps: [
149
+ {
150
+ target: '#upload',
151
+ renderContent(container, ctx) {
152
+ container.innerHTML = `
153
+ <h3>上传文件(${ctx.index + 1}/${ctx.total})</h3>
154
+ <p>支持拖拽上传,单文件不超过 100MB。</p>
155
+ <video src="/demo.mp4" controls></video>
156
+ `;
157
+ }
158
+ }
159
+ ]
160
+ });
161
+ ```
162
+
163
+ ## 开发
164
+
165
+ ```bash
166
+ npm run dev # 启动 demo 页
167
+ npm test # 运行测试(vitest)
168
+ npm run build # 构建产物到 dist/
169
+ ```
170
+
171
+ ## License
172
+
173
+ MIT
@@ -0,0 +1,3 @@
1
+ import { TourInstance, TourOptions } from './types';
2
+ /** 创建一个新手引导实例 */
3
+ export declare function createTour(options: TourOptions): TourInstance;
@@ -0,0 +1,5 @@
1
+ export { Tour } from './tour';
2
+ export { createTour } from './create-tour';
3
+ export { sanitizeHTML } from './sanitize';
4
+ export type { TourStep, TourOptions, TourInstance, StepContext, ButtonTexts } from './types';
5
+ export type { Placement } from './position';
Binary file
@@ -0,0 +1,139 @@
1
+ /*
2
+ 样式隔离:所有选择器都挂在 .lumen-tour-root 下,选择器优先级为 (0,2,0),
3
+ 高于外部常见的单类选择器(如全局 reset 或业务样式 .lt-xxx),
4
+ 避免宿主页面的样式轻易污染引导组件。
5
+ */
6
+ .lumen-tour-root,
7
+ .lumen-tour-root *,
8
+ .lumen-tour-root .lt-highlight,
9
+ .lumen-tour-root .lt-card,
10
+ .lumen-tour-root .lt-card * {
11
+ box-sizing: border-box;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC',
13
+ 'Microsoft YaHei', 'Helvetica Neue', Arial, sans-serif;
14
+ }
15
+
16
+ /* 引导根容器:全屏透明层,拦截引导期间对下层 DOM 的鼠标交互。
17
+ z-index 抬到常规页面浮层(导航栏/弹窗,常见 ≤9999)之上;
18
+ 高亮框与卡片是它的子节点,天然位于拦截层之上可正常交互 */
19
+ .lumen-tour-root {
20
+ position: fixed;
21
+ inset: 0;
22
+ z-index: 10000;
23
+ }
24
+
25
+ /* 挖孔高亮框(遮罩由 box-shadow 生成) */
26
+ .lumen-tour-root .lt-highlight {
27
+ position: fixed;
28
+ border-radius: 8px;
29
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
30
+ transition: all 0.25s ease;
31
+ pointer-events: none;
32
+ }
33
+
34
+ /* 卡片 */
35
+ .lumen-tour-root .lt-card {
36
+ position: fixed;
37
+ width: 320px;
38
+ max-width: calc(100vw - 32px);
39
+ background: #ffffff;
40
+ border-radius: 8px;
41
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16);
42
+ transition: top 0.25s ease, left 0.25s ease;
43
+ }
44
+
45
+ /* 指向目标的箭头:位置由 JS 计算在卡片与目标的 gap 中,这里只负责旋转 */
46
+ .lumen-tour-root .lt-arrow {
47
+ position: absolute;
48
+ width: 24px;
49
+ height: 27px;
50
+ pointer-events: none;
51
+ }
52
+
53
+ .lumen-tour-root .lt-arrow-bottom { transform: rotate(90deg); } /* 指向上 */
54
+ .lumen-tour-root .lt-arrow-top { transform: rotate(-90deg); } /* 指向下 */
55
+ .lumen-tour-root .lt-arrow-left { transform: rotate(180deg); } /* 指向右 */
56
+ .lumen-tour-root .lt-arrow-right { transform: rotate(0deg); } /* 指向左(原图方向) */
57
+
58
+ .lumen-tour-root .lt-media img {
59
+ display: block;
60
+ width: 100%;
61
+ border-top-left-radius: 8px;
62
+ border-top-right-radius: 8px;
63
+ background: #edf1fa;
64
+ }
65
+
66
+ .lumen-tour-root .lt-body {
67
+ }
68
+
69
+ .lumen-tour-root .lt-title {
70
+ font-size: 16px;
71
+ font-weight: 600;
72
+ color: #1E1F1F;
73
+ line-height: 1.4;
74
+ }
75
+
76
+ .lumen-tour-root .lt-content {
77
+ padding: 16px 16px 8px 16px;
78
+ }
79
+
80
+ .lumen-tour-root .lt-desc {
81
+ margin-top: 12px;
82
+ font-size: 12px;
83
+ color: #1E1F1F;
84
+ line-height: 1.6;
85
+ }
86
+
87
+ .lumen-tour-root .lt-footer {
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: space-between;
91
+ padding: 8px 16px 16px 16px;
92
+ }
93
+
94
+ .lumen-tour-root .lt-counter {
95
+ font-size: 14px;
96
+ color: rgba(142, 144, 145, 1);
97
+ }
98
+
99
+ .lumen-tour-root .lt-counter-cur {
100
+ /*color: #1f2329;*/
101
+ /*font-weight: 600;*/
102
+ }
103
+
104
+ .lumen-tour-root .lt-actions {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 8px;
108
+ }
109
+
110
+ .lumen-tour-root .lt-btn {
111
+ font-size: 12px;
112
+ line-height: 1;
113
+ cursor: pointer;
114
+ border-radius: 4px;
115
+ padding: 8px;
116
+ height: 28px;
117
+ box-sizing: border-box;
118
+ border: 1px solid #E6E8EB;
119
+ background-color: #FFF;
120
+ color: #1E1F1F;
121
+ transition: background 0.15s ease;
122
+ }
123
+
124
+ .lumen-tour-root .lt-btn:hover {
125
+ border-color: #0080FF;
126
+ color: #0080FF;
127
+ }
128
+
129
+ .lumen-tour-root .lt-btn-primary {
130
+ background-color: #0080FF;
131
+ border-color: #0080FF;
132
+ color: #ffffff;
133
+ }
134
+
135
+ .lumen-tour-root .lt-btn-primary:hover {
136
+ background-color: #59acff;
137
+ border-color: #59acff;
138
+ color: #ffffff;
139
+ }
Binary file
@@ -0,0 +1,17 @@
1
+ import { Rect } from './position';
2
+ /**
3
+ * 挖孔高亮框:用巨大 box-shadow 形成半透明遮罩(纯视觉)。
4
+ * 拦截点击由根容器 .lumen-tour-root 完成,挖孔与卡片都是它的子节点。
5
+ */
6
+ export declare class Overlay {
7
+ readonly el: HTMLElement;
8
+ private color;
9
+ constructor(root: HTMLElement, color?: string);
10
+ /** 按目标元素矩形(视口坐标)+ padding 更新挖孔位置 */
11
+ update(rect: Rect, padding: {
12
+ x: number;
13
+ y: number;
14
+ }): void;
15
+ hide(): void;
16
+ destroy(): void;
17
+ }
@@ -0,0 +1,40 @@
1
+ import { PositionResult, Rect } from './position';
2
+ import { ButtonTexts, StepContext } from './types';
3
+ export interface PopoverHandlers {
4
+ onPrev(): void;
5
+ onNext(): void;
6
+ onSkip(): void;
7
+ }
8
+ /**
9
+ * 引导卡片:内容区(快捷模板或 renderContent)+ 按钮区。
10
+ * 按钮区始终由组件渲染,导航逻辑收口在 Tour 中。
11
+ */
12
+ export declare class Popover {
13
+ readonly el: HTMLElement;
14
+ private contentEl;
15
+ private mediaEl;
16
+ private counterEl;
17
+ private prevBtn;
18
+ private nextBtn;
19
+ private skipBtn;
20
+ private texts;
21
+ private customTextKeys;
22
+ private hideSkipOnLast;
23
+ private placement?;
24
+ private arrowEl;
25
+ private root;
26
+ constructor(root: HTMLElement, texts: Partial<ButtonTexts>, handlers: PopoverHandlers, options?: {
27
+ hideSkipOnLast?: boolean;
28
+ });
29
+ /** 渲染某一步的卡片内容并定位 */
30
+ render(ctx: StepContext, targetRect: Rect): PositionResult;
31
+ /**
32
+ * 箭头定位在卡片与目标之间的 gap 中、指向目标(对齐 Figma:小号三角居中悬浮,
33
+ * 与卡片、目标之间各留空隙,不贴边)。
34
+ */
35
+ private updateArrow;
36
+ /** 不重渲染内容,仅按新目标矩形重新定位(resize/scroll 用) */
37
+ reposition(targetRect: Rect): void;
38
+ hide(): void;
39
+ destroy(): void;
40
+ }
@@ -0,0 +1,33 @@
1
+ export interface Rect {
2
+ top: number;
3
+ left: number;
4
+ width: number;
5
+ height: number;
6
+ }
7
+ export interface Viewport {
8
+ width: number;
9
+ height: number;
10
+ }
11
+ /**
12
+ * 卡片相对高亮框的方向。
13
+ * - top/bottom/left/right:贴对应边,交叉轴以高亮框为中心居中
14
+ * - topLeft/topRight/bottomLeft/bottomRight:贴上/下边,左/右缘对齐高亮框左/右缘
15
+ * - leftTop/leftBottom/rightTop/rightBottom:贴左/右边,上/下缘对齐高亮框上/下缘
16
+ */
17
+ export type Placement = 'top' | 'bottom' | 'left' | 'right' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
18
+ export interface PositionResult {
19
+ top: number;
20
+ left: number;
21
+ placement: Placement;
22
+ }
23
+ /**
24
+ * 计算卡片(position: fixed)相对高亮框的位置(调用方需传入含 padding 的高亮矩形)。
25
+ * 未指定 preferred 时:优先高亮框下方;下方放不下翻转到上方。
26
+ * 指定 preferred(12 个方向之一)时优先使用该方向,主轴放不下再回退到自动策略;
27
+ * 交叉轴溢出只夹紧在视口内,不触发回退。
28
+ * 交叉轴以高亮框为中心居中;溢出视口时与高亮框对应边缘对齐(而非贴视口边缘)。
29
+ */
30
+ export declare function computePosition(target: Rect, card: {
31
+ width: number;
32
+ height: number;
33
+ }, viewport: Viewport, gap?: number, preferred?: Placement): PositionResult;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 最小化 HTML 消毒器:基于白名单(allowlist)。
3
+ * 仅保留安全的标签与属性,剥离脚本、事件处理器与危险协议,用于 title / description 的富文本渲染。
4
+ * 零依赖,解析发生在脱离浏览上下文的 inert document 中(不执行脚本、不加载图片)。
5
+ */
6
+ /**
7
+ * 将任意 HTML 字符串清洗为安全的 DocumentFragment。
8
+ * 在脱离浏览上下文的 inert document 中解析,脚本不会执行、图片不会加载;
9
+ * 仅返回白名单内的节点。直接 appendChild 即可渲染。
10
+ */
11
+ export declare function sanitizeHTML(html: string): DocumentFragment;
package/dist/tour.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { TourInstance, TourOptions } from './types';
2
+ /**
3
+ * 引导状态机:管理当前步、遮罩、卡片与事件监听。
4
+ * 通过 createTour() 创建,不要直接 new。
5
+ */
6
+ export declare class Tour implements TourInstance {
7
+ private options;
8
+ private root;
9
+ private overlay;
10
+ private popover;
11
+ private index;
12
+ private started;
13
+ private destroyed;
14
+ private advancing;
15
+ constructor(options: TourOptions);
16
+ private get ctx();
17
+ start(): void;
18
+ next(): void;
19
+ private tryAdvance;
20
+ prev(): void;
21
+ skip(): void;
22
+ finish(): void;
23
+ goTo(index: number): void;
24
+ destroy(): void;
25
+ /** 结束本轮引导(保留实例,可再次 start) */
26
+ private end;
27
+ /** 找到第一个 target 可解析的步骤索引 */
28
+ private firstValidIndex;
29
+ private moveTo;
30
+ private resolveTarget;
31
+ /** 目标上下留出的最小余量:保证卡片(约 400px)大概率能放在目标下方 */
32
+ private static readonly SCROLL_MARGIN;
33
+ /**
34
+ * 目标不在可视区域内、或离上下边缘不足 SCROLL_MARGIN 时自动滚动,
35
+ * 用 block:'center' 把目标带到视口中部(而不是刚好贴边),
36
+ * 上下都留出空间放卡片;scrollIntoView 同时兼容嵌套滚动容器。
37
+ * 随后立刻测量位置;CSS smooth 滚动场景由 scroll 监听跟随重定位。
38
+ */
39
+ private scrollIntoViewIfNeeded;
40
+ private targetRect;
41
+ private handleReposition;
42
+ private handleKeydown;
43
+ private assertAlive;
44
+ }
@@ -0,0 +1,62 @@
1
+ /** 每一步的上下文,传给所有回调与自定义渲染函数 */
2
+ export interface StepContext {
3
+ /** 当前步索引(从 0 开始) */
4
+ index: number;
5
+ /** 总步数 */
6
+ total: number;
7
+ /** 当前步骤配置 */
8
+ step: TourStep;
9
+ }
10
+ export interface TourStep {
11
+ /** 必填:目标元素选择器或元素本身 */
12
+ target: string | HTMLElement;
13
+ /** 卡片相对目标的方向,默认自动(下方优先,放不下翻转到上方) */
14
+ placement?: import('./position').Placement;
15
+ /** 卡片标题,支持受限 HTML(白名单清洗:剥离 script/事件处理器/危险协议) */
16
+ title?: string;
17
+ /** 卡片描述文本,支持受限 HTML(同 title,经白名单清洗) */
18
+ description?: string;
19
+ /** 插图区图片 URL,可选 */
20
+ media?: string;
21
+ /** 传入则完全接管插图区(替代 media 图片) */
22
+ renderMedia?: (container: HTMLElement, ctx: StepContext) => void;
23
+ /** 传入则完全接管卡片内容区(按钮区除外) */
24
+ renderContent?: (container: HTMLElement, ctx: StepContext) => void;
25
+ }
26
+ export interface ButtonTexts {
27
+ prev: string;
28
+ next: string;
29
+ skip: string;
30
+ finish: string;
31
+ }
32
+ export interface TourOptions {
33
+ steps: TourStep[];
34
+ /** 覆盖任意按钮文案,默认 { prev:'上一步', next:'下一步', skip:'跳过', finish:'完成' } */
35
+ texts?: Partial<ButtonTexts>;
36
+ /** 遮罩颜色,默认 rgba(0,0,0,0.5) */
37
+ overlayColor?: string;
38
+ /**
39
+ * 挖孔相对目标元素的内边距,默认 8。
40
+ * 传 number 则四周等距;传 [x, y] 则水平 x、垂直 y。
41
+ */
42
+ padding?: number | [number, number];
43
+ /** 到达最后一步时是否隐藏"跳过"按钮,默认 false(保留显示) */
44
+ hideSkipOnLast?: boolean;
45
+ /** 键盘导航(→ 下一步、← 上一步、Esc 跳过),默认 true */
46
+ keyboard?: boolean;
47
+ /** 返回 false 可阻止前进(支持异步校验) */
48
+ onNext?: (ctx: StepContext) => void | boolean | Promise<void | boolean>;
49
+ onPrev?: (ctx: StepContext) => void;
50
+ onSkip?: (ctx: StepContext) => void;
51
+ onFinish?: (ctx: StepContext) => void;
52
+ onStepChange?: (ctx: StepContext) => void;
53
+ }
54
+ export interface TourInstance {
55
+ start(): void;
56
+ next(): void;
57
+ prev(): void;
58
+ skip(): void;
59
+ finish(): void;
60
+ goTo(index: number): void;
61
+ destroy(): void;
62
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "lumen-tour",
3
+ "version": "0.2.0",
4
+ "description": "Lightweight zero-dependency product tour / onboarding guide. TypeScript + native CSS.",
5
+ "author": "felixfjing",
6
+ "private": false,
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/felixfjing/lumen-tour.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/felixfjing/lumen-tour/issues"
14
+ },
15
+ "homepage": "https://github.com/felixfjing/lumen-tour#readme",
16
+ "type": "module",
17
+ "main": "./dist/lumen-tour.cjs",
18
+ "module": "./dist/lumen-tour.es.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/lumen-tour.es.js",
24
+ "require": "./dist/lumen-tour.cjs"
25
+ },
26
+ "./style.css": "./dist/lumen-tour.css"
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "sideEffects": [
32
+ "**/*.css"
33
+ ],
34
+ "scripts": {
35
+ "dev": "vite",
36
+ "demo": "vite",
37
+ "build": "vite build && node -e \"require('fs').copyFileSync('src/style.css','dist/lumen-tour.css')\"",
38
+ "test": "vitest run",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "keywords": [
42
+ "tour",
43
+ "onboarding",
44
+ "guide",
45
+ "intro",
46
+ "spotlight",
47
+ "vanilla",
48
+ "lightweight"
49
+ ],
50
+ "devDependencies": {
51
+ "happy-dom": "^20.11.8",
52
+ "typescript": "^5.9.3",
53
+ "vite": "^8.2.2",
54
+ "vite-plugin-dts": "^5.0.3",
55
+ "vitest": "^4.1.11"
56
+ }
57
+ }