@nextsparkjs/plugin-walkme 0.1.0-beta.104
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/.env.example +23 -0
- package/LICENSE +21 -0
- package/README.md +625 -0
- package/components/WalkmeBeacon.tsx +64 -0
- package/components/WalkmeControls.tsx +111 -0
- package/components/WalkmeModal.tsx +144 -0
- package/components/WalkmeOverlay.tsx +107 -0
- package/components/WalkmeProgress.tsx +53 -0
- package/components/WalkmeProvider.tsx +674 -0
- package/components/WalkmeSpotlight.tsx +188 -0
- package/components/WalkmeTooltip.tsx +152 -0
- package/examples/basic-tour.ts +38 -0
- package/examples/conditional-tour.ts +56 -0
- package/examples/cross-window-tour.ts +54 -0
- package/hooks/useTour.ts +52 -0
- package/hooks/useTourProgress.ts +38 -0
- package/hooks/useTourState.ts +146 -0
- package/hooks/useWalkme.ts +52 -0
- package/jest.config.cjs +27 -0
- package/lib/conditions.ts +113 -0
- package/lib/core.ts +323 -0
- package/lib/plugin-env.ts +87 -0
- package/lib/positioning.ts +172 -0
- package/lib/storage.ts +203 -0
- package/lib/targeting.ts +186 -0
- package/lib/triggers.ts +127 -0
- package/lib/validation.ts +122 -0
- package/messages/en.json +21 -0
- package/messages/es.json +21 -0
- package/package.json +18 -0
- package/plugin.config.ts +26 -0
- package/providers/walkme-context.ts +17 -0
- package/tests/lib/conditions.test.ts +172 -0
- package/tests/lib/core.test.ts +514 -0
- package/tests/lib/positioning.test.ts +43 -0
- package/tests/lib/storage.test.ts +232 -0
- package/tests/lib/targeting.test.ts +191 -0
- package/tests/lib/triggers.test.ts +198 -0
- package/tests/lib/validation.test.ts +249 -0
- package/tests/setup.ts +52 -0
- package/tests/tsconfig.json +32 -0
- package/tsconfig.json +47 -0
- package/types/walkme.types.ts +316 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TourTriggerSchema,
|
|
3
|
+
TourStepSchema,
|
|
4
|
+
TourSchema,
|
|
5
|
+
TourArraySchema,
|
|
6
|
+
validateTour,
|
|
7
|
+
validateTours,
|
|
8
|
+
validateStep,
|
|
9
|
+
} from '../../lib/validation'
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Fixtures
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
const validStep = {
|
|
16
|
+
id: 'step-1',
|
|
17
|
+
type: 'modal',
|
|
18
|
+
title: 'Title',
|
|
19
|
+
content: 'Content',
|
|
20
|
+
actions: ['next'],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validTooltipStep = {
|
|
24
|
+
id: 'tooltip-1',
|
|
25
|
+
type: 'tooltip',
|
|
26
|
+
target: '#my-element',
|
|
27
|
+
title: 'Tooltip',
|
|
28
|
+
content: 'Content',
|
|
29
|
+
position: 'bottom',
|
|
30
|
+
actions: ['next', 'prev'],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const validTour = {
|
|
34
|
+
id: 'tour-1',
|
|
35
|
+
name: 'My Tour',
|
|
36
|
+
trigger: { type: 'manual' },
|
|
37
|
+
steps: [validStep],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// TourTriggerSchema
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
describe('TourTriggerSchema', () => {
|
|
45
|
+
it('accepts all valid trigger types', () => {
|
|
46
|
+
for (const type of ['onFirstVisit', 'onRouteEnter', 'onEvent', 'manual', 'scheduled']) {
|
|
47
|
+
expect(TourTriggerSchema.safeParse({ type }).success).toBe(true)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('rejects invalid trigger type', () => {
|
|
52
|
+
expect(TourTriggerSchema.safeParse({ type: 'invalid' }).success).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('accepts optional delay, route, event fields', () => {
|
|
56
|
+
const result = TourTriggerSchema.safeParse({
|
|
57
|
+
type: 'onRouteEnter',
|
|
58
|
+
delay: 500,
|
|
59
|
+
route: '/dashboard/*',
|
|
60
|
+
})
|
|
61
|
+
expect(result.success).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('rejects negative delay', () => {
|
|
65
|
+
const result = TourTriggerSchema.safeParse({ type: 'manual', delay: -1 })
|
|
66
|
+
expect(result.success).toBe(false)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// TourStepSchema
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
describe('TourStepSchema', () => {
|
|
75
|
+
it('accepts a valid modal step (no target required)', () => {
|
|
76
|
+
const result = TourStepSchema.safeParse(validStep)
|
|
77
|
+
expect(result.success).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('accepts a valid tooltip step with target', () => {
|
|
81
|
+
const result = TourStepSchema.safeParse(validTooltipStep)
|
|
82
|
+
expect(result.success).toBe(true)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('rejects tooltip step without target', () => {
|
|
86
|
+
const result = TourStepSchema.safeParse({
|
|
87
|
+
id: 'tooltip-bad',
|
|
88
|
+
type: 'tooltip',
|
|
89
|
+
title: 'Bad',
|
|
90
|
+
content: 'No target',
|
|
91
|
+
actions: ['next'],
|
|
92
|
+
})
|
|
93
|
+
expect(result.success).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('rejects spotlight step without target', () => {
|
|
97
|
+
const result = TourStepSchema.safeParse({
|
|
98
|
+
id: 'spotlight-bad',
|
|
99
|
+
type: 'spotlight',
|
|
100
|
+
title: 'Bad',
|
|
101
|
+
content: 'No target',
|
|
102
|
+
actions: ['next'],
|
|
103
|
+
})
|
|
104
|
+
expect(result.success).toBe(false)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('rejects beacon step without target', () => {
|
|
108
|
+
const result = TourStepSchema.safeParse({
|
|
109
|
+
id: 'beacon-bad',
|
|
110
|
+
type: 'beacon',
|
|
111
|
+
title: 'Bad',
|
|
112
|
+
content: 'No target',
|
|
113
|
+
actions: ['next'],
|
|
114
|
+
})
|
|
115
|
+
expect(result.success).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('rejects step with empty id', () => {
|
|
119
|
+
const result = TourStepSchema.safeParse({ ...validStep, id: '' })
|
|
120
|
+
expect(result.success).toBe(false)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('rejects step with empty actions array', () => {
|
|
124
|
+
const result = TourStepSchema.safeParse({ ...validStep, actions: [] })
|
|
125
|
+
expect(result.success).toBe(false)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('rejects step with invalid action', () => {
|
|
129
|
+
const result = TourStepSchema.safeParse({ ...validStep, actions: ['invalid'] })
|
|
130
|
+
expect(result.success).toBe(false)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('accepts all valid step types', () => {
|
|
134
|
+
for (const type of ['tooltip', 'modal', 'spotlight', 'beacon', 'floating']) {
|
|
135
|
+
const step = type === 'modal' || type === 'floating'
|
|
136
|
+
? { ...validStep, type }
|
|
137
|
+
: { ...validStep, type, target: '#el' }
|
|
138
|
+
expect(TourStepSchema.safeParse(step).success).toBe(true)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// TourSchema
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
describe('TourSchema', () => {
|
|
148
|
+
it('accepts a valid tour', () => {
|
|
149
|
+
const result = TourSchema.safeParse(validTour)
|
|
150
|
+
expect(result.success).toBe(true)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('rejects tour with empty id', () => {
|
|
154
|
+
const result = TourSchema.safeParse({ ...validTour, id: '' })
|
|
155
|
+
expect(result.success).toBe(false)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('rejects tour with no steps', () => {
|
|
159
|
+
const result = TourSchema.safeParse({ ...validTour, steps: [] })
|
|
160
|
+
expect(result.success).toBe(false)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('accepts tour with conditions', () => {
|
|
164
|
+
const result = TourSchema.safeParse({
|
|
165
|
+
...validTour,
|
|
166
|
+
conditions: {
|
|
167
|
+
userRole: ['admin'],
|
|
168
|
+
completedTours: ['onboarding'],
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
expect(result.success).toBe(true)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('accepts tour with priority', () => {
|
|
175
|
+
const result = TourSchema.safeParse({ ...validTour, priority: 10 })
|
|
176
|
+
expect(result.success).toBe(true)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// TourArraySchema
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
describe('TourArraySchema', () => {
|
|
185
|
+
it('validates an array of tours', () => {
|
|
186
|
+
const result = TourArraySchema.safeParse([validTour, { ...validTour, id: 'tour-2' }])
|
|
187
|
+
expect(result.success).toBe(true)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('rejects if any tour is invalid', () => {
|
|
191
|
+
const result = TourArraySchema.safeParse([validTour, { id: '' }])
|
|
192
|
+
expect(result.success).toBe(false)
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// validateTour
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
describe('validateTour', () => {
|
|
201
|
+
it('returns valid: true for a valid tour', () => {
|
|
202
|
+
const result = validateTour(validTour)
|
|
203
|
+
expect(result.valid).toBe(true)
|
|
204
|
+
expect(result.tour).toBeDefined()
|
|
205
|
+
expect(result.errors).toBeUndefined()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('returns valid: false with errors for invalid tour', () => {
|
|
209
|
+
const result = validateTour({ id: '' })
|
|
210
|
+
expect(result.valid).toBe(false)
|
|
211
|
+
expect(result.errors).toBeDefined()
|
|
212
|
+
expect(result.tour).toBeUndefined()
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// validateTours
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
describe('validateTours', () => {
|
|
221
|
+
it('filters valid tours from mixed input', () => {
|
|
222
|
+
const result = validateTours([validTour, { id: '' }, { ...validTour, id: 'tour-2' }])
|
|
223
|
+
expect(result.validTours).toHaveLength(2)
|
|
224
|
+
expect(result.errors).toHaveLength(1)
|
|
225
|
+
expect(result.valid).toBe(false)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('returns valid: true when all tours are valid', () => {
|
|
229
|
+
const result = validateTours([validTour])
|
|
230
|
+
expect(result.valid).toBe(true)
|
|
231
|
+
expect(result.errors).toBeUndefined()
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// validateStep
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
describe('validateStep', () => {
|
|
240
|
+
it('returns valid: true for a valid step', () => {
|
|
241
|
+
expect(validateStep(validStep).valid).toBe(true)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('returns valid: false for invalid step', () => {
|
|
245
|
+
const result = validateStep({ id: '' })
|
|
246
|
+
expect(result.valid).toBe(false)
|
|
247
|
+
expect(result.errors).toBeDefined()
|
|
248
|
+
})
|
|
249
|
+
})
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest test setup for WalkMe plugin.
|
|
3
|
+
* Polyfills and mocks needed for jsdom environment.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Mock localStorage
|
|
7
|
+
const localStorageMock = (() => {
|
|
8
|
+
let store: Record<string, string> = {}
|
|
9
|
+
return {
|
|
10
|
+
getItem: jest.fn((key: string) => store[key] ?? null),
|
|
11
|
+
setItem: jest.fn((key: string, value: string) => {
|
|
12
|
+
store[key] = value
|
|
13
|
+
}),
|
|
14
|
+
removeItem: jest.fn((key: string) => {
|
|
15
|
+
delete store[key]
|
|
16
|
+
}),
|
|
17
|
+
clear: jest.fn(() => {
|
|
18
|
+
store = {}
|
|
19
|
+
}),
|
|
20
|
+
get length() {
|
|
21
|
+
return Object.keys(store).length
|
|
22
|
+
},
|
|
23
|
+
key: jest.fn((index: number) => Object.keys(store)[index] ?? null),
|
|
24
|
+
}
|
|
25
|
+
})()
|
|
26
|
+
|
|
27
|
+
Object.defineProperty(window, 'localStorage', {
|
|
28
|
+
value: localStorageMock,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Mock MutationObserver
|
|
32
|
+
global.MutationObserver = jest.fn().mockImplementation(() => ({
|
|
33
|
+
observe: jest.fn(),
|
|
34
|
+
disconnect: jest.fn(),
|
|
35
|
+
takeRecords: jest.fn().mockReturnValue([]),
|
|
36
|
+
}))
|
|
37
|
+
|
|
38
|
+
// Mock ResizeObserver
|
|
39
|
+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
40
|
+
observe: jest.fn(),
|
|
41
|
+
unobserve: jest.fn(),
|
|
42
|
+
disconnect: jest.fn(),
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
// Mock scrollIntoView
|
|
46
|
+
Element.prototype.scrollIntoView = jest.fn()
|
|
47
|
+
|
|
48
|
+
// Reset localStorage between tests
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
localStorageMock.clear()
|
|
51
|
+
jest.clearAllMocks()
|
|
52
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"types": ["jest"],
|
|
11
|
+
"typeRoots": [
|
|
12
|
+
"../../../packages/core/node_modules/@types"
|
|
13
|
+
],
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"baseUrl": "..",
|
|
16
|
+
"paths": {
|
|
17
|
+
"~/*": ["./*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": [
|
|
21
|
+
"**/*.test.ts",
|
|
22
|
+
"**/*.test.tsx",
|
|
23
|
+
"setup.ts",
|
|
24
|
+
"../lib/**/*.ts",
|
|
25
|
+
"../types/**/*.ts",
|
|
26
|
+
"../hooks/**/*.ts",
|
|
27
|
+
"../hooks/**/*.tsx",
|
|
28
|
+
"../components/**/*.tsx",
|
|
29
|
+
"../providers/**/*.ts"
|
|
30
|
+
],
|
|
31
|
+
"exclude": ["node_modules"]
|
|
32
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"baseUrl": ".",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"paths": {
|
|
8
|
+
"@/*": ["../../../*"],
|
|
9
|
+
"@/core/*": ["../../../core/*"],
|
|
10
|
+
"@/contents/*": ["../../../contents/*"],
|
|
11
|
+
"~/*": ["./*"]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"watchOptions": {
|
|
15
|
+
"watchFile": "useFsEvents",
|
|
16
|
+
"watchDirectory": "useFsEvents",
|
|
17
|
+
"fallbackPolling": "dynamicPriority",
|
|
18
|
+
"synchronousWatchDirectory": false,
|
|
19
|
+
"excludeDirectories": [
|
|
20
|
+
"**/node_modules",
|
|
21
|
+
"**/.next",
|
|
22
|
+
"**/dist",
|
|
23
|
+
"**/build",
|
|
24
|
+
"**/.turbo",
|
|
25
|
+
"**/coverage",
|
|
26
|
+
"**/.git"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"include": [
|
|
30
|
+
"**/*.ts",
|
|
31
|
+
"**/*.tsx",
|
|
32
|
+
"plugin.config.ts"
|
|
33
|
+
],
|
|
34
|
+
"exclude": [
|
|
35
|
+
"node_modules",
|
|
36
|
+
".next",
|
|
37
|
+
"dist",
|
|
38
|
+
"build",
|
|
39
|
+
".turbo",
|
|
40
|
+
"coverage",
|
|
41
|
+
".git",
|
|
42
|
+
"**/*.test.ts",
|
|
43
|
+
"**/*.test.tsx",
|
|
44
|
+
"**/__tests__/**",
|
|
45
|
+
"**/tsconfig.tsbuildinfo"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WalkMe Plugin - Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Complete type system for the guided tour and onboarding plugin.
|
|
5
|
+
* All types are exported from this single file as the source of truth.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Enums / Literal Unions
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/** Tour lifecycle states */
|
|
13
|
+
export type TourStatus = 'idle' | 'active' | 'paused' | 'completed' | 'skipped'
|
|
14
|
+
|
|
15
|
+
/** Visual step types */
|
|
16
|
+
export type StepType = 'tooltip' | 'modal' | 'spotlight' | 'beacon' | 'floating'
|
|
17
|
+
|
|
18
|
+
/** Tooltip positioning relative to target */
|
|
19
|
+
export type StepPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto'
|
|
20
|
+
|
|
21
|
+
/** How a tour is activated */
|
|
22
|
+
export type TriggerType = 'onFirstVisit' | 'onRouteEnter' | 'onEvent' | 'manual' | 'scheduled'
|
|
23
|
+
|
|
24
|
+
/** User actions available within a step */
|
|
25
|
+
export type StepAction = 'next' | 'prev' | 'skip' | 'complete' | 'close'
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Tour Configuration
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/** Defines a complete guided tour */
|
|
32
|
+
export interface Tour {
|
|
33
|
+
/** Unique identifier for the tour */
|
|
34
|
+
id: string
|
|
35
|
+
/** Human-readable name */
|
|
36
|
+
name: string
|
|
37
|
+
/** Optional description */
|
|
38
|
+
description?: string
|
|
39
|
+
/** How and when the tour should be triggered */
|
|
40
|
+
trigger: TourTrigger
|
|
41
|
+
/** Optional conditions that must be met to show the tour */
|
|
42
|
+
conditions?: TourConditions
|
|
43
|
+
/** Ordered list of steps in the tour */
|
|
44
|
+
steps: TourStep[]
|
|
45
|
+
/** Callback when the tour is completed */
|
|
46
|
+
onComplete?: () => void
|
|
47
|
+
/** Callback when the tour is skipped */
|
|
48
|
+
onSkip?: () => void
|
|
49
|
+
/** Priority for auto-trigger ordering (lower = higher priority) */
|
|
50
|
+
priority?: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Defines a single step within a tour */
|
|
54
|
+
export interface TourStep {
|
|
55
|
+
/** Unique identifier for the step */
|
|
56
|
+
id: string
|
|
57
|
+
/** Visual type of the step */
|
|
58
|
+
type: StepType
|
|
59
|
+
/** Step title */
|
|
60
|
+
title: string
|
|
61
|
+
/** Step content / description */
|
|
62
|
+
content: string
|
|
63
|
+
/** CSS selector or data-walkme-target value to anchor to */
|
|
64
|
+
target?: string
|
|
65
|
+
/** Route path for cross-page tours (triggers navigation if different from current) */
|
|
66
|
+
route?: string
|
|
67
|
+
/** Positioning for tooltip/spotlight types */
|
|
68
|
+
position?: StepPosition
|
|
69
|
+
/** Available user actions for this step */
|
|
70
|
+
actions: StepAction[]
|
|
71
|
+
/** Delay in ms before showing this step */
|
|
72
|
+
delay?: number
|
|
73
|
+
/** Auto-advance to next step after this many ms */
|
|
74
|
+
autoAdvance?: number
|
|
75
|
+
/** Callback before the step is shown */
|
|
76
|
+
beforeShow?: () => Promise<void> | void
|
|
77
|
+
/** Callback after the step is shown */
|
|
78
|
+
afterShow?: () => Promise<void> | void
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Defines when a tour should be activated */
|
|
82
|
+
export interface TourTrigger {
|
|
83
|
+
/** Trigger mechanism */
|
|
84
|
+
type: TriggerType
|
|
85
|
+
/** Delay in ms before activating after trigger condition is met */
|
|
86
|
+
delay?: number
|
|
87
|
+
/** Route pattern for onRouteEnter trigger */
|
|
88
|
+
route?: string
|
|
89
|
+
/** Event name for onEvent trigger */
|
|
90
|
+
event?: string
|
|
91
|
+
/** Activate after this many visits (for scheduled trigger) */
|
|
92
|
+
afterVisits?: number
|
|
93
|
+
/** Activate after this many days since first visit (for scheduled trigger) */
|
|
94
|
+
afterDays?: number
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Conditions that must be met before a tour can start */
|
|
98
|
+
export interface TourConditions {
|
|
99
|
+
/** User must have one of these roles */
|
|
100
|
+
userRole?: string[]
|
|
101
|
+
/** All of these feature flags must be active */
|
|
102
|
+
featureFlags?: string[]
|
|
103
|
+
/** All of these tours must be completed first */
|
|
104
|
+
completedTours?: string[]
|
|
105
|
+
/** None of these tours should be completed */
|
|
106
|
+
notCompletedTours?: string[]
|
|
107
|
+
/** Custom condition function */
|
|
108
|
+
custom?: (context: ConditionContext) => boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Context passed to condition evaluators */
|
|
112
|
+
export interface ConditionContext {
|
|
113
|
+
/** Current user role */
|
|
114
|
+
userRole?: string
|
|
115
|
+
/** Active feature flags */
|
|
116
|
+
featureFlags?: string[]
|
|
117
|
+
/** IDs of completed tours */
|
|
118
|
+
completedTourIds: string[]
|
|
119
|
+
/** Total number of page visits */
|
|
120
|
+
visitCount: number
|
|
121
|
+
/** ISO date string of first visit */
|
|
122
|
+
firstVisitDate?: string
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// State Management
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/** Runtime state for a single tour */
|
|
130
|
+
export interface TourState {
|
|
131
|
+
/** Tour identifier */
|
|
132
|
+
tourId: string
|
|
133
|
+
/** Current status */
|
|
134
|
+
status: TourStatus
|
|
135
|
+
/** Index of the current step */
|
|
136
|
+
currentStepIndex: number
|
|
137
|
+
/** ISO timestamp when the tour was started */
|
|
138
|
+
startedAt: string
|
|
139
|
+
/** ISO timestamp when completed */
|
|
140
|
+
completedAt?: string
|
|
141
|
+
/** ISO timestamp when skipped */
|
|
142
|
+
skippedAt?: string
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Global state for the WalkMe system */
|
|
146
|
+
export interface WalkmeState {
|
|
147
|
+
/** Map of registered tours by ID */
|
|
148
|
+
tours: Record<string, Tour>
|
|
149
|
+
/** Currently active tour state (null if no tour is active) */
|
|
150
|
+
activeTour: TourState | null
|
|
151
|
+
/** IDs of completed tours */
|
|
152
|
+
completedTours: string[]
|
|
153
|
+
/** IDs of skipped tours */
|
|
154
|
+
skippedTours: string[]
|
|
155
|
+
/** Historical state of all tours */
|
|
156
|
+
tourHistory: Record<string, TourState>
|
|
157
|
+
/** Whether the system has been initialized */
|
|
158
|
+
initialized: boolean
|
|
159
|
+
/** Debug mode flag */
|
|
160
|
+
debug: boolean
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Discriminated union of all reducer actions */
|
|
164
|
+
export type WalkmeAction =
|
|
165
|
+
| { type: 'INITIALIZE'; tours: Tour[] }
|
|
166
|
+
| { type: 'UPDATE_TOURS'; tours: Tour[] }
|
|
167
|
+
| { type: 'START_TOUR'; tourId: string }
|
|
168
|
+
| { type: 'NEXT_STEP' }
|
|
169
|
+
| { type: 'PREV_STEP' }
|
|
170
|
+
| { type: 'SKIP_TOUR' }
|
|
171
|
+
| { type: 'COMPLETE_TOUR' }
|
|
172
|
+
| { type: 'PAUSE_TOUR' }
|
|
173
|
+
| { type: 'RESUME_TOUR' }
|
|
174
|
+
| { type: 'RESET_TOUR'; tourId: string }
|
|
175
|
+
| { type: 'RESET_ALL' }
|
|
176
|
+
| { type: 'NAVIGATE_TO_STEP'; stepIndex: number }
|
|
177
|
+
| { type: 'SET_DEBUG'; enabled: boolean }
|
|
178
|
+
| { type: 'RESTORE_STATE'; completedTours: string[]; skippedTours: string[]; tourHistory: Record<string, TourState>; activeTour: TourState | null }
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Labels / i18n
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
/** Translatable UI strings for walkme components */
|
|
185
|
+
export interface WalkmeLabels {
|
|
186
|
+
/** "Next" button text */
|
|
187
|
+
next: string
|
|
188
|
+
/** "Previous" button text */
|
|
189
|
+
prev: string
|
|
190
|
+
/** "Skip tour" button text */
|
|
191
|
+
skip: string
|
|
192
|
+
/** "Complete" button text */
|
|
193
|
+
complete: string
|
|
194
|
+
/** "Close" button aria-label */
|
|
195
|
+
close: string
|
|
196
|
+
/** Progress text template, receives {current} and {total} */
|
|
197
|
+
progress: string
|
|
198
|
+
/** Beacon fallback aria-label */
|
|
199
|
+
tourAvailable: string
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Context / Provider
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
/** Value exposed by WalkmeContext to consumers */
|
|
207
|
+
export interface WalkmeContextValue {
|
|
208
|
+
/** Current global state */
|
|
209
|
+
state: WalkmeState
|
|
210
|
+
/** Start a specific tour by ID */
|
|
211
|
+
startTour: (tourId: string) => void
|
|
212
|
+
/** Pause the active tour */
|
|
213
|
+
pauseTour: () => void
|
|
214
|
+
/** Resume a paused tour */
|
|
215
|
+
resumeTour: () => void
|
|
216
|
+
/** Skip the active tour */
|
|
217
|
+
skipTour: () => void
|
|
218
|
+
/** Complete the active tour */
|
|
219
|
+
completeTour: () => void
|
|
220
|
+
/** Reset a specific tour (remove from completed/skipped) */
|
|
221
|
+
resetTour: (tourId: string) => void
|
|
222
|
+
/** Reset all tours to initial state */
|
|
223
|
+
resetAllTours: () => void
|
|
224
|
+
/** Advance to the next step */
|
|
225
|
+
nextStep: () => void
|
|
226
|
+
/** Go back to the previous step */
|
|
227
|
+
prevStep: () => void
|
|
228
|
+
/** Jump to a specific step index */
|
|
229
|
+
goToStep: (stepIndex: number) => void
|
|
230
|
+
/** Check if a tour has been completed */
|
|
231
|
+
isTourCompleted: (tourId: string) => boolean
|
|
232
|
+
/** Check if a tour is currently active */
|
|
233
|
+
isTourActive: (tourId: string) => boolean
|
|
234
|
+
/** Get the full Tour object for the active tour */
|
|
235
|
+
getActiveTour: () => Tour | null
|
|
236
|
+
/** Get the current TourStep for the active tour */
|
|
237
|
+
getActiveStep: () => TourStep | null
|
|
238
|
+
/** Emit a custom event (for onEvent triggers) */
|
|
239
|
+
emitEvent: (eventName: string) => void
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Props for the WalkmeProvider component */
|
|
243
|
+
export interface WalkmeProviderProps {
|
|
244
|
+
/** Array of tour definitions */
|
|
245
|
+
tours: Tour[]
|
|
246
|
+
/** React children */
|
|
247
|
+
children: React.ReactNode
|
|
248
|
+
/** Enable debug logging */
|
|
249
|
+
debug?: boolean
|
|
250
|
+
/** Auto-start eligible tours */
|
|
251
|
+
autoStart?: boolean
|
|
252
|
+
/** Delay before auto-starting tours (ms) */
|
|
253
|
+
autoStartDelay?: number
|
|
254
|
+
/** Persist tour state in localStorage */
|
|
255
|
+
persistState?: boolean
|
|
256
|
+
/** Callback when a tour starts */
|
|
257
|
+
onTourStart?: (event: TourEvent) => void
|
|
258
|
+
/** Callback when a tour is completed */
|
|
259
|
+
onTourComplete?: (event: TourEvent) => void
|
|
260
|
+
/** Callback when a tour is skipped */
|
|
261
|
+
onTourSkip?: (event: TourEvent) => void
|
|
262
|
+
/** Callback when a step changes */
|
|
263
|
+
onStepChange?: (event: TourEvent) => void
|
|
264
|
+
/** Context for evaluating tour conditions */
|
|
265
|
+
conditionContext?: Omit<ConditionContext, 'completedTourIds' | 'visitCount' | 'firstVisitDate'>
|
|
266
|
+
/** Translated UI labels (defaults to English if not provided) */
|
|
267
|
+
labels?: Partial<WalkmeLabels>
|
|
268
|
+
/** User ID for scoping localStorage persistence per user */
|
|
269
|
+
userId?: string
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Storage
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
/** Schema for localStorage persistence */
|
|
277
|
+
export interface StorageSchema {
|
|
278
|
+
/** Schema version for migrations */
|
|
279
|
+
version: number
|
|
280
|
+
/** IDs of completed tours */
|
|
281
|
+
completedTours: string[]
|
|
282
|
+
/** IDs of skipped tours */
|
|
283
|
+
skippedTours: string[]
|
|
284
|
+
/** Currently active tour state */
|
|
285
|
+
activeTour: TourState | null
|
|
286
|
+
/** Historical state of all tours */
|
|
287
|
+
tourHistory: Record<string, TourState>
|
|
288
|
+
/** Total page visit count */
|
|
289
|
+
visitCount: number
|
|
290
|
+
/** ISO date string of first visit */
|
|
291
|
+
firstVisitDate: string
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Events / Analytics
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
/** Event emitted for analytics tracking */
|
|
299
|
+
export interface TourEvent {
|
|
300
|
+
/** Event type */
|
|
301
|
+
type: 'tour_started' | 'tour_completed' | 'tour_skipped' | 'step_changed' | 'step_completed'
|
|
302
|
+
/** Tour identifier */
|
|
303
|
+
tourId: string
|
|
304
|
+
/** Tour human-readable name */
|
|
305
|
+
tourName: string
|
|
306
|
+
/** Current step identifier */
|
|
307
|
+
stepId?: string
|
|
308
|
+
/** Current step index */
|
|
309
|
+
stepIndex?: number
|
|
310
|
+
/** Total steps in the tour */
|
|
311
|
+
totalSteps?: number
|
|
312
|
+
/** Unix timestamp */
|
|
313
|
+
timestamp: number
|
|
314
|
+
/** Additional metadata */
|
|
315
|
+
metadata?: Record<string, unknown>
|
|
316
|
+
}
|