@guidelytech/guide-runtime 0.5.0 → 0.6.2

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
@@ -32,13 +32,14 @@ __export(src_exports, {
32
32
  createScope: () => createScope,
33
33
  describeElement: () => describeElement,
34
34
  destroyHost: () => destroyHost,
35
+ enhancePdfViewers: () => enhancePdfViewers,
35
36
  ensureHost: () => ensureHost,
36
37
  generateSelectors: () => generateSelectors,
37
38
  highlightSelectors: () => highlightSelectors,
38
39
  isAnchoredStep: () => isAnchoredStep,
39
- isChecklistComplete: () => import_runtime5.isChecklistComplete,
40
+ isChecklistComplete: () => import_runtime6.isChecklistComplete,
40
41
  isGuidelyElement: () => isGuidelyElement,
41
- isQuestionVisible: () => import_runtime6.isQuestionVisible,
42
+ isQuestionVisible: () => import_runtime7.isQuestionVisible,
42
43
  isStableId: () => isStableId,
43
44
  isVisible: () => isVisible,
44
45
  normalizeText: () => normalizeText,
@@ -356,7 +357,7 @@ function describeElement(el2) {
356
357
  var import_runtime3 = require("@guidelytech/shared/runtime");
357
358
  var ALLOWED_TAGS = /* @__PURE__ */ new Set(["p", "br", "strong", "b", "em", "i", "u", "a", "ul", "ol", "li", "img", "h1", "h2", "h3", "h4", "code", "pre", "blockquote", "span", "div", "hr", "button"]);
358
359
  var DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "object", "embed", "form", "input", "textarea", "select", "link", "meta", "base", "template", "svg", "math", "frame", "frameset", "noscript"]);
359
- var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["href", "src", "alt", "title", "target", "rel", "class", "data-g-action", "data-g-value"]);
360
+ var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["href", "src", "alt", "title", "target", "rel", "class", "width", "height", "data-g-action", "data-g-value"]);
360
361
  function isSafeUrl(value, attr, tag) {
361
362
  const v = value.trim().replace(/[\u0000-\u001f\s]/g, "").toLowerCase();
362
363
  if (!v) return false;
@@ -383,6 +384,7 @@ function sanitizeElement(el2) {
383
384
  }
384
385
  if (tag === "button") el2.setAttribute("type", "button");
385
386
  if (tag === "img") {
387
+ for (const dim of ["width", "height"]) if (el2.hasAttribute(dim) && !/^\d{1,5}$/.test(el2.getAttribute(dim))) el2.removeAttribute(dim);
386
388
  el2.setAttribute("loading", "lazy");
387
389
  if (!el2.getAttribute("alt")) el2.setAttribute("alt", "");
388
390
  }
@@ -455,6 +457,9 @@ function setSanitizedHtml(el2, html) {
455
457
  el2.appendChild(sanitizeToFragment(html, el2.ownerDocument));
456
458
  }
457
459
 
460
+ // src/pdf-viewer.ts
461
+ var import_runtime4 = require("@guidelytech/shared/runtime");
462
+
458
463
  // src/styles.ts
459
464
  var STYLES = `
460
465
  :host{all:initial;position:static;display:block;contain:none}
@@ -504,6 +509,35 @@ var STYLES = `
504
509
  .g-body video,.g-body iframe{display:block;width:100%;max-width:100%;border:0;border-radius:6px;margin:8px 0;background:#000}
505
510
  .g-body video{height:auto;max-height:60vh}
506
511
  .g-body iframe{aspect-ratio:16/9;height:auto}
512
+ .g-body .gd-pdf{margin:10px 0;border:1px solid var(--g-border);border-radius:10px;background:var(--g-subtle);overflow:hidden}
513
+ .g-body a.gd-file{display:flex;align-items:center;gap:8px;padding:8px 10px;color:var(--g-text);font-size:13px;font-weight:500;text-decoration:none;min-height:40px;overflow-wrap:anywhere}
514
+ .g-body a.gd-file::before{content:"PDF";flex:none;font-size:9px;font-weight:700;letter-spacing:.04em;line-height:1;color:#fff;background:#e5484d;border-radius:4px;padding:4px 5px}
515
+ .g-body a.gd-file::after{content:"\u2922";margin-left:auto;padding-left:8px;color:var(--g-muted);font-size:15px}
516
+ .g-body a.gd-file:hover::after,.g-body a.gd-file:focus-visible::after{color:var(--g-primary)}
517
+ .g-body a.gd-file:focus-visible{outline:2px solid var(--g-primary);outline-offset:-2px;border-radius:10px}
518
+ .g-body > a.gd-file,.g-body p > a.gd-file{margin:10px 0;border:1px solid var(--g-border);border-radius:10px;background:var(--g-subtle)}
519
+ .g-body > a.gd-file::after,.g-body p > a.gd-file::after,.g-body .gd-pdf:not([data-g-ready]) > a.gd-file::after{content:"\u2197"}
520
+ .g-pdf-viewport{position:relative}
521
+ .g-body .gd-pdf-pages{display:flex;gap:10px;overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;scrollbar-width:none;padding:0 10px 10px;scroll-padding:0 10px}
522
+ .g-body .gd-pdf-pages::-webkit-scrollbar{display:none}
523
+ .g-body .gd-pdf-pages:focus-visible{outline:2px solid var(--g-primary);outline-offset:-2px}
524
+ .g-body .gd-pdf-pages img{flex:0 0 100%;width:100%;height:auto;margin:0;scroll-snap-align:center;background:#fff;border-radius:4px;box-shadow:0 1px 3px rgba(0,0,0,.18);cursor:zoom-in}
525
+ .g-pdf-viewport .gd-pdf-pages{padding:0;scroll-padding:0;margin:0 10px 10px}
526
+ .g-pdf-count{position:absolute;right:18px;bottom:18px;padding:2px 8px;border-radius:999px;background:rgba(17,17,17,.72);color:#fff;font-size:11px;font-weight:500;pointer-events:none;font-variant-numeric:tabular-nums}
527
+ .g-pdf-nav{position:absolute;top:calc(50% - 21px);width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:999px;background:rgba(17,17,17,.6);color:#fff;font:inherit;font-size:20px;line-height:1;cursor:pointer;opacity:0;transition:opacity .15s}
528
+ .g-pdf-prev{left:16px}.g-pdf-next{right:16px}
529
+ .g-pdf-viewport:hover .g-pdf-nav,.g-pdf-nav:focus-visible{opacity:1}
530
+ .g-pdf-nav:disabled,.g-pdf-single .g-pdf-nav,.g-pdf-single .g-pdf-count{display:none}
531
+ .g-pdf-nav:focus-visible{outline:2px solid #fff;outline-offset:1px}
532
+ @media (hover:none){.g-pdf-nav{display:none}}
533
+ .g-pdf-reader{position:fixed;inset:0;z-index:calc(var(--g-z) + 10);display:flex;flex-direction:column;background:#0b0b0d;pointer-events:auto;color:#fff}
534
+ .g-pdf-bar{display:flex;align-items:center;gap:10px;padding:8px 8px 8px 16px;min-height:56px}
535
+ .g-pdf-title{flex:1;min-width:0;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
536
+ .g-pdf-bar .g-btn{min-height:36px;color:#fff;background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.18)}
537
+ .g-pdf-close{position:static;color:#fff}
538
+ .g-pdf-close:hover,.g-pdf-close:focus-visible{background:rgba(255,255,255,.14);color:#fff}
539
+ .g-pdf-scroll{position:relative;flex:1;overflow-y:auto;overscroll-behavior:contain;padding:4px 16px 24px;display:flex;flex-direction:column;align-items:center;gap:12px;outline:none}
540
+ .g-pdf-scroll img{display:block;width:100%;max-width:900px;height:auto;background:#fff;border-radius:2px;box-shadow:0 2px 12px rgba(0,0,0,.4)}
507
541
  .g-body code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;background:var(--g-subtle);padding:1px 4px;border-radius:4px}
508
542
  .g-body pre{background:var(--g-subtle);padding:10px;border-radius:6px;overflow:auto}
509
543
  .g-body blockquote{margin:6px 0;padding-left:10px;border-left:3px solid var(--g-border);color:var(--g-muted)}
@@ -731,6 +765,135 @@ function createScope(theme, appearance, doc = document) {
731
765
  };
732
766
  }
733
767
 
768
+ // src/pdf-viewer.ts
769
+ function enhancePdfViewers(root, doc = root.ownerDocument) {
770
+ for (const block of Array.from(root.querySelectorAll(`div.${import_runtime4.PDF_BLOCK_CLASS}`))) {
771
+ const link = block.querySelector(`a.${import_runtime4.FILE_LINK_CLASS}`);
772
+ const strip = block.querySelector(`.${import_runtime4.PDF_PAGES_CLASS}`);
773
+ if (!link || !strip || block.hasAttribute("data-g-ready")) continue;
774
+ for (const img of Array.from(strip.querySelectorAll("img:not([src])"))) img.remove();
775
+ const pages = Array.from(strip.querySelectorAll("img"));
776
+ if (!pages.length) {
777
+ strip.remove();
778
+ continue;
779
+ }
780
+ block.setAttribute("data-g-ready", "");
781
+ const name = (link.textContent || "").trim() || "PDF";
782
+ block.setAttribute("role", "group");
783
+ block.setAttribute("aria-label", `${name}, ${pages.length} ${pages.length === 1 ? "page" : "pages"}`);
784
+ link.setAttribute("aria-label", `Read ${name} full screen`);
785
+ link.addEventListener("click", (e) => {
786
+ e.preventDefault();
787
+ openReader(doc, block, link, pages, 0);
788
+ });
789
+ const viewport = doc.createElement("div");
790
+ viewport.className = "g-pdf-viewport";
791
+ strip.replaceWith(viewport);
792
+ viewport.appendChild(strip);
793
+ strip.tabIndex = 0;
794
+ strip.setAttribute("aria-label", `${name} pages`);
795
+ const counter = doc.createElement("span");
796
+ counter.className = "g-pdf-count";
797
+ counter.setAttribute("aria-live", "polite");
798
+ const prev = navButton(doc, "prev", "Previous page", "\u2039");
799
+ const next = navButton(doc, "next", "Next page", "\u203A");
800
+ viewport.append(prev, next, counter);
801
+ const stride = () => Math.max(1, pages.length > 1 ? pages[1].offsetLeft - pages[0].offsetLeft : strip.clientWidth);
802
+ const current2 = () => Math.round(strip.scrollLeft / stride());
803
+ const go = (i) => strip.scrollTo({ left: Math.max(0, Math.min(pages.length - 1, i)) * stride(), behavior: prefersReducedMotion() ? "auto" : "smooth" });
804
+ const update = () => {
805
+ const i = Math.min(pages.length - 1, current2());
806
+ counter.textContent = `${i + 1} / ${pages.length}`;
807
+ prev.disabled = i === 0;
808
+ next.disabled = i >= pages.length - 1;
809
+ };
810
+ prev.addEventListener("click", () => go(current2() - 1));
811
+ next.addEventListener("click", () => go(current2() + 1));
812
+ strip.addEventListener("scroll", update, { passive: true });
813
+ strip.addEventListener("keydown", (e) => {
814
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
815
+ e.preventDefault();
816
+ go(current2() + (e.key === "ArrowRight" ? 1 : -1));
817
+ } else if (e.key === "Enter") openReader(doc, block, link, pages, current2());
818
+ });
819
+ pages.forEach((img, i) => img.addEventListener("click", () => openReader(doc, block, link, pages, i)));
820
+ if (pages.length === 1) viewport.classList.add("g-pdf-single");
821
+ update();
822
+ }
823
+ }
824
+ function navButton(doc, cls, label, text) {
825
+ const b = doc.createElement("button");
826
+ b.type = "button";
827
+ b.className = `g-pdf-nav g-pdf-${cls}`;
828
+ b.setAttribute("aria-label", label);
829
+ b.textContent = text;
830
+ return b;
831
+ }
832
+ function openReader(doc, block, link, pages, start) {
833
+ const host = block.closest(".g-scope") ?? doc.body;
834
+ const returnFocus = block.getRootNode().activeElement;
835
+ const name = (link.textContent || "").trim() || "PDF";
836
+ const reader = doc.createElement("div");
837
+ reader.className = "g-pdf-reader";
838
+ reader.setAttribute("role", "dialog");
839
+ reader.setAttribute("aria-modal", "true");
840
+ reader.setAttribute("aria-label", name);
841
+ const bar = doc.createElement("div");
842
+ bar.className = "g-pdf-bar";
843
+ const title = doc.createElement("span");
844
+ title.className = "g-pdf-title";
845
+ title.textContent = name;
846
+ const original = doc.createElement("a");
847
+ original.className = "g-btn g-btn-secondary";
848
+ original.href = link.href;
849
+ original.target = "_blank";
850
+ original.rel = "noopener noreferrer";
851
+ original.textContent = "Open PDF \u2197";
852
+ const close = doc.createElement("button");
853
+ close.type = "button";
854
+ close.className = "g-close g-pdf-close";
855
+ close.setAttribute("aria-label", "Close");
856
+ close.textContent = "\xD7";
857
+ bar.append(title, original, close);
858
+ const scroller = doc.createElement("div");
859
+ scroller.className = "g-pdf-scroll";
860
+ scroller.tabIndex = 0;
861
+ for (const page of pages) {
862
+ const img = doc.createElement("img");
863
+ img.src = page.src;
864
+ img.alt = page.alt;
865
+ img.decoding = "async";
866
+ for (const dim of ["width", "height"]) if (page.hasAttribute(dim)) img.setAttribute(dim, page.getAttribute(dim));
867
+ scroller.appendChild(img);
868
+ }
869
+ reader.append(bar, scroller);
870
+ const onKey = (e) => {
871
+ if (e.key === "Escape") {
872
+ e.stopPropagation();
873
+ done();
874
+ } else if (e.key === "Tab") {
875
+ const items = [original, close, scroller];
876
+ const at = items.indexOf(reader.getRootNode().activeElement);
877
+ e.preventDefault();
878
+ items[(at + (e.shiftKey ? items.length - 1 : 1)) % items.length].focus();
879
+ }
880
+ };
881
+ const done = () => {
882
+ reader.remove();
883
+ returnFocus?.focus?.({ preventScroll: true });
884
+ };
885
+ close.addEventListener("click", done);
886
+ reader.addEventListener("keydown", onKey);
887
+ host.appendChild(reader);
888
+ close.focus();
889
+ const target = scroller.children[start];
890
+ if (target && start > 0) {
891
+ const land = () => scroller.scrollTop = target.offsetTop - 12;
892
+ requestAnimationFrame(land);
893
+ if (!target.hasAttribute("height")) for (const img of Array.from(scroller.children).slice(0, start)) img.addEventListener("load", land, { once: true });
894
+ }
895
+ }
896
+
734
897
  // src/position.ts
735
898
  var import_dom = require("@floating-ui/dom");
736
899
  function anchor(reference, floating, options = {}) {
@@ -1197,7 +1360,7 @@ function isAnchoredStep(step) {
1197
1360
  }
1198
1361
 
1199
1362
  // src/controller.ts
1200
- var import_runtime4 = require("@guidelytech/shared/runtime");
1363
+ var import_runtime5 = require("@guidelytech/shared/runtime");
1201
1364
  function noop() {
1202
1365
  }
1203
1366
  function runGuide(guide, options = {}) {
@@ -1279,7 +1442,7 @@ function runGuide(guide, options = {}) {
1279
1442
  const ctx = getCtx();
1280
1443
  if (step && ctx) {
1281
1444
  for (const branch of step.branches) {
1282
- if ((0, import_runtime4.evaluateRules)(branch.when, ctx)) {
1445
+ if ((0, import_runtime5.evaluateRules)(branch.when, ctx)) {
1283
1446
  target = steps.findIndex((s) => s.id === branch.goTo);
1284
1447
  if (target >= 0) break;
1285
1448
  }
@@ -1433,7 +1596,7 @@ function runGuide(guide, options = {}) {
1433
1596
  index = i;
1434
1597
  const step = steps[i];
1435
1598
  const ctx = getCtx();
1436
- if (step.targeting && step.targeting.rules.length && ctx && !(0, import_runtime4.evaluateRules)(step.targeting, ctx)) {
1599
+ if (step.targeting && step.targeting.rules.length && ctx && !(0, import_runtime5.evaluateRules)(step.targeting, ctx)) {
1437
1600
  const nextIndex = i + direction;
1438
1601
  if (nextIndex >= total) return end("completed");
1439
1602
  if (nextIndex < 0) return;
@@ -1527,7 +1690,7 @@ function runGuide(guide, options = {}) {
1527
1690
  }
1528
1691
 
1529
1692
  // src/checklist.ts
1530
- var import_runtime5 = require("@guidelytech/shared/runtime");
1693
+ var import_runtime6 = require("@guidelytech/shared/runtime");
1531
1694
  function renderChecklist(options) {
1532
1695
  const { scope, config } = options;
1533
1696
  const doc = scope.ownerDocument;
@@ -1602,7 +1765,7 @@ function renderChecklist(options) {
1602
1765
  bar.setAttribute("aria-valuemax", "100");
1603
1766
  body.textContent = "";
1604
1767
  foot.textContent = "";
1605
- const complete = (0, import_runtime5.isChecklistComplete)(config, progress);
1768
+ const complete = (0, import_runtime6.isChecklistComplete)(config, progress);
1606
1769
  if (complete) {
1607
1770
  const wrap = doc.createElement("div");
1608
1771
  wrap.className = "g-complete";
@@ -1690,7 +1853,7 @@ function renderChecklist(options) {
1690
1853
  }
1691
1854
 
1692
1855
  // src/survey.ts
1693
- var import_runtime6 = require("@guidelytech/shared/runtime");
1856
+ var import_runtime7 = require("@guidelytech/shared/runtime");
1694
1857
  function renderSurvey(options) {
1695
1858
  const { scope, config } = options;
1696
1859
  const doc = scope.ownerDocument;
@@ -1882,14 +2045,14 @@ function renderSurvey(options) {
1882
2045
  config.questions.forEach((q) => {
1883
2046
  const el2 = questionEls.get(q.id);
1884
2047
  if (!el2) return;
1885
- const visible = (0, import_runtime6.isQuestionVisible)(q, answers);
2048
+ const visible = (0, import_runtime7.isQuestionVisible)(q, answers);
1886
2049
  el2.hidden = !visible;
1887
2050
  if (!visible && answers[q.id] !== void 0) delete answers[q.id];
1888
2051
  });
1889
2052
  }
1890
2053
  form.addEventListener("submit", (e) => {
1891
2054
  e.preventDefault();
1892
- const missing = config.questions.filter((q) => q.required && (0, import_runtime6.isQuestionVisible)(q, answers) && answers[q.id] === void 0);
2055
+ const missing = config.questions.filter((q) => q.required && (0, import_runtime7.isQuestionVisible)(q, answers) && answers[q.id] === void 0);
1893
2056
  if (missing.length) {
1894
2057
  errorEl.textContent = `Please answer: ${missing.map((q) => q.title).join(", ")}`;
1895
2058
  questionEls.get(missing[0].id)?.querySelector("button,textarea")?.focus();
@@ -1938,7 +2101,7 @@ function renderSurvey(options) {
1938
2101
  }
1939
2102
 
1940
2103
  // src/help-view.ts
1941
- var import_runtime7 = require("@guidelytech/shared/runtime");
2104
+ var import_runtime8 = require("@guidelytech/shared/runtime");
1942
2105
  function el(doc, tag, cls, text) {
1943
2106
  const e = doc.createElement(tag);
1944
2107
  if (cls) e.className = cls;
@@ -2010,8 +2173,10 @@ function createHelpView(options) {
2010
2173
  const title = el(doc, "h3", "g-title", article.title);
2011
2174
  const content = el(doc, "div", "g-body");
2012
2175
  if (loading) content.appendChild(el(doc, "div", "g-rc-empty", "Loading\u2026"));
2013
- else if (article.body) setSanitizedHtml(content, article.body);
2014
- else content.appendChild(el(doc, "div", "g-rc-empty", "This article couldn't be loaded."));
2176
+ else if (article.body) {
2177
+ setSanitizedHtml(content, article.body);
2178
+ enhancePdfViewers(content, doc);
2179
+ } else content.appendChild(el(doc, "div", "g-rc-empty", "This article couldn't be loaded."));
2015
2180
  wrap.append(back, title, content);
2016
2181
  body.appendChild(wrap);
2017
2182
  back.focus?.({ preventScroll: true });
@@ -2031,19 +2196,29 @@ function createHelpView(options) {
2031
2196
  if (active) return renderArticle(active);
2032
2197
  const data = options.data();
2033
2198
  const member = options.audienceIds?.() ?? [];
2034
- const sections = (0, import_runtime7.resourceCenterSections)(data.collections, data.articles, member);
2199
+ const sections = (0, import_runtime8.resourceCenterSections)(data.collections, data.articles, member);
2035
2200
  const q = query.trim().toLowerCase();
2201
+ body.append(...options.extra?.(q) ?? []);
2036
2202
  if (q) {
2037
2203
  const visible = sections.flatMap((s) => s.articles);
2038
2204
  const local = visible.filter((a) => a.title.toLowerCase().includes(q) || a.summary.toLowerCase().includes(q));
2039
2205
  const seen = new Set(local.map((a) => a.id));
2040
2206
  const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
2041
- const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && (0, import_runtime7.isArticleListed)(data.collections, a, member))];
2207
+ const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && (0, import_runtime8.isArticleListed)(data.collections, a, member))];
2042
2208
  section(`Results (${merged.length})`);
2043
2209
  if (!merged.length) body.appendChild(el(doc, "div", "g-rc-empty", "No articles match your search."));
2044
2210
  merged.forEach(articleButton);
2045
2211
  return;
2046
2212
  }
2213
+ if (data.suggested?.items.length) {
2214
+ section(data.suggested.title);
2215
+ const glyph = { guide: "\u25B6", howto: "\u2630", article: "\u{1F4C4}" };
2216
+ data.suggested.items.forEach((item) => {
2217
+ const b = listItem(doc, glyph[item.kind], item.title, item.summary);
2218
+ b.addEventListener("click", item.run);
2219
+ body.appendChild(b);
2220
+ });
2221
+ }
2047
2222
  if (data.recommended?.length) {
2048
2223
  section("Recommended");
2049
2224
  data.recommended.forEach((g) => {
@@ -2419,6 +2594,7 @@ function highlightSelectors(selectors, options = {}) {
2419
2594
  createScope,
2420
2595
  describeElement,
2421
2596
  destroyHost,
2597
+ enhancePdfViewers,
2422
2598
  ensureHost,
2423
2599
  generateSelectors,
2424
2600
  highlightSelectors,