@guidelytech/guide-runtime 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2096 @@
1
+ import {
2
+ __publicField,
3
+ createHelpView,
4
+ el,
5
+ sanitizeHtml,
6
+ sanitizeToFragment,
7
+ setSanitizedHtml
8
+ } from "./chunk-SLBUGXNH.js";
9
+
10
+ // src/selectors.ts
11
+ import { matchUrlPattern } from "@guidelytech/shared/runtime";
12
+ function urlMatches(pattern, url) {
13
+ if (!pattern) return true;
14
+ if (matchUrlPattern(pattern, url)) return true;
15
+ try {
16
+ const u = new URL(url, "http://localhost");
17
+ if (!pattern.includes("?") && !pattern.includes("#")) return matchUrlPattern(pattern, u.pathname) || matchUrlPattern(pattern, u.origin + u.pathname);
18
+ } catch {
19
+ }
20
+ return false;
21
+ }
22
+ var TEXT_CANDIDATES = "button, a, [role='button'], [role='link'], [role='tab'], [role='menuitem'], h1, h2, h3, h4, h5, h6, label, summary";
23
+ var HOST_ID = "guidely-root";
24
+ function docOf(root) {
25
+ return root.nodeType === 9 ? root : root.ownerDocument;
26
+ }
27
+ function hasLayout(doc) {
28
+ const de = doc.documentElement;
29
+ if (!de) return false;
30
+ const r = de.getBoundingClientRect();
31
+ return r.width > 0 || r.height > 0;
32
+ }
33
+ function normalizeText(text) {
34
+ return (text || "").replace(/\s+/g, " ").trim();
35
+ }
36
+ function isVisible(el2) {
37
+ if (!el2.isConnected) return false;
38
+ const doc = el2.ownerDocument;
39
+ const win = doc.defaultView;
40
+ let cur = el2;
41
+ while (cur && cur !== doc.documentElement) {
42
+ if (cur.hidden) return false;
43
+ if (win) {
44
+ const cs = win.getComputedStyle(cur);
45
+ if (cs.display === "none" || cs.visibility === "hidden") return false;
46
+ }
47
+ cur = cur.parentElement ?? cur.getRootNode().host ?? null;
48
+ }
49
+ if (hasLayout(doc)) {
50
+ const r = el2.getBoundingClientRect();
51
+ if (r.width === 0 && r.height === 0) return false;
52
+ }
53
+ return true;
54
+ }
55
+ function isGuidelyElement(el2) {
56
+ if (!el2) return false;
57
+ const root = el2.getRootNode();
58
+ if (root instanceof ShadowRoot && root.host.id === HOST_ID) return true;
59
+ return el2.id === HOST_ID || !!el2.closest?.(`#${HOST_ID}`);
60
+ }
61
+ function safeQueryAll(root, selector) {
62
+ try {
63
+ return Array.from(root.querySelectorAll(selector));
64
+ } catch {
65
+ return [];
66
+ }
67
+ }
68
+ function escapeCss(value) {
69
+ const g = globalThis;
70
+ if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
71
+ return value.replace(/["\\]/g, "\\$&");
72
+ }
73
+ function queryXPath(root, expr) {
74
+ const doc = docOf(root);
75
+ const d = doc;
76
+ if (typeof d.evaluate !== "function") return [];
77
+ try {
78
+ const result = d.evaluate(expr, root, null, 7, null);
79
+ const out = [];
80
+ for (let i = 0; i < result.snapshotLength; i++) {
81
+ const n = result.snapshotItem(i);
82
+ if (n && n.nodeType === 1) out.push(n);
83
+ }
84
+ return out;
85
+ } catch {
86
+ return [];
87
+ }
88
+ }
89
+ function queryText(root, text) {
90
+ const needle = normalizeText(text).toLowerCase();
91
+ if (!needle) return [];
92
+ const candidates = safeQueryAll(root, TEXT_CANDIDATES);
93
+ const exact = candidates.filter((c) => normalizeText(c.textContent).toLowerCase() === needle);
94
+ if (exact.length) return exact;
95
+ return candidates.filter((c) => normalizeText(c.textContent).toLowerCase().includes(needle));
96
+ }
97
+ function querySelectorStrategy(strategy, root) {
98
+ const value = strategy.value.trim();
99
+ if (!value) return [];
100
+ switch (strategy.kind) {
101
+ case "id": {
102
+ const id = value.startsWith("#") ? value.slice(1) : value;
103
+ return safeQueryAll(root, `#${escapeCss(id)}`);
104
+ }
105
+ case "data-attr":
106
+ case "aria":
107
+ case "css":
108
+ return safeQueryAll(root, value.startsWith("[") || /^[a-z*#.[]/i.test(value) ? value : `[${value}]`);
109
+ case "text":
110
+ return queryText(root, value);
111
+ case "xpath":
112
+ return queryXPath(root, value);
113
+ default:
114
+ return [];
115
+ }
116
+ }
117
+ function resolveTarget(target, root = document) {
118
+ const selectors = Array.isArray(target) ? target : target.selectors;
119
+ for (const strategy of selectors) {
120
+ const matches = querySelectorStrategy(strategy, root).filter((el2) => !isGuidelyElement(el2) && isVisible(el2));
121
+ if (matches.length === 1) return matches[0];
122
+ }
123
+ return null;
124
+ }
125
+ var TargetTimeoutError = class extends Error {
126
+ constructor(message = "Target element not found before timeout") {
127
+ super(message);
128
+ __publicField(this, "name", "TargetTimeoutError");
129
+ }
130
+ };
131
+ function waitForTarget(target, options = {}) {
132
+ const timeoutMs = options.timeoutMs ?? 5e3;
133
+ const root = options.root ?? document;
134
+ const pollMs = Math.min(250, Math.max(16, options.pollMs ?? 250));
135
+ return new Promise((resolve, reject) => {
136
+ const immediate = resolveTarget(target, root);
137
+ if (immediate) return resolve(immediate);
138
+ if (options.signal?.aborted) return reject(new DOMException("Aborted", "AbortError"));
139
+ let done = false;
140
+ let observer = null;
141
+ let timer = null;
142
+ let timeout = null;
143
+ const cleanup = () => {
144
+ done = true;
145
+ observer?.disconnect();
146
+ if (timer) clearInterval(timer);
147
+ if (timeout) clearTimeout(timeout);
148
+ options.signal?.removeEventListener("abort", onAbort);
149
+ };
150
+ const check = () => {
151
+ if (done) return;
152
+ const el2 = resolveTarget(target, root);
153
+ if (el2) {
154
+ cleanup();
155
+ resolve(el2);
156
+ }
157
+ };
158
+ const onAbort = () => {
159
+ cleanup();
160
+ reject(new DOMException("Aborted", "AbortError"));
161
+ };
162
+ options.signal?.addEventListener("abort", onAbort);
163
+ const observeRoot = root.nodeType === 9 ? root.documentElement : root;
164
+ if (typeof MutationObserver !== "undefined" && observeRoot) {
165
+ observer = new MutationObserver(check);
166
+ observer.observe(observeRoot, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style", "hidden", "id", "data-tour", "aria-label"] });
167
+ }
168
+ timer = setInterval(check, pollMs);
169
+ if (timeoutMs > 0) {
170
+ timeout = setTimeout(() => {
171
+ cleanup();
172
+ reject(new TargetTimeoutError());
173
+ }, timeoutMs);
174
+ }
175
+ });
176
+ }
177
+ var GENERATED_ID = /(\d{4,}|:r[0-9a-z]+:|^radix-|^headlessui-|^mui-|^ember\d|^react-|^\w*-\d+$|__|^[a-f0-9]{8,}$|^svelte-)/i;
178
+ var GENERATED_CLASS = /(^css-|^sc-|^jss\d|^svelte-|^emotion-|^chakra-|^Mui|^_|[0-9a-f]{6,}|^\w{1,2}$|^[a-z]+-\d+$|[:[\]/])/;
179
+ var PREFERRED_DATA_ATTRS = ["data-testid", "data-tour", "data-test", "data-test-id", "data-cy", "data-qa", "data-id", "data-name", "data-guidely"];
180
+ var IGNORED_DATA_ATTRS = /^data-(state|v-|reactid|react|radix|headlessui|svelte|hydrated|orientation|highlighted|focus|key|index|rbd|dnd|rk-|sveltekit)/i;
181
+ function isStableId(id) {
182
+ return !!id && /^[A-Za-z][\w-]*$/.test(id) && !GENERATED_ID.test(id);
183
+ }
184
+ function isStableClass(cls) {
185
+ return !!cls && !GENERATED_CLASS.test(cls) && cls.length <= 40;
186
+ }
187
+ function uniqueIn(doc, selector, el2) {
188
+ const all = safeQueryAll(doc, selector);
189
+ return all.length === 1 && all[0] === el2;
190
+ }
191
+ function attrSelector(name, value) {
192
+ return `[${name}='${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
193
+ }
194
+ function cssPath(el2, doc) {
195
+ const segments = [];
196
+ let cur = el2;
197
+ while (cur && cur.nodeType === 1 && cur !== doc.body && cur !== doc.documentElement) {
198
+ const tag = cur.tagName.toLowerCase();
199
+ if (isStableId(cur.id)) {
200
+ segments.unshift(`#${cur.id}`);
201
+ break;
202
+ }
203
+ let seg = tag;
204
+ const parent = cur.parentElement;
205
+ if (parent) {
206
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
207
+ if (sameTag.length > 1) {
208
+ const stableCls = Array.from(cur.classList).find((c) => isStableClass(c) && sameTag.filter((s) => s.classList.contains(c)).length === 1);
209
+ seg += stableCls ? `.${escapeCss(stableCls)}` : `:nth-of-type(${sameTag.indexOf(cur) + 1})`;
210
+ }
211
+ }
212
+ segments.unshift(seg);
213
+ const candidate = segments.join(" > ");
214
+ if (uniqueIn(doc, candidate, el2)) return candidate;
215
+ cur = parent;
216
+ }
217
+ const full = segments.join(" > ");
218
+ return uniqueIn(doc, full, el2) ? full : null;
219
+ }
220
+ function textSnapshotOf(el2) {
221
+ const he = el2;
222
+ const own = normalizeText(he.innerText ?? el2.textContent);
223
+ const alt = normalizeText(el2.getAttribute("aria-label") || el2.getAttribute("title") || el2.getAttribute("placeholder") || el2.getAttribute("alt") || el2.value);
224
+ return (own || alt).slice(0, 200);
225
+ }
226
+ function generateSelectors(el2) {
227
+ const doc = el2.ownerDocument;
228
+ const out = [];
229
+ const seen = /* @__PURE__ */ new Set();
230
+ const push = (s) => {
231
+ const k = `${s.kind}:${s.value}`;
232
+ if (!seen.has(k)) {
233
+ seen.add(k);
234
+ out.push(s);
235
+ }
236
+ };
237
+ const tag = el2.tagName.toLowerCase();
238
+ if (isStableId(el2.id) && uniqueIn(doc, `#${escapeCss(el2.id)}`, el2)) push({ kind: "id", value: `#${el2.id}`, label: `id ${el2.id}` });
239
+ const dataAttrs = Array.from(el2.attributes).filter((a) => a.name.startsWith("data-") && !IGNORED_DATA_ATTRS.test(a.name) && a.value && a.value.length <= 120 && !/\d{5,}/.test(a.value));
240
+ dataAttrs.sort((a, b) => {
241
+ const ia = PREFERRED_DATA_ATTRS.indexOf(a.name);
242
+ const ib = PREFERRED_DATA_ATTRS.indexOf(b.name);
243
+ return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
244
+ });
245
+ for (const a of dataAttrs) {
246
+ const sel = attrSelector(a.name, a.value);
247
+ if (uniqueIn(doc, sel, el2)) push({ kind: "data-attr", value: sel, label: `${a.name}=${a.value}` });
248
+ else if (uniqueIn(doc, tag + sel, el2)) push({ kind: "data-attr", value: tag + sel, label: `${a.name}=${a.value}` });
249
+ }
250
+ const aria = el2.getAttribute("aria-label");
251
+ if (aria && aria.length <= 120) {
252
+ const sel = attrSelector("aria-label", aria);
253
+ if (uniqueIn(doc, sel, el2)) push({ kind: "aria", value: sel, label: aria });
254
+ else if (uniqueIn(doc, tag + sel, el2)) push({ kind: "aria", value: tag + sel, label: aria });
255
+ }
256
+ const name = el2.getAttribute("name");
257
+ if (name && uniqueIn(doc, tag + attrSelector("name", name), el2)) push({ kind: "css", value: tag + attrSelector("name", name), label: `${tag} name=${name}` });
258
+ const role = el2.getAttribute("role");
259
+ if (role) {
260
+ const title = el2.getAttribute("title") || el2.getAttribute("aria-labelledby");
261
+ const sel = title ? `[role='${role}']${attrSelector(el2.getAttribute("title") ? "title" : "aria-labelledby", title)}` : `[role='${role}']`;
262
+ if (uniqueIn(doc, sel, el2)) push({ kind: "css", value: sel, label: `role ${role}` });
263
+ }
264
+ if (tag === "a") {
265
+ const href = el2.getAttribute("href");
266
+ if (href && href.length <= 200 && uniqueIn(doc, `a${attrSelector("href", href)}`, el2)) push({ kind: "css", value: `a${attrSelector("href", href)}`, label: `link ${href}` });
267
+ }
268
+ const path = cssPath(el2, doc);
269
+ if (path) push({ kind: "css", value: path, label: "css path" });
270
+ if (/^(button|a|h[1-6]|label|summary)$/.test(tag) || role === "button" || role === "link" || role === "tab" || role === "menuitem") {
271
+ const text = normalizeText(el2.innerText ?? el2.textContent);
272
+ if (text && text.length <= 80) push({ kind: "text", value: text, label: `text "${text}"` });
273
+ }
274
+ return out.slice(0, 10);
275
+ }
276
+ function describeElement(el2) {
277
+ const r = el2.getBoundingClientRect();
278
+ return {
279
+ selectors: generateSelectors(el2),
280
+ textSnapshot: textSnapshotOf(el2),
281
+ rect: { x: r.x, y: r.y, width: r.width, height: r.height },
282
+ tag: el2.tagName.toLowerCase()
283
+ };
284
+ }
285
+
286
+ // src/styles.ts
287
+ var STYLES = `
288
+ :host{all:initial;position:static;display:block;contain:none}
289
+ *,*::before,*::after{box-sizing:border-box}
290
+ .g-scope [hidden]{display:none!important}
291
+ .g-scope{
292
+ --g-primary:#4f46e5;--g-bg:#ffffff;--g-text:#111827;--g-radius:12px;--g-font:Inter,system-ui,-apple-system,sans-serif;--g-z:2147483000;--g-overlay:.5;
293
+ --g-muted:color-mix(in srgb,var(--g-text) 62%,transparent);--g-border:color-mix(in srgb,var(--g-text) 12%,transparent);
294
+ --g-subtle:color-mix(in srgb,var(--g-text) 5%,transparent);--g-shadow:0 10px 40px rgba(0,0,0,.18),0 2px 8px rgba(0,0,0,.08);
295
+ --g-primary-text:#ffffff;
296
+ font-family:var(--g-font);font-size:14px;line-height:1.5;color:var(--g-text);-webkit-font-smoothing:antialiased;
297
+ }
298
+ .g-scope[data-theme="dark"]{--g-shadow:0 10px 40px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.3)}
299
+ .g-live{position:fixed;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
300
+ .g-layer{position:fixed;inset:0;z-index:var(--g-z);pointer-events:none}
301
+ .g-overlay{position:fixed;inset:0;background:rgba(0,0,0,var(--g-overlay));z-index:calc(var(--g-z) + 3);pointer-events:auto}
302
+ .g-overlay.g-transparent{background:transparent}
303
+ .g-spot-rect{position:fixed;background:rgba(0,0,0,var(--g-overlay));z-index:calc(var(--g-z) + 3);pointer-events:auto}
304
+ .g-spot-ring{position:fixed;z-index:calc(var(--g-z) + 3);pointer-events:none;border-radius:6px;box-shadow:0 0 0 2px var(--g-primary),0 0 0 6px color-mix(in srgb,var(--g-primary) 30%,transparent)}
305
+ .g-card{
306
+ position:fixed;z-index:calc(var(--g-z) + 4);pointer-events:auto;background:var(--g-bg);color:var(--g-text);border-radius:var(--g-radius);
307
+ box-shadow:var(--g-shadow);border:1px solid var(--g-border);width:min(360px,calc(100vw - 32px));max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);overflow:auto;outline:none;
308
+ }
309
+ .g-card.g-modal{left:50%;top:50%;transform:translate(-50%,-50%);width:min(480px,calc(100vw - 32px))}
310
+ .g-card.g-tooltip,.g-card.g-popover,.g-card.g-spotlight-card,.g-card.g-hotspot-card{top:0;left:0;width:min(320px,calc(100vw - 32px))}
311
+ .g-card.g-slideout{width:min(360px,calc(100vw - 32px));margin:16px}
312
+ .g-pos-bottom-right{right:0;bottom:0}.g-pos-bottom-left{left:0;bottom:0}.g-pos-top-right{right:0;top:0}.g-pos-top-left{left:0;top:0}
313
+ .g-pos-center{left:50%;top:50%;transform:translate(-50%,-50%)}
314
+ .g-pos-top{top:0;left:0;right:0}.g-pos-bottom{bottom:0;left:0;right:0}
315
+ .g-banner{position:fixed;left:0;right:0;z-index:calc(var(--g-z) + 1);pointer-events:auto;background:var(--g-primary);color:var(--g-primary-text);padding:10px 48px 10px 16px;display:flex;align-items:center;gap:12px;font-size:14px;box-shadow:0 2px 10px rgba(0,0,0,.15);max-width:100vw}
316
+ .g-banner .g-body{flex:1;min-width:0}
317
+ .g-banner .g-body a{color:inherit;text-decoration:underline}
318
+ .g-banner .g-close{color:inherit;right:8px;top:50%;transform:translateY(-50%)}
319
+ .g-banner .g-btn{background:rgba(255,255,255,.18);color:inherit;border-color:transparent;min-height:36px}
320
+ .g-banner.g-pos-top{top:0}.g-banner.g-pos-bottom{bottom:0}
321
+ .g-inner{padding:20px}
322
+ .g-close{position:absolute;top:8px;right:8px;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;color:var(--g-muted);border-radius:999px;cursor:pointer;font:inherit;font-size:20px;line-height:1}
323
+ .g-close:hover,.g-close:focus-visible{background:var(--g-subtle);color:var(--g-text);outline:none}
324
+ .g-close:focus-visible,.g-btn:focus-visible,.g-task:focus-visible,.g-choice:focus-visible,.g-scale button:focus-visible,.g-launcher:focus-visible,.g-input:focus-visible,.g-rc-item:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
325
+ .g-image{display:block;width:100%;max-height:220px;object-fit:cover;border-radius:var(--g-radius) var(--g-radius) 0 0}
326
+ .g-title{margin:0 28px 6px 0;font-size:16px;font-weight:600;line-height:1.3}
327
+ .g-body{font-size:14px;color:var(--g-text)}
328
+ .g-body p{margin:0 0 8px}.g-body p:last-child{margin-bottom:0}
329
+ .g-body ul,.g-body ol{margin:6px 0;padding-left:20px}
330
+ .g-body a{color:var(--g-primary)}
331
+ .g-body img{max-width:100%;height:auto;border-radius:6px}
332
+ .g-body code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;background:var(--g-subtle);padding:1px 4px;border-radius:4px}
333
+ .g-body pre{background:var(--g-subtle);padding:10px;border-radius:6px;overflow:auto}
334
+ .g-body blockquote{margin:6px 0;padding-left:10px;border-left:3px solid var(--g-border);color:var(--g-muted)}
335
+ .g-footer{display:flex;align-items:center;gap:8px;margin-top:14px;flex-wrap:wrap}
336
+ .g-progress{font-size:12px;color:var(--g-muted);margin-right:auto}
337
+ .g-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:8px 14px;border-radius:calc(var(--g-radius) * .66);border:1px solid transparent;font:inherit;font-size:14px;font-weight:500;cursor:pointer;text-decoration:none;transition:background .15s,color .15s,border-color .15s}
338
+ .g-btn-primary{background:var(--g-primary);color:var(--g-primary-text)}
339
+ .g-btn-primary:hover{filter:brightness(1.08)}
340
+ .g-btn-secondary{background:var(--g-subtle);color:var(--g-text);border-color:var(--g-border)}
341
+ .g-btn-secondary:hover{background:color-mix(in srgb,var(--g-text) 10%,transparent)}
342
+ .g-btn-ghost{background:transparent;color:var(--g-muted)}
343
+ .g-btn-ghost:hover{background:var(--g-subtle);color:var(--g-text)}
344
+ .g-btn-link{background:transparent;color:var(--g-primary);padding-left:4px;padding-right:4px}
345
+ .g-btn-link:hover{text-decoration:underline}
346
+ .g-btn:disabled{opacity:.5;cursor:not-allowed}
347
+ .g-arrow{position:absolute;width:12px;height:12px;background:var(--g-bg);transform:rotate(45deg);border:1px solid var(--g-border);z-index:-1}
348
+ .g-hotspot{position:fixed;z-index:calc(var(--g-z) + 1);pointer-events:auto;border-radius:999px;background:var(--g-primary);border:2px solid #fff;box-shadow:0 0 0 2px color-mix(in srgb,var(--g-primary) 40%,transparent);cursor:pointer;padding:0;margin:0}
349
+ .g-hotspot::after{content:"";position:absolute;inset:-4px;border-radius:999px;border:2px solid var(--g-primary);opacity:0}
350
+ .g-hotspot.g-pulse::after{animation:g-pulse 1.8s ease-out infinite}
351
+ .g-hotspot-hit{position:absolute;inset:-14px}
352
+ .g-custom{position:fixed;z-index:calc(var(--g-z) + 4);pointer-events:auto;left:50%;top:50%;transform:translate(-50%,-50%);max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);overflow:auto}
353
+ /* animations */
354
+ .g-scope[data-anim="fade"] .g-card,.g-scope[data-anim="fade"] .g-banner,.g-scope[data-anim="fade"] .g-panel{animation:g-fade .18s ease-out}
355
+ .g-scope[data-anim="slide"] .g-card,.g-scope[data-anim="slide"] .g-panel{animation:g-slide .22s ease-out}
356
+ .g-scope[data-anim="none"] *{animation:none!important;transition:none!important}
357
+ @keyframes g-fade{from{opacity:0}to{opacity:1}}
358
+ @keyframes g-slide{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
359
+ @keyframes g-pulse{0%{transform:scale(.6);opacity:.9}100%{transform:scale(2.2);opacity:0}}
360
+ @media (prefers-reduced-motion:reduce){.g-scope *{animation:none!important;transition:none!important}}
361
+ /* launcher + panel (checklist / resource center) */
362
+ .g-launcher{position:fixed;z-index:calc(var(--g-z) + 1);pointer-events:auto;display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:10px 16px;border-radius:999px;border:0;background:var(--g-primary);color:var(--g-primary-text);font:inherit;font-size:14px;font-weight:600;cursor:pointer;box-shadow:var(--g-shadow);margin:16px}
363
+ .g-launcher .g-ring{width:22px;height:22px;border-radius:999px;background:conic-gradient(#fff var(--g-pct,0%),rgba(255,255,255,.3) 0);display:inline-flex;align-items:center;justify-content:center;font-size:10px;position:relative}
364
+ .g-launcher .g-ring::after{content:"";position:absolute;inset:3px;border-radius:999px;background:var(--g-primary)}
365
+ .g-launcher .g-count{position:relative;z-index:1}
366
+ .g-panel{position:fixed;z-index:calc(var(--g-z) + 2);pointer-events:auto;width:min(380px,calc(100vw - 32px));max-height:min(620px,calc(100vh - 100px));display:flex;flex-direction:column;background:var(--g-bg);color:var(--g-text);border-radius:var(--g-radius);box-shadow:var(--g-shadow);border:1px solid var(--g-border);margin:16px;bottom:60px;overflow:hidden}
367
+ .g-panel-head{padding:16px 48px 12px 20px;border-bottom:1px solid var(--g-border);position:relative}
368
+ .g-panel-title{margin:0;font-size:16px;font-weight:600}
369
+ .g-panel-sub{margin:4px 0 0;font-size:12px;color:var(--g-muted)}
370
+ .g-panel-body{overflow:auto;padding:8px 0;flex:1}
371
+ .g-help{display:flex;flex-direction:column;min-height:0;flex:1}
372
+ .g-bar{height:6px;border-radius:999px;background:var(--g-subtle);overflow:hidden;margin-top:10px}
373
+ .g-bar > span{display:block;height:100%;background:var(--g-primary);transition:width .3s}
374
+ .g-task{display:flex;gap:12px;align-items:flex-start;width:100%;padding:12px 20px;border:0;background:transparent;text-align:left;font:inherit;color:inherit;cursor:pointer;min-height:44px}
375
+ .g-task:hover{background:var(--g-subtle)}
376
+ .g-task[disabled]{cursor:not-allowed;opacity:.55}
377
+ .g-task .g-check{flex:none;width:22px;height:22px;border-radius:999px;border:2px solid var(--g-border);display:inline-flex;align-items:center;justify-content:center;font-size:13px;margin-top:1px;color:#fff}
378
+ .g-task.g-done .g-check{background:var(--g-primary);border-color:var(--g-primary)}
379
+ .g-task.g-done .g-task-title{text-decoration:line-through;color:var(--g-muted)}
380
+ .g-task-title{font-weight:500}
381
+ .g-task-desc{font-size:12px;color:var(--g-muted);margin-top:2px}
382
+ .g-panel-foot{padding:10px 20px;border-top:1px solid var(--g-border);display:flex;justify-content:flex-end;gap:8px}
383
+ .g-complete{padding:28px 20px;text-align:center}
384
+ .g-complete .g-title{margin-right:0;font-size:18px}
385
+ /* survey */
386
+ .g-question{margin-bottom:16px}
387
+ .g-q-title{font-weight:600;margin:0 0 4px}
388
+ .g-q-desc{font-size:12px;color:var(--g-muted);margin:0 0 8px}
389
+ .g-scale{display:flex;gap:4px;flex-wrap:wrap}
390
+ .g-scale button{min-width:34px;height:40px;flex:1;padding:0 4px;border-radius:6px;border:1px solid var(--g-border);background:var(--g-subtle);color:var(--g-text);font:inherit;cursor:pointer}
391
+ .g-scale button[aria-pressed="true"],.g-scale button:hover{background:var(--g-primary);color:var(--g-primary-text);border-color:var(--g-primary)}
392
+ .g-scale-labels{display:flex;justify-content:space-between;font-size:11px;color:var(--g-muted);margin-top:4px}
393
+ .g-stars button,.g-thumbs button{font-size:22px;background:transparent;border:0;cursor:pointer;color:var(--g-muted);min-width:40px;height:40px;font:inherit}
394
+ .g-stars button[aria-pressed="true"],.g-stars button.g-on{color:#f59e0b}
395
+ .g-thumbs button[aria-pressed="true"]{color:var(--g-primary)}
396
+ .g-choice{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--g-border);border-radius:8px;margin-bottom:6px;cursor:pointer;min-height:40px;font:inherit;background:transparent;color:inherit;width:100%;text-align:left}
397
+ .g-choice[aria-checked="true"]{border-color:var(--g-primary);background:color-mix(in srgb,var(--g-primary) 8%,transparent)}
398
+ .g-choice .g-dot{width:16px;height:16px;border-radius:999px;border:2px solid var(--g-border);flex:none}
399
+ .g-choice.g-multi .g-dot{border-radius:4px}
400
+ .g-choice[aria-checked="true"] .g-dot{background:var(--g-primary);border-color:var(--g-primary)}
401
+ .g-input{width:100%;min-height:40px;padding:8px 10px;border:1px solid var(--g-border);border-radius:8px;background:var(--g-bg);color:var(--g-text);font:inherit;resize:vertical}
402
+ .g-error{color:#dc2626;font-size:12px;margin-top:4px}
403
+ /* resource center */
404
+ .g-rc-search{padding:12px 20px 4px}
405
+ .g-rc-section{padding:8px 20px 4px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--g-muted);font-weight:600}
406
+ .g-rc-item{display:flex;align-items:flex-start;gap:10px;width:100%;padding:10px 20px;border:0;background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;min-height:44px;text-decoration:none}
407
+ .g-rc-item:hover{background:var(--g-subtle)}
408
+ .g-rc-item .g-rc-title{font-weight:500}
409
+ .g-rc-item .g-rc-sum{font-size:12px;color:var(--g-muted);margin-top:2px}
410
+ .g-rc-item .g-rc-icon{flex:none;width:20px;text-align:center;color:var(--g-primary)}
411
+ .g-rc-article{padding:16px 20px}
412
+ .g-rc-back{margin-bottom:8px}
413
+ .g-rc-empty{padding:24px 20px;color:var(--g-muted);text-align:center}
414
+ .g-faq-a{padding:0 20px 12px 50px;font-size:13px;color:var(--g-muted)}
415
+ .g-faq-a[hidden]{display:none}
416
+ /* authoring */
417
+ .g-auth-bar{position:fixed;top:0;left:0;right:0;z-index:calc(var(--g-z) + 10);pointer-events:auto;display:flex;align-items:center;gap:12px;padding:8px 16px;background:#111827;color:#fff;font:500 13px/1.4 var(--g-font);box-shadow:0 2px 10px rgba(0,0,0,.3)}
418
+ .g-auth-bar .g-dot-live{width:8px;height:8px;border-radius:999px;background:#22c55e;box-shadow:0 0 0 3px rgba(34,197,94,.3)}
419
+ .g-auth-bar .g-spacer{flex:1}
420
+ .g-auth-bar .g-btn{min-height:32px;padding:4px 12px;font-size:13px}
421
+ .g-auth-bar .g-btn-secondary{background:rgba(255,255,255,.12);color:#fff;border-color:transparent}
422
+ .g-auth-outline{position:fixed;z-index:calc(var(--g-z) + 9);pointer-events:none;border:2px solid var(--g-primary);background:color-mix(in srgb,var(--g-primary) 12%,transparent);border-radius:4px;transition:all .06s linear}
423
+ .g-auth-label{position:absolute;left:-2px;top:-24px;background:var(--g-primary);color:#fff;font:600 11px/1 var(--g-font);padding:5px 7px;border-radius:4px;white-space:nowrap;max-width:320px;overflow:hidden;text-overflow:ellipsis}
424
+ .g-auth-outline.g-picked{border-style:dashed;border-color:#22c55e;background:rgba(34,197,94,.12)}
425
+ .g-auth-outline.g-picked .g-auth-label{background:#16a34a}
426
+ .g-highlight{position:fixed;z-index:calc(var(--g-z) + 8);pointer-events:none;border:2px dashed #f59e0b;background:rgba(245,158,11,.12);border-radius:4px}
427
+ @media (max-width:480px){.g-card.g-modal,.g-panel{width:calc(100vw - 32px)}.g-inner{padding:16px}}
428
+ `;
429
+
430
+ // src/host.ts
431
+ var HOST_ID2 = "guidely-root";
432
+ var current = null;
433
+ var DEFAULT_THEME = {
434
+ primaryColor: "#4f46e5",
435
+ backgroundColor: "#ffffff",
436
+ textColor: "#111827",
437
+ borderRadius: 12,
438
+ fontFamily: "Inter, system-ui, -apple-system, sans-serif"
439
+ };
440
+ function ensureHost(doc = document) {
441
+ if (current && current.host.isConnected && current.host.ownerDocument === doc) return current;
442
+ let host = doc.getElementById(HOST_ID2);
443
+ if (!host) {
444
+ host = doc.createElement("div");
445
+ host.id = HOST_ID2;
446
+ host.setAttribute("data-guidely", "");
447
+ (doc.body || doc.documentElement).appendChild(host);
448
+ }
449
+ const shadow = host.shadowRoot ?? host.attachShadow({ mode: "open" });
450
+ if (!shadow.querySelector("style[data-g-base]")) {
451
+ const style = doc.createElement("style");
452
+ style.setAttribute("data-g-base", "");
453
+ style.textContent = STYLES;
454
+ shadow.appendChild(style);
455
+ }
456
+ let live = shadow.querySelector(".g-live");
457
+ if (!live) {
458
+ live = doc.createElement("div");
459
+ live.className = "g-live";
460
+ live.setAttribute("aria-live", "polite");
461
+ live.setAttribute("aria-atomic", "true");
462
+ shadow.appendChild(live);
463
+ }
464
+ const liveEl = live;
465
+ current = {
466
+ host,
467
+ shadow,
468
+ live: liveEl,
469
+ announce(text) {
470
+ liveEl.textContent = "";
471
+ setTimeout(() => {
472
+ liveEl.textContent = text;
473
+ }, 30);
474
+ },
475
+ destroy() {
476
+ host?.remove();
477
+ if (current?.host === host) current = null;
478
+ }
479
+ };
480
+ return current;
481
+ }
482
+ function destroyHost() {
483
+ current?.destroy();
484
+ const stray = typeof document !== "undefined" ? document.getElementById(HOST_ID2) : null;
485
+ stray?.remove();
486
+ current = null;
487
+ }
488
+ function prefersDark() {
489
+ try {
490
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
491
+ } catch {
492
+ return false;
493
+ }
494
+ }
495
+ function prefersReducedMotion() {
496
+ try {
497
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
498
+ } catch {
499
+ return false;
500
+ }
501
+ }
502
+ function resolveTheme(theme, appearance) {
503
+ const t = { ...DEFAULT_THEME, ...theme || {} };
504
+ const a = appearance || {};
505
+ const requested = a.theme ?? "auto";
506
+ const mode = requested === "auto" ? prefersDark() ? "dark" : "light" : requested;
507
+ const dark = mode === "dark";
508
+ return {
509
+ primary: a.primaryColor || t.primaryColor,
510
+ bg: a.backgroundColor || (dark ? "#1f2937" : t.backgroundColor),
511
+ text: a.textColor || (dark ? "#f9fafb" : t.textColor),
512
+ radius: a.borderRadius ?? t.borderRadius,
513
+ font: a.fontFamily || t.fontFamily,
514
+ z: a.zIndex ?? 2147483e3,
515
+ overlay: a.overlayOpacity ?? 0.5,
516
+ mode,
517
+ animation: prefersReducedMotion() ? "none" : a.animation ?? "fade"
518
+ };
519
+ }
520
+ function applyTheme(el2, vars) {
521
+ el2.style.setProperty("--g-primary", vars.primary);
522
+ el2.style.setProperty("--g-bg", vars.bg);
523
+ el2.style.setProperty("--g-text", vars.text);
524
+ el2.style.setProperty("--g-radius", `${vars.radius}px`);
525
+ el2.style.setProperty("--g-font", vars.font);
526
+ el2.style.setProperty("--g-z", String(vars.z));
527
+ el2.style.setProperty("--g-overlay", String(vars.overlay));
528
+ el2.setAttribute("data-theme", vars.mode);
529
+ el2.setAttribute("data-anim", vars.animation);
530
+ }
531
+ function createScope(theme, appearance, doc = document) {
532
+ const handle = ensureHost(doc);
533
+ const el2 = doc.createElement("div");
534
+ el2.className = "g-scope";
535
+ const vars = resolveTheme(theme, appearance);
536
+ applyTheme(el2, vars);
537
+ handle.shadow.appendChild(el2);
538
+ let mq = null;
539
+ const onChange = () => applyTheme(el2, resolveTheme(theme, appearance));
540
+ if ((appearance?.theme ?? "auto") === "auto" && typeof window !== "undefined" && typeof window.matchMedia === "function") {
541
+ try {
542
+ mq = window.matchMedia("(prefers-color-scheme: dark)");
543
+ mq.addEventListener?.("change", onChange);
544
+ } catch {
545
+ mq = null;
546
+ }
547
+ }
548
+ return {
549
+ el: el2,
550
+ vars,
551
+ destroy() {
552
+ mq?.removeEventListener?.("change", onChange);
553
+ el2.remove();
554
+ }
555
+ };
556
+ }
557
+
558
+ // src/position.ts
559
+ import { arrow, autoPlacement, autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
560
+ function anchor(reference, floating, options = {}) {
561
+ const placement = options.placement ?? "auto";
562
+ const gap = options.offset ?? 10;
563
+ const middleware = [offset(gap + (options.arrowEl ? 4 : 0))];
564
+ if (placement === "auto") middleware.push(autoPlacement({ padding: 16 }));
565
+ else middleware.push(flip({ padding: 16 }));
566
+ middleware.push(shift({ padding: 16 }));
567
+ if (options.arrowEl) middleware.push(arrow({ element: options.arrowEl, padding: 8 }));
568
+ let stopped = false;
569
+ const update = async () => {
570
+ if (stopped || !floating.isConnected) return;
571
+ try {
572
+ const result = await computePosition(reference, floating, {
573
+ placement: placement === "auto" ? "bottom" : placement,
574
+ strategy: "fixed",
575
+ middleware
576
+ });
577
+ if (stopped) return;
578
+ floating.style.left = `${Math.round(result.x)}px`;
579
+ floating.style.top = `${Math.round(result.y)}px`;
580
+ floating.setAttribute("data-placement", result.placement);
581
+ if (options.arrowEl && result.middlewareData.arrow) {
582
+ const { x, y } = result.middlewareData.arrow;
583
+ const side = result.placement.split("-")[0];
584
+ const staticSide = { top: "bottom", right: "left", bottom: "top", left: "right" }[side];
585
+ Object.assign(options.arrowEl.style, {
586
+ left: x != null ? `${x}px` : "",
587
+ top: y != null ? `${y}px` : "",
588
+ right: "",
589
+ bottom: "",
590
+ [staticSide]: "-7px"
591
+ });
592
+ }
593
+ options.onUpdate?.(result.placement);
594
+ } catch {
595
+ }
596
+ };
597
+ let stopAuto = null;
598
+ try {
599
+ stopAuto = autoUpdate(reference, floating, () => void update(), { animationFrame: false });
600
+ } catch {
601
+ void update();
602
+ }
603
+ return () => {
604
+ stopped = true;
605
+ stopAuto?.();
606
+ };
607
+ }
608
+ function trackRect(el2, cb) {
609
+ let raf = 0;
610
+ let stopped = false;
611
+ const run = () => {
612
+ if (stopped) return;
613
+ cb(el2.getBoundingClientRect());
614
+ };
615
+ const schedule = () => {
616
+ if (stopped) return;
617
+ if (typeof requestAnimationFrame === "function") {
618
+ cancelAnimationFrame(raf);
619
+ raf = requestAnimationFrame(run);
620
+ } else run();
621
+ };
622
+ run();
623
+ const win = el2.ownerDocument.defaultView;
624
+ win?.addEventListener("scroll", schedule, true);
625
+ win?.addEventListener("resize", schedule);
626
+ let ro = null;
627
+ if (typeof ResizeObserver !== "undefined") {
628
+ try {
629
+ ro = new ResizeObserver(schedule);
630
+ ro.observe(el2);
631
+ if (el2.ownerDocument.documentElement) ro.observe(el2.ownerDocument.documentElement);
632
+ } catch {
633
+ ro = null;
634
+ }
635
+ }
636
+ const interval = setInterval(run, 500);
637
+ return () => {
638
+ stopped = true;
639
+ win?.removeEventListener("scroll", schedule, true);
640
+ win?.removeEventListener("resize", schedule);
641
+ ro?.disconnect();
642
+ clearInterval(interval);
643
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(raf);
644
+ };
645
+ }
646
+
647
+ // src/render.ts
648
+ import { visibleButtons } from "@guidelytech/shared";
649
+ var FOCUSABLE = "button:not([disabled]),[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex='-1'])";
650
+ var uid = 0;
651
+ var nextId = (p) => `g-${p}-${++uid}`;
652
+ function h(doc, tag, className, text) {
653
+ const el2 = doc.createElement(tag);
654
+ if (className) el2.className = className;
655
+ if (text != null) el2.textContent = text;
656
+ return el2;
657
+ }
658
+ function buildButtons(doc, step, canGoBack, onButton) {
659
+ return visibleButtons(step, canGoBack).map((b) => {
660
+ const btn = h(doc, "button", `g-btn g-btn-${b.variant}`, b.label);
661
+ btn.type = "button";
662
+ btn.setAttribute("data-action", b.action);
663
+ btn.addEventListener("click", (e) => {
664
+ e.preventDefault();
665
+ onButton(b);
666
+ });
667
+ return btn;
668
+ });
669
+ }
670
+ function buildCard(doc, opts, extraClass) {
671
+ const { step } = opts;
672
+ const card = h(doc, "div", `g-card ${extraClass}`);
673
+ card.tabIndex = -1;
674
+ card.setAttribute("data-step-id", step.id);
675
+ if (step.width) card.style.width = `min(${step.width}px, calc(100vw - 32px))`;
676
+ const titleId = nextId("title");
677
+ const bodyId = nextId("body");
678
+ if (step.imageUrl) {
679
+ const img = h(doc, "img", "g-image");
680
+ img.src = step.imageUrl;
681
+ img.alt = "";
682
+ card.appendChild(img);
683
+ }
684
+ const inner = h(doc, "div", "g-inner");
685
+ if (step.showClose) {
686
+ const close = h(doc, "button", "g-close", "\xD7");
687
+ close.type = "button";
688
+ close.setAttribute("aria-label", "Close");
689
+ close.addEventListener("click", (e) => {
690
+ e.preventDefault();
691
+ opts.onClose();
692
+ });
693
+ card.appendChild(close);
694
+ }
695
+ if (step.title) {
696
+ const t = h(doc, "h2", "g-title", step.title);
697
+ t.id = titleId;
698
+ inner.appendChild(t);
699
+ }
700
+ if (step.body) {
701
+ const b = h(doc, "div", "g-body");
702
+ b.id = bodyId;
703
+ setSanitizedHtml(b, step.body);
704
+ inner.appendChild(b);
705
+ }
706
+ const buttons = buildButtons(doc, step, opts.canGoBack ?? opts.index > 0, opts.onButton);
707
+ if (buttons.length || step.showProgress && opts.total > 1) {
708
+ const footer = h(doc, "div", "g-footer");
709
+ if (step.showProgress && opts.total > 1) footer.appendChild(h(doc, "span", "g-progress", `${opts.index + 1} of ${opts.total}`));
710
+ buttons.forEach((b) => footer.appendChild(b));
711
+ inner.appendChild(footer);
712
+ }
713
+ card.appendChild(inner);
714
+ if (step.title) card.setAttribute("aria-labelledby", titleId);
715
+ if (step.body) card.setAttribute("aria-describedby", bodyId);
716
+ return { card, inner, titleId, bodyId };
717
+ }
718
+ function focusables(root) {
719
+ return Array.from(root.querySelectorAll(FOCUSABLE));
720
+ }
721
+ function installKeyboard(card, step, trap, onClose) {
722
+ const onKey = (e) => {
723
+ if (e.key === "Escape" && step.showClose) {
724
+ e.stopPropagation();
725
+ onClose();
726
+ return;
727
+ }
728
+ if (trap && e.key === "Tab") {
729
+ const items = focusables(card);
730
+ if (!items.length) {
731
+ e.preventDefault();
732
+ card.focus();
733
+ return;
734
+ }
735
+ const first = items[0];
736
+ const last = items[items.length - 1];
737
+ const active = card.getRootNode().activeElement;
738
+ if (e.shiftKey && (active === first || active === card)) {
739
+ e.preventDefault();
740
+ last.focus();
741
+ } else if (!e.shiftKey && active === last) {
742
+ e.preventDefault();
743
+ first.focus();
744
+ }
745
+ }
746
+ };
747
+ card.addEventListener("keydown", onKey);
748
+ return () => card.removeEventListener("keydown", onKey);
749
+ }
750
+ function focusInto(card) {
751
+ const doc = card.ownerDocument;
752
+ const previous = doc.activeElement;
753
+ const primary = card.querySelector(".g-btn-primary") || focusables(card)[0] || card;
754
+ try {
755
+ primary.focus({ preventScroll: true });
756
+ } catch {
757
+ }
758
+ return () => {
759
+ try {
760
+ if (previous && previous.isConnected && typeof previous.focus === "function") previous.focus({ preventScroll: true });
761
+ } catch {
762
+ }
763
+ };
764
+ }
765
+ function overlayEl(doc, opts, transparent = false) {
766
+ const overlay = h(doc, "div", `g-overlay${transparent ? " g-transparent" : ""}`);
767
+ if (opts.step.dismissOnOutsideClick) overlay.addEventListener("click", () => opts.onClose());
768
+ return overlay;
769
+ }
770
+ function positionClass(position) {
771
+ return `g-pos-${position}`;
772
+ }
773
+ function renderModal(doc, opts) {
774
+ const cleanups = [];
775
+ const { card } = buildCard(doc, opts, "g-modal");
776
+ card.setAttribute("role", "dialog");
777
+ card.setAttribute("aria-modal", "true");
778
+ const wrapper = h(doc, "div", "g-step");
779
+ if (opts.step.showOverlay || opts.asModal) wrapper.appendChild(overlayEl(doc, opts));
780
+ else if (opts.step.dismissOnOutsideClick) wrapper.appendChild(overlayEl(doc, opts, true));
781
+ wrapper.appendChild(card);
782
+ opts.scope.appendChild(wrapper);
783
+ cleanups.push(installKeyboard(card, opts.step, true, opts.onClose));
784
+ cleanups.push(focusInto(card));
785
+ return { el: wrapper, card, destroy: () => cleanupAll(cleanups, wrapper) };
786
+ }
787
+ function cleanupAll(cleanups, el2) {
788
+ cleanups.splice(0).forEach((fn) => {
789
+ try {
790
+ fn();
791
+ } catch {
792
+ }
793
+ });
794
+ el2.remove();
795
+ }
796
+ function setTargetDescribedBy(target, id) {
797
+ const prev = target.getAttribute("aria-describedby");
798
+ try {
799
+ target.setAttribute("aria-describedby", prev ? `${prev} ${id}` : id);
800
+ } catch {
801
+ return () => {
802
+ };
803
+ }
804
+ return () => {
805
+ if (prev) target.setAttribute("aria-describedby", prev);
806
+ else target.removeAttribute("aria-describedby");
807
+ };
808
+ }
809
+ function renderTooltip(doc, opts, kind) {
810
+ const target = opts.target;
811
+ if (!target) return renderModal(doc, { ...opts, asModal: true });
812
+ const cleanups = [];
813
+ const { card } = buildCard(doc, opts, `g-${kind}`);
814
+ const arrowEl = h(doc, "div", "g-arrow");
815
+ card.appendChild(arrowEl);
816
+ card.setAttribute("role", kind === "tooltip" ? "tooltip" : "dialog");
817
+ card.id = nextId(kind);
818
+ const wrapper = h(doc, "div", "g-step");
819
+ if (opts.step.showOverlay) wrapper.appendChild(overlayEl(doc, opts));
820
+ else if (opts.step.dismissOnOutsideClick) wrapper.appendChild(overlayEl(doc, opts, true));
821
+ wrapper.appendChild(card);
822
+ opts.scope.appendChild(wrapper);
823
+ if (opts.step.target?.scrollIntoView !== false) scrollTo(target);
824
+ cleanups.push(anchor(target, card, { placement: opts.step.placement, offset: opts.step.offset, arrowEl }));
825
+ cleanups.push(setTargetDescribedBy(target, card.id));
826
+ cleanups.push(installKeyboard(card, opts.step, false, opts.onClose));
827
+ cleanups.push(focusInto(card));
828
+ return { el: wrapper, card, destroy: () => cleanupAll(cleanups, wrapper) };
829
+ }
830
+ function scrollTo(target) {
831
+ try {
832
+ target.scrollIntoView?.({ block: "nearest", inline: "nearest", behavior: "auto" });
833
+ } catch {
834
+ }
835
+ }
836
+ function renderSpotlight(doc, opts) {
837
+ const target = opts.target;
838
+ if (!target) return renderModal(doc, { ...opts, asModal: true });
839
+ const cleanups = [];
840
+ const wrapper = h(doc, "div", "g-step");
841
+ const pad = opts.step.spotlightPadding ?? 6;
842
+ const rects = ["top", "left", "right", "bottom"].map((side) => {
843
+ const r = h(doc, "div", `g-spot-rect g-spot-${side}`);
844
+ if (opts.step.dismissOnOutsideClick) r.addEventListener("click", () => opts.onClose());
845
+ wrapper.appendChild(r);
846
+ return r;
847
+ });
848
+ const ring = h(doc, "div", "g-spot-ring");
849
+ wrapper.appendChild(ring);
850
+ const { card } = buildCard(doc, opts, "g-spotlight-card");
851
+ card.setAttribute("role", "dialog");
852
+ const arrowEl = h(doc, "div", "g-arrow");
853
+ card.appendChild(arrowEl);
854
+ wrapper.appendChild(card);
855
+ opts.scope.appendChild(wrapper);
856
+ if (opts.step.target?.scrollIntoView !== false) scrollTo(target);
857
+ const win = doc.defaultView;
858
+ const layout = (r) => {
859
+ const vw = win?.innerWidth ?? 0;
860
+ const vh = win?.innerHeight ?? 0;
861
+ const x = Math.max(0, r.left - pad);
862
+ const y = Math.max(0, r.top - pad);
863
+ const w = Math.min(vw, r.right + pad) - x;
864
+ const hgt = Math.min(vh, r.bottom + pad) - y;
865
+ const [top, left, right, bottom] = rects;
866
+ Object.assign(top.style, { left: "0", top: "0", width: "100%", height: `${y}px` });
867
+ Object.assign(left.style, { left: "0", top: `${y}px`, width: `${x}px`, height: `${hgt}px` });
868
+ Object.assign(right.style, { left: `${x + w}px`, top: `${y}px`, width: `${Math.max(0, vw - x - w)}px`, height: `${hgt}px` });
869
+ Object.assign(bottom.style, { left: "0", top: `${y + hgt}px`, width: "100%", height: `${Math.max(0, vh - y - hgt)}px` });
870
+ Object.assign(ring.style, { left: `${x}px`, top: `${y}px`, width: `${w}px`, height: `${hgt}px` });
871
+ };
872
+ cleanups.push(trackRect(target, layout));
873
+ cleanups.push(anchor(target, card, { placement: opts.step.placement, offset: opts.step.offset + pad, arrowEl }));
874
+ cleanups.push(installKeyboard(card, opts.step, false, opts.onClose));
875
+ cleanups.push(focusInto(card));
876
+ return { el: wrapper, card, destroy: () => cleanupAll(cleanups, wrapper) };
877
+ }
878
+ function renderHotspot(doc, opts) {
879
+ const target = opts.target;
880
+ if (!target) return renderModal(doc, { ...opts, asModal: true });
881
+ const cleanups = [];
882
+ const cfg = opts.step.hotspot ?? { size: 14, pulse: true, openOn: "click" };
883
+ const wrapper = h(doc, "div", "g-step");
884
+ const dot = h(doc, "button", `g-hotspot${cfg.pulse ? " g-pulse" : ""}`);
885
+ dot.type = "button";
886
+ dot.setAttribute("aria-label", opts.step.title || "Open hint");
887
+ dot.setAttribute("aria-expanded", "false");
888
+ dot.style.width = `${cfg.size}px`;
889
+ dot.style.height = `${cfg.size}px`;
890
+ dot.appendChild(h(doc, "span", "g-hotspot-hit"));
891
+ wrapper.appendChild(dot);
892
+ opts.scope.appendChild(wrapper);
893
+ cleanups.push(trackRect(target, (r) => {
894
+ dot.style.left = `${Math.round(r.right - cfg.size / 2)}px`;
895
+ dot.style.top = `${Math.round(r.top - cfg.size / 2)}px`;
896
+ }));
897
+ let card = null;
898
+ let stopAnchor = null;
899
+ let stopKeys = null;
900
+ const close = () => {
901
+ stopAnchor?.();
902
+ stopKeys?.();
903
+ card?.remove();
904
+ card = null;
905
+ dot.setAttribute("aria-expanded", "false");
906
+ };
907
+ const open = () => {
908
+ if (card) return;
909
+ const built = buildCard(doc, { ...opts, onClose: () => opts.onClose() }, "g-hotspot-card");
910
+ card = built.card;
911
+ card.setAttribute("role", "dialog");
912
+ const arrowEl = h(doc, "div", "g-arrow");
913
+ card.appendChild(arrowEl);
914
+ wrapper.appendChild(card);
915
+ stopAnchor = anchor(target, card, { placement: opts.step.placement, offset: opts.step.offset, arrowEl });
916
+ stopKeys = installKeyboard(card, opts.step, false, close);
917
+ dot.setAttribute("aria-expanded", "true");
918
+ opts.onOpen?.();
919
+ if (cfg.openOn === "click") focusInto(card);
920
+ };
921
+ dot.addEventListener("click", (e) => {
922
+ e.preventDefault();
923
+ if (card) close();
924
+ else open();
925
+ });
926
+ if (cfg.openOn === "hover") {
927
+ dot.addEventListener("mouseenter", open);
928
+ dot.addEventListener("focus", open);
929
+ }
930
+ cleanups.push(close);
931
+ return { el: wrapper, card: dot, destroy: () => cleanupAll(cleanups, wrapper) };
932
+ }
933
+ function renderBanner(doc, opts) {
934
+ const { step } = opts;
935
+ const cleanups = [];
936
+ const pos = step.position === "top" || step.placement === "top" || step.position.startsWith("top") ? "top" : "bottom";
937
+ const banner = h(doc, "div", `g-banner g-pos-${pos}`);
938
+ banner.setAttribute("role", "status");
939
+ banner.setAttribute("data-step-id", step.id);
940
+ const body = h(doc, "div", "g-body");
941
+ if (step.title) body.appendChild(h(doc, "strong", "", `${step.title} `));
942
+ const text = h(doc, "span");
943
+ setSanitizedHtml(text, step.body);
944
+ body.appendChild(text);
945
+ banner.appendChild(body);
946
+ buildButtons(doc, step, opts.canGoBack ?? opts.index > 0, opts.onButton).forEach((b) => banner.appendChild(b));
947
+ if (step.showClose) {
948
+ const close = h(doc, "button", "g-close", "\xD7");
949
+ close.type = "button";
950
+ close.setAttribute("aria-label", "Dismiss");
951
+ close.addEventListener("click", () => opts.onClose());
952
+ banner.appendChild(close);
953
+ }
954
+ opts.scope.appendChild(banner);
955
+ cleanups.push(installKeyboard(banner, step, false, opts.onClose));
956
+ return { el: banner, card: banner, destroy: () => cleanupAll(cleanups, banner) };
957
+ }
958
+ function renderSlideout(doc, opts) {
959
+ const cleanups = [];
960
+ const pos = opts.step.position === "top" ? "top-right" : opts.step.position === "bottom" ? "bottom-right" : opts.step.position;
961
+ const { card } = buildCard(doc, opts, `g-slideout ${positionClass(pos)}`);
962
+ card.setAttribute("role", "dialog");
963
+ const wrapper = h(doc, "div", "g-step");
964
+ if (opts.step.dismissOnOutsideClick) wrapper.appendChild(overlayEl(doc, opts, true));
965
+ wrapper.appendChild(card);
966
+ opts.scope.appendChild(wrapper);
967
+ cleanups.push(installKeyboard(card, opts.step, false, opts.onClose));
968
+ cleanups.push(focusInto(card));
969
+ return { el: wrapper, card, destroy: () => cleanupAll(cleanups, wrapper) };
970
+ }
971
+ function renderCustom(doc, opts) {
972
+ const cleanups = [];
973
+ const wrapper = h(doc, "div", "g-step");
974
+ if (opts.step.showOverlay) wrapper.appendChild(overlayEl(doc, opts));
975
+ const el2 = h(doc, "div", "g-custom");
976
+ el2.tabIndex = -1;
977
+ el2.setAttribute("data-step-id", opts.step.id);
978
+ setSanitizedHtml(el2, opts.step.customHtml || opts.step.body || "");
979
+ const onClick = (e) => {
980
+ const t = e.target?.closest?.("[data-g-action]");
981
+ const action = t?.getAttribute("data-g-action");
982
+ if (!action) return;
983
+ e.preventDefault();
984
+ if (action === "dismiss") opts.onClose();
985
+ else opts.onButton({ id: `custom-${action}`, label: action, action, variant: "primary", value: t?.getAttribute("data-g-value") || void 0 });
986
+ };
987
+ el2.addEventListener("click", onClick);
988
+ wrapper.appendChild(el2);
989
+ opts.scope.appendChild(wrapper);
990
+ cleanups.push(installKeyboard(el2, opts.step, false, opts.onClose));
991
+ cleanups.push(focusInto(el2));
992
+ return { el: wrapper, card: el2, destroy: () => cleanupAll(cleanups, wrapper) };
993
+ }
994
+ function renderStep(opts) {
995
+ const doc = opts.scope.ownerDocument;
996
+ const type = opts.asModal ? "modal" : opts.step.type;
997
+ switch (type) {
998
+ case "tooltip":
999
+ return renderTooltip(doc, opts, "tooltip");
1000
+ case "popover":
1001
+ return renderTooltip(doc, opts, "popover");
1002
+ case "spotlight":
1003
+ return renderSpotlight(doc, opts);
1004
+ case "hotspot":
1005
+ return renderHotspot(doc, opts);
1006
+ case "banner":
1007
+ return renderBanner(doc, opts);
1008
+ case "slideout":
1009
+ return renderSlideout(doc, opts);
1010
+ case "custom":
1011
+ return renderCustom(doc, opts);
1012
+ case "modal":
1013
+ case "checklist":
1014
+ case "survey":
1015
+ default:
1016
+ return renderModal(doc, opts);
1017
+ }
1018
+ }
1019
+ function isAnchoredStep(step) {
1020
+ return (step.type === "tooltip" || step.type === "popover" || step.type === "spotlight" || step.type === "hotspot") && !!step.target && step.target.selectors.length > 0;
1021
+ }
1022
+
1023
+ // src/controller.ts
1024
+ import { evaluateRules } from "@guidelytech/shared/runtime";
1025
+ function noop() {
1026
+ }
1027
+ function runGuide(guide, options = {}) {
1028
+ const doc = options.doc ?? (options.container?.ownerDocument || document);
1029
+ const steps = guide.config.steps;
1030
+ const total = steps.length;
1031
+ const onEvent = options.onEvent ?? noop;
1032
+ const onError = options.onError ?? noop;
1033
+ const getCtx = () => (typeof options.ctx === "function" ? options.ctx() : options.ctx) ?? null;
1034
+ const currentUrl = options.currentUrl ?? (() => typeof location !== "undefined" ? location.href : "");
1035
+ let status = "idle";
1036
+ let index = -1;
1037
+ let pendingIndex = -1;
1038
+ let view = null;
1039
+ const stepCleanups = [];
1040
+ let started = false;
1041
+ let stepDone = false;
1042
+ let ended = false;
1043
+ const history = [];
1044
+ let scope = null;
1045
+ const emit = (name, step, stepIndex, properties) => {
1046
+ try {
1047
+ onEvent({ name, guideId: guide.id, guideKey: guide.key, guideVersion: guide.version, stepId: step?.id, stepIndex, properties });
1048
+ } catch (err) {
1049
+ onError(err);
1050
+ }
1051
+ };
1052
+ const announce = (text) => {
1053
+ try {
1054
+ ensureHost(doc).announce(text);
1055
+ } catch {
1056
+ }
1057
+ };
1058
+ const getScope = () => {
1059
+ if (options.container) return options.container;
1060
+ if (!scope) scope = createScope(options.theme, guide.config.appearance, doc);
1061
+ return scope.el;
1062
+ };
1063
+ const clearStep = () => {
1064
+ stepCleanups.splice(0).forEach((fn) => {
1065
+ try {
1066
+ fn();
1067
+ } catch {
1068
+ }
1069
+ });
1070
+ try {
1071
+ view?.destroy();
1072
+ } catch (err) {
1073
+ onError(err);
1074
+ }
1075
+ view = null;
1076
+ stepDone = false;
1077
+ };
1078
+ const end = (reason) => {
1079
+ if (ended) return;
1080
+ ended = true;
1081
+ const step = steps[index];
1082
+ clearStep();
1083
+ status = "ended";
1084
+ if (reason === "completed") emit("guide_completed", step, index);
1085
+ else if (reason === "dismissed") emit("guide_dismissed", step, index);
1086
+ else if (reason === "skipped") emit("guide_skipped", step, index);
1087
+ scope?.destroy();
1088
+ scope = null;
1089
+ try {
1090
+ options.onEnd?.(reason);
1091
+ } catch (err) {
1092
+ onError(err);
1093
+ }
1094
+ };
1095
+ const completeStep = () => {
1096
+ if (stepDone || index < 0) return;
1097
+ stepDone = true;
1098
+ emit("step_completed", steps[index], index);
1099
+ };
1100
+ const goNext = (overrideStepId) => {
1101
+ const step = steps[index];
1102
+ let target = -1;
1103
+ const ctx = getCtx();
1104
+ if (step && ctx) {
1105
+ for (const branch of step.branches) {
1106
+ if (evaluateRules(branch.when, ctx)) {
1107
+ target = steps.findIndex((s) => s.id === branch.goTo);
1108
+ if (target >= 0) break;
1109
+ }
1110
+ }
1111
+ }
1112
+ if (target < 0 && overrideStepId) target = steps.findIndex((s) => s.id === overrideStepId);
1113
+ if (target < 0) target = index + 1;
1114
+ if (target >= total) return end("completed");
1115
+ history.push(index);
1116
+ showStep(target, 1);
1117
+ };
1118
+ const advance = () => {
1119
+ if (ended) return;
1120
+ completeStep();
1121
+ goNext();
1122
+ };
1123
+ const handleButton = (b) => {
1124
+ try {
1125
+ switch (b.action) {
1126
+ case "next":
1127
+ completeStep();
1128
+ goNext(b.value);
1129
+ break;
1130
+ case "back":
1131
+ controller.back();
1132
+ break;
1133
+ case "dismiss":
1134
+ end("dismissed");
1135
+ break;
1136
+ case "complete":
1137
+ completeStep();
1138
+ end("completed");
1139
+ break;
1140
+ case "skip":
1141
+ end("skipped");
1142
+ break;
1143
+ case "url": {
1144
+ completeStep();
1145
+ const url = b.value || "";
1146
+ if (options.onNavigate) options.onNavigate(url, b.newTab);
1147
+ else if (b.newTab) window.open(url, "_blank", "noopener");
1148
+ else location.assign(url);
1149
+ if (index + 1 >= total) end("completed");
1150
+ else goNext();
1151
+ break;
1152
+ }
1153
+ case "start_guide":
1154
+ completeStep();
1155
+ end("completed");
1156
+ options.onAction?.("start_guide", b.value, b);
1157
+ break;
1158
+ case "event":
1159
+ options.onAction?.("event", b.value, b);
1160
+ break;
1161
+ case "open_resource_center":
1162
+ options.onAction?.("open_resource_center", b.value, b);
1163
+ break;
1164
+ default:
1165
+ break;
1166
+ }
1167
+ } catch (err) {
1168
+ onError(err);
1169
+ }
1170
+ };
1171
+ const setupAdvance = (step) => {
1172
+ const modes = step.advanceOn === "any" ? ["element_click", "element_visible", "event", "delay"] : [step.advanceOn];
1173
+ for (const mode of modes) {
1174
+ if (mode === "element_click" && step.advanceValue) {
1175
+ const selector = step.advanceValue;
1176
+ const handler = (e) => {
1177
+ const t = e.target;
1178
+ try {
1179
+ if (t && typeof t.closest === "function" && t.closest(selector)) advance();
1180
+ } catch {
1181
+ }
1182
+ };
1183
+ doc.addEventListener("click", handler, true);
1184
+ doc.addEventListener("focusin", handler, true);
1185
+ stepCleanups.push(() => {
1186
+ doc.removeEventListener("click", handler, true);
1187
+ doc.removeEventListener("focusin", handler, true);
1188
+ });
1189
+ } else if (mode === "element_visible" && step.advanceValue) {
1190
+ const ac = new AbortController();
1191
+ stepCleanups.push(() => ac.abort());
1192
+ waitForTarget([{ kind: "css", value: step.advanceValue }], { timeoutMs: 0, signal: ac.signal, root: doc }).then((el2) => {
1193
+ if (ac.signal.aborted) return;
1194
+ if (typeof IntersectionObserver === "undefined") return advance();
1195
+ const io = new IntersectionObserver((entries) => {
1196
+ if (entries.some((en) => en.isIntersecting)) {
1197
+ io.disconnect();
1198
+ advance();
1199
+ }
1200
+ });
1201
+ io.observe(el2);
1202
+ stepCleanups.push(() => io.disconnect());
1203
+ }).catch(noop);
1204
+ } else if (mode === "event" && step.advanceValue && options.eventBus) {
1205
+ stepCleanups.push(options.eventBus.on(step.advanceValue, advance));
1206
+ } else if (mode === "delay") {
1207
+ const t = setTimeout(advance, step.advanceDelayMs ?? 3e3);
1208
+ stepCleanups.push(() => clearTimeout(t));
1209
+ }
1210
+ }
1211
+ };
1212
+ const render = (step, target, asModal = false) => {
1213
+ if (ended) return;
1214
+ status = "running";
1215
+ try {
1216
+ view = renderStep({
1217
+ scope: getScope(),
1218
+ step,
1219
+ index,
1220
+ total,
1221
+ canGoBack: history.length > 0 || index > 0,
1222
+ target,
1223
+ asModal,
1224
+ onButton: handleButton,
1225
+ onClose: () => end("dismissed"),
1226
+ announce
1227
+ });
1228
+ } catch (err) {
1229
+ onError(err);
1230
+ view = null;
1231
+ if (index + 1 < total) return goNext();
1232
+ return end("skipped");
1233
+ }
1234
+ if (!started) {
1235
+ started = true;
1236
+ emit("guide_started", step, index);
1237
+ }
1238
+ emit("step_viewed", step, index, asModal ? { fallback: "show_as_modal" } : void 0);
1239
+ announce(step.title ? `${step.title}. Step ${index + 1} of ${total}` : `Step ${index + 1} of ${total}`);
1240
+ setupAdvance(step);
1241
+ };
1242
+ const applyFallback = (step, direction) => {
1243
+ const fallback = step.target?.fallback ?? "skip";
1244
+ emit("step_viewed", step, index, { fallback, targetMissing: true });
1245
+ if (fallback === "show_as_modal") return render(step, null, true);
1246
+ if (fallback === "end_guide") return end("skipped");
1247
+ const nextIndex = index + direction;
1248
+ if (nextIndex < 0) return showStep(0, 1);
1249
+ if (nextIndex >= total) return end("completed");
1250
+ showStep(nextIndex, direction);
1251
+ };
1252
+ function showStep(i, direction = 1) {
1253
+ if (ended) return;
1254
+ clearStep();
1255
+ if (i < 0) i = 0;
1256
+ if (i >= total) return end("completed");
1257
+ index = i;
1258
+ const step = steps[i];
1259
+ const ctx = getCtx();
1260
+ if (step.targeting && step.targeting.rules.length && ctx && !evaluateRules(step.targeting, ctx)) {
1261
+ const nextIndex = i + direction;
1262
+ if (nextIndex >= total) return end("completed");
1263
+ if (nextIndex < 0) return;
1264
+ return showStep(nextIndex, direction);
1265
+ }
1266
+ if (step.url && !urlMatches(step.url, currentUrl())) {
1267
+ status = "waiting_url";
1268
+ pendingIndex = i;
1269
+ return;
1270
+ }
1271
+ pendingIndex = -1;
1272
+ if (isAnchoredStep(step) && step.target) {
1273
+ status = "waiting_target";
1274
+ const ac = new AbortController();
1275
+ stepCleanups.push(() => ac.abort());
1276
+ const timeoutMs = step.target.fallback === "wait" ? 0 : step.target.waitMs;
1277
+ waitForTarget(step.target, { timeoutMs, signal: ac.signal, root: doc }).then((el2) => {
1278
+ if (ac.signal.aborted || ended) return;
1279
+ render(step, el2);
1280
+ }).catch((err) => {
1281
+ if (ac.signal.aborted || ended) return;
1282
+ if (err && err.name === "AbortError") return;
1283
+ applyFallback(step, direction);
1284
+ });
1285
+ return;
1286
+ }
1287
+ render(step, null);
1288
+ }
1289
+ const controller = {
1290
+ guide,
1291
+ start() {
1292
+ if (status !== "idle") return;
1293
+ emit("guide_viewed");
1294
+ if (total === 0) return end("completed");
1295
+ let startIndex = options.startAtStep ? steps.findIndex((s) => s.id === options.startAtStep) : 0;
1296
+ if (startIndex < 0) startIndex = 0;
1297
+ showStep(startIndex, 1);
1298
+ },
1299
+ next() {
1300
+ if (ended || index < 0) return;
1301
+ completeStep();
1302
+ goNext();
1303
+ },
1304
+ back() {
1305
+ if (ended) return;
1306
+ const prev = history.pop();
1307
+ if (prev == null) {
1308
+ if (index > 0) showStep(index - 1, -1);
1309
+ return;
1310
+ }
1311
+ showStep(prev, -1);
1312
+ },
1313
+ goTo(stepId) {
1314
+ const i = steps.findIndex((s) => s.id === stepId);
1315
+ if (i >= 0 && !ended) {
1316
+ if (index >= 0) history.push(index);
1317
+ showStep(i, 1);
1318
+ }
1319
+ },
1320
+ dismiss: () => end("dismissed"),
1321
+ complete() {
1322
+ completeStep();
1323
+ end("completed");
1324
+ },
1325
+ skip: () => end("skipped"),
1326
+ resume() {
1327
+ if (ended) return;
1328
+ if (status === "waiting_url" && pendingIndex >= 0) {
1329
+ showStep(pendingIndex, 1);
1330
+ return;
1331
+ }
1332
+ const step = steps[index];
1333
+ if (step?.url && status === "running" && !urlMatches(step.url, currentUrl())) showStep(index, 1);
1334
+ },
1335
+ destroy() {
1336
+ if (ended) return;
1337
+ ended = true;
1338
+ clearStep();
1339
+ status = "ended";
1340
+ scope?.destroy();
1341
+ scope = null;
1342
+ try {
1343
+ options.onEnd?.("destroyed");
1344
+ } catch (err) {
1345
+ onError(err);
1346
+ }
1347
+ },
1348
+ getState: () => ({ status, stepId: steps[index]?.id ?? null, stepIndex: index, total })
1349
+ };
1350
+ return controller;
1351
+ }
1352
+
1353
+ // src/checklist.ts
1354
+ function isChecklistComplete(config, progress) {
1355
+ const done = new Set(progress.completedTaskIds);
1356
+ return config.tasks.filter((t) => t.required).every((t) => done.has(t.id));
1357
+ }
1358
+ function renderChecklist(options) {
1359
+ const { scope, config } = options;
1360
+ const doc = scope.ownerDocument;
1361
+ let progress = options.progress ?? { completedTaskIds: [] };
1362
+ let open = options.open ?? config.autoOpen;
1363
+ const pos = `g-pos-${config.position}`;
1364
+ const root = doc.createElement("div");
1365
+ root.className = "g-checklist";
1366
+ const launcher = doc.createElement("button");
1367
+ launcher.type = "button";
1368
+ launcher.className = `g-launcher ${pos}`;
1369
+ launcher.setAttribute("aria-expanded", "false");
1370
+ const ring = doc.createElement("span");
1371
+ ring.className = "g-ring";
1372
+ const count = doc.createElement("span");
1373
+ count.className = "g-count";
1374
+ ring.appendChild(count);
1375
+ launcher.appendChild(ring);
1376
+ const label = doc.createElement("span");
1377
+ label.textContent = config.launcherLabel;
1378
+ launcher.appendChild(label);
1379
+ const panel = doc.createElement("div");
1380
+ panel.className = `g-panel ${pos}`;
1381
+ panel.setAttribute("role", "dialog");
1382
+ panel.setAttribute("aria-label", config.launcherLabel);
1383
+ panel.hidden = true;
1384
+ const head = doc.createElement("div");
1385
+ head.className = "g-panel-head";
1386
+ const title = doc.createElement("h2");
1387
+ title.className = "g-panel-title";
1388
+ title.textContent = config.launcherLabel;
1389
+ const sub = doc.createElement("p");
1390
+ sub.className = "g-panel-sub";
1391
+ const bar = doc.createElement("div");
1392
+ bar.className = "g-bar";
1393
+ const fill = doc.createElement("span");
1394
+ bar.appendChild(fill);
1395
+ const close = doc.createElement("button");
1396
+ close.type = "button";
1397
+ close.className = "g-close";
1398
+ close.textContent = "\xD7";
1399
+ close.setAttribute("aria-label", "Minimize checklist");
1400
+ head.append(title, sub, bar, close);
1401
+ const body = doc.createElement("div");
1402
+ body.className = "g-panel-body";
1403
+ const foot = doc.createElement("div");
1404
+ foot.className = "g-panel-foot";
1405
+ panel.append(head, body, foot);
1406
+ root.append(launcher, panel);
1407
+ scope.appendChild(root);
1408
+ const setOpen = (v) => {
1409
+ open = v;
1410
+ panel.hidden = !v;
1411
+ launcher.setAttribute("aria-expanded", String(v));
1412
+ if (v) {
1413
+ (panel.querySelector(".g-task:not([disabled])") || close).focus?.({ preventScroll: true });
1414
+ options.onOpen?.();
1415
+ } else options.onClose?.();
1416
+ };
1417
+ const renderTasks = () => {
1418
+ const done = new Set(progress.completedTaskIds);
1419
+ const doneCount = config.tasks.filter((t) => done.has(t.id)).length;
1420
+ const pct = config.tasks.length ? Math.round(doneCount / config.tasks.length * 100) : 0;
1421
+ count.textContent = `${doneCount}/${config.tasks.length}`;
1422
+ launcher.style.setProperty("--g-pct", `${pct}%`);
1423
+ launcher.setAttribute("aria-label", `${config.launcherLabel}: ${doneCount} of ${config.tasks.length} tasks completed`);
1424
+ sub.textContent = `${doneCount} of ${config.tasks.length} completed`;
1425
+ fill.style.width = `${pct}%`;
1426
+ bar.setAttribute("role", "progressbar");
1427
+ bar.setAttribute("aria-valuenow", String(pct));
1428
+ bar.setAttribute("aria-valuemin", "0");
1429
+ bar.setAttribute("aria-valuemax", "100");
1430
+ body.textContent = "";
1431
+ foot.textContent = "";
1432
+ const complete = isChecklistComplete(config, progress);
1433
+ if (complete) {
1434
+ const wrap = doc.createElement("div");
1435
+ wrap.className = "g-complete";
1436
+ const t = doc.createElement("h3");
1437
+ t.className = "g-title";
1438
+ t.textContent = config.completionTitle;
1439
+ const b = doc.createElement("div");
1440
+ b.className = "g-body";
1441
+ setSanitizedHtml(b, config.completionBody);
1442
+ wrap.append(t, b);
1443
+ body.appendChild(wrap);
1444
+ }
1445
+ const list = doc.createElement("div");
1446
+ list.setAttribute("role", "list");
1447
+ let locked = false;
1448
+ config.tasks.forEach((task, i) => {
1449
+ const isDone = done.has(task.id);
1450
+ const btn = doc.createElement("button");
1451
+ btn.type = "button";
1452
+ btn.className = `g-task${isDone ? " g-done" : ""}`;
1453
+ btn.setAttribute("role", "listitem");
1454
+ btn.setAttribute("data-task-id", task.id);
1455
+ btn.setAttribute("aria-checked", String(isDone));
1456
+ if (config.ordered && locked && !isDone) {
1457
+ btn.disabled = true;
1458
+ btn.title = "Complete the previous task first";
1459
+ }
1460
+ if (config.ordered && !isDone) locked = true;
1461
+ const check = doc.createElement("span");
1462
+ check.className = "g-check";
1463
+ check.setAttribute("aria-hidden", "true");
1464
+ check.textContent = isDone ? "\u2713" : "";
1465
+ const text = doc.createElement("span");
1466
+ const tt = doc.createElement("div");
1467
+ tt.className = "g-task-title";
1468
+ tt.textContent = `${i + 1}. ${task.title}`;
1469
+ text.appendChild(tt);
1470
+ if (task.description) {
1471
+ const td = doc.createElement("div");
1472
+ td.className = "g-task-desc";
1473
+ td.textContent = task.description;
1474
+ text.appendChild(td);
1475
+ }
1476
+ btn.append(check, text);
1477
+ btn.addEventListener("click", () => options.onTaskClick?.(task));
1478
+ list.appendChild(btn);
1479
+ });
1480
+ body.appendChild(list);
1481
+ if (config.dismissible) {
1482
+ const dismiss = doc.createElement("button");
1483
+ dismiss.type = "button";
1484
+ dismiss.className = "g-btn g-btn-ghost";
1485
+ dismiss.textContent = complete ? "Close" : "Dismiss checklist";
1486
+ dismiss.addEventListener("click", () => options.onDismiss?.());
1487
+ foot.appendChild(dismiss);
1488
+ }
1489
+ };
1490
+ launcher.addEventListener("click", () => setOpen(!open));
1491
+ close.addEventListener("click", () => setOpen(false));
1492
+ const onKey = (e) => {
1493
+ if (e.key === "Escape" && open) {
1494
+ e.stopPropagation();
1495
+ setOpen(false);
1496
+ launcher.focus();
1497
+ }
1498
+ };
1499
+ panel.addEventListener("keydown", onKey);
1500
+ renderTasks();
1501
+ if (open) setOpen(true);
1502
+ return {
1503
+ el: root,
1504
+ open: () => setOpen(true),
1505
+ close: () => setOpen(false),
1506
+ toggle: () => setOpen(!open),
1507
+ isOpen: () => open,
1508
+ setProgress(p) {
1509
+ progress = p;
1510
+ renderTasks();
1511
+ },
1512
+ destroy() {
1513
+ panel.removeEventListener("keydown", onKey);
1514
+ root.remove();
1515
+ }
1516
+ };
1517
+ }
1518
+
1519
+ // src/survey.ts
1520
+ function isQuestionVisible(q, answers) {
1521
+ if (!q.showWhen) return true;
1522
+ const actual = answers[q.showWhen.questionId];
1523
+ if (actual === void 0) return false;
1524
+ const expected = q.showWhen.value;
1525
+ switch (q.showWhen.op) {
1526
+ case "equals":
1527
+ return Array.isArray(actual) ? actual.includes(String(expected)) : String(actual) === String(expected);
1528
+ case "lte":
1529
+ return Number(actual) <= Number(expected);
1530
+ case "gte":
1531
+ return Number(actual) >= Number(expected);
1532
+ case "in": {
1533
+ const list = Array.isArray(expected) ? expected : [String(expected)];
1534
+ return Array.isArray(actual) ? actual.some((a) => list.includes(a)) : list.includes(String(actual));
1535
+ }
1536
+ default:
1537
+ return false;
1538
+ }
1539
+ }
1540
+ function renderSurvey(options) {
1541
+ const { scope, config } = options;
1542
+ const doc = scope.ownerDocument;
1543
+ const answers = {};
1544
+ const card = doc.createElement("div");
1545
+ card.className = `g-card g-survey g-slideout g-pos-${config.position}`;
1546
+ card.setAttribute("role", "dialog");
1547
+ card.setAttribute("aria-label", options.title || "Survey");
1548
+ card.tabIndex = -1;
1549
+ const inner = doc.createElement("div");
1550
+ inner.className = "g-inner";
1551
+ card.appendChild(inner);
1552
+ if (options.onDismiss) {
1553
+ const close = doc.createElement("button");
1554
+ close.type = "button";
1555
+ close.className = "g-close";
1556
+ close.textContent = "\xD7";
1557
+ close.setAttribute("aria-label", "Close survey");
1558
+ close.addEventListener("click", () => options.onDismiss?.());
1559
+ card.appendChild(close);
1560
+ }
1561
+ scope.appendChild(card);
1562
+ const form = doc.createElement("form");
1563
+ form.noValidate = true;
1564
+ inner.appendChild(form);
1565
+ const errorEl = doc.createElement("div");
1566
+ errorEl.className = "g-error";
1567
+ errorEl.setAttribute("role", "alert");
1568
+ const setAnswer = (id, v) => {
1569
+ if (v === void 0 || v === "" || Array.isArray(v) && v.length === 0) delete answers[id];
1570
+ else answers[id] = v;
1571
+ options.onChange?.({ ...answers });
1572
+ updateVisibility();
1573
+ };
1574
+ const questionEls = /* @__PURE__ */ new Map();
1575
+ const scaleButtons = (q, min, max, labels) => {
1576
+ const wrap = doc.createElement("div");
1577
+ const row = doc.createElement("div");
1578
+ row.className = "g-scale";
1579
+ row.setAttribute("role", "radiogroup");
1580
+ row.setAttribute("aria-label", q.title);
1581
+ for (let i = min; i <= max; i++) {
1582
+ const b = doc.createElement("button");
1583
+ b.type = "button";
1584
+ b.textContent = String(i);
1585
+ b.setAttribute("aria-pressed", "false");
1586
+ b.addEventListener("click", () => {
1587
+ row.querySelectorAll("button").forEach((x) => x.setAttribute("aria-pressed", "false"));
1588
+ b.setAttribute("aria-pressed", "true");
1589
+ setAnswer(q.id, i);
1590
+ });
1591
+ row.appendChild(b);
1592
+ }
1593
+ wrap.appendChild(row);
1594
+ if (labels && (q.lowLabel || q.highLabel)) {
1595
+ const l = doc.createElement("div");
1596
+ l.className = "g-scale-labels";
1597
+ l.append(Object.assign(doc.createElement("span"), { textContent: q.lowLabel || "" }), Object.assign(doc.createElement("span"), { textContent: q.highLabel || "" }));
1598
+ wrap.appendChild(l);
1599
+ }
1600
+ return wrap;
1601
+ };
1602
+ const buildInput = (q) => {
1603
+ switch (q.type) {
1604
+ case "nps":
1605
+ return scaleButtons(q, 0, 10, true);
1606
+ case "csat":
1607
+ return scaleButtons(q, 1, q.scaleMax ?? 5, true);
1608
+ case "rating": {
1609
+ const max = q.scaleMax ?? 5;
1610
+ const row = doc.createElement("div");
1611
+ row.className = "g-stars";
1612
+ row.setAttribute("role", "radiogroup");
1613
+ row.setAttribute("aria-label", q.title);
1614
+ const btns = [];
1615
+ for (let i = 1; i <= max; i++) {
1616
+ const b = doc.createElement("button");
1617
+ b.type = "button";
1618
+ b.textContent = "\u2605";
1619
+ b.setAttribute("aria-label", `${i} of ${max}`);
1620
+ b.setAttribute("aria-pressed", "false");
1621
+ b.addEventListener("click", () => {
1622
+ btns.forEach((x, j) => {
1623
+ x.classList.toggle("g-on", j < i);
1624
+ x.setAttribute("aria-pressed", String(j === i - 1));
1625
+ });
1626
+ setAnswer(q.id, i);
1627
+ });
1628
+ btns.push(b);
1629
+ row.appendChild(b);
1630
+ }
1631
+ return row;
1632
+ }
1633
+ case "thumbs": {
1634
+ const row = doc.createElement("div");
1635
+ row.className = "g-thumbs";
1636
+ row.setAttribute("role", "radiogroup");
1637
+ [
1638
+ ["up", "\u{1F44D}", "Thumbs up"],
1639
+ ["down", "\u{1F44E}", "Thumbs down"]
1640
+ ].forEach(([val, icon, lbl]) => {
1641
+ const b = doc.createElement("button");
1642
+ b.type = "button";
1643
+ b.textContent = icon;
1644
+ b.setAttribute("aria-label", lbl);
1645
+ b.setAttribute("aria-pressed", "false");
1646
+ b.addEventListener("click", () => {
1647
+ row.querySelectorAll("button").forEach((x) => x.setAttribute("aria-pressed", "false"));
1648
+ b.setAttribute("aria-pressed", "true");
1649
+ setAnswer(q.id, val === "up");
1650
+ });
1651
+ row.appendChild(b);
1652
+ });
1653
+ return row;
1654
+ }
1655
+ case "single_choice":
1656
+ case "multiple_choice": {
1657
+ const multi = q.type === "multiple_choice";
1658
+ const group = doc.createElement("div");
1659
+ group.setAttribute("role", multi ? "group" : "radiogroup");
1660
+ group.setAttribute("aria-label", q.title);
1661
+ q.options.forEach((opt) => {
1662
+ const b = doc.createElement("button");
1663
+ b.type = "button";
1664
+ b.className = `g-choice${multi ? " g-multi" : ""}`;
1665
+ b.setAttribute("role", multi ? "checkbox" : "radio");
1666
+ b.setAttribute("aria-checked", "false");
1667
+ b.setAttribute("data-option-id", opt.id);
1668
+ const dot = doc.createElement("span");
1669
+ dot.className = "g-dot";
1670
+ b.append(dot, Object.assign(doc.createElement("span"), { textContent: opt.label }));
1671
+ b.addEventListener("click", () => {
1672
+ if (multi) {
1673
+ const cur = new Set(answers[q.id] ?? []);
1674
+ if (cur.has(opt.id)) cur.delete(opt.id);
1675
+ else cur.add(opt.id);
1676
+ b.setAttribute("aria-checked", String(cur.has(opt.id)));
1677
+ setAnswer(q.id, Array.from(cur));
1678
+ } else {
1679
+ group.querySelectorAll(".g-choice").forEach((x) => x.setAttribute("aria-checked", "false"));
1680
+ b.setAttribute("aria-checked", "true");
1681
+ setAnswer(q.id, opt.id);
1682
+ }
1683
+ });
1684
+ group.appendChild(b);
1685
+ });
1686
+ return group;
1687
+ }
1688
+ case "text":
1689
+ default: {
1690
+ const ta = doc.createElement("textarea");
1691
+ ta.className = "g-input";
1692
+ ta.rows = 3;
1693
+ ta.placeholder = q.placeholder || "";
1694
+ ta.setAttribute("aria-label", q.title);
1695
+ ta.addEventListener("input", () => setAnswer(q.id, ta.value.slice(0, 5e3)));
1696
+ return ta;
1697
+ }
1698
+ }
1699
+ };
1700
+ config.questions.forEach((q) => {
1701
+ const wrap = doc.createElement("div");
1702
+ wrap.className = "g-question";
1703
+ wrap.setAttribute("data-question-id", q.id);
1704
+ const t = doc.createElement("p");
1705
+ t.className = "g-q-title";
1706
+ t.textContent = q.title + (q.required ? "" : " (optional)");
1707
+ wrap.appendChild(t);
1708
+ if (q.description) {
1709
+ const d = doc.createElement("p");
1710
+ d.className = "g-q-desc";
1711
+ d.textContent = q.description;
1712
+ wrap.appendChild(d);
1713
+ }
1714
+ wrap.appendChild(buildInput(q));
1715
+ questionEls.set(q.id, wrap);
1716
+ form.appendChild(wrap);
1717
+ });
1718
+ const footer = doc.createElement("div");
1719
+ footer.className = "g-footer";
1720
+ const submit = doc.createElement("button");
1721
+ submit.type = "submit";
1722
+ submit.className = "g-btn g-btn-primary";
1723
+ submit.textContent = "Submit";
1724
+ footer.appendChild(submit);
1725
+ form.appendChild(errorEl);
1726
+ form.appendChild(footer);
1727
+ function updateVisibility() {
1728
+ config.questions.forEach((q) => {
1729
+ const el2 = questionEls.get(q.id);
1730
+ if (!el2) return;
1731
+ const visible = isQuestionVisible(q, answers);
1732
+ el2.hidden = !visible;
1733
+ if (!visible && answers[q.id] !== void 0) delete answers[q.id];
1734
+ });
1735
+ }
1736
+ form.addEventListener("submit", (e) => {
1737
+ e.preventDefault();
1738
+ const missing = config.questions.filter((q) => q.required && isQuestionVisible(q, answers) && answers[q.id] === void 0);
1739
+ if (missing.length) {
1740
+ errorEl.textContent = `Please answer: ${missing.map((q) => q.title).join(", ")}`;
1741
+ questionEls.get(missing[0].id)?.querySelector("button,textarea")?.focus();
1742
+ return;
1743
+ }
1744
+ errorEl.textContent = "";
1745
+ options.onSubmit({ ...answers });
1746
+ });
1747
+ const onKey = (e) => {
1748
+ if (e.key === "Escape" && options.onDismiss) {
1749
+ e.stopPropagation();
1750
+ options.onDismiss();
1751
+ }
1752
+ };
1753
+ card.addEventListener("keydown", onKey);
1754
+ updateVisibility();
1755
+ (form.querySelector("button,textarea") || card).focus?.({ preventScroll: true });
1756
+ return {
1757
+ el: card,
1758
+ getAnswers: () => ({ ...answers }),
1759
+ showThankYou() {
1760
+ inner.textContent = "";
1761
+ const wrap = doc.createElement("div");
1762
+ wrap.className = "g-complete";
1763
+ const t = doc.createElement("h3");
1764
+ t.className = "g-title";
1765
+ t.textContent = config.thankYouTitle;
1766
+ const b = doc.createElement("div");
1767
+ b.className = "g-body";
1768
+ setSanitizedHtml(b, config.thankYouBody);
1769
+ const done = doc.createElement("button");
1770
+ done.type = "button";
1771
+ done.className = "g-btn g-btn-primary";
1772
+ done.textContent = "Done";
1773
+ done.style.marginTop = "12px";
1774
+ done.addEventListener("click", () => options.onDismiss?.());
1775
+ wrap.append(t, b, done);
1776
+ inner.appendChild(wrap);
1777
+ done.focus?.({ preventScroll: true });
1778
+ },
1779
+ destroy() {
1780
+ card.removeEventListener("keydown", onKey);
1781
+ card.remove();
1782
+ }
1783
+ };
1784
+ }
1785
+
1786
+ // src/resource-center.ts
1787
+ function renderResourceCenter(options) {
1788
+ const { scope, config } = options;
1789
+ const doc = scope.ownerDocument;
1790
+ const guides = options.guides ?? [];
1791
+ let open = false;
1792
+ const pos = `g-pos-${config.launcher.position}`;
1793
+ const byId = (ids) => ids.map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g);
1794
+ const root = el(doc, "div", "g-resource-center");
1795
+ const launcher = el(doc, "button", `g-launcher ${pos}`);
1796
+ launcher.type = "button";
1797
+ launcher.setAttribute("aria-expanded", "false");
1798
+ if (config.launcher.showIcon) {
1799
+ const icon = el(doc, "span", "", "?");
1800
+ icon.setAttribute("aria-hidden", "true");
1801
+ launcher.appendChild(icon);
1802
+ }
1803
+ launcher.appendChild(el(doc, "span", "", config.launcher.label || config.title));
1804
+ launcher.setAttribute("aria-label", config.launcher.label || config.title);
1805
+ if (options.hideLauncher) launcher.hidden = true;
1806
+ const panel = el(doc, "div", `g-panel ${pos}`);
1807
+ panel.setAttribute("role", "dialog");
1808
+ panel.setAttribute("aria-label", config.title);
1809
+ panel.hidden = true;
1810
+ const head = el(doc, "div", "g-panel-head");
1811
+ head.appendChild(el(doc, "h2", "g-panel-title", config.title));
1812
+ const close = el(doc, "button", "g-close", "\xD7");
1813
+ close.type = "button";
1814
+ close.setAttribute("aria-label", "Close");
1815
+ head.appendChild(close);
1816
+ const help = createHelpView({
1817
+ doc,
1818
+ data: () => ({ collections: config.collections, articles: config.articles, recommended: byId(config.recommendedGuideIds), checklists: byId(config.checklistGuideIds), faqs: config.faqs, links: config.links }),
1819
+ search: options.search,
1820
+ loadArticle: options.loadArticle,
1821
+ onStartGuide: options.onStartGuide,
1822
+ onOpenChecklist: options.onOpenChecklist,
1823
+ onArticleView: options.onArticleView
1824
+ });
1825
+ panel.append(head, help.el);
1826
+ root.append(launcher, panel);
1827
+ scope.appendChild(root);
1828
+ const setOpen = (v) => {
1829
+ if (open === v) return;
1830
+ open = v;
1831
+ panel.hidden = !v;
1832
+ launcher.setAttribute("aria-expanded", String(v));
1833
+ if (v) {
1834
+ help.render();
1835
+ help.focusSearch();
1836
+ options.onOpen?.();
1837
+ } else {
1838
+ help.reset();
1839
+ options.onClose?.();
1840
+ }
1841
+ };
1842
+ launcher.addEventListener("click", () => setOpen(!open));
1843
+ close.addEventListener("click", () => setOpen(false));
1844
+ const onKey = (e) => {
1845
+ if (e.key === "Escape") {
1846
+ e.stopPropagation();
1847
+ setOpen(false);
1848
+ launcher.focus();
1849
+ }
1850
+ };
1851
+ panel.addEventListener("keydown", onKey);
1852
+ return {
1853
+ el: root,
1854
+ open: () => setOpen(true),
1855
+ close: () => setOpen(false),
1856
+ toggle: () => setOpen(!open),
1857
+ isOpen: () => open,
1858
+ destroy() {
1859
+ help.destroy();
1860
+ panel.removeEventListener("keydown", onKey);
1861
+ root.remove();
1862
+ }
1863
+ };
1864
+ }
1865
+
1866
+ // src/authoring.ts
1867
+ var DEFAULT_MESSAGE = "Guidely authoring \u2014 hover an element and click to select \xB7 Esc to cancel";
1868
+ function startAuthoring(options) {
1869
+ const doc = options.doc ?? document;
1870
+ const win = doc.defaultView ?? window;
1871
+ const scope = createScope(null, { theme: "light", animation: "none" }, doc);
1872
+ const bar = doc.createElement("div");
1873
+ bar.className = "g-auth-bar";
1874
+ bar.setAttribute("role", "toolbar");
1875
+ const dot = doc.createElement("span");
1876
+ dot.className = "g-dot-live";
1877
+ const status = doc.createElement("span");
1878
+ status.textContent = options.message ?? DEFAULT_MESSAGE;
1879
+ const spacer = doc.createElement("span");
1880
+ spacer.className = "g-spacer";
1881
+ const done = doc.createElement("button");
1882
+ done.type = "button";
1883
+ done.className = "g-btn g-btn-primary";
1884
+ done.textContent = "Done";
1885
+ const exit = doc.createElement("button");
1886
+ exit.type = "button";
1887
+ exit.className = "g-btn g-btn-secondary";
1888
+ exit.textContent = "Exit authoring";
1889
+ bar.append(dot, status, spacer, done, exit);
1890
+ const outline = doc.createElement("div");
1891
+ outline.className = "g-auth-outline";
1892
+ outline.hidden = true;
1893
+ const label = doc.createElement("span");
1894
+ label.className = "g-auth-label";
1895
+ outline.appendChild(label);
1896
+ scope.el.append(bar, outline);
1897
+ let picking = true;
1898
+ let hovered = null;
1899
+ let stopped = false;
1900
+ const describe = (el2) => {
1901
+ const tag = el2.tagName.toLowerCase();
1902
+ const id = el2.id ? `#${el2.id}` : "";
1903
+ const tour = el2.getAttribute("data-tour") || el2.getAttribute("data-testid");
1904
+ const text = (el2.textContent || "").replace(/\s+/g, " ").trim().slice(0, 40);
1905
+ return `${tag}${id}${tour ? `[${tour}]` : ""}${text ? ` \u201C${text}\u201D` : ""}`;
1906
+ };
1907
+ const paint = (el2, picked = false) => {
1908
+ if (!el2) {
1909
+ outline.hidden = true;
1910
+ return;
1911
+ }
1912
+ const r = el2.getBoundingClientRect();
1913
+ outline.hidden = false;
1914
+ outline.classList.toggle("g-picked", picked);
1915
+ Object.assign(outline.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
1916
+ label.textContent = describe(el2);
1917
+ label.style.top = r.top < 30 ? "100%" : "-24px";
1918
+ };
1919
+ const elementAt = (x, y) => {
1920
+ const el2 = doc.elementFromPoint(x, y);
1921
+ if (!el2 || isGuidelyElement(el2) || el2 === doc.documentElement || el2 === doc.body) return null;
1922
+ return el2;
1923
+ };
1924
+ const onMove = (e) => {
1925
+ if (!picking || stopped) return;
1926
+ const el2 = elementAt(e.clientX, e.clientY);
1927
+ if (el2 !== hovered) {
1928
+ hovered = el2;
1929
+ paint(el2);
1930
+ }
1931
+ };
1932
+ const onScroll = () => {
1933
+ if (hovered) paint(hovered);
1934
+ };
1935
+ const onClick = (e) => {
1936
+ if (!picking || stopped) return;
1937
+ if (e.target && isGuidelyElement(e.target)) return;
1938
+ const el2 = elementAt(e.clientX, e.clientY) ?? hovered;
1939
+ if (!el2) return;
1940
+ e.preventDefault();
1941
+ e.stopPropagation();
1942
+ e.stopImmediatePropagation();
1943
+ const d = describeElement(el2);
1944
+ paint(el2, true);
1945
+ status.textContent = `Selected ${describe(el2)} \u2014 click another element or press Done`;
1946
+ options.onPick({ selectors: d.selectors, textSnapshot: d.textSnapshot, rect: d.rect, url: win.location.href, element: el2 });
1947
+ };
1948
+ const swallow = (e) => {
1949
+ if (!picking || stopped) return;
1950
+ const t = e.target;
1951
+ if (t && isGuidelyElement(t)) return;
1952
+ e.preventDefault();
1953
+ e.stopPropagation();
1954
+ };
1955
+ const onKey = (e) => {
1956
+ if (e.key === "Escape") {
1957
+ e.preventDefault();
1958
+ e.stopPropagation();
1959
+ stop();
1960
+ options.onExit();
1961
+ }
1962
+ };
1963
+ const stop = () => {
1964
+ if (stopped) return;
1965
+ stopped = true;
1966
+ doc.removeEventListener("mousemove", onMove, true);
1967
+ doc.removeEventListener("click", onClick, true);
1968
+ doc.removeEventListener("mousedown", swallow, true);
1969
+ doc.removeEventListener("mouseup", swallow, true);
1970
+ doc.removeEventListener("keydown", onKey, true);
1971
+ win.removeEventListener("scroll", onScroll, true);
1972
+ win.removeEventListener("resize", onScroll);
1973
+ scope.destroy();
1974
+ };
1975
+ done.addEventListener("click", () => {
1976
+ stop();
1977
+ options.onExit();
1978
+ });
1979
+ exit.addEventListener("click", () => {
1980
+ stop();
1981
+ options.onExit();
1982
+ });
1983
+ doc.addEventListener("mousemove", onMove, true);
1984
+ doc.addEventListener("click", onClick, true);
1985
+ doc.addEventListener("mousedown", swallow, true);
1986
+ doc.addEventListener("mouseup", swallow, true);
1987
+ doc.addEventListener("keydown", onKey, true);
1988
+ win.addEventListener("scroll", onScroll, true);
1989
+ win.addEventListener("resize", onScroll);
1990
+ return {
1991
+ setStatus: (text) => {
1992
+ status.textContent = text;
1993
+ },
1994
+ setPicking: (enabled) => {
1995
+ picking = enabled;
1996
+ if (!enabled) paint(null);
1997
+ },
1998
+ stop
1999
+ };
2000
+ }
2001
+ function previewStep(step, theme, options = {}) {
2002
+ const previewStepConfig = { ...step, target: step.target ? { ...step.target, fallback: "show_as_modal", waitMs: Math.min(step.target.waitMs, 1500) } : step.target };
2003
+ const guide = {
2004
+ id: `preview-${step.id}`,
2005
+ key: "preview",
2006
+ name: "Preview",
2007
+ kind: "flow",
2008
+ version: 0,
2009
+ config: { steps: [previewStepConfig], targeting: { id: "root", op: "and", rules: [] }, trigger: { type: "manual", delayMs: 0 }, frequency: { mode: "every_time", cooldownHours: 0, priority: 0, mutuallyExclusiveWith: [], reshowAfterDismiss: true }, appearance: options.appearance ?? { theme: "auto", animation: "fade" }, urlPatterns: [] }
2010
+ };
2011
+ const controller = runGuide(guide, { theme, doc: options.doc, onEnd: () => options.onEnd?.(), onNavigate: () => {
2012
+ }, onAction: () => {
2013
+ } });
2014
+ controller.start();
2015
+ return { controller, destroy: () => controller.destroy() };
2016
+ }
2017
+ function highlightSelectors(selectors, options = {}) {
2018
+ const doc = options.doc ?? document;
2019
+ const scope = createScope(null, { theme: "light", animation: "none" }, doc);
2020
+ const boxes = [];
2021
+ const counts = [];
2022
+ let resolved = null;
2023
+ const matched = /* @__PURE__ */ new Set();
2024
+ selectors.forEach((s) => {
2025
+ const els = querySelectorStrategy(s, doc).filter((el2) => !isGuidelyElement(el2));
2026
+ const visible = els.filter(isVisible);
2027
+ counts.push(els.length);
2028
+ if (!resolved && visible.length === 1) resolved = visible[0];
2029
+ els.forEach((el2) => matched.add(el2));
2030
+ });
2031
+ matched.forEach((el2) => {
2032
+ const r = el2.getBoundingClientRect();
2033
+ const box = doc.createElement("div");
2034
+ box.className = "g-highlight";
2035
+ Object.assign(box.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
2036
+ if (el2 === resolved) box.style.borderColor = "#22c55e";
2037
+ scope.el.appendChild(box);
2038
+ boxes.push(box);
2039
+ });
2040
+ let timer = null;
2041
+ const destroy = () => {
2042
+ if (timer) clearTimeout(timer);
2043
+ scope.destroy();
2044
+ };
2045
+ if (options.durationMs !== 0) timer = setTimeout(destroy, options.durationMs ?? 2500);
2046
+ ensureHost(doc);
2047
+ return { counts, resolved, destroy };
2048
+ }
2049
+ export {
2050
+ DEFAULT_THEME,
2051
+ HOST_ID2 as HOST_ID,
2052
+ STYLES,
2053
+ TargetTimeoutError,
2054
+ anchor,
2055
+ applyTheme,
2056
+ buildCard,
2057
+ createScope,
2058
+ describeElement,
2059
+ destroyHost,
2060
+ ensureHost,
2061
+ generateSelectors,
2062
+ highlightSelectors,
2063
+ isAnchoredStep,
2064
+ isChecklistComplete,
2065
+ isGuidelyElement,
2066
+ isQuestionVisible,
2067
+ isStableId,
2068
+ isVisible,
2069
+ normalizeText,
2070
+ prefersReducedMotion,
2071
+ previewStep,
2072
+ querySelectorStrategy,
2073
+ renderBanner,
2074
+ renderChecklist,
2075
+ renderCustom,
2076
+ renderHotspot,
2077
+ renderModal,
2078
+ renderResourceCenter,
2079
+ renderSlideout,
2080
+ renderSpotlight,
2081
+ renderStep,
2082
+ renderSurvey,
2083
+ renderTooltip,
2084
+ resolveTarget,
2085
+ resolveTheme,
2086
+ runGuide,
2087
+ sanitizeHtml,
2088
+ sanitizeToFragment,
2089
+ setSanitizedHtml,
2090
+ startAuthoring,
2091
+ textSnapshotOf,
2092
+ trackRect,
2093
+ urlMatches,
2094
+ waitForTarget
2095
+ };
2096
+ //# sourceMappingURL=index.js.map