@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.js CHANGED
@@ -5,9 +5,16 @@ import {
5
5
  sanitizeHtml,
6
6
  sanitizeToFragment,
7
7
  setSanitizedHtml
8
- } from "./chunk-4FKYF6OB.js";
8
+ } from "./chunk-KJRXCITD.js";
9
9
  import {
10
+ SHADOW_SEPARATOR,
10
11
  TargetTimeoutError,
12
+ closestComposed,
13
+ componentAnchor,
14
+ composedParent,
15
+ composedTarget,
16
+ containsComposed,
17
+ deepElementFromPoint,
11
18
  describeElement,
12
19
  generateSelectors,
13
20
  isGuidelyElement,
@@ -16,10 +23,12 @@ import {
16
23
  normalizeText,
17
24
  querySelectorStrategy,
18
25
  resolveTarget,
26
+ shadowHostChain,
19
27
  textSnapshotOf,
20
28
  urlMatches,
29
+ visualRect,
21
30
  waitForTarget
22
- } from "./chunk-GUSKGNNR.js";
31
+ } from "./chunk-GUH2PQJ7.js";
23
32
  import {
24
33
  DEFAULT_THEME,
25
34
  HOST_ID,
@@ -30,11 +39,12 @@ import {
30
39
  ensureHost,
31
40
  prefersReducedMotion,
32
41
  resolveTheme
33
- } from "./chunk-7DOGZKWD.js";
42
+ } from "./chunk-PG7CMBHD.js";
34
43
 
35
44
  // src/position.ts
36
45
  import { arrow, autoPlacement, autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
37
- function anchor(reference, floating, options = {}) {
46
+ function anchor(target, floating, options = {}) {
47
+ const reference = target.shadowRoot ? { getBoundingClientRect: () => visualRect(target), contextElement: target } : target;
38
48
  const placement = options.placement ?? "auto";
39
49
  const gap = options.offset ?? 10;
40
50
  const middleware = [offset(gap + (options.arrowEl ? 4 : 0))];
@@ -87,7 +97,7 @@ function trackRect(el2, cb) {
87
97
  let stopped = false;
88
98
  const run = () => {
89
99
  if (stopped) return;
90
- cb(el2.getBoundingClientRect());
100
+ cb(visualRect(el2));
91
101
  };
92
102
  const schedule = () => {
93
103
  if (stopped) return;
@@ -100,6 +110,9 @@ function trackRect(el2, cb) {
100
110
  const win = el2.ownerDocument.defaultView;
101
111
  win?.addEventListener("scroll", schedule, true);
102
112
  win?.addEventListener("resize", schedule);
113
+ const shadowRoots = [];
114
+ for (let root = el2.getRootNode(); root.nodeType === 11 && root.host; root = root.host.getRootNode()) shadowRoots.push(root);
115
+ for (const root of shadowRoots) root.addEventListener("scroll", schedule, true);
103
116
  let ro = null;
104
117
  if (typeof ResizeObserver !== "undefined") {
105
118
  try {
@@ -115,6 +128,7 @@ function trackRect(el2, cb) {
115
128
  stopped = true;
116
129
  win?.removeEventListener("scroll", schedule, true);
117
130
  win?.removeEventListener("resize", schedule);
131
+ for (const root of shadowRoots) root.removeEventListener("scroll", schedule, true);
118
132
  ro?.disconnect();
119
133
  clearInterval(interval);
120
134
  if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(raf);
@@ -501,6 +515,17 @@ function isAnchoredStep(step) {
501
515
  import { evaluateRules } from "@guidelytech/shared/runtime";
502
516
  function noop() {
503
517
  }
518
+ var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
519
+ function navigableUrl(pattern) {
520
+ if (!pattern || /[*?#\s]/.test(pattern) || pattern.includes("/:")) return null;
521
+ if (pattern.startsWith("/") && !pattern.startsWith("//")) return pattern;
522
+ try {
523
+ const u = new URL(pattern);
524
+ if (typeof location !== "undefined" && u.origin === location.origin) return u.pathname;
525
+ } catch {
526
+ }
527
+ return null;
528
+ }
504
529
  function runGuide(guide, options = {}) {
505
530
  const doc = options.doc ?? (options.container?.ownerDocument || document);
506
531
  const steps = guide.config.steps;
@@ -645,15 +670,15 @@ function runGuide(guide, options = {}) {
645
670
  onError(err);
646
671
  }
647
672
  };
648
- const setupAdvance = (step) => {
673
+ const setupAdvance = (step, target) => {
649
674
  const modes = step.advanceOn === "any" ? ["element_click", "element_visible", "event", "delay"] : [step.advanceOn];
650
675
  for (const mode of modes) {
651
- if (mode === "element_click" && step.advanceValue) {
676
+ if (mode === "element_click" && (step.advanceValue || target)) {
652
677
  const selector = step.advanceValue;
653
678
  const handler = (e) => {
654
- const t = e.target;
679
+ const t = composedTarget(e);
655
680
  try {
656
- if (t && typeof t.closest === "function" && t.closest(selector)) advance();
681
+ if (selector ? t && closestComposed(t, selector) : t && target && containsComposed(target, t)) advance();
657
682
  } catch {
658
683
  }
659
684
  };
@@ -689,10 +714,11 @@ function runGuide(guide, options = {}) {
689
714
  const render = (step, target, asModal = false) => {
690
715
  if (ended) return;
691
716
  status = "running";
717
+ 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;
692
718
  try {
693
719
  view = renderStep({
694
720
  scope: getScope(),
695
- step,
721
+ step: shown,
696
722
  index,
697
723
  total,
698
724
  canGoBack: history.length > 0 || index > 0,
@@ -714,7 +740,43 @@ function runGuide(guide, options = {}) {
714
740
  }
715
741
  emit("step_viewed", step, index, asModal ? { fallback: "show_as_modal" } : void 0);
716
742
  announce(step.title ? `${step.title}. Step ${index + 1} of ${total}` : `Step ${index + 1} of ${total}`);
717
- setupAdvance(step);
743
+ setupAdvance(step, target);
744
+ };
745
+ const showUrlPrompt = (step) => {
746
+ if (ended || status !== "waiting_url" || view) return;
747
+ const href = navigableUrl(step.url);
748
+ const where = (step.url ?? "").replace(/^https?:\/\/[^/]+/i, "") || "another page";
749
+ const prompt = {
750
+ ...step,
751
+ type: "modal",
752
+ title: "Continue on another page",
753
+ body: `<p>The next step${step.title ? `, <strong>${escapeHtml(step.title)}</strong>,` : ""} is on <code>${escapeHtml(where)}</code>.</p>`,
754
+ imageUrl: "",
755
+ buttons: [
756
+ ...href ? [{ id: "g-go", label: "Take me there", action: "url", value: href, variant: "primary" }] : [],
757
+ { id: "g-exit", label: "Exit", action: "dismiss", variant: href ? "secondary" : "primary" }
758
+ ]
759
+ };
760
+ try {
761
+ view = renderStep({
762
+ scope: getScope(),
763
+ step: prompt,
764
+ index,
765
+ total,
766
+ canGoBack: false,
767
+ target: null,
768
+ asModal: true,
769
+ onButton: (b) => {
770
+ if (b.action !== "url" || !b.value) return end("dismissed");
771
+ if (options.onNavigate) options.onNavigate(b.value);
772
+ else location.assign(b.value);
773
+ },
774
+ onClose: () => end("dismissed"),
775
+ announce
776
+ });
777
+ } catch (err) {
778
+ onError(err);
779
+ }
718
780
  };
719
781
  const applyFallback = (step, direction) => {
720
782
  const fallback = step.target?.fallback ?? "skip";
@@ -740,9 +802,18 @@ function runGuide(guide, options = {}) {
740
802
  if (nextIndex < 0) return;
741
803
  return showStep(nextIndex, direction);
742
804
  }
805
+ try {
806
+ options.onPosition?.(step.id);
807
+ } catch (err) {
808
+ onError(err);
809
+ }
743
810
  if (step.url && !urlMatches(step.url, currentUrl())) {
744
811
  status = "waiting_url";
745
812
  pendingIndex = i;
813
+ if (options.promptForUrl) {
814
+ const t = setTimeout(() => showUrlPrompt(step), options.urlPromptDelayMs ?? 1200);
815
+ stepCleanups.push(() => clearTimeout(t));
816
+ }
746
817
  return;
747
818
  }
748
819
  pendingIndex = -1;
@@ -1276,7 +1347,14 @@ function renderResourceCenter(options) {
1276
1347
  loadArticle: options.loadArticle,
1277
1348
  onStartGuide: options.onStartGuide,
1278
1349
  onOpenChecklist: options.onOpenChecklist,
1279
- onArticleView: options.onArticleView
1350
+ onArticleView: options.onArticleView,
1351
+ guides: () => guides,
1352
+ onShowMe: (key) => {
1353
+ setOpen(false);
1354
+ options.onStartGuide?.(key);
1355
+ },
1356
+ // Articles read in a larger panel.
1357
+ onArticleChange: (reading) => panel.classList.toggle("g-panel-reading", reading)
1280
1358
  });
1281
1359
  panel.append(head, help.el);
1282
1360
  root.append(launcher, panel);
@@ -1365,7 +1443,7 @@ function startAuthoring(options) {
1365
1443
  outline.hidden = true;
1366
1444
  return;
1367
1445
  }
1368
- const r = el2.getBoundingClientRect();
1446
+ const r = visualRect(el2);
1369
1447
  outline.hidden = false;
1370
1448
  outline.classList.toggle("g-picked", picked);
1371
1449
  Object.assign(outline.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
@@ -1373,9 +1451,9 @@ function startAuthoring(options) {
1373
1451
  label.style.top = r.top < 30 ? "100%" : "-24px";
1374
1452
  };
1375
1453
  const elementAt = (x, y) => {
1376
- const el2 = doc.elementFromPoint(x, y);
1454
+ const el2 = deepElementFromPoint(doc, x, y);
1377
1455
  if (!el2 || isGuidelyElement(el2) || el2 === doc.documentElement || el2 === doc.body) return null;
1378
- return el2;
1456
+ return componentAnchor(el2);
1379
1457
  };
1380
1458
  const onMove = (e) => {
1381
1459
  if (!picking || stopped) return;
@@ -1390,7 +1468,7 @@ function startAuthoring(options) {
1390
1468
  };
1391
1469
  const onClick = (e) => {
1392
1470
  if (!picking || stopped) return;
1393
- if (e.target && isGuidelyElement(e.target)) return;
1471
+ if (isGuidelyElement(composedTarget(e))) return;
1394
1472
  const el2 = elementAt(e.clientX, e.clientY) ?? hovered;
1395
1473
  if (!el2) return;
1396
1474
  e.preventDefault();
@@ -1403,8 +1481,7 @@ function startAuthoring(options) {
1403
1481
  };
1404
1482
  const swallow = (e) => {
1405
1483
  if (!picking || stopped) return;
1406
- const t = e.target;
1407
- if (t && isGuidelyElement(t)) return;
1484
+ if (isGuidelyElement(composedTarget(e))) return;
1408
1485
  e.preventDefault();
1409
1486
  e.stopPropagation();
1410
1487
  };
@@ -1462,7 +1539,7 @@ function previewStep(step, theme, options = {}) {
1462
1539
  name: "Preview",
1463
1540
  kind: "flow",
1464
1541
  version: 0,
1465
- 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"] }
1542
+ 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"] }
1466
1543
  };
1467
1544
  const controller = runGuide(guide, { theme, doc: options.doc, onEnd: () => options.onEnd?.(), onNavigate: () => {
1468
1545
  }, onAction: () => {
@@ -1475,17 +1552,15 @@ function highlightSelectors(selectors, options = {}) {
1475
1552
  const scope = createScope(null, { theme: "light", animation: "none" }, doc);
1476
1553
  const boxes = [];
1477
1554
  const counts = [];
1478
- let resolved = null;
1555
+ const resolved = resolveTarget(selectors, doc);
1479
1556
  const matched = /* @__PURE__ */ new Set();
1480
1557
  selectors.forEach((s) => {
1481
1558
  const els = querySelectorStrategy(s, doc).filter((el2) => !isGuidelyElement(el2));
1482
- const visible = els.filter(isVisible);
1483
1559
  counts.push(els.length);
1484
- if (!resolved && visible.length === 1) resolved = visible[0];
1485
1560
  els.forEach((el2) => matched.add(el2));
1486
1561
  });
1487
1562
  matched.forEach((el2) => {
1488
- const r = el2.getBoundingClientRect();
1563
+ const r = visualRect(el2);
1489
1564
  const box = doc.createElement("div");
1490
1565
  box.className = "g-highlight";
1491
1566
  Object.assign(box.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
@@ -1505,12 +1580,19 @@ function highlightSelectors(selectors, options = {}) {
1505
1580
  export {
1506
1581
  DEFAULT_THEME,
1507
1582
  HOST_ID,
1583
+ SHADOW_SEPARATOR,
1508
1584
  STYLES,
1509
1585
  TargetTimeoutError,
1510
1586
  anchor,
1511
1587
  applyTheme,
1512
1588
  buildCard,
1589
+ closestComposed,
1590
+ componentAnchor,
1591
+ composedParent,
1592
+ composedTarget,
1593
+ containsComposed,
1513
1594
  createScope,
1595
+ deepElementFromPoint,
1514
1596
  describeElement,
1515
1597
  destroyHost,
1516
1598
  enhancePdfViewers,
@@ -1544,10 +1626,12 @@ export {
1544
1626
  sanitizeHtml,
1545
1627
  sanitizeToFragment,
1546
1628
  setSanitizedHtml,
1629
+ shadowHostChain,
1547
1630
  startAuthoring,
1548
1631
  textSnapshotOf,
1549
1632
  trackRect,
1550
1633
  urlMatches,
1634
+ visualRect,
1551
1635
  waitForTarget
1552
1636
  };
1553
1637
  //# sourceMappingURL=index.js.map