@guidelytech/guide-runtime 0.8.0 → 0.10.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.cjs CHANGED
@@ -24,12 +24,19 @@ var src_exports = {};
24
24
  __export(src_exports, {
25
25
  DEFAULT_THEME: () => DEFAULT_THEME,
26
26
  HOST_ID: () => HOST_ID2,
27
+ SHADOW_SEPARATOR: () => SHADOW_SEPARATOR,
27
28
  STYLES: () => STYLES,
28
29
  TargetTimeoutError: () => TargetTimeoutError,
29
30
  anchor: () => anchor,
30
31
  applyTheme: () => applyTheme,
31
32
  buildCard: () => buildCard,
33
+ closestComposed: () => closestComposed,
34
+ componentAnchor: () => componentAnchor,
35
+ composedParent: () => composedParent,
36
+ composedTarget: () => composedTarget,
37
+ containsComposed: () => containsComposed,
32
38
  createScope: () => createScope,
39
+ deepElementFromPoint: () => deepElementFromPoint,
33
40
  describeElement: () => describeElement,
34
41
  destroyHost: () => destroyHost,
35
42
  enhancePdfViewers: () => enhancePdfViewers,
@@ -63,10 +70,12 @@ __export(src_exports, {
63
70
  sanitizeHtml: () => sanitizeHtml,
64
71
  sanitizeToFragment: () => sanitizeToFragment,
65
72
  setSanitizedHtml: () => setSanitizedHtml,
73
+ shadowHostChain: () => shadowHostChain,
66
74
  startAuthoring: () => startAuthoring,
67
75
  textSnapshotOf: () => textSnapshotOf,
68
76
  trackRect: () => trackRect,
69
77
  urlMatches: () => urlMatches,
78
+ visualRect: () => visualRect,
70
79
  waitForTarget: () => waitForTarget
71
80
  });
72
81
  module.exports = __toCommonJS(src_exports);
@@ -129,6 +138,82 @@ function safeQueryAll(root, selector) {
129
138
  return [];
130
139
  }
131
140
  }
141
+ var SHADOW_SEPARATOR = " >>> ";
142
+ var SHADOW_SPLIT = /\s*>>>\s*/;
143
+ function isShadowRoot(node) {
144
+ return !!node && node.nodeType === 11 && !!node.host;
145
+ }
146
+ function shadowRootOf(el2) {
147
+ const root = el2.getRootNode?.();
148
+ return isShadowRoot(root) ? root : null;
149
+ }
150
+ function composedParent(el2) {
151
+ return el2.parentElement ?? shadowRootOf(el2)?.host ?? null;
152
+ }
153
+ function containsComposed(ancestor, node) {
154
+ let cur = node ? node.nodeType === 1 ? node : node.parentElement : null;
155
+ for (; cur; cur = composedParent(cur)) if (cur === ancestor) return true;
156
+ return false;
157
+ }
158
+ function composedTarget(e) {
159
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
160
+ const first = path[0] ?? e.target;
161
+ if (!first) return null;
162
+ return first.nodeType === 1 ? first : first.parentElement;
163
+ }
164
+ var shadowRootCache = null;
165
+ function shadowRootsIn(root) {
166
+ const cached = shadowRootCache?.get(root);
167
+ if (cached) return cached;
168
+ const out = [];
169
+ const visit = (scope) => {
170
+ const own = scope.nodeType === 1 ? scope.shadowRoot : null;
171
+ const hosts = own ? [scope] : [];
172
+ for (const el2 of safeQueryAll(scope, "*")) if (el2.shadowRoot) hosts.push(el2);
173
+ for (const host of hosts) {
174
+ if (host.id === HOST_ID || !host.shadowRoot || out.includes(host.shadowRoot)) continue;
175
+ out.push(host.shadowRoot);
176
+ visit(host.shadowRoot);
177
+ }
178
+ };
179
+ visit(root);
180
+ shadowRootCache?.set(root, out);
181
+ return out;
182
+ }
183
+ function queryAllDeep(root, selector) {
184
+ const out = safeQueryAll(root, selector);
185
+ for (const sr of shadowRootsIn(root)) out.push(...safeQueryAll(sr, selector));
186
+ return out;
187
+ }
188
+ function queryCss(root, selector, deep) {
189
+ if (!selector.includes(">>>")) return deep ? queryAllDeep(root, selector) : safeQueryAll(root, selector);
190
+ const steps = selector.split(SHADOW_SPLIT).filter(Boolean);
191
+ let scopes = [root];
192
+ let matches = [];
193
+ steps.forEach((step, i) => {
194
+ matches = Array.from(new Set(scopes.flatMap((scope) => queryAllDeep(scope, step))));
195
+ if (i < steps.length - 1) scopes = matches.map((el2) => el2.shadowRoot).filter((sr) => !!sr);
196
+ });
197
+ return matches;
198
+ }
199
+ function closestComposed(el2, selector) {
200
+ if (selector.includes(">>>")) {
201
+ const hits = new Set(queryCss(docOf(el2), selector, true));
202
+ for (let cur = el2; cur; cur = composedParent(cur)) if (hits.has(cur)) return cur;
203
+ return null;
204
+ }
205
+ for (let cur = el2; cur; cur = composedParent(cur)) if (cur.matches(selector)) return cur;
206
+ return null;
207
+ }
208
+ function deepElementFromPoint(doc, x, y) {
209
+ let el2 = doc.elementFromPoint(x, y);
210
+ while (el2?.shadowRoot && el2.id !== HOST_ID && typeof el2.shadowRoot.elementFromPoint === "function") {
211
+ const inner = el2.shadowRoot.elementFromPoint(x, y);
212
+ if (!inner || inner === el2) break;
213
+ el2 = inner;
214
+ }
215
+ return el2;
216
+ }
132
217
  function escapeCss(value) {
133
218
  const g = globalThis;
134
219
  if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
@@ -150,32 +235,32 @@ function queryXPath(root, expr) {
150
235
  return [];
151
236
  }
152
237
  }
153
- function queryText(root, text) {
238
+ function queryText(root, text, deep) {
154
239
  const needle = normalizeText(text).toLowerCase();
155
240
  if (!needle) return [];
156
- const candidates = safeQueryAll(root, TEXT_CANDIDATES);
241
+ const candidates = queryCss(root, TEXT_CANDIDATES, deep);
157
242
  const exact = candidates.filter((c) => normalizeText(c.textContent).toLowerCase() === needle);
158
243
  if (exact.length) return exact;
159
244
  return candidates.filter((c) => normalizeText(c.textContent).toLowerCase().includes(needle));
160
245
  }
161
- function querySelectorStrategy(strategy, root) {
246
+ function querySelectorStrategy(strategy, root, deep = true) {
162
247
  const value = strategy.value.trim();
163
248
  if (!value) return [];
164
249
  switch (strategy.kind) {
165
250
  case "id": {
166
251
  const id = value.startsWith("#") ? value.slice(1) : value;
167
- return safeQueryAll(root, `#${escapeCss(id)}`);
252
+ return queryCss(root, `#${escapeCss(id)}`, deep);
168
253
  }
169
254
  case "guidely-id": {
170
255
  const id = value.startsWith("#") ? value.slice(1) : value;
171
- return safeQueryAll(root, attrSelector("data-guidely-id", id));
256
+ return queryCss(root, attrSelector("data-guidely-id", id), deep);
172
257
  }
173
258
  case "data-attr":
174
259
  case "aria":
175
260
  case "css":
176
- return safeQueryAll(root, value.startsWith("[") || /^[a-z*#.[]/i.test(value) ? value : `[${value}]`);
261
+ return queryCss(root, value.startsWith("[") || /^[a-z*#.[]/i.test(value) ? value : `[${value}]`, deep);
177
262
  case "text":
178
- return queryText(root, value);
263
+ return queryText(root, value, deep);
179
264
  case "xpath":
180
265
  return queryXPath(root, value);
181
266
  default:
@@ -184,11 +269,19 @@ function querySelectorStrategy(strategy, root) {
184
269
  }
185
270
  function resolveTarget(target, root = document) {
186
271
  const selectors = Array.isArray(target) ? target : target.selectors;
187
- for (const strategy of selectors) {
188
- const matches = querySelectorStrategy(strategy, root).filter((el2) => !isGuidelyElement(el2) && isVisible(el2));
189
- if (matches.length === 1) return matches[0];
272
+ const visibleMatches = (strategy, deep) => querySelectorStrategy(strategy, root, deep).filter((el2) => !isGuidelyElement(el2) && isVisible(el2));
273
+ const outer = shadowRootCache;
274
+ shadowRootCache ?? (shadowRootCache = /* @__PURE__ */ new Map());
275
+ try {
276
+ for (const strategy of selectors) {
277
+ let matches = visibleMatches(strategy, false);
278
+ if (!matches.length && strategy.kind !== "xpath") matches = visibleMatches(strategy, true);
279
+ if (matches.length === 1) return matches[0];
280
+ }
281
+ return null;
282
+ } finally {
283
+ shadowRootCache = outer;
190
284
  }
191
- return null;
192
285
  }
193
286
  var TargetTimeoutError = class extends Error {
194
287
  constructor(message = "Target element not found before timeout") {
@@ -252,14 +345,15 @@ function isStableId(id) {
252
345
  function isStableClass(cls) {
253
346
  return !!cls && !GENERATED_CLASS.test(cls) && cls.length <= 40;
254
347
  }
255
- function uniqueIn(doc, selector, el2) {
256
- const all = safeQueryAll(doc, selector);
348
+ function uniqueWithin(root, selector, el2) {
349
+ const all = safeQueryAll(root, selector);
257
350
  return all.length === 1 && all[0] === el2;
258
351
  }
259
352
  function attrSelector(name, value) {
260
353
  return `[${name}='${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
261
354
  }
262
- function cssPath(el2, doc) {
355
+ function cssPath(el2, root) {
356
+ const doc = el2.ownerDocument;
263
357
  const segments = [];
264
358
  let cur = el2;
265
359
  while (cur && cur.nodeType === 1 && cur !== doc.body && cur !== doc.documentElement) {
@@ -279,20 +373,48 @@ function cssPath(el2, doc) {
279
373
  }
280
374
  segments.unshift(seg);
281
375
  const candidate = segments.join(" > ");
282
- if (uniqueIn(doc, candidate, el2)) return candidate;
376
+ if (uniqueWithin(root, candidate, el2)) return candidate;
283
377
  cur = parent;
284
378
  }
285
379
  const full = segments.join(" > ");
286
- return uniqueIn(doc, full, el2) ? full : null;
380
+ return uniqueWithin(root, full, el2) ? full : null;
381
+ }
382
+ function shadowHostChain(el2) {
383
+ const steps = [];
384
+ for (let root = shadowRootOf(el2); root; root = shadowRootOf(root.host)) {
385
+ const host = root.host;
386
+ const step = cssPath(host, shadowRootOf(host) ?? host.ownerDocument);
387
+ if (!step) return null;
388
+ steps.unshift(step);
389
+ }
390
+ return steps.join(SHADOW_SEPARATOR);
391
+ }
392
+ function selectorFor(el2, selector) {
393
+ const root = shadowRootOf(el2);
394
+ if (!root) return uniqueWithin(el2.ownerDocument, selector, el2) ? selector : null;
395
+ if (!uniqueWithin(root, selector, el2)) return null;
396
+ const doc = el2.ownerDocument;
397
+ const only = (sel) => {
398
+ const all = queryCss(doc, sel, true);
399
+ return all.length === 1 && all[0] === el2;
400
+ };
401
+ if (only(selector)) return selector;
402
+ const chain = shadowHostChain(el2);
403
+ const scoped = chain ? chain + SHADOW_SEPARATOR + selector : null;
404
+ return scoped && only(scoped) ? scoped : null;
405
+ }
406
+ function shadowText(el2) {
407
+ if (!el2.shadowRoot) return "";
408
+ const parts = Array.from(el2.shadowRoot.children).filter((c) => !/^(STYLE|SCRIPT|TEMPLATE)$/.test(c.tagName));
409
+ return normalizeText(parts.map((c) => c.innerText ?? c.textContent).join(" "));
287
410
  }
288
411
  function textSnapshotOf(el2) {
289
412
  const he = el2;
290
- const own = normalizeText(he.innerText ?? el2.textContent);
413
+ const own = normalizeText(he.innerText ?? el2.textContent) || shadowText(el2);
291
414
  const alt = normalizeText(el2.getAttribute("aria-label") || el2.getAttribute("title") || el2.getAttribute("placeholder") || el2.getAttribute("alt") || el2.value);
292
415
  return (own || alt).slice(0, 200);
293
416
  }
294
417
  function generateSelectors(el2) {
295
- const doc = el2.ownerDocument;
296
418
  const out = [];
297
419
  const seen = /* @__PURE__ */ new Set();
298
420
  const push = (s) => {
@@ -303,9 +425,15 @@ function generateSelectors(el2) {
303
425
  }
304
426
  };
305
427
  const tag = el2.tagName.toLowerCase();
428
+ const pushFor = (kind, selector, value, label) => {
429
+ const resolved = selectorFor(el2, selector);
430
+ if (resolved === selector) push({ kind, value, label });
431
+ else if (resolved) push({ kind: "css", value: resolved, label });
432
+ return !!resolved;
433
+ };
306
434
  const guidelyId = el2.getAttribute("data-guidely-id");
307
- if (guidelyId && uniqueIn(doc, attrSelector("data-guidely-id", guidelyId), el2)) push({ kind: "guidely-id", value: guidelyId, label: `Guidely id ${guidelyId}` });
308
- if (isStableId(el2.id) && uniqueIn(doc, `#${escapeCss(el2.id)}`, el2)) push({ kind: "id", value: `#${el2.id}`, label: `id ${el2.id}` });
435
+ if (guidelyId) pushFor("guidely-id", attrSelector("data-guidely-id", guidelyId), guidelyId, `Guidely id ${guidelyId}`);
436
+ if (isStableId(el2.id)) pushFor("id", `#${escapeCss(el2.id)}`, `#${el2.id}`, `id ${el2.id}`);
309
437
  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));
310
438
  dataAttrs.sort((a, b) => {
311
439
  const ia = PREFERRED_DATA_ATTRS.indexOf(a.name);
@@ -314,42 +442,75 @@ function generateSelectors(el2) {
314
442
  });
315
443
  for (const a of dataAttrs) {
316
444
  const sel = attrSelector(a.name, a.value);
317
- if (uniqueIn(doc, sel, el2)) push({ kind: "data-attr", value: sel, label: `${a.name}=${a.value}` });
318
- else if (uniqueIn(doc, tag + sel, el2)) push({ kind: "data-attr", value: tag + sel, label: `${a.name}=${a.value}` });
445
+ if (!pushFor("data-attr", sel, sel, `${a.name}=${a.value}`)) pushFor("data-attr", tag + sel, tag + sel, `${a.name}=${a.value}`);
319
446
  }
320
447
  const aria = el2.getAttribute("aria-label");
321
448
  if (aria && aria.length <= 120) {
322
449
  const sel = attrSelector("aria-label", aria);
323
- if (uniqueIn(doc, sel, el2)) push({ kind: "aria", value: sel, label: aria });
324
- else if (uniqueIn(doc, tag + sel, el2)) push({ kind: "aria", value: tag + sel, label: aria });
450
+ if (!pushFor("aria", sel, sel, aria)) pushFor("aria", tag + sel, tag + sel, aria);
325
451
  }
326
452
  const name = el2.getAttribute("name");
327
- if (name && uniqueIn(doc, tag + attrSelector("name", name), el2)) push({ kind: "css", value: tag + attrSelector("name", name), label: `${tag} name=${name}` });
453
+ if (name) pushFor("css", tag + attrSelector("name", name), tag + attrSelector("name", name), `${tag} name=${name}`);
328
454
  const role = el2.getAttribute("role");
329
455
  if (role) {
330
456
  const title = el2.getAttribute("title") || el2.getAttribute("aria-labelledby");
331
457
  const sel = title ? `[role='${role}']${attrSelector(el2.getAttribute("title") ? "title" : "aria-labelledby", title)}` : `[role='${role}']`;
332
- if (uniqueIn(doc, sel, el2)) push({ kind: "css", value: sel, label: `role ${role}` });
458
+ pushFor("css", sel, sel, `role ${role}`);
333
459
  }
334
460
  if (tag === "a") {
335
461
  const href = el2.getAttribute("href");
336
- if (href && href.length <= 200 && uniqueIn(doc, `a${attrSelector("href", href)}`, el2)) push({ kind: "css", value: `a${attrSelector("href", href)}`, label: `link ${href}` });
462
+ if (href && href.length <= 200) pushFor("css", `a${attrSelector("href", href)}`, `a${attrSelector("href", href)}`, `link ${href}`);
337
463
  }
338
- const path = cssPath(el2, doc);
339
- if (path) push({ kind: "css", value: path, label: "css path" });
464
+ const path = cssPath(el2, shadowRootOf(el2) ?? el2.ownerDocument);
465
+ if (path) pushFor("css", path, path, "css path");
340
466
  if (/^(button|a|h[1-6]|label|summary)$/.test(tag) || role === "button" || role === "link" || role === "tab" || role === "menuitem") {
341
467
  const text = normalizeText(el2.innerText ?? el2.textContent);
342
468
  if (text && text.length <= 80) push({ kind: "text", value: text, label: `text "${text}"` });
343
469
  }
344
470
  return out.slice(0, 10);
345
471
  }
472
+ function visualRect(el2) {
473
+ const own = el2.getBoundingClientRect();
474
+ if (!el2.shadowRoot) return own;
475
+ const boxes = [own, ...Array.from(el2.shadowRoot.children).map((c) => c.getBoundingClientRect())].filter((r) => r.width > 0 && r.height > 0);
476
+ if (boxes.length < 2) return boxes[0] ?? own;
477
+ const left = Math.min(...boxes.map((r) => r.left));
478
+ const top = Math.min(...boxes.map((r) => r.top));
479
+ const right = Math.max(...boxes.map((r) => r.right));
480
+ const bottom = Math.max(...boxes.map((r) => r.bottom));
481
+ return new DOMRect(left, top, right - left, bottom - top);
482
+ }
483
+ var ANCHOR_MAX_AREA_RATIO = 4;
484
+ var CONTROL_SELECTOR = "button, a[href], input, select, textarea, [role='button'], [role='link'], [role='checkbox'], [role='radio'], [role='switch'], [role='tab'], [role='menuitem'], [role='combobox'], [role='option']";
485
+ function componentAnchor(el2) {
486
+ if (!hasLayout(el2.ownerDocument)) return el2;
487
+ let cur = el2;
488
+ for (let root = shadowRootOf(cur); root; root = shadowRootOf(cur)) {
489
+ const host = root.host;
490
+ if (host.id === HOST_ID) break;
491
+ let control = null;
492
+ try {
493
+ control = cur.closest(CONTROL_SELECTOR);
494
+ } catch {
495
+ }
496
+ const r = visualRect(control ?? cur);
497
+ const area = r.width * r.height;
498
+ const h2 = visualRect(host);
499
+ const hostArea = h2.width * h2.height;
500
+ const inside = r.left >= h2.left - 1 && r.top >= h2.top - 1 && r.right <= h2.right + 1 && r.bottom <= h2.bottom + 1;
501
+ if (!area || !inside || hostArea > area * ANCHOR_MAX_AREA_RATIO) break;
502
+ cur = host;
503
+ }
504
+ return cur;
505
+ }
346
506
  function describeElement(el2) {
347
- const r = el2.getBoundingClientRect();
507
+ const target = componentAnchor(el2);
508
+ const r = visualRect(target);
348
509
  return {
349
- selectors: generateSelectors(el2),
350
- textSnapshot: textSnapshotOf(el2),
510
+ selectors: generateSelectors(target),
511
+ textSnapshot: textSnapshotOf(target) || textSnapshotOf(el2),
351
512
  rect: { x: r.x, y: r.y, width: r.width, height: r.height },
352
- tag: el2.tagName.toLowerCase()
513
+ tag: target.tagName.toLowerCase()
353
514
  };
354
515
  }
355
516
 
@@ -618,7 +779,15 @@ var STYLES = `
618
779
  .g-rc-item .g-rc-title{font-weight:500}
619
780
  .g-rc-item .g-rc-sum{font-size:12px;color:var(--g-muted);margin-top:2px}
620
781
  .g-rc-item .g-rc-icon{flex:none;width:20px;text-align:center;color:var(--g-primary)}
782
+ .g-rc-row{display:flex;align-items:center;gap:6px;padding-right:16px}
783
+ .g-rc-row>.g-rc-item{flex:1;min-width:0;padding-right:4px}
784
+ .g-rc-row:hover{background:var(--g-subtle)}
785
+ .g-rc-row:hover>.g-rc-item{background:transparent}
786
+ .g-rc-showme{flex:none;min-height:32px;padding:4px 10px;font-size:12px;white-space:nowrap}
787
+ .g-help-showme{margin:4px 0 12px}
621
788
  .g-rc-article{padding:16px 20px}
789
+ .g-resource-center .g-panel{transition:width .2s ease,max-height .2s ease}
790
+ .g-resource-center .g-panel.g-panel-reading{width:min(720px,calc(100vw - 32px));max-height:calc(100vh - 100px)}
622
791
  .g-rc-back{margin-bottom:8px}
623
792
  .g-rc-empty{padding:24px 20px;color:var(--g-muted);text-align:center}
624
793
  .g-faq-a{padding:0 20px 12px 50px;font-size:13px;color:var(--g-muted)}
@@ -896,7 +1065,8 @@ function openReader(doc, block, link, pages, start) {
896
1065
 
897
1066
  // src/position.ts
898
1067
  var import_dom = require("@floating-ui/dom");
899
- function anchor(reference, floating, options = {}) {
1068
+ function anchor(target, floating, options = {}) {
1069
+ const reference = target.shadowRoot ? { getBoundingClientRect: () => visualRect(target), contextElement: target } : target;
900
1070
  const placement = options.placement ?? "auto";
901
1071
  const gap = options.offset ?? 10;
902
1072
  const middleware = [(0, import_dom.offset)(gap + (options.arrowEl ? 4 : 0))];
@@ -949,7 +1119,7 @@ function trackRect(el2, cb) {
949
1119
  let stopped = false;
950
1120
  const run = () => {
951
1121
  if (stopped) return;
952
- cb(el2.getBoundingClientRect());
1122
+ cb(visualRect(el2));
953
1123
  };
954
1124
  const schedule = () => {
955
1125
  if (stopped) return;
@@ -962,6 +1132,9 @@ function trackRect(el2, cb) {
962
1132
  const win = el2.ownerDocument.defaultView;
963
1133
  win?.addEventListener("scroll", schedule, true);
964
1134
  win?.addEventListener("resize", schedule);
1135
+ const shadowRoots = [];
1136
+ for (let root = el2.getRootNode(); root.nodeType === 11 && root.host; root = root.host.getRootNode()) shadowRoots.push(root);
1137
+ for (const root of shadowRoots) root.addEventListener("scroll", schedule, true);
965
1138
  let ro = null;
966
1139
  if (typeof ResizeObserver !== "undefined") {
967
1140
  try {
@@ -977,6 +1150,7 @@ function trackRect(el2, cb) {
977
1150
  stopped = true;
978
1151
  win?.removeEventListener("scroll", schedule, true);
979
1152
  win?.removeEventListener("resize", schedule);
1153
+ for (const root of shadowRoots) root.removeEventListener("scroll", schedule, true);
980
1154
  ro?.disconnect();
981
1155
  clearInterval(interval);
982
1156
  if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(raf);
@@ -1363,6 +1537,17 @@ function isAnchoredStep(step) {
1363
1537
  var import_runtime5 = require("@guidelytech/shared/runtime");
1364
1538
  function noop() {
1365
1539
  }
1540
+ var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
1541
+ function navigableUrl(pattern) {
1542
+ if (!pattern || /[*?#\s]/.test(pattern) || pattern.includes("/:")) return null;
1543
+ if (pattern.startsWith("/") && !pattern.startsWith("//")) return pattern;
1544
+ try {
1545
+ const u = new URL(pattern);
1546
+ if (typeof location !== "undefined" && u.origin === location.origin) return u.pathname;
1547
+ } catch {
1548
+ }
1549
+ return null;
1550
+ }
1366
1551
  function runGuide(guide, options = {}) {
1367
1552
  const doc = options.doc ?? (options.container?.ownerDocument || document);
1368
1553
  const steps = guide.config.steps;
@@ -1507,15 +1692,15 @@ function runGuide(guide, options = {}) {
1507
1692
  onError(err);
1508
1693
  }
1509
1694
  };
1510
- const setupAdvance = (step) => {
1695
+ const setupAdvance = (step, target) => {
1511
1696
  const modes = step.advanceOn === "any" ? ["element_click", "element_visible", "event", "delay"] : [step.advanceOn];
1512
1697
  for (const mode of modes) {
1513
- if (mode === "element_click" && step.advanceValue) {
1698
+ if (mode === "element_click" && (step.advanceValue || target)) {
1514
1699
  const selector = step.advanceValue;
1515
1700
  const handler = (e) => {
1516
- const t = e.target;
1701
+ const t = composedTarget(e);
1517
1702
  try {
1518
- if (t && typeof t.closest === "function" && t.closest(selector)) advance();
1703
+ if (selector ? t && closestComposed(t, selector) : t && target && containsComposed(target, t)) advance();
1519
1704
  } catch {
1520
1705
  }
1521
1706
  };
@@ -1551,10 +1736,11 @@ function runGuide(guide, options = {}) {
1551
1736
  const render = (step, target, asModal = false) => {
1552
1737
  if (ended) return;
1553
1738
  status = "running";
1739
+ const shown = !target && step.advanceOn === "element_click" && !step.advanceValue && !step.buttons.length ? { ...step, buttons: [{ id: "g-next", label: index + 1 >= total ? "Done" : "Next", action: index + 1 >= total ? "complete" : "next", variant: "primary" }] } : step;
1554
1740
  try {
1555
1741
  view = renderStep({
1556
1742
  scope: getScope(),
1557
- step,
1743
+ step: shown,
1558
1744
  index,
1559
1745
  total,
1560
1746
  canGoBack: history.length > 0 || index > 0,
@@ -1576,7 +1762,43 @@ function runGuide(guide, options = {}) {
1576
1762
  }
1577
1763
  emit("step_viewed", step, index, asModal ? { fallback: "show_as_modal" } : void 0);
1578
1764
  announce(step.title ? `${step.title}. Step ${index + 1} of ${total}` : `Step ${index + 1} of ${total}`);
1579
- setupAdvance(step);
1765
+ setupAdvance(step, target);
1766
+ };
1767
+ const showUrlPrompt = (step) => {
1768
+ if (ended || status !== "waiting_url" || view) return;
1769
+ const href = navigableUrl(step.url);
1770
+ const where = (step.url ?? "").replace(/^https?:\/\/[^/]+/i, "") || "another page";
1771
+ const prompt = {
1772
+ ...step,
1773
+ type: "modal",
1774
+ title: "Continue on another page",
1775
+ body: `<p>The next step${step.title ? `, <strong>${escapeHtml(step.title)}</strong>,` : ""} is on <code>${escapeHtml(where)}</code>.</p>`,
1776
+ imageUrl: "",
1777
+ buttons: [
1778
+ ...href ? [{ id: "g-go", label: "Take me there", action: "url", value: href, variant: "primary" }] : [],
1779
+ { id: "g-exit", label: "Exit", action: "dismiss", variant: href ? "secondary" : "primary" }
1780
+ ]
1781
+ };
1782
+ try {
1783
+ view = renderStep({
1784
+ scope: getScope(),
1785
+ step: prompt,
1786
+ index,
1787
+ total,
1788
+ canGoBack: false,
1789
+ target: null,
1790
+ asModal: true,
1791
+ onButton: (b) => {
1792
+ if (b.action !== "url" || !b.value) return end("dismissed");
1793
+ if (options.onNavigate) options.onNavigate(b.value);
1794
+ else location.assign(b.value);
1795
+ },
1796
+ onClose: () => end("dismissed"),
1797
+ announce
1798
+ });
1799
+ } catch (err) {
1800
+ onError(err);
1801
+ }
1580
1802
  };
1581
1803
  const applyFallback = (step, direction) => {
1582
1804
  const fallback = step.target?.fallback ?? "skip";
@@ -1602,9 +1824,18 @@ function runGuide(guide, options = {}) {
1602
1824
  if (nextIndex < 0) return;
1603
1825
  return showStep(nextIndex, direction);
1604
1826
  }
1827
+ try {
1828
+ options.onPosition?.(step.id);
1829
+ } catch (err) {
1830
+ onError(err);
1831
+ }
1605
1832
  if (step.url && !urlMatches(step.url, currentUrl())) {
1606
1833
  status = "waiting_url";
1607
1834
  pendingIndex = i;
1835
+ if (options.promptForUrl) {
1836
+ const t = setTimeout(() => showUrlPrompt(step), options.urlPromptDelayMs ?? 1200);
1837
+ stepCleanups.push(() => clearTimeout(t));
1838
+ }
1608
1839
  return;
1609
1840
  }
1610
1841
  pendingIndex = -1;
@@ -2102,6 +2333,38 @@ function renderSurvey(options) {
2102
2333
 
2103
2334
  // src/help-view.ts
2104
2335
  var import_runtime8 = require("@guidelytech/shared/runtime");
2336
+ var SHOW_ME_KINDS = /* @__PURE__ */ new Set(["flow", "modal", "tooltip", "announcement"]);
2337
+ function startableGuide(guides, keyOrId) {
2338
+ if (!keyOrId || !guides) return null;
2339
+ return guides.find((g) => (g.key === keyOrId || g.id === keyOrId) && SHOW_ME_KINDS.has(g.kind)) ?? null;
2340
+ }
2341
+ function resultRow(doc, icon, title, summary, onOpen, showMe) {
2342
+ const item = listItem(doc, icon, title, summary);
2343
+ item.addEventListener("click", onOpen);
2344
+ if (!showMe) return item;
2345
+ const row = el(doc, "div", "g-rc-row");
2346
+ const b = el(doc, "button", "g-btn g-btn-primary g-rc-showme", "\u25B6 Show me");
2347
+ b.type = "button";
2348
+ b.setAttribute("aria-label", `Show me: ${title}`);
2349
+ b.addEventListener("click", showMe);
2350
+ row.append(item, b);
2351
+ return row;
2352
+ }
2353
+ function enhanceShowMe(root, doc, canStart, start) {
2354
+ for (const a of Array.from(root.querySelectorAll(`a[href^="${import_runtime8.SHOW_ME_HREF_PREFIX}"]`))) {
2355
+ const key = (0, import_runtime8.showMeKeyFromHref)(a.getAttribute("href"));
2356
+ const holder = a.closest("p.gd-show-me") ?? a;
2357
+ if (!key || !canStart(key)) {
2358
+ if (holder !== a) holder.remove();
2359
+ else a.replaceWith(doc.createTextNode(a.textContent ?? ""));
2360
+ continue;
2361
+ }
2362
+ const b = el(doc, "button", "g-btn g-btn-primary g-help-showme", `\u25B6 ${(a.textContent ?? "").trim() || "Show me"}`);
2363
+ b.type = "button";
2364
+ b.addEventListener("click", () => start(key));
2365
+ a.replaceWith(b);
2366
+ }
2367
+ }
2105
2368
  function el(doc, tag, cls, text) {
2106
2369
  const e = doc.createElement(tag);
2107
2370
  if (cls) e.className = cls;
@@ -2126,6 +2389,11 @@ function createHelpView(options) {
2126
2389
  let searchSeq = 0;
2127
2390
  let openSeq = 0;
2128
2391
  let active = null;
2392
+ const setActive = (next) => {
2393
+ const was = !!active;
2394
+ active = next;
2395
+ if (was !== !!next) options.onArticleChange?.(!!next);
2396
+ };
2129
2397
  const root = el(doc, "div", "g-help");
2130
2398
  const searchWrap = el(doc, "div", "g-rc-search");
2131
2399
  const input = el(doc, "input", "g-input");
@@ -2136,6 +2404,8 @@ function createHelpView(options) {
2136
2404
  const body = el(doc, "div", "g-panel-body");
2137
2405
  root.append(searchWrap, body);
2138
2406
  const hasBody = (a) => typeof a.body === "string" && !a.bodyOmitted;
2407
+ const canShowMe = (key) => !!options.onShowMe && !!startableGuide(options.guides?.(), key);
2408
+ const showMe = (key) => options.onShowMe?.(startableGuide(options.guides?.(), key)?.key ?? key);
2139
2409
  const openArticle = (article) => {
2140
2410
  if (article.externalUrl) {
2141
2411
  window.open(article.externalUrl, "_blank", "noopener,noreferrer");
@@ -2144,20 +2414,20 @@ function createHelpView(options) {
2144
2414
  }
2145
2415
  const seq = ++openSeq;
2146
2416
  if (hasBody(article) || !options.loadArticle) {
2147
- active = { article, loading: false };
2417
+ setActive({ article, loading: false });
2148
2418
  if (hasBody(article)) options.onArticleView?.(article);
2149
2419
  return render();
2150
2420
  }
2151
- active = { article, loading: true };
2421
+ setActive({ article, loading: true });
2152
2422
  render();
2153
2423
  options.loadArticle(article.id).then((full) => {
2154
2424
  if (seq !== openSeq || !active) return;
2155
- active = { article: full ?? { ...article, body: "" }, loading: false };
2425
+ setActive({ article: full ?? { ...article, body: "" }, loading: false });
2156
2426
  if (full) options.onArticleView?.(full);
2157
2427
  render();
2158
2428
  }).catch(() => {
2159
2429
  if (seq !== openSeq || !active) return;
2160
- active = { article: { ...article, body: "" }, loading: false };
2430
+ setActive({ article: { ...article, body: "" }, loading: false });
2161
2431
  render();
2162
2432
  });
2163
2433
  };
@@ -2167,7 +2437,7 @@ function createHelpView(options) {
2167
2437
  back.type = "button";
2168
2438
  back.addEventListener("click", () => {
2169
2439
  openSeq++;
2170
- active = null;
2440
+ setActive(null);
2171
2441
  render();
2172
2442
  });
2173
2443
  const title = el(doc, "h3", "g-title", article.title);
@@ -2176,6 +2446,7 @@ function createHelpView(options) {
2176
2446
  else if (article.body) {
2177
2447
  setSanitizedHtml(content, article.body);
2178
2448
  enhancePdfViewers(content, doc);
2449
+ enhanceShowMe(content, doc, canShowMe, showMe);
2179
2450
  } else content.appendChild(el(doc, "div", "g-rc-empty", "This article couldn't be loaded."));
2180
2451
  wrap.append(back, title, content);
2181
2452
  body.appendChild(wrap);
@@ -2187,9 +2458,9 @@ function createHelpView(options) {
2187
2458
  return s;
2188
2459
  };
2189
2460
  const articleButton = (a) => {
2190
- const b = listItem(doc, "\u{1F4C4}", a.title, a.summary);
2191
- b.addEventListener("click", () => openArticle(a));
2192
- body.appendChild(b);
2461
+ const key = a.walkthroughs?.find(canShowMe);
2462
+ const summary = a.hasVideo ? a.summary ? `Video \xB7 ${a.summary}` : "Video" : a.summary;
2463
+ body.appendChild(resultRow(doc, a.hasVideo ? "\u25B6" : "\u{1F4C4}", a.title, summary, () => openArticle(a), key ? () => showMe(key) : void 0));
2193
2464
  };
2194
2465
  function render() {
2195
2466
  body.textContent = "";
@@ -2198,10 +2469,17 @@ function createHelpView(options) {
2198
2469
  const member = options.audienceIds?.() ?? [];
2199
2470
  const sections = (0, import_runtime8.resourceCenterSections)(data.collections, data.articles, member);
2200
2471
  const q = query.trim().toLowerCase();
2472
+ if (q) {
2473
+ const walkthroughs = (data.recommended ?? []).filter((g) => (0, import_runtime8.matchesHelpQuery)(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));
2474
+ if (walkthroughs.length) {
2475
+ section(`Walkthroughs (${walkthroughs.length})`);
2476
+ for (const g of walkthroughs) body.appendChild(resultRow(doc, "\u25B6", g.name, "Show me, step by step", () => options.onStartGuide?.(g.id)));
2477
+ }
2478
+ }
2201
2479
  body.append(...options.extra?.(q) ?? []);
2202
2480
  if (q) {
2203
2481
  const visible = sections.flatMap((s) => s.articles);
2204
- const local = visible.filter((a) => a.title.toLowerCase().includes(q) || a.summary.toLowerCase().includes(q));
2482
+ const local = visible.filter((a) => (0, import_runtime8.matchesHelpQuery)(`${a.title} ${a.summary}`, q));
2205
2483
  const seen = new Set(local.map((a) => a.id));
2206
2484
  const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
2207
2485
  const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && (0, import_runtime8.isArticleListed)(data.collections, a, member))];
@@ -2273,7 +2551,7 @@ function createHelpView(options) {
2273
2551
  const onInput = () => {
2274
2552
  query = input.value;
2275
2553
  openSeq++;
2276
- active = null;
2554
+ setActive(null);
2277
2555
  render();
2278
2556
  if (debounce) clearTimeout(debounce);
2279
2557
  if (!options.search || !query.trim()) {
@@ -2296,7 +2574,7 @@ function createHelpView(options) {
2296
2574
  render,
2297
2575
  reset() {
2298
2576
  openSeq++;
2299
- active = null;
2577
+ setActive(null);
2300
2578
  query = "";
2301
2579
  input.value = "";
2302
2580
  serverResults = [];
@@ -2305,6 +2583,7 @@ function createHelpView(options) {
2305
2583
  const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: "Article", summary: "", updatedAt: "", bodyOmitted: true };
2306
2584
  openArticle(listed);
2307
2585
  },
2586
+ isArticleOpen: () => !!active,
2308
2587
  focusSearch: () => input.focus?.({ preventScroll: true }),
2309
2588
  setQuery(q) {
2310
2589
  input.value = q;
@@ -2356,7 +2635,14 @@ function renderResourceCenter(options) {
2356
2635
  loadArticle: options.loadArticle,
2357
2636
  onStartGuide: options.onStartGuide,
2358
2637
  onOpenChecklist: options.onOpenChecklist,
2359
- onArticleView: options.onArticleView
2638
+ onArticleView: options.onArticleView,
2639
+ guides: () => guides,
2640
+ onShowMe: (key) => {
2641
+ setOpen(false);
2642
+ options.onStartGuide?.(key);
2643
+ },
2644
+ // Articles read in a larger panel.
2645
+ onArticleChange: (reading) => panel.classList.toggle("g-panel-reading", reading)
2360
2646
  });
2361
2647
  panel.append(head, help.el);
2362
2648
  root.append(launcher, panel);
@@ -2445,7 +2731,7 @@ function startAuthoring(options) {
2445
2731
  outline.hidden = true;
2446
2732
  return;
2447
2733
  }
2448
- const r = el2.getBoundingClientRect();
2734
+ const r = visualRect(el2);
2449
2735
  outline.hidden = false;
2450
2736
  outline.classList.toggle("g-picked", picked);
2451
2737
  Object.assign(outline.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
@@ -2453,9 +2739,9 @@ function startAuthoring(options) {
2453
2739
  label.style.top = r.top < 30 ? "100%" : "-24px";
2454
2740
  };
2455
2741
  const elementAt = (x, y) => {
2456
- const el2 = doc.elementFromPoint(x, y);
2742
+ const el2 = deepElementFromPoint(doc, x, y);
2457
2743
  if (!el2 || isGuidelyElement(el2) || el2 === doc.documentElement || el2 === doc.body) return null;
2458
- return el2;
2744
+ return componentAnchor(el2);
2459
2745
  };
2460
2746
  const onMove = (e) => {
2461
2747
  if (!picking || stopped) return;
@@ -2470,7 +2756,7 @@ function startAuthoring(options) {
2470
2756
  };
2471
2757
  const onClick = (e) => {
2472
2758
  if (!picking || stopped) return;
2473
- if (e.target && isGuidelyElement(e.target)) return;
2759
+ if (isGuidelyElement(composedTarget(e))) return;
2474
2760
  const el2 = elementAt(e.clientX, e.clientY) ?? hovered;
2475
2761
  if (!el2) return;
2476
2762
  e.preventDefault();
@@ -2483,8 +2769,7 @@ function startAuthoring(options) {
2483
2769
  };
2484
2770
  const swallow = (e) => {
2485
2771
  if (!picking || stopped) return;
2486
- const t = e.target;
2487
- if (t && isGuidelyElement(t)) return;
2772
+ if (isGuidelyElement(composedTarget(e))) return;
2488
2773
  e.preventDefault();
2489
2774
  e.stopPropagation();
2490
2775
  };
@@ -2542,7 +2827,7 @@ function previewStep(step, theme, options = {}) {
2542
2827
  name: "Preview",
2543
2828
  kind: "flow",
2544
2829
  version: 0,
2545
- 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: [], platforms: ["web"] }
2830
+ config: { steps: [previewStepConfig], visibility: "everyone", 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: [], platforms: ["web"] }
2546
2831
  };
2547
2832
  const controller = runGuide(guide, { theme, doc: options.doc, onEnd: () => options.onEnd?.(), onNavigate: () => {
2548
2833
  }, onAction: () => {
@@ -2555,17 +2840,15 @@ function highlightSelectors(selectors, options = {}) {
2555
2840
  const scope = createScope(null, { theme: "light", animation: "none" }, doc);
2556
2841
  const boxes = [];
2557
2842
  const counts = [];
2558
- let resolved = null;
2843
+ const resolved = resolveTarget(selectors, doc);
2559
2844
  const matched = /* @__PURE__ */ new Set();
2560
2845
  selectors.forEach((s) => {
2561
2846
  const els = querySelectorStrategy(s, doc).filter((el2) => !isGuidelyElement(el2));
2562
- const visible = els.filter(isVisible);
2563
2847
  counts.push(els.length);
2564
- if (!resolved && visible.length === 1) resolved = visible[0];
2565
2848
  els.forEach((el2) => matched.add(el2));
2566
2849
  });
2567
2850
  matched.forEach((el2) => {
2568
- const r = el2.getBoundingClientRect();
2851
+ const r = visualRect(el2);
2569
2852
  const box = doc.createElement("div");
2570
2853
  box.className = "g-highlight";
2571
2854
  Object.assign(box.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
@@ -2586,12 +2869,19 @@ function highlightSelectors(selectors, options = {}) {
2586
2869
  0 && (module.exports = {
2587
2870
  DEFAULT_THEME,
2588
2871
  HOST_ID,
2872
+ SHADOW_SEPARATOR,
2589
2873
  STYLES,
2590
2874
  TargetTimeoutError,
2591
2875
  anchor,
2592
2876
  applyTheme,
2593
2877
  buildCard,
2878
+ closestComposed,
2879
+ componentAnchor,
2880
+ composedParent,
2881
+ composedTarget,
2882
+ containsComposed,
2594
2883
  createScope,
2884
+ deepElementFromPoint,
2595
2885
  describeElement,
2596
2886
  destroyHost,
2597
2887
  enhancePdfViewers,
@@ -2625,10 +2915,12 @@ function highlightSelectors(selectors, options = {}) {
2625
2915
  sanitizeHtml,
2626
2916
  sanitizeToFragment,
2627
2917
  setSanitizedHtml,
2918
+ shadowHostChain,
2628
2919
  startAuthoring,
2629
2920
  textSnapshotOf,
2630
2921
  trackRect,
2631
2922
  urlMatches,
2923
+ visualRect,
2632
2924
  waitForTarget
2633
2925
  });
2634
2926
  //# sourceMappingURL=index.cjs.map