@eventop/sdk 1.1.4 → 1.1.5
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/dist/index.cjs +1076 -15
- package/dist/index.js +1076 -15
- package/dist/react/index.cjs +1266 -205
- package/dist/react/index.js +1267 -206
- package/package.json +2 -1
package/dist/react/index.js
CHANGED
|
@@ -1,223 +1,1047 @@
|
|
|
1
|
-
import { createContext, useContext, useRef, useEffect, Children, cloneElement, useState
|
|
1
|
+
import { createContext, useContext, useRef, useCallback, useEffect, Children, cloneElement, useState } from 'react';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
const EventopRegistryContext = /*#__PURE__*/createContext(null);
|
|
4
|
+
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
5
|
+
|
|
6
|
+
var core = {exports: {}};
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* ╔══════════════════════════════════════════════════════════════╗
|
|
10
|
+
* ║ @eventop/sdk v1.1.0 ║
|
|
11
|
+
* ║ AI-powered guided tours — themeable, provider-agnostic ║
|
|
12
|
+
* ║ ║
|
|
13
|
+
* ║ Provider: always proxy through your own server. ║
|
|
14
|
+
* ║ Never expose API keys in client-side code. ║
|
|
15
|
+
* ╚══════════════════════════════════════════════════════════════╝
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
17
|
+
(function (module) {
|
|
18
|
+
(function (global, factory) {
|
|
19
|
+
if (module.exports) {
|
|
20
|
+
module.exports = factory();
|
|
21
|
+
} else {
|
|
22
|
+
global.Eventop = factory();
|
|
23
|
+
}
|
|
24
|
+
})(typeof globalThis !== 'undefined' ? globalThis : commonjsGlobal, function () {
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
26
|
+
const SHEPHERD_CSS = 'https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/css/shepherd.css';
|
|
27
|
+
const SHEPHERD_JS = 'https://cdn.jsdelivr.net/npm/shepherd.js@11.2.0/dist/js/shepherd.min.js';
|
|
28
|
+
|
|
29
|
+
// ─── Internal state ──────────────────────────────────────────────────────────
|
|
30
|
+
let _provider = null;
|
|
31
|
+
let _config = null;
|
|
32
|
+
let _tour = null;
|
|
33
|
+
let _isOpen = false;
|
|
34
|
+
let _messages = [];
|
|
35
|
+
let _mediaQuery = null;
|
|
36
|
+
|
|
37
|
+
// Pause/resume state
|
|
38
|
+
let _pausedSteps = null;
|
|
39
|
+
let _pausedIndex = 0;
|
|
40
|
+
|
|
41
|
+
// Active cleanup callbacks — cleared when tour ends or is paused
|
|
42
|
+
let _cleanups = [];
|
|
43
|
+
|
|
44
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
+
// THEME ENGINE
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
47
|
+
|
|
48
|
+
const DARK_TOKENS = {
|
|
49
|
+
accent: '#e94560',
|
|
50
|
+
accentSecondary: '#a855f7',
|
|
51
|
+
bg: '#0f0f1a',
|
|
52
|
+
surface: '#1a1a2e',
|
|
53
|
+
border: '#2a2a4a',
|
|
54
|
+
text: '#e0e0f0',
|
|
55
|
+
textDim: '#6060a0',
|
|
56
|
+
radius: '16px',
|
|
57
|
+
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif"
|
|
58
|
+
};
|
|
59
|
+
const LIGHT_TOKENS = {
|
|
60
|
+
accent: '#e94560',
|
|
61
|
+
accentSecondary: '#7c3aed',
|
|
62
|
+
bg: '#ffffff',
|
|
63
|
+
surface: '#f8f8fc',
|
|
64
|
+
border: '#e4e4f0',
|
|
65
|
+
text: '#1a1a2e',
|
|
66
|
+
textDim: '#888899',
|
|
67
|
+
radius: '16px',
|
|
68
|
+
fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif"
|
|
69
|
+
};
|
|
70
|
+
const PRESETS = {
|
|
71
|
+
default: {},
|
|
72
|
+
minimal: {
|
|
73
|
+
accent: '#000000',
|
|
74
|
+
accentSecondary: '#333333',
|
|
75
|
+
radius: '8px'
|
|
76
|
+
},
|
|
77
|
+
soft: {
|
|
78
|
+
accent: '#6366f1',
|
|
79
|
+
accentSecondary: '#8b5cf6',
|
|
80
|
+
radius: '20px'
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
function resolveTheme(themeConfig = {}) {
|
|
84
|
+
var _window$matchMedia, _window;
|
|
85
|
+
const {
|
|
86
|
+
mode = 'auto',
|
|
87
|
+
preset = 'default',
|
|
88
|
+
tokens = {}
|
|
89
|
+
} = themeConfig;
|
|
90
|
+
const isDark = mode === 'auto' ? ((_window$matchMedia = (_window = window).matchMedia) === null || _window$matchMedia === void 0 ? void 0 : _window$matchMedia.call(_window, '(prefers-color-scheme: dark)').matches) ?? true : mode === 'dark';
|
|
91
|
+
const base = isDark ? {
|
|
92
|
+
...DARK_TOKENS
|
|
93
|
+
} : {
|
|
94
|
+
...LIGHT_TOKENS
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
...base,
|
|
98
|
+
...(PRESETS[preset] || {}),
|
|
99
|
+
...tokens,
|
|
100
|
+
_isDark: isDark
|
|
101
|
+
};
|
|
46
102
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
wrapped = /*#__PURE__*/cloneElement(child, {
|
|
66
|
-
[dataAttr]: '',
|
|
67
|
-
ref: node => {
|
|
68
|
-
ref.current = node;
|
|
69
|
-
const originalRef = child.ref;
|
|
70
|
-
if (typeof originalRef === 'function') originalRef(node);else if (originalRef && 'current' in originalRef) originalRef.current = node;
|
|
103
|
+
function buildCSSVars(t) {
|
|
104
|
+
return `
|
|
105
|
+
--sai-accent: ${t.accent};
|
|
106
|
+
--sai-accent2: ${t.accentSecondary};
|
|
107
|
+
--sai-bg: ${t.bg};
|
|
108
|
+
--sai-surface: ${t.surface};
|
|
109
|
+
--sai-border: ${t.border};
|
|
110
|
+
--sai-text: ${t.text};
|
|
111
|
+
--sai-text-dim: ${t.textDim};
|
|
112
|
+
--sai-radius: ${t.radius};
|
|
113
|
+
--sai-font: ${t.fontFamily};
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
function applyTheme(t) {
|
|
117
|
+
const panel = document.getElementById('sai-panel');
|
|
118
|
+
const trigger = document.getElementById('sai-trigger');
|
|
119
|
+
if (panel) {
|
|
120
|
+
panel.style.cssText += buildCSSVars(t);
|
|
71
121
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
ref: ref,
|
|
77
|
-
style: {
|
|
78
|
-
display: 'contents'
|
|
79
|
-
},
|
|
80
|
-
children: child
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
return /*#__PURE__*/jsx(EventopFeatureScopeContext.Provider, {
|
|
84
|
-
value: id,
|
|
85
|
-
children: wrapped
|
|
86
|
-
});
|
|
87
|
-
}
|
|
122
|
+
if (trigger) {
|
|
123
|
+
trigger.style.cssText += buildCSSVars(t);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
88
126
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
128
|
+
// POSITIONING
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
130
|
+
|
|
131
|
+
function resolvePosition(posConfig = {}) {
|
|
132
|
+
const {
|
|
133
|
+
corner = 'bottom-right',
|
|
134
|
+
offsetX = 28,
|
|
135
|
+
offsetY = 28
|
|
136
|
+
} = posConfig;
|
|
137
|
+
const isLeft = corner.includes('left');
|
|
138
|
+
const isTop = corner.includes('top');
|
|
139
|
+
return {
|
|
140
|
+
trigger: {
|
|
141
|
+
[isLeft ? 'left' : 'right']: `${offsetX}px`,
|
|
142
|
+
[isTop ? 'top' : 'bottom']: `${offsetY}px`
|
|
143
|
+
},
|
|
144
|
+
panel: {
|
|
145
|
+
[isLeft ? 'left' : 'right']: `${offsetX}px`,
|
|
146
|
+
[isTop ? 'top' : 'bottom']: `${offsetY + 56 + 12}px`,
|
|
147
|
+
transformOrigin: isTop ? 'top center' : 'bottom center'
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function positionToCSS(obj) {
|
|
152
|
+
return Object.entries(obj).map(([k, v]) => `${k}:${v}`).join(';');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
156
|
+
// PROVIDER
|
|
157
|
+
// Only custom() is supported — route AI calls through your own server.
|
|
158
|
+
//
|
|
159
|
+
// Your endpoint receives: POST { systemPrompt, messages }
|
|
160
|
+
// Your endpoint must return: { message: string, steps: Step[] }
|
|
161
|
+
//
|
|
162
|
+
// @example
|
|
163
|
+
// Eventop.init({
|
|
164
|
+
// provider: Eventop.providers.custom(async ({ systemPrompt, messages }) => {
|
|
165
|
+
// const res = await fetch('/api/guide', {
|
|
166
|
+
// method: 'POST',
|
|
167
|
+
// headers: { 'Content-Type': 'application/json' },
|
|
168
|
+
// body: JSON.stringify({ systemPrompt, messages }),
|
|
169
|
+
// });
|
|
170
|
+
// return res.json();
|
|
171
|
+
// }),
|
|
172
|
+
// config: { ... },
|
|
173
|
+
// });
|
|
174
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
175
|
+
|
|
176
|
+
const providers = {
|
|
177
|
+
custom(fn) {
|
|
178
|
+
if (typeof fn !== 'function') throw new Error('[Eventop] providers.custom() requires a function');
|
|
179
|
+
return fn;
|
|
130
180
|
}
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
wrapped = /*#__PURE__*/jsx("span", {
|
|
134
|
-
[dataAttr]: '',
|
|
135
|
-
ref: ref,
|
|
136
|
-
style: {
|
|
137
|
-
display: 'contents'
|
|
138
|
-
},
|
|
139
|
-
children: child
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
return wrapped;
|
|
143
|
-
}
|
|
181
|
+
};
|
|
144
182
|
|
|
145
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
// Access the SDK programmatic API from inside any component.
|
|
149
|
-
// Use for stepComplete(), stepFail(), open(), close() etc.
|
|
150
|
-
//
|
|
151
|
-
// @example
|
|
152
|
-
// function CheckoutForm() {
|
|
153
|
-
// const { stepComplete, stepFail } = useEventopAI();
|
|
154
|
-
//
|
|
155
|
-
// async function handleNext() {
|
|
156
|
-
// const ok = await validateEmail(email);
|
|
157
|
-
// if (ok) stepComplete();
|
|
158
|
-
// else stepFail('Please enter a valid email address.');
|
|
159
|
-
// }
|
|
160
|
-
// }
|
|
161
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
183
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
184
|
+
// SYSTEM PROMPT
|
|
185
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
162
186
|
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
187
|
+
function buildSystemPrompt() {
|
|
188
|
+
const screens = [...new Set((_config.features || []).filter(f => {
|
|
189
|
+
var _f$screen;
|
|
190
|
+
return (_f$screen = f.screen) === null || _f$screen === void 0 ? void 0 : _f$screen.id;
|
|
191
|
+
}).map(f => f.screen.id))];
|
|
192
|
+
const featureSummary = (_config.features || []).map(f => {
|
|
193
|
+
var _f$screen2, _f$flow;
|
|
194
|
+
const entry = {
|
|
195
|
+
id: f.id,
|
|
196
|
+
name: f.name,
|
|
197
|
+
description: f.description,
|
|
198
|
+
screen: ((_f$screen2 = f.screen) === null || _f$screen2 === void 0 ? void 0 : _f$screen2.id) || 'default'
|
|
199
|
+
};
|
|
200
|
+
if ((_f$flow = f.flow) !== null && _f$flow !== void 0 && _f$flow.length) {
|
|
201
|
+
entry.note = `This feature has ${f.flow.length} sequential sub-steps. Include ONE step per flow entry.`;
|
|
202
|
+
}
|
|
203
|
+
return entry;
|
|
204
|
+
});
|
|
205
|
+
return `
|
|
206
|
+
You are an in-app assistant called "${_config.assistantName || 'AI Guide'}" for "${_config.appName}".
|
|
207
|
+
Your ONLY job: guide users step-by-step through tasks using the feature map below.
|
|
208
|
+
|
|
209
|
+
${screens.length > 1 ? `SCREENS: This app has multiple screens: ${screens.join(', ')}. Features are screen-specific. The SDK handles navigation — just pick the right features.` : ''}
|
|
210
|
+
|
|
211
|
+
FEATURE MAP (only reference IDs from this list):
|
|
212
|
+
${JSON.stringify(featureSummary, null, 2)}
|
|
213
|
+
|
|
214
|
+
RESPOND ONLY with this exact JSON — no markdown, no extra text:
|
|
215
|
+
{
|
|
216
|
+
"message": "Friendly 1-2 sentence intro about what you are helping with.",
|
|
217
|
+
"steps": [
|
|
218
|
+
{
|
|
219
|
+
"id": "feature-id-from-map",
|
|
220
|
+
"title": "3-5 word title",
|
|
221
|
+
"text": "Exact instruction. Max 2 sentences.",
|
|
222
|
+
"selector": "#selector-from-feature-map",
|
|
223
|
+
"position": "bottom"
|
|
224
|
+
}
|
|
225
|
+
]
|
|
176
226
|
}
|
|
177
227
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
228
|
+
RULES:
|
|
229
|
+
1. The step "id" MUST match a feature id from the feature map.
|
|
230
|
+
2. Only use selectors and IDs from the feature map. Never invent them.
|
|
231
|
+
3. No matching feature → steps: [], explain kindly in message.
|
|
232
|
+
4. position values: top | bottom | left | right | auto only.
|
|
233
|
+
5. Order steps logically. For multi-step flows, order as the user encounters them.
|
|
234
|
+
6. For forms: ALWAYS include a step for the form section or first input BEFORE the
|
|
235
|
+
continue/submit button. The button step must always be LAST in its section.
|
|
236
|
+
7. If a feature has a flow, include one step per flow entry using the same feature id —
|
|
237
|
+
the SDK expands them automatically.
|
|
238
|
+
8. Never skip features in a required sequence. Include every step end-to-end.
|
|
239
|
+
`.trim();
|
|
240
|
+
}
|
|
241
|
+
async function callAI(userMessage) {
|
|
242
|
+
const systemPrompt = buildSystemPrompt();
|
|
243
|
+
const messagesWithNew = [..._messages, {
|
|
244
|
+
role: 'user',
|
|
245
|
+
content: userMessage
|
|
246
|
+
}];
|
|
247
|
+
const result = await _provider({
|
|
248
|
+
systemPrompt,
|
|
249
|
+
messages: messagesWithNew
|
|
250
|
+
});
|
|
251
|
+
_messages.push({
|
|
252
|
+
role: 'user',
|
|
253
|
+
content: userMessage
|
|
254
|
+
});
|
|
255
|
+
_messages.push({
|
|
256
|
+
role: 'assistant',
|
|
257
|
+
content: result.message
|
|
258
|
+
});
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
263
|
+
// SCREEN NAVIGATION
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
265
|
+
|
|
266
|
+
async function ensureOnCorrectScreen(feature) {
|
|
267
|
+
if (!feature.screen) return;
|
|
268
|
+
if (typeof feature.screen.check === 'function' && feature.screen.check()) return;
|
|
269
|
+
addMsg('ai', 'Taking you to the right screen first…');
|
|
270
|
+
if (typeof feature.screen.navigate === 'function') {
|
|
271
|
+
feature.screen.navigate();
|
|
272
|
+
}
|
|
273
|
+
const waitSelector = feature.screen.waitFor || feature.selector;
|
|
274
|
+
if (waitSelector) await waitForElement(waitSelector, 10000);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
278
|
+
// FLOW EXPANSION
|
|
279
|
+
// A feature with flow[] gets expanded into multiple Shepherd steps.
|
|
280
|
+
// Developer supplies selectors; AI supplies copy for the parent step.
|
|
281
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
282
|
+
|
|
283
|
+
function expandFlowSteps(aiStep, feature) {
|
|
284
|
+
var _feature$flow;
|
|
285
|
+
if (!((_feature$flow = feature.flow) !== null && _feature$flow !== void 0 && _feature$flow.length)) return [aiStep];
|
|
286
|
+
return feature.flow.map((entry, i) => ({
|
|
287
|
+
id: `${aiStep.id}-flow-${i}`,
|
|
288
|
+
title: i === 0 ? aiStep.title : `${aiStep.title} (${i + 1}/${feature.flow.length})`,
|
|
289
|
+
text: i === 0 ? aiStep.text : `Step ${i + 1} of ${feature.flow.length}: continue with the action highlighted below.`,
|
|
290
|
+
position: aiStep.position || 'bottom',
|
|
291
|
+
selector: entry.selector || null,
|
|
292
|
+
waitFor: entry.waitFor || null,
|
|
293
|
+
advanceOn: entry.advanceOn || null,
|
|
294
|
+
_parentId: aiStep.id
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
299
|
+
// SHEPHERD RUNNER
|
|
300
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
301
|
+
|
|
302
|
+
function loadCSS(href) {
|
|
303
|
+
if (document.querySelector(`link[href="${href}"]`)) return;
|
|
304
|
+
const l = document.createElement('link');
|
|
305
|
+
l.rel = 'stylesheet';
|
|
306
|
+
l.href = href;
|
|
307
|
+
document.head.appendChild(l);
|
|
308
|
+
}
|
|
309
|
+
function loadScript(src) {
|
|
310
|
+
return new Promise((res, rej) => {
|
|
311
|
+
if (document.querySelector(`script[src="${src}"]`)) return res();
|
|
312
|
+
const s = document.createElement('script');
|
|
313
|
+
s.src = src;
|
|
314
|
+
s.onload = res;
|
|
315
|
+
s.onerror = rej;
|
|
316
|
+
document.head.appendChild(s);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async function ensureShepherd() {
|
|
320
|
+
if (typeof Shepherd !== 'undefined') return;
|
|
321
|
+
loadCSS(SHEPHERD_CSS);
|
|
322
|
+
await loadScript(SHEPHERD_JS);
|
|
323
|
+
}
|
|
324
|
+
function waitForElement(selector, timeout = 8000) {
|
|
325
|
+
return new Promise(resolve => {
|
|
326
|
+
if (document.querySelector(selector)) return resolve();
|
|
327
|
+
const timer = setTimeout(() => {
|
|
328
|
+
observer.disconnect();
|
|
329
|
+
console.warn(`[Eventop] waitFor("${selector}") timed out — continuing`);
|
|
330
|
+
resolve();
|
|
331
|
+
}, timeout);
|
|
332
|
+
const observer = new MutationObserver(() => {
|
|
333
|
+
if (document.querySelector(selector)) {
|
|
334
|
+
clearTimeout(timer);
|
|
335
|
+
observer.disconnect();
|
|
336
|
+
resolve();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
observer.observe(document.body, {
|
|
340
|
+
childList: true,
|
|
341
|
+
subtree: true
|
|
342
|
+
});
|
|
343
|
+
_cleanups.push(() => {
|
|
344
|
+
clearTimeout(timer);
|
|
345
|
+
observer.disconnect();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function mergeWithFeature(step) {
|
|
350
|
+
var _config2;
|
|
351
|
+
if (!((_config2 = _config) !== null && _config2 !== void 0 && _config2.features)) return step;
|
|
352
|
+
const feature = _config.features.find(f => f.id === step.id);
|
|
353
|
+
if (!feature) return step;
|
|
354
|
+
return {
|
|
355
|
+
waitFor: feature.waitFor || null,
|
|
356
|
+
advanceOn: feature.advanceOn || null,
|
|
357
|
+
validate: feature.validate || null,
|
|
358
|
+
screen: feature.screen || null,
|
|
359
|
+
flow: feature.flow || null,
|
|
360
|
+
...step,
|
|
361
|
+
selector: feature.selector || step.selector // feature map always wins
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function wireAdvanceOn(shepherdStep, advanceOn, tour) {
|
|
365
|
+
if (!(advanceOn !== null && advanceOn !== void 0 && advanceOn.selector) || !(advanceOn !== null && advanceOn !== void 0 && advanceOn.event)) return;
|
|
366
|
+
function handler(e) {
|
|
367
|
+
if (e.target.matches(advanceOn.selector) || e.target.closest(advanceOn.selector)) {
|
|
368
|
+
setTimeout(() => {
|
|
369
|
+
if (tour && !tour.isActive()) return;
|
|
370
|
+
tour.next();
|
|
371
|
+
}, advanceOn.delay || 300);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
document.addEventListener(advanceOn.event, handler, true);
|
|
375
|
+
_cleanups.push(() => document.removeEventListener(advanceOn.event, handler, true));
|
|
376
|
+
shepherdStep.on('show', () => {
|
|
377
|
+
var _shepherdStep$getElem;
|
|
378
|
+
const nextBtn = (_shepherdStep$getElem = shepherdStep.getElement()) === null || _shepherdStep$getElem === void 0 ? void 0 : _shepherdStep$getElem.querySelector('.shepherd-footer .shepherd-button:not(.shepherd-button-secondary)');
|
|
379
|
+
if (nextBtn && !shepherdStep._isLast) {
|
|
380
|
+
nextBtn.style.opacity = '0.4';
|
|
381
|
+
nextBtn.title = 'Complete the action above to continue';
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function addProgressIndicator(shepherdStep, index, total) {
|
|
386
|
+
shepherdStep.on('show', () => {
|
|
387
|
+
var _shepherdStep$getElem2;
|
|
388
|
+
const header = (_shepherdStep$getElem2 = shepherdStep.getElement()) === null || _shepherdStep$getElem2 === void 0 ? void 0 : _shepherdStep$getElem2.querySelector('.shepherd-header');
|
|
389
|
+
if (!header || header.querySelector('.sai-progress')) return;
|
|
390
|
+
const pct = Math.round((index + 1) / total * 100);
|
|
391
|
+
const wrapper = document.createElement('div');
|
|
392
|
+
wrapper.className = 'sai-progress';
|
|
393
|
+
wrapper.innerHTML = `
|
|
394
|
+
<div class="sai-progress-bar">
|
|
395
|
+
<div class="sai-progress-fill" style="width:${pct}%"></div>
|
|
396
|
+
</div>
|
|
397
|
+
<span class="sai-progress-label">${index + 1} / ${total}</span>
|
|
398
|
+
`;
|
|
399
|
+
header.appendChild(wrapper);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function showResumeButton(fromIndex) {
|
|
403
|
+
var _document$getElementB, _document$getElementB2;
|
|
404
|
+
const msgs = document.getElementById('sai-messages');
|
|
405
|
+
if (!msgs) return;
|
|
406
|
+
(_document$getElementB = document.getElementById('sai-resume-prompt')) === null || _document$getElementB === void 0 || _document$getElementB.remove();
|
|
407
|
+
const div = document.createElement('div');
|
|
408
|
+
div.id = 'sai-resume-prompt';
|
|
409
|
+
div.className = 'sai-msg sai-ai';
|
|
410
|
+
div.innerHTML = `
|
|
411
|
+
Tour paused. Ready when you are.
|
|
412
|
+
<br/>
|
|
413
|
+
<button class="sai-chip" id="sai-resume-btn" style="display:inline-block;margin-top:8px;">
|
|
414
|
+
▶ Resume from step ${fromIndex + 1}
|
|
415
|
+
</button>
|
|
416
|
+
`;
|
|
417
|
+
msgs.appendChild(div);
|
|
418
|
+
msgs.scrollTop = msgs.scrollHeight;
|
|
419
|
+
(_document$getElementB2 = document.getElementById('sai-resume-btn')) === null || _document$getElementB2 === void 0 || _document$getElementB2.addEventListener('click', () => {
|
|
420
|
+
div.remove();
|
|
421
|
+
if (!_pausedSteps) return;
|
|
422
|
+
const steps = _pausedSteps;
|
|
423
|
+
const idx = _pausedIndex;
|
|
424
|
+
_pausedSteps = null;
|
|
425
|
+
_pausedIndex = 0;
|
|
426
|
+
if (_isOpen) togglePanel();
|
|
427
|
+
runTour(steps.slice(idx));
|
|
428
|
+
});
|
|
429
|
+
if (!_isOpen) togglePanel();
|
|
430
|
+
}
|
|
431
|
+
async function runTour(steps, options = {}) {
|
|
432
|
+
var _config3;
|
|
433
|
+
await ensureShepherd();
|
|
434
|
+
if (_tour) {
|
|
435
|
+
_tour.cancel();
|
|
436
|
+
}
|
|
437
|
+
_cleanups.forEach(fn => fn());
|
|
438
|
+
_cleanups = [];
|
|
439
|
+
_tour = null;
|
|
440
|
+
if (!(steps !== null && steps !== void 0 && steps.length)) return;
|
|
441
|
+
const {
|
|
442
|
+
showProgress = true,
|
|
443
|
+
waitTimeout = 8000
|
|
444
|
+
} = options;
|
|
445
|
+
|
|
446
|
+
// Merge AI steps with feature map
|
|
447
|
+
const mergedSteps = steps.map(mergeWithFeature);
|
|
448
|
+
|
|
449
|
+
// Navigate to the correct screen for the first step if needed
|
|
450
|
+
const firstFeature = (_config3 = _config) === null || _config3 === void 0 || (_config3 = _config3.features) === null || _config3 === void 0 ? void 0 : _config3.find(f => {
|
|
451
|
+
var _mergedSteps$;
|
|
452
|
+
return f.id === ((_mergedSteps$ = mergedSteps[0]) === null || _mergedSteps$ === void 0 ? void 0 : _mergedSteps$.id);
|
|
453
|
+
});
|
|
454
|
+
if (firstFeature) await ensureOnCorrectScreen(firstFeature);
|
|
455
|
+
|
|
456
|
+
// Expand flow[] features into individual Shepherd steps
|
|
457
|
+
const expandedSteps = mergedSteps.flatMap(step => {
|
|
458
|
+
var _config4;
|
|
459
|
+
const feature = (_config4 = _config) === null || _config4 === void 0 || (_config4 = _config4.features) === null || _config4 === void 0 ? void 0 : _config4.find(f => f.id === step.id);
|
|
460
|
+
return feature ? expandFlowSteps(step, feature) : [step];
|
|
461
|
+
});
|
|
462
|
+
const ShepherdClass = typeof Shepherd !== 'undefined' ? Shepherd : window.Shepherd;
|
|
463
|
+
_tour = new ShepherdClass.Tour({
|
|
464
|
+
useModalOverlay: true,
|
|
465
|
+
defaultStepOptions: {
|
|
466
|
+
scrollTo: {
|
|
467
|
+
behavior: 'smooth',
|
|
468
|
+
block: 'center'
|
|
469
|
+
},
|
|
470
|
+
cancelIcon: {
|
|
471
|
+
enabled: true
|
|
472
|
+
},
|
|
473
|
+
classes: 'sai-shepherd-step'
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
expandedSteps.forEach((step, i) => {
|
|
477
|
+
var _step$advanceOn, _step$advanceOn2;
|
|
478
|
+
const isLast = i === expandedSteps.length - 1;
|
|
479
|
+
const hasAuto = !!((_step$advanceOn = step.advanceOn) !== null && _step$advanceOn !== void 0 && _step$advanceOn.selector && (_step$advanceOn2 = step.advanceOn) !== null && _step$advanceOn2 !== void 0 && _step$advanceOn2.event);
|
|
480
|
+
const buttons = [];
|
|
481
|
+
if (i > 0) {
|
|
482
|
+
buttons.push({
|
|
483
|
+
text: '← Back',
|
|
484
|
+
action: _tour.back,
|
|
485
|
+
secondary: true
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
buttons.push({
|
|
489
|
+
text: isLast ? 'Done ✓' : 'Next →',
|
|
490
|
+
action: isLast ? _tour.complete : _tour.next
|
|
491
|
+
});
|
|
492
|
+
buttons.push({
|
|
493
|
+
text: '⏸ Pause',
|
|
494
|
+
secondary: true,
|
|
495
|
+
action: function () {
|
|
496
|
+
_tour.cancel();
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
const shepherdStep = _tour.addStep({
|
|
500
|
+
id: step.id || `sai-step-${i}`,
|
|
501
|
+
title: step.title,
|
|
502
|
+
text: step.text,
|
|
503
|
+
attachTo: step.selector ? {
|
|
504
|
+
element: step.selector,
|
|
505
|
+
on: step.position || 'bottom'
|
|
506
|
+
} : undefined,
|
|
507
|
+
beforeShowPromise: step.waitFor ? () => waitForElement(step.waitFor, waitTimeout) : undefined,
|
|
508
|
+
buttons
|
|
509
|
+
});
|
|
510
|
+
shepherdStep._isLast = isLast;
|
|
511
|
+
if (hasAuto) wireAdvanceOn(shepherdStep, step.advanceOn, _tour);
|
|
512
|
+
if (showProgress && expandedSteps.length > 1) {
|
|
513
|
+
addProgressIndicator(shepherdStep, i, expandedSteps.length);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
_tour.on('complete', () => {
|
|
517
|
+
_cleanups.forEach(fn => fn());
|
|
518
|
+
_cleanups = [];
|
|
519
|
+
_pausedSteps = null;
|
|
520
|
+
_pausedIndex = 0;
|
|
521
|
+
_tour = null;
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// Cancel → pause instead of hard destroy
|
|
525
|
+
_tour.on('cancel', () => {
|
|
526
|
+
const currentStepEl = _tour.getCurrentStep();
|
|
527
|
+
const currentIdx = currentStepEl ? expandedSteps.findIndex(s => s.id === currentStepEl.id) : 0;
|
|
528
|
+
_cleanups.forEach(fn => fn());
|
|
529
|
+
_cleanups = [];
|
|
530
|
+
_pausedSteps = expandedSteps;
|
|
531
|
+
_pausedIndex = Math.max(0, currentIdx);
|
|
532
|
+
_tour = null;
|
|
533
|
+
showResumeButton(_pausedIndex);
|
|
534
|
+
});
|
|
535
|
+
_tour.start();
|
|
536
|
+
}
|
|
537
|
+
function stepComplete() {
|
|
538
|
+
var _tour2;
|
|
539
|
+
if ((_tour2 = _tour) !== null && _tour2 !== void 0 && _tour2.isActive()) _tour.next();
|
|
540
|
+
}
|
|
541
|
+
function stepFail(message) {
|
|
542
|
+
var _tour3, _el$querySelector;
|
|
543
|
+
if (!((_tour3 = _tour) !== null && _tour3 !== void 0 && _tour3.isActive())) return;
|
|
544
|
+
const current = _tour.getCurrentStep();
|
|
545
|
+
if (!current) return;
|
|
546
|
+
const el = current.getElement();
|
|
547
|
+
el === null || el === void 0 || (_el$querySelector = el.querySelector('.sai-step-error')) === null || _el$querySelector === void 0 || _el$querySelector.remove();
|
|
548
|
+
if (message) {
|
|
549
|
+
var _el$querySelector2;
|
|
550
|
+
const err = document.createElement('div');
|
|
551
|
+
err.className = 'sai-step-error';
|
|
552
|
+
err.textContent = '⚠ ' + message;
|
|
553
|
+
el === null || el === void 0 || (_el$querySelector2 = el.querySelector('.shepherd-text')) === null || _el$querySelector2 === void 0 || _el$querySelector2.appendChild(err);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
558
|
+
// STYLES
|
|
559
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
560
|
+
|
|
561
|
+
function injectStyles(theme, pos) {
|
|
562
|
+
if (document.getElementById('sai-styles')) return;
|
|
563
|
+
const triggerCSS = positionToCSS(pos.trigger);
|
|
564
|
+
const panelCSS = positionToCSS(pos.panel);
|
|
565
|
+
const isDark = theme._isDark;
|
|
566
|
+
const stepBg = isDark ? 'var(--sai-bg)' : '#ffffff';
|
|
567
|
+
const stepSurf = isDark ? 'var(--sai-surface)' : '#f8f8fc';
|
|
568
|
+
const stepText = isDark ? 'var(--sai-text)' : '#1a1a2e';
|
|
569
|
+
const stepBorder = isDark ? 'var(--sai-border)' : '#e4e4f0';
|
|
570
|
+
const style = document.createElement('style');
|
|
571
|
+
style.id = 'sai-styles';
|
|
572
|
+
style.textContent = `
|
|
573
|
+
#sai-trigger, #sai-panel { ${buildCSSVars(theme)} }
|
|
574
|
+
|
|
575
|
+
/* ── Trigger ── */
|
|
576
|
+
#sai-trigger {
|
|
577
|
+
position: fixed; ${triggerCSS};
|
|
578
|
+
width: 54px; height: 54px; border-radius: 50%;
|
|
579
|
+
background: var(--sai-surface); border: 2px solid var(--sai-accent);
|
|
580
|
+
color: var(--sai-text); font-size: 20px; cursor: pointer; z-index: 99998;
|
|
581
|
+
display: flex; align-items: center; justify-content: center;
|
|
582
|
+
box-shadow: 0 4px 20px color-mix(in srgb, var(--sai-accent) 40%, transparent);
|
|
583
|
+
transition: transform .2s ease, box-shadow .2s ease;
|
|
584
|
+
font-family: var(--sai-font); padding: 0;
|
|
585
|
+
}
|
|
586
|
+
#sai-trigger:hover {
|
|
587
|
+
transform: scale(1.08);
|
|
588
|
+
box-shadow: 0 6px 28px color-mix(in srgb, var(--sai-accent) 55%, transparent);
|
|
589
|
+
}
|
|
590
|
+
#sai-trigger .sai-pulse {
|
|
591
|
+
position: absolute; width: 100%; height: 100%; border-radius: 50%;
|
|
592
|
+
background: color-mix(in srgb, var(--sai-accent) 30%, transparent);
|
|
593
|
+
animation: sai-pulse 2.2s ease-out infinite; pointer-events: none;
|
|
594
|
+
}
|
|
595
|
+
#sai-trigger.sai-paused .sai-pulse { animation: none; }
|
|
596
|
+
#sai-trigger.sai-paused::after {
|
|
597
|
+
content: '⏸'; position: absolute; bottom: -2px; right: -2px;
|
|
598
|
+
font-size: 12px; background: var(--sai-accent); border-radius: 50%;
|
|
599
|
+
width: 18px; height: 18px; display: flex; align-items: center;
|
|
600
|
+
justify-content: center; color: #fff; line-height: 1;
|
|
601
|
+
}
|
|
602
|
+
@keyframes sai-pulse {
|
|
603
|
+
0% { transform: scale(1); opacity: .8; }
|
|
604
|
+
100% { transform: scale(1.75); opacity: 0; }
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/* ── Panel ── */
|
|
608
|
+
#sai-panel {
|
|
609
|
+
position: fixed; ${panelCSS};
|
|
610
|
+
width: 340px; background: var(--sai-bg);
|
|
611
|
+
border: 1px solid var(--sai-border); border-radius: var(--sai-radius);
|
|
612
|
+
display: flex; flex-direction: column; z-index: 99999; overflow: hidden;
|
|
613
|
+
box-shadow: 0 16px 48px rgba(0,0,0,.18); font-family: var(--sai-font);
|
|
614
|
+
transform: translateY(12px) scale(.97); opacity: 0; pointer-events: none;
|
|
615
|
+
transition: transform .25s cubic-bezier(.34,1.56,.64,1), opacity .2s ease;
|
|
616
|
+
max-height: 520px;
|
|
617
|
+
}
|
|
618
|
+
#sai-panel.sai-open { transform: translateY(0) scale(1); opacity: 1; pointer-events: all; }
|
|
619
|
+
|
|
620
|
+
/* ── Header ── */
|
|
621
|
+
#sai-header {
|
|
622
|
+
display: flex; align-items: center; gap: 10px; padding: 13px 15px;
|
|
623
|
+
background: var(--sai-surface); border-bottom: 1px solid var(--sai-border);
|
|
624
|
+
}
|
|
625
|
+
#sai-avatar {
|
|
626
|
+
width: 30px; height: 30px; border-radius: 50%;
|
|
627
|
+
background: linear-gradient(135deg, var(--sai-accent), var(--sai-accent2));
|
|
628
|
+
display: flex; align-items: center; justify-content: center;
|
|
629
|
+
font-size: 14px; flex-shrink: 0; color: #fff;
|
|
630
|
+
}
|
|
631
|
+
#sai-header-info { flex: 1; min-width: 0; }
|
|
632
|
+
#sai-header-info h4 {
|
|
633
|
+
margin: 0; font-size: 13px; font-weight: 600; color: var(--sai-text);
|
|
634
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
635
|
+
}
|
|
636
|
+
#sai-header-info p {
|
|
637
|
+
margin: 0; font-size: 11px; color: var(--sai-text-dim);
|
|
638
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
639
|
+
}
|
|
640
|
+
#sai-close {
|
|
641
|
+
background: none; border: none; color: var(--sai-text-dim); cursor: pointer;
|
|
642
|
+
font-size: 18px; line-height: 1; padding: 0; flex-shrink: 0; transition: color .15s;
|
|
643
|
+
}
|
|
644
|
+
#sai-close:hover { color: var(--sai-text); }
|
|
645
|
+
|
|
646
|
+
/* ── Messages ── */
|
|
647
|
+
#sai-messages {
|
|
648
|
+
flex: 1; overflow-y: auto; padding: 12px;
|
|
649
|
+
display: flex; flex-direction: column; gap: 9px;
|
|
650
|
+
min-height: 160px; max-height: 280px;
|
|
651
|
+
scrollbar-width: thin; scrollbar-color: var(--sai-border) transparent;
|
|
652
|
+
}
|
|
653
|
+
#sai-messages::-webkit-scrollbar { width: 4px; }
|
|
654
|
+
#sai-messages::-webkit-scrollbar-thumb { background: var(--sai-border); border-radius: 2px; }
|
|
655
|
+
.sai-msg {
|
|
656
|
+
max-width: 86%; padding: 8px 11px; border-radius: 11px;
|
|
657
|
+
font-size: 13px; line-height: 1.55; animation: sai-in .18s ease both;
|
|
658
|
+
}
|
|
659
|
+
@keyframes sai-in {
|
|
660
|
+
from { opacity: 0; transform: translateY(5px); }
|
|
661
|
+
to { opacity: 1; transform: translateY(0); }
|
|
662
|
+
}
|
|
663
|
+
.sai-msg.sai-ai {
|
|
664
|
+
background: var(--sai-surface); color: var(--sai-text);
|
|
665
|
+
border-bottom-left-radius: 3px; align-self: flex-start;
|
|
666
|
+
border: 1px solid var(--sai-border);
|
|
667
|
+
}
|
|
668
|
+
.sai-msg.sai-user {
|
|
669
|
+
background: var(--sai-accent); color: #fff;
|
|
670
|
+
border-bottom-right-radius: 3px; align-self: flex-end;
|
|
671
|
+
}
|
|
672
|
+
.sai-msg.sai-error {
|
|
673
|
+
background: color-mix(in srgb, #ef4444 10%, var(--sai-bg));
|
|
674
|
+
color: #ef4444;
|
|
675
|
+
border: 1px solid color-mix(in srgb, #ef4444 25%, var(--sai-bg));
|
|
676
|
+
align-self: flex-start;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/* ── Typing indicator ── */
|
|
680
|
+
.sai-typing {
|
|
681
|
+
display: flex; gap: 4px; padding: 9px 12px; align-self: flex-start;
|
|
682
|
+
background: var(--sai-surface); border: 1px solid var(--sai-border);
|
|
683
|
+
border-radius: 11px; border-bottom-left-radius: 3px;
|
|
684
|
+
}
|
|
685
|
+
.sai-typing span {
|
|
686
|
+
width: 5px; height: 5px; border-radius: 50%;
|
|
687
|
+
background: var(--sai-text-dim); animation: sai-bounce 1.2s infinite;
|
|
688
|
+
}
|
|
689
|
+
.sai-typing span:nth-child(2) { animation-delay: .15s; }
|
|
690
|
+
.sai-typing span:nth-child(3) { animation-delay: .3s; }
|
|
691
|
+
@keyframes sai-bounce {
|
|
692
|
+
0%,80%,100% { transform: translateY(0); }
|
|
693
|
+
40% { transform: translateY(-5px); }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/* ── Suggestions ── */
|
|
697
|
+
#sai-suggestions { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 12px 10px; }
|
|
698
|
+
.sai-chip {
|
|
699
|
+
font-size: 11px; padding: 5px 10px; border-radius: 20px;
|
|
700
|
+
background: transparent; border: 1px solid var(--sai-border);
|
|
701
|
+
color: var(--sai-text-dim); cursor: pointer; font-family: inherit; transition: all .15s;
|
|
702
|
+
}
|
|
703
|
+
.sai-chip:hover {
|
|
704
|
+
border-color: var(--sai-accent); color: var(--sai-accent);
|
|
705
|
+
background: color-mix(in srgb, var(--sai-accent) 6%, transparent);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/* ── Input row ── */
|
|
709
|
+
#sai-inputrow {
|
|
710
|
+
display: flex; align-items: center; gap: 7px; padding: 9px 11px;
|
|
711
|
+
border-top: 1px solid var(--sai-border); background: var(--sai-bg);
|
|
712
|
+
}
|
|
713
|
+
#sai-input {
|
|
714
|
+
flex: 1; background: var(--sai-surface); border: 1px solid var(--sai-border);
|
|
715
|
+
border-radius: 8px; padding: 7px 11px; color: var(--sai-text);
|
|
716
|
+
font-size: 13px; outline: none; transition: border-color .15s; font-family: inherit;
|
|
717
|
+
}
|
|
718
|
+
#sai-input::placeholder { color: var(--sai-text-dim); }
|
|
719
|
+
#sai-input:focus { border-color: var(--sai-accent); }
|
|
720
|
+
#sai-send {
|
|
721
|
+
width: 32px; height: 32px; flex-shrink: 0; border-radius: 8px;
|
|
722
|
+
background: var(--sai-accent); border: none; color: #fff; font-size: 14px;
|
|
723
|
+
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
724
|
+
transition: filter .15s, transform .1s; line-height: 1;
|
|
725
|
+
}
|
|
726
|
+
#sai-send:hover { filter: brightness(1.1); }
|
|
727
|
+
#sai-send:active { transform: scale(.93); }
|
|
728
|
+
#sai-send:disabled { background: var(--sai-border); cursor: not-allowed; }
|
|
729
|
+
|
|
730
|
+
/* ── Shepherd step overrides ── */
|
|
731
|
+
.sai-shepherd-step {
|
|
732
|
+
background: ${stepBg} !important;
|
|
733
|
+
border: 1px solid ${stepBorder} !important;
|
|
734
|
+
border-radius: 12px !important;
|
|
735
|
+
box-shadow: 0 8px 36px rgba(0,0,0,.18) !important;
|
|
736
|
+
max-width: 290px !important;
|
|
737
|
+
font-family: var(--sai-font, system-ui) !important;
|
|
738
|
+
}
|
|
739
|
+
.sai-shepherd-step.shepherd-has-title .shepherd-content .shepherd-header {
|
|
740
|
+
background: ${stepSurf} !important; padding: 11px 15px 8px !important;
|
|
741
|
+
border-bottom: 1px solid ${stepBorder} !important;
|
|
742
|
+
}
|
|
743
|
+
.sai-shepherd-step .shepherd-title {
|
|
744
|
+
color: var(--sai-accent, #e94560) !important;
|
|
745
|
+
font-size: 13px !important; font-weight: 700 !important;
|
|
746
|
+
}
|
|
747
|
+
.sai-shepherd-step .shepherd-text {
|
|
748
|
+
color: ${stepText} !important; font-size: 13px !important;
|
|
749
|
+
line-height: 1.6 !important; padding: 10px 15px !important;
|
|
750
|
+
}
|
|
751
|
+
.sai-shepherd-step .shepherd-footer {
|
|
752
|
+
border-top: 1px solid ${stepBorder} !important; padding: 8px 11px !important;
|
|
753
|
+
background: ${stepBg} !important; display: flex; gap: 5px; flex-wrap: wrap;
|
|
754
|
+
}
|
|
755
|
+
.sai-shepherd-step .shepherd-button {
|
|
756
|
+
background: var(--sai-accent, #e94560) !important; color: #fff !important;
|
|
757
|
+
border: none !important; border-radius: 6px !important; padding: 6px 13px !important;
|
|
758
|
+
font-size: 12px !important; font-weight: 600 !important; cursor: pointer !important;
|
|
759
|
+
transition: filter .15s !important; font-family: var(--sai-font, system-ui) !important;
|
|
760
|
+
}
|
|
761
|
+
.sai-shepherd-step .shepherd-button:hover { filter: brightness(1.1) !important; }
|
|
762
|
+
.sai-shepherd-step .shepherd-button-secondary {
|
|
763
|
+
background: transparent !important; color: var(--sai-text-dim, #888) !important;
|
|
764
|
+
border: 1px solid ${stepBorder} !important;
|
|
765
|
+
}
|
|
766
|
+
.sai-shepherd-step .shepherd-button-secondary:hover {
|
|
767
|
+
background: ${stepSurf} !important; color: ${stepText} !important;
|
|
768
|
+
}
|
|
769
|
+
.sai-shepherd-step .shepherd-cancel-icon { color: var(--sai-text-dim, #888) !important; }
|
|
770
|
+
.sai-shepherd-step .shepherd-cancel-icon:hover { color: ${stepText} !important; }
|
|
771
|
+
.sai-shepherd-step[data-popper-placement] > .shepherd-arrow::before {
|
|
772
|
+
background: ${stepBorder} !important;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/* ── Progress bar ── */
|
|
776
|
+
.sai-progress { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-shrink: 0; }
|
|
777
|
+
.sai-progress-bar {
|
|
778
|
+
width: 60px; height: 3px; border-radius: 2px; background: ${stepBorder}; overflow: hidden;
|
|
779
|
+
}
|
|
780
|
+
.sai-progress-fill {
|
|
781
|
+
height: 100%; border-radius: 2px;
|
|
782
|
+
background: var(--sai-accent, #e94560); transition: width .3s ease;
|
|
783
|
+
}
|
|
784
|
+
.sai-progress-label {
|
|
785
|
+
font-size: 10px; color: var(--sai-text-dim, #888);
|
|
786
|
+
white-space: nowrap; font-family: var(--sai-font, system-ui);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/* ── Step error ── */
|
|
790
|
+
.sai-step-error {
|
|
791
|
+
margin-top: 8px; padding: 6px 10px;
|
|
792
|
+
background: color-mix(in srgb, #ef4444 12%, ${stepBg});
|
|
793
|
+
border: 1px solid color-mix(in srgb, #ef4444 25%, ${stepBorder});
|
|
794
|
+
border-radius: 6px; color: #ef4444; font-size: 12px; line-height: 1.5;
|
|
795
|
+
animation: sai-in .18s ease;
|
|
796
|
+
}
|
|
797
|
+
`;
|
|
798
|
+
document.head.appendChild(style);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
802
|
+
// CHAT UI
|
|
803
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
804
|
+
|
|
805
|
+
function buildChat(theme, positionCSS) {
|
|
806
|
+
var _config$suggestions, _config$theme, _config$theme2;
|
|
807
|
+
if (document.getElementById('sai-trigger')) return;
|
|
808
|
+
injectStyles(theme, positionCSS);
|
|
809
|
+
const trigger = document.createElement('button');
|
|
810
|
+
trigger.id = 'sai-trigger';
|
|
811
|
+
trigger.title = 'Need help?';
|
|
812
|
+
trigger.setAttribute('aria-label', 'Open help assistant');
|
|
813
|
+
trigger.innerHTML = '<span class="sai-pulse" aria-hidden="true"></span>✦';
|
|
814
|
+
document.body.appendChild(trigger);
|
|
815
|
+
const panel = document.createElement('div');
|
|
816
|
+
panel.id = 'sai-panel';
|
|
817
|
+
panel.setAttribute('role', 'dialog');
|
|
818
|
+
panel.setAttribute('aria-label', `${_config.assistantName || 'AI Guide'} chat`);
|
|
819
|
+
panel.innerHTML = `
|
|
820
|
+
<div id="sai-header">
|
|
821
|
+
<div id="sai-avatar" aria-hidden="true">✦</div>
|
|
822
|
+
<div id="sai-header-info">
|
|
823
|
+
<h4>${escHTML(_config.assistantName || 'AI Guide')}</h4>
|
|
824
|
+
<p>Ask me anything about ${escHTML(_config.appName)}</p>
|
|
825
|
+
</div>
|
|
826
|
+
<button id="sai-close" aria-label="Close help assistant">×</button>
|
|
827
|
+
</div>
|
|
828
|
+
<div id="sai-messages" role="log" aria-live="polite"></div>
|
|
829
|
+
<div id="sai-suggestions" aria-label="Suggested questions"></div>
|
|
830
|
+
<div id="sai-inputrow">
|
|
831
|
+
<input id="sai-input" type="text" placeholder="What do you need help with?"
|
|
832
|
+
autocomplete="off" aria-label="Ask a question"/>
|
|
833
|
+
<button id="sai-send" aria-label="Send message">➤</button>
|
|
834
|
+
</div>
|
|
835
|
+
`;
|
|
836
|
+
document.body.appendChild(panel);
|
|
837
|
+
if ((_config$suggestions = _config.suggestions) !== null && _config$suggestions !== void 0 && _config$suggestions.length) {
|
|
838
|
+
const container = panel.querySelector('#sai-suggestions');
|
|
839
|
+
_config.suggestions.forEach(s => {
|
|
840
|
+
const btn = document.createElement('button');
|
|
841
|
+
btn.className = 'sai-chip';
|
|
842
|
+
btn.textContent = s;
|
|
843
|
+
btn.addEventListener('click', () => handleSend(s));
|
|
844
|
+
container.appendChild(btn);
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
trigger.addEventListener('click', togglePanel);
|
|
848
|
+
panel.querySelector('#sai-close').addEventListener('click', togglePanel);
|
|
849
|
+
panel.querySelector('#sai-send').addEventListener('click', () => {
|
|
850
|
+
const v = panel.querySelector('#sai-input').value.trim();
|
|
851
|
+
if (v) handleSend(v);
|
|
852
|
+
});
|
|
853
|
+
panel.querySelector('#sai-input').addEventListener('keydown', e => {
|
|
854
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
855
|
+
e.preventDefault();
|
|
856
|
+
const v = e.target.value.trim();
|
|
857
|
+
if (v) handleSend(v);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
addMsg('ai', `Hey! 👋 I can guide you through ${_config.appName}. What would you like to do?`);
|
|
861
|
+
|
|
862
|
+
// Auto theme switching
|
|
863
|
+
if (((_config$theme = _config.theme) === null || _config$theme === void 0 ? void 0 : _config$theme.mode) === 'auto' || !((_config$theme2 = _config.theme) !== null && _config$theme2 !== void 0 && _config$theme2.mode)) {
|
|
864
|
+
_mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
865
|
+
_mediaQuery.addEventListener('change', () => {
|
|
866
|
+
var _document$getElementB3;
|
|
867
|
+
const newTheme = resolveTheme(_config.theme);
|
|
868
|
+
applyTheme(newTheme);
|
|
869
|
+
(_document$getElementB3 = document.getElementById('sai-styles')) === null || _document$getElementB3 === void 0 || _document$getElementB3.remove();
|
|
870
|
+
injectStyles(newTheme, positionCSS);
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function escHTML(str) {
|
|
875
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
876
|
+
}
|
|
877
|
+
function togglePanel() {
|
|
878
|
+
var _document$getElementB4, _document$getElementB5, _document$getElementB6;
|
|
879
|
+
_isOpen = !_isOpen;
|
|
880
|
+
(_document$getElementB4 = document.getElementById('sai-panel')) === null || _document$getElementB4 === void 0 || _document$getElementB4.classList.toggle('sai-open', _isOpen);
|
|
881
|
+
(_document$getElementB5 = document.getElementById('sai-trigger')) === null || _document$getElementB5 === void 0 || _document$getElementB5.classList.toggle('sai-paused', !!_pausedSteps);
|
|
882
|
+
if (_isOpen) (_document$getElementB6 = document.getElementById('sai-input')) === null || _document$getElementB6 === void 0 || _document$getElementB6.focus();
|
|
883
|
+
}
|
|
884
|
+
function addMsg(type, text) {
|
|
885
|
+
const msgs = document.getElementById('sai-messages');
|
|
886
|
+
if (!msgs) return;
|
|
887
|
+
const div = document.createElement('div');
|
|
888
|
+
div.className = `sai-msg sai-${type}`;
|
|
889
|
+
div.textContent = text;
|
|
890
|
+
msgs.appendChild(div);
|
|
891
|
+
msgs.scrollTop = msgs.scrollHeight;
|
|
892
|
+
}
|
|
893
|
+
function showTyping() {
|
|
894
|
+
const msgs = document.getElementById('sai-messages');
|
|
895
|
+
const el = document.createElement('div');
|
|
896
|
+
el.className = 'sai-typing';
|
|
897
|
+
el.id = 'sai-typing';
|
|
898
|
+
el.innerHTML = '<span></span><span></span><span></span>';
|
|
899
|
+
msgs.appendChild(el);
|
|
900
|
+
msgs.scrollTop = msgs.scrollHeight;
|
|
901
|
+
}
|
|
902
|
+
function hideTyping() {
|
|
903
|
+
var _document$getElementB7;
|
|
904
|
+
(_document$getElementB7 = document.getElementById('sai-typing')) === null || _document$getElementB7 === void 0 || _document$getElementB7.remove();
|
|
905
|
+
}
|
|
906
|
+
function setDisabled(d) {
|
|
907
|
+
['sai-input', 'sai-send'].forEach(id => {
|
|
908
|
+
const el = document.getElementById(id);
|
|
909
|
+
if (el) el.disabled = d;
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
async function handleSend(text) {
|
|
913
|
+
var _document$getElementB8, _document$getElementB9;
|
|
914
|
+
const input = document.getElementById('sai-input');
|
|
915
|
+
if (input) input.value = '';
|
|
916
|
+
document.getElementById('sai-suggestions').style.display = 'none';
|
|
917
|
+
|
|
918
|
+
// New message clears any existing pause state
|
|
919
|
+
_pausedSteps = null;
|
|
920
|
+
_pausedIndex = 0;
|
|
921
|
+
(_document$getElementB8 = document.getElementById('sai-resume-prompt')) === null || _document$getElementB8 === void 0 || _document$getElementB8.remove();
|
|
922
|
+
(_document$getElementB9 = document.getElementById('sai-trigger')) === null || _document$getElementB9 === void 0 || _document$getElementB9.classList.remove('sai-paused');
|
|
923
|
+
addMsg('user', text);
|
|
924
|
+
setDisabled(true);
|
|
925
|
+
showTyping();
|
|
926
|
+
try {
|
|
927
|
+
var _result$steps;
|
|
928
|
+
const result = await callAI(text);
|
|
929
|
+
hideTyping();
|
|
930
|
+
addMsg('ai', result.message);
|
|
931
|
+
if ((_result$steps = result.steps) !== null && _result$steps !== void 0 && _result$steps.length) {
|
|
932
|
+
setTimeout(() => {
|
|
933
|
+
togglePanel();
|
|
934
|
+
runTour(result.steps);
|
|
935
|
+
}, 600);
|
|
936
|
+
}
|
|
937
|
+
} catch (err) {
|
|
938
|
+
hideTyping();
|
|
939
|
+
addMsg('error', err.message || 'Something went wrong. Please try again.');
|
|
940
|
+
console.error('[Eventop]', err);
|
|
941
|
+
} finally {
|
|
942
|
+
var _document$getElementB0;
|
|
943
|
+
setDisabled(false);
|
|
944
|
+
(_document$getElementB0 = document.getElementById('sai-input')) === null || _document$getElementB0 === void 0 || _document$getElementB0.focus();
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
949
|
+
// PUBLIC API
|
|
950
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
196
951
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
952
|
+
const Eventop = {
|
|
953
|
+
providers,
|
|
954
|
+
init(opts = {}) {
|
|
955
|
+
var _opts$config, _opts$config2;
|
|
956
|
+
if (!opts.provider) throw new Error('[Eventop] provider is required');
|
|
957
|
+
if (!((_opts$config = opts.config) !== null && _opts$config !== void 0 && _opts$config.appName)) throw new Error('[Eventop] config.appName is required');
|
|
958
|
+
if (!((_opts$config2 = opts.config) !== null && _opts$config2 !== void 0 && _opts$config2.features)) throw new Error('[Eventop] config.features is required');
|
|
959
|
+
_provider = opts.provider;
|
|
960
|
+
_config = opts.config;
|
|
961
|
+
const theme = resolveTheme(_config.theme);
|
|
962
|
+
const posCSS = resolvePosition(_config.position);
|
|
963
|
+
if (document.readyState === 'loading') {
|
|
964
|
+
document.addEventListener('DOMContentLoaded', () => buildChat(theme, posCSS));
|
|
965
|
+
} else {
|
|
966
|
+
buildChat(theme, posCSS);
|
|
967
|
+
}
|
|
968
|
+
ensureShepherd();
|
|
969
|
+
},
|
|
970
|
+
open() {
|
|
971
|
+
if (!_isOpen) togglePanel();
|
|
972
|
+
},
|
|
973
|
+
close() {
|
|
974
|
+
if (_isOpen) togglePanel();
|
|
975
|
+
},
|
|
976
|
+
runTour,
|
|
977
|
+
cancelTour() {
|
|
978
|
+
var _document$getElementB1, _document$getElementB10;
|
|
979
|
+
_pausedSteps = null;
|
|
980
|
+
_pausedIndex = 0;
|
|
981
|
+
if (_tour) {
|
|
982
|
+
_tour.cancel();
|
|
983
|
+
}
|
|
984
|
+
_cleanups.forEach(fn => fn());
|
|
985
|
+
_cleanups = [];
|
|
986
|
+
_tour = null;
|
|
987
|
+
(_document$getElementB1 = document.getElementById('sai-trigger')) === null || _document$getElementB1 === void 0 || _document$getElementB1.classList.remove('sai-paused');
|
|
988
|
+
(_document$getElementB10 = document.getElementById('sai-resume-prompt')) === null || _document$getElementB10 === void 0 || _document$getElementB10.remove();
|
|
989
|
+
},
|
|
990
|
+
resumeTour() {
|
|
991
|
+
var _document$getElementB11, _document$getElementB12;
|
|
992
|
+
if (!_pausedSteps) return;
|
|
993
|
+
const steps = _pausedSteps;
|
|
994
|
+
const idx = _pausedIndex;
|
|
995
|
+
_pausedSteps = null;
|
|
996
|
+
_pausedIndex = 0;
|
|
997
|
+
(_document$getElementB11 = document.getElementById('sai-resume-prompt')) === null || _document$getElementB11 === void 0 || _document$getElementB11.remove();
|
|
998
|
+
(_document$getElementB12 = document.getElementById('sai-trigger')) === null || _document$getElementB12 === void 0 || _document$getElementB12.classList.remove('sai-paused');
|
|
999
|
+
if (_isOpen) togglePanel();
|
|
1000
|
+
runTour(steps.slice(idx));
|
|
1001
|
+
},
|
|
1002
|
+
isPaused() {
|
|
1003
|
+
return !!_pausedSteps;
|
|
1004
|
+
},
|
|
1005
|
+
isActive() {
|
|
1006
|
+
var _tour4;
|
|
1007
|
+
return !!((_tour4 = _tour) !== null && _tour4 !== void 0 && _tour4.isActive());
|
|
1008
|
+
},
|
|
1009
|
+
stepComplete,
|
|
1010
|
+
stepFail,
|
|
1011
|
+
/** @internal — used by the React package to sync the live feature registry */
|
|
1012
|
+
_updateConfig(partial) {
|
|
1013
|
+
if (!_config) return;
|
|
1014
|
+
_config = {
|
|
1015
|
+
..._config,
|
|
1016
|
+
...partial
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
return Eventop;
|
|
201
1021
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
1022
|
+
})(core);
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Root context — holds the global feature registry.
|
|
1026
|
+
* Set by EventopAIProvider at the root of the app.
|
|
1027
|
+
*/
|
|
1028
|
+
const EventopRegistryContext = /*#__PURE__*/createContext(null);
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Feature scope context — set by EventopTarget.
|
|
1032
|
+
* Tells any EventopStep inside the tree which feature it belongs to,
|
|
1033
|
+
* so you can nest EventopStep inside a EventopTarget without repeating
|
|
1034
|
+
* the feature id. Also supports explicit feature="id" on EventopStep
|
|
1035
|
+
* for steps that live outside their parent EventopTarget in the tree.
|
|
1036
|
+
*/
|
|
1037
|
+
const EventopFeatureScopeContext = /*#__PURE__*/createContext(null);
|
|
1038
|
+
function useRegistry() {
|
|
1039
|
+
const ctx = useContext(EventopRegistryContext);
|
|
1040
|
+
if (!ctx) throw new Error('[EventopAI] Must be used inside <EventopAIProvider>.');
|
|
1041
|
+
return ctx;
|
|
1042
|
+
}
|
|
1043
|
+
function useFeatureScope() {
|
|
1044
|
+
return useContext(EventopFeatureScopeContext); // null is fine — steps can declare feature explicitly
|
|
221
1045
|
}
|
|
222
1046
|
|
|
223
1047
|
/**
|
|
@@ -396,8 +1220,9 @@ function EventopProvider({
|
|
|
396
1220
|
const registry = useRef(createFeatureRegistry()).current;
|
|
397
1221
|
const sdkReady = useRef(false);
|
|
398
1222
|
const syncToSDK = useCallback(() => {
|
|
1223
|
+
var _window$Eventop$_upda, _window$Eventop;
|
|
399
1224
|
if (!sdkReady.current || !window.Eventop) return;
|
|
400
|
-
window.Eventop._updateConfig
|
|
1225
|
+
(_window$Eventop$_upda = (_window$Eventop = window.Eventop)._updateConfig) === null || _window$Eventop$_upda === void 0 || _window$Eventop$_upda.call(_window$Eventop, {
|
|
401
1226
|
features: registry.snapshot()
|
|
402
1227
|
});
|
|
403
1228
|
}, [registry]);
|
|
@@ -421,8 +1246,9 @@ function EventopProvider({
|
|
|
421
1246
|
boot();
|
|
422
1247
|
const unsub = registry.subscribe(syncToSDK);
|
|
423
1248
|
return () => {
|
|
1249
|
+
var _window$Eventop2;
|
|
424
1250
|
unsub();
|
|
425
|
-
window.Eventop
|
|
1251
|
+
(_window$Eventop2 = window.Eventop) === null || _window$Eventop2 === void 0 || _window$Eventop2.cancelTour();
|
|
426
1252
|
};
|
|
427
1253
|
}, []);
|
|
428
1254
|
const ctx = {
|
|
@@ -438,6 +1264,241 @@ function EventopProvider({
|
|
|
438
1264
|
});
|
|
439
1265
|
}
|
|
440
1266
|
|
|
1267
|
+
function EventopTarget({
|
|
1268
|
+
children,
|
|
1269
|
+
id,
|
|
1270
|
+
name,
|
|
1271
|
+
description,
|
|
1272
|
+
navigate,
|
|
1273
|
+
navigateWaitFor,
|
|
1274
|
+
advanceOn,
|
|
1275
|
+
waitFor,
|
|
1276
|
+
...rest
|
|
1277
|
+
}) {
|
|
1278
|
+
const registry = useRegistry();
|
|
1279
|
+
const ref = useRef(null);
|
|
1280
|
+
const dataAttr = `data-evtp-${id}`;
|
|
1281
|
+
const selector = `[${dataAttr}]`;
|
|
1282
|
+
useEffect(() => {
|
|
1283
|
+
if (!id || !name) {
|
|
1284
|
+
console.warn('[Eventop] <EventopTarget> requires id and name props.');
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
registry.registerFeature({
|
|
1288
|
+
id,
|
|
1289
|
+
name,
|
|
1290
|
+
description,
|
|
1291
|
+
selector,
|
|
1292
|
+
navigate,
|
|
1293
|
+
navigateWaitFor,
|
|
1294
|
+
waitFor,
|
|
1295
|
+
advanceOn: advanceOn ? {
|
|
1296
|
+
selector,
|
|
1297
|
+
...advanceOn
|
|
1298
|
+
} : null
|
|
1299
|
+
});
|
|
1300
|
+
return () => registry.unregisterFeature(id);
|
|
1301
|
+
}, [id, name, description]);
|
|
1302
|
+
const child = Children.only(children);
|
|
1303
|
+
let wrapped;
|
|
1304
|
+
try {
|
|
1305
|
+
wrapped = /*#__PURE__*/cloneElement(child, {
|
|
1306
|
+
[dataAttr]: '',
|
|
1307
|
+
ref: node => {
|
|
1308
|
+
ref.current = node;
|
|
1309
|
+
const originalRef = child.ref;
|
|
1310
|
+
if (typeof originalRef === 'function') originalRef(node);else if (originalRef && 'current' in originalRef) originalRef.current = node;
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
} catch {
|
|
1314
|
+
wrapped = /*#__PURE__*/jsx("span", {
|
|
1315
|
+
[dataAttr]: '',
|
|
1316
|
+
ref: ref,
|
|
1317
|
+
style: {
|
|
1318
|
+
display: 'contents'
|
|
1319
|
+
},
|
|
1320
|
+
children: child
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
return /*#__PURE__*/jsx(EventopFeatureScopeContext.Provider, {
|
|
1324
|
+
value: id,
|
|
1325
|
+
children: wrapped
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function EventopStep({
|
|
1330
|
+
children,
|
|
1331
|
+
feature,
|
|
1332
|
+
index,
|
|
1333
|
+
parentStep,
|
|
1334
|
+
waitFor,
|
|
1335
|
+
advanceOn
|
|
1336
|
+
}) {
|
|
1337
|
+
const registry = useRegistry();
|
|
1338
|
+
const featureScope = useFeatureScope();
|
|
1339
|
+
const featureId = feature || featureScope;
|
|
1340
|
+
const ref = useRef(null);
|
|
1341
|
+
if (!featureId) {
|
|
1342
|
+
console.warn('[Eventop] <EventopStep> needs either a feature prop or an <EventopTarget> ancestor.');
|
|
1343
|
+
}
|
|
1344
|
+
if (index == null) {
|
|
1345
|
+
console.warn('[Eventop] <EventopStep> requires an index prop.');
|
|
1346
|
+
}
|
|
1347
|
+
const dataAttr = `data-evtp-step-${featureId}-${parentStep != null ? `${parentStep}-` : ''}${index}`;
|
|
1348
|
+
const selector = `[${dataAttr}]`;
|
|
1349
|
+
useEffect(() => {
|
|
1350
|
+
if (!featureId || index == null) return;
|
|
1351
|
+
registry.registerStep(featureId, index, parentStep ?? null, {
|
|
1352
|
+
selector,
|
|
1353
|
+
waitFor: waitFor || null,
|
|
1354
|
+
advanceOn: advanceOn ? {
|
|
1355
|
+
selector,
|
|
1356
|
+
...advanceOn
|
|
1357
|
+
} : null
|
|
1358
|
+
});
|
|
1359
|
+
return () => registry.unregisterStep(featureId, index, parentStep ?? null);
|
|
1360
|
+
}, [featureId, index, parentStep]);
|
|
1361
|
+
const child = Children.only(children);
|
|
1362
|
+
let wrapped;
|
|
1363
|
+
try {
|
|
1364
|
+
wrapped = /*#__PURE__*/cloneElement(child, {
|
|
1365
|
+
[dataAttr]: '',
|
|
1366
|
+
ref: node => {
|
|
1367
|
+
ref.current = node;
|
|
1368
|
+
const originalRef = child.ref;
|
|
1369
|
+
if (typeof originalRef === 'function') originalRef(node);else if (originalRef && 'current' in originalRef) originalRef.current = node;
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
} catch {
|
|
1373
|
+
wrapped = /*#__PURE__*/jsx("span", {
|
|
1374
|
+
[dataAttr]: '',
|
|
1375
|
+
ref: ref,
|
|
1376
|
+
style: {
|
|
1377
|
+
display: 'contents'
|
|
1378
|
+
},
|
|
1379
|
+
children: child
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
return wrapped;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1386
|
+
// useEventopAI
|
|
1387
|
+
//
|
|
1388
|
+
// Access the SDK programmatic API from inside any component.
|
|
1389
|
+
// Use for stepComplete(), stepFail(), open(), close() etc.
|
|
1390
|
+
//
|
|
1391
|
+
// @example
|
|
1392
|
+
// function CheckoutForm() {
|
|
1393
|
+
// const { stepComplete, stepFail } = useEventopAI();
|
|
1394
|
+
//
|
|
1395
|
+
// async function handleNext() {
|
|
1396
|
+
// const ok = await validateEmail(email);
|
|
1397
|
+
// if (ok) stepComplete();
|
|
1398
|
+
// else stepFail('Please enter a valid email address.');
|
|
1399
|
+
// }
|
|
1400
|
+
// }
|
|
1401
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1402
|
+
|
|
1403
|
+
function useEventopAI() {
|
|
1404
|
+
const sdk = () => window.Eventop;
|
|
1405
|
+
return {
|
|
1406
|
+
open: () => {
|
|
1407
|
+
var _sdk;
|
|
1408
|
+
return (_sdk = sdk()) === null || _sdk === void 0 ? void 0 : _sdk.open();
|
|
1409
|
+
},
|
|
1410
|
+
close: () => {
|
|
1411
|
+
var _sdk2;
|
|
1412
|
+
return (_sdk2 = sdk()) === null || _sdk2 === void 0 ? void 0 : _sdk2.close();
|
|
1413
|
+
},
|
|
1414
|
+
cancelTour: () => {
|
|
1415
|
+
var _sdk3;
|
|
1416
|
+
return (_sdk3 = sdk()) === null || _sdk3 === void 0 ? void 0 : _sdk3.cancelTour();
|
|
1417
|
+
},
|
|
1418
|
+
resumeTour: () => {
|
|
1419
|
+
var _sdk4;
|
|
1420
|
+
return (_sdk4 = sdk()) === null || _sdk4 === void 0 ? void 0 : _sdk4.resumeTour();
|
|
1421
|
+
},
|
|
1422
|
+
isActive: () => {
|
|
1423
|
+
var _sdk5;
|
|
1424
|
+
return ((_sdk5 = sdk()) === null || _sdk5 === void 0 ? void 0 : _sdk5.isActive()) ?? false;
|
|
1425
|
+
},
|
|
1426
|
+
isPaused: () => {
|
|
1427
|
+
var _sdk6;
|
|
1428
|
+
return ((_sdk6 = sdk()) === null || _sdk6 === void 0 ? void 0 : _sdk6.isPaused()) ?? false;
|
|
1429
|
+
},
|
|
1430
|
+
stepComplete: () => {
|
|
1431
|
+
var _sdk7;
|
|
1432
|
+
return (_sdk7 = sdk()) === null || _sdk7 === void 0 ? void 0 : _sdk7.stepComplete();
|
|
1433
|
+
},
|
|
1434
|
+
stepFail: msg => {
|
|
1435
|
+
var _sdk8;
|
|
1436
|
+
return (_sdk8 = sdk()) === null || _sdk8 === void 0 ? void 0 : _sdk8.stepFail(msg);
|
|
1437
|
+
},
|
|
1438
|
+
runTour: steps => {
|
|
1439
|
+
var _sdk9;
|
|
1440
|
+
return (_sdk9 = sdk()) === null || _sdk9 === void 0 ? void 0 : _sdk9.runTour(steps);
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1446
|
+
// useEventopTour
|
|
1447
|
+
//
|
|
1448
|
+
// Reactively tracks tour state so you can render your own UI.
|
|
1449
|
+
// Polls at 300ms — lightweight enough for a status indicator.
|
|
1450
|
+
//
|
|
1451
|
+
// @example
|
|
1452
|
+
// function TourBar() {
|
|
1453
|
+
// const { isActive, isPaused, resume, cancel } = useEventopTour();
|
|
1454
|
+
// if (!isActive && !isPaused) return null;
|
|
1455
|
+
// return (
|
|
1456
|
+
// <div>
|
|
1457
|
+
// {isPaused && <button onClick={resume}>Resume tour</button>}
|
|
1458
|
+
// <button onClick={cancel}>End</button>
|
|
1459
|
+
// </div>
|
|
1460
|
+
// );
|
|
1461
|
+
// }
|
|
1462
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1463
|
+
|
|
1464
|
+
function useEventopTour() {
|
|
1465
|
+
const [state, setState] = useState({
|
|
1466
|
+
isActive: false,
|
|
1467
|
+
isPaused: false
|
|
1468
|
+
});
|
|
1469
|
+
useEffect(() => {
|
|
1470
|
+
const id = setInterval(() => {
|
|
1471
|
+
const sdk = window.Eventop;
|
|
1472
|
+
if (!sdk) return;
|
|
1473
|
+
const next = {
|
|
1474
|
+
isActive: sdk.isActive(),
|
|
1475
|
+
isPaused: sdk.isPaused()
|
|
1476
|
+
};
|
|
1477
|
+
setState(prev => prev.isActive !== next.isActive || prev.isPaused !== next.isPaused ? next : prev);
|
|
1478
|
+
}, 300);
|
|
1479
|
+
return () => clearInterval(id);
|
|
1480
|
+
}, []);
|
|
1481
|
+
return {
|
|
1482
|
+
...state,
|
|
1483
|
+
resume: useCallback(() => {
|
|
1484
|
+
var _window$Eventop;
|
|
1485
|
+
return (_window$Eventop = window.Eventop) === null || _window$Eventop === void 0 ? void 0 : _window$Eventop.resumeTour();
|
|
1486
|
+
}, []),
|
|
1487
|
+
cancel: useCallback(() => {
|
|
1488
|
+
var _window$Eventop2;
|
|
1489
|
+
return (_window$Eventop2 = window.Eventop) === null || _window$Eventop2 === void 0 ? void 0 : _window$Eventop2.cancelTour();
|
|
1490
|
+
}, []),
|
|
1491
|
+
open: useCallback(() => {
|
|
1492
|
+
var _window$Eventop3;
|
|
1493
|
+
return (_window$Eventop3 = window.Eventop) === null || _window$Eventop3 === void 0 ? void 0 : _window$Eventop3.open();
|
|
1494
|
+
}, []),
|
|
1495
|
+
close: useCallback(() => {
|
|
1496
|
+
var _window$Eventop4;
|
|
1497
|
+
return (_window$Eventop4 = window.Eventop) === null || _window$Eventop4 === void 0 ? void 0 : _window$Eventop4.close();
|
|
1498
|
+
}, [])
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
|
|
441
1502
|
window.EventopAI = {
|
|
442
1503
|
init,
|
|
443
1504
|
open,
|