@guidelytech/guide-runtime 0.9.0 → 0.11.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,
@@ -37,9 +44,10 @@ __export(src_exports, {
37
44
  generateSelectors: () => generateSelectors,
38
45
  highlightSelectors: () => highlightSelectors,
39
46
  isAnchoredStep: () => isAnchoredStep,
40
- isChecklistComplete: () => import_runtime6.isChecklistComplete,
47
+ isChecklistComplete: () => import_runtime7.isChecklistComplete,
41
48
  isGuidelyElement: () => isGuidelyElement,
42
- isQuestionVisible: () => import_runtime7.isQuestionVisible,
49
+ isQuestionVisible: () => import_runtime8.isQuestionVisible,
50
+ isStableClass: () => isStableClass,
43
51
  isStableId: () => isStableId,
44
52
  isVisible: () => isVisible,
45
53
  normalizeText: () => normalizeText,
@@ -63,10 +71,12 @@ __export(src_exports, {
63
71
  sanitizeHtml: () => sanitizeHtml,
64
72
  sanitizeToFragment: () => sanitizeToFragment,
65
73
  setSanitizedHtml: () => setSanitizedHtml,
74
+ shadowHostChain: () => shadowHostChain,
66
75
  startAuthoring: () => startAuthoring,
67
76
  textSnapshotOf: () => textSnapshotOf,
68
77
  trackRect: () => trackRect,
69
78
  urlMatches: () => urlMatches,
79
+ visualRect: () => visualRect,
70
80
  waitForTarget: () => waitForTarget
71
81
  });
72
82
  module.exports = __toCommonJS(src_exports);
@@ -129,6 +139,82 @@ function safeQueryAll(root, selector) {
129
139
  return [];
130
140
  }
131
141
  }
142
+ var SHADOW_SEPARATOR = " >>> ";
143
+ var SHADOW_SPLIT = /\s*>>>\s*/;
144
+ function isShadowRoot(node) {
145
+ return !!node && node.nodeType === 11 && !!node.host;
146
+ }
147
+ function shadowRootOf(el2) {
148
+ const root = el2.getRootNode?.();
149
+ return isShadowRoot(root) ? root : null;
150
+ }
151
+ function composedParent(el2) {
152
+ return el2.parentElement ?? shadowRootOf(el2)?.host ?? null;
153
+ }
154
+ function containsComposed(ancestor, node) {
155
+ let cur = node ? node.nodeType === 1 ? node : node.parentElement : null;
156
+ for (; cur; cur = composedParent(cur)) if (cur === ancestor) return true;
157
+ return false;
158
+ }
159
+ function composedTarget(e) {
160
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
161
+ const first = path[0] ?? e.target;
162
+ if (!first) return null;
163
+ return first.nodeType === 1 ? first : first.parentElement;
164
+ }
165
+ var shadowRootCache = null;
166
+ function shadowRootsIn(root) {
167
+ const cached = shadowRootCache?.get(root);
168
+ if (cached) return cached;
169
+ const out = [];
170
+ const visit = (scope) => {
171
+ const own = scope.nodeType === 1 ? scope.shadowRoot : null;
172
+ const hosts = own ? [scope] : [];
173
+ for (const el2 of safeQueryAll(scope, "*")) if (el2.shadowRoot) hosts.push(el2);
174
+ for (const host of hosts) {
175
+ if (host.id === HOST_ID || !host.shadowRoot || out.includes(host.shadowRoot)) continue;
176
+ out.push(host.shadowRoot);
177
+ visit(host.shadowRoot);
178
+ }
179
+ };
180
+ visit(root);
181
+ shadowRootCache?.set(root, out);
182
+ return out;
183
+ }
184
+ function queryAllDeep(root, selector) {
185
+ const out = safeQueryAll(root, selector);
186
+ for (const sr of shadowRootsIn(root)) out.push(...safeQueryAll(sr, selector));
187
+ return out;
188
+ }
189
+ function queryCss(root, selector, deep) {
190
+ if (!selector.includes(">>>")) return deep ? queryAllDeep(root, selector) : safeQueryAll(root, selector);
191
+ const steps = selector.split(SHADOW_SPLIT).filter(Boolean);
192
+ let scopes = [root];
193
+ let matches = [];
194
+ steps.forEach((step, i) => {
195
+ matches = Array.from(new Set(scopes.flatMap((scope) => queryAllDeep(scope, step))));
196
+ if (i < steps.length - 1) scopes = matches.map((el2) => el2.shadowRoot).filter((sr) => !!sr);
197
+ });
198
+ return matches;
199
+ }
200
+ function closestComposed(el2, selector) {
201
+ if (selector.includes(">>>")) {
202
+ const hits = new Set(queryCss(docOf(el2), selector, true));
203
+ for (let cur = el2; cur; cur = composedParent(cur)) if (hits.has(cur)) return cur;
204
+ return null;
205
+ }
206
+ for (let cur = el2; cur; cur = composedParent(cur)) if (cur.matches(selector)) return cur;
207
+ return null;
208
+ }
209
+ function deepElementFromPoint(doc, x, y) {
210
+ let el2 = doc.elementFromPoint(x, y);
211
+ while (el2?.shadowRoot && el2.id !== HOST_ID && typeof el2.shadowRoot.elementFromPoint === "function") {
212
+ const inner = el2.shadowRoot.elementFromPoint(x, y);
213
+ if (!inner || inner === el2) break;
214
+ el2 = inner;
215
+ }
216
+ return el2;
217
+ }
132
218
  function escapeCss(value) {
133
219
  const g = globalThis;
134
220
  if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
@@ -150,32 +236,32 @@ function queryXPath(root, expr) {
150
236
  return [];
151
237
  }
152
238
  }
153
- function queryText(root, text) {
239
+ function queryText(root, text, deep) {
154
240
  const needle = normalizeText(text).toLowerCase();
155
241
  if (!needle) return [];
156
- const candidates = safeQueryAll(root, TEXT_CANDIDATES);
242
+ const candidates = queryCss(root, TEXT_CANDIDATES, deep);
157
243
  const exact = candidates.filter((c) => normalizeText(c.textContent).toLowerCase() === needle);
158
244
  if (exact.length) return exact;
159
245
  return candidates.filter((c) => normalizeText(c.textContent).toLowerCase().includes(needle));
160
246
  }
161
- function querySelectorStrategy(strategy, root) {
247
+ function querySelectorStrategy(strategy, root, deep = true) {
162
248
  const value = strategy.value.trim();
163
249
  if (!value) return [];
164
250
  switch (strategy.kind) {
165
251
  case "id": {
166
252
  const id = value.startsWith("#") ? value.slice(1) : value;
167
- return safeQueryAll(root, `#${escapeCss(id)}`);
253
+ return queryCss(root, `#${escapeCss(id)}`, deep);
168
254
  }
169
255
  case "guidely-id": {
170
256
  const id = value.startsWith("#") ? value.slice(1) : value;
171
- return safeQueryAll(root, attrSelector("data-guidely-id", id));
257
+ return queryCss(root, attrSelector("data-guidely-id", id), deep);
172
258
  }
173
259
  case "data-attr":
174
260
  case "aria":
175
261
  case "css":
176
- return safeQueryAll(root, value.startsWith("[") || /^[a-z*#.[]/i.test(value) ? value : `[${value}]`);
262
+ return queryCss(root, value.startsWith("[") || /^[a-z*#.[]/i.test(value) ? value : `[${value}]`, deep);
177
263
  case "text":
178
- return queryText(root, value);
264
+ return queryText(root, value, deep);
179
265
  case "xpath":
180
266
  return queryXPath(root, value);
181
267
  default:
@@ -184,11 +270,19 @@ function querySelectorStrategy(strategy, root) {
184
270
  }
185
271
  function resolveTarget(target, root = document) {
186
272
  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];
273
+ const visibleMatches = (strategy, deep) => querySelectorStrategy(strategy, root, deep).filter((el2) => !isGuidelyElement(el2) && isVisible(el2));
274
+ const outer = shadowRootCache;
275
+ shadowRootCache ?? (shadowRootCache = /* @__PURE__ */ new Map());
276
+ try {
277
+ for (const strategy of selectors) {
278
+ let matches = visibleMatches(strategy, false);
279
+ if (!matches.length && strategy.kind !== "xpath") matches = visibleMatches(strategy, true);
280
+ if (matches.length === 1) return matches[0];
281
+ }
282
+ return null;
283
+ } finally {
284
+ shadowRootCache = outer;
190
285
  }
191
- return null;
192
286
  }
193
287
  var TargetTimeoutError = class extends Error {
194
288
  constructor(message = "Target element not found before timeout") {
@@ -252,14 +346,15 @@ function isStableId(id) {
252
346
  function isStableClass(cls) {
253
347
  return !!cls && !GENERATED_CLASS.test(cls) && cls.length <= 40;
254
348
  }
255
- function uniqueIn(doc, selector, el2) {
256
- const all = safeQueryAll(doc, selector);
349
+ function uniqueWithin(root, selector, el2) {
350
+ const all = safeQueryAll(root, selector);
257
351
  return all.length === 1 && all[0] === el2;
258
352
  }
259
353
  function attrSelector(name, value) {
260
354
  return `[${name}='${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
261
355
  }
262
- function cssPath(el2, doc) {
356
+ function cssPath(el2, root) {
357
+ const doc = el2.ownerDocument;
263
358
  const segments = [];
264
359
  let cur = el2;
265
360
  while (cur && cur.nodeType === 1 && cur !== doc.body && cur !== doc.documentElement) {
@@ -279,20 +374,48 @@ function cssPath(el2, doc) {
279
374
  }
280
375
  segments.unshift(seg);
281
376
  const candidate = segments.join(" > ");
282
- if (uniqueIn(doc, candidate, el2)) return candidate;
377
+ if (uniqueWithin(root, candidate, el2)) return candidate;
283
378
  cur = parent;
284
379
  }
285
380
  const full = segments.join(" > ");
286
- return uniqueIn(doc, full, el2) ? full : null;
381
+ return uniqueWithin(root, full, el2) ? full : null;
382
+ }
383
+ function shadowHostChain(el2) {
384
+ const steps = [];
385
+ for (let root = shadowRootOf(el2); root; root = shadowRootOf(root.host)) {
386
+ const host = root.host;
387
+ const step = cssPath(host, shadowRootOf(host) ?? host.ownerDocument);
388
+ if (!step) return null;
389
+ steps.unshift(step);
390
+ }
391
+ return steps.join(SHADOW_SEPARATOR);
392
+ }
393
+ function selectorFor(el2, selector) {
394
+ const root = shadowRootOf(el2);
395
+ if (!root) return uniqueWithin(el2.ownerDocument, selector, el2) ? selector : null;
396
+ if (!uniqueWithin(root, selector, el2)) return null;
397
+ const doc = el2.ownerDocument;
398
+ const only = (sel) => {
399
+ const all = queryCss(doc, sel, true);
400
+ return all.length === 1 && all[0] === el2;
401
+ };
402
+ if (only(selector)) return selector;
403
+ const chain = shadowHostChain(el2);
404
+ const scoped = chain ? chain + SHADOW_SEPARATOR + selector : null;
405
+ return scoped && only(scoped) ? scoped : null;
406
+ }
407
+ function shadowText(el2) {
408
+ if (!el2.shadowRoot) return "";
409
+ const parts = Array.from(el2.shadowRoot.children).filter((c) => !/^(STYLE|SCRIPT|TEMPLATE)$/.test(c.tagName));
410
+ return normalizeText(parts.map((c) => c.innerText ?? c.textContent).join(" "));
287
411
  }
288
412
  function textSnapshotOf(el2) {
289
413
  const he = el2;
290
- const own = normalizeText(he.innerText ?? el2.textContent);
414
+ const own = normalizeText(he.innerText ?? el2.textContent) || shadowText(el2);
291
415
  const alt = normalizeText(el2.getAttribute("aria-label") || el2.getAttribute("title") || el2.getAttribute("placeholder") || el2.getAttribute("alt") || el2.value);
292
416
  return (own || alt).slice(0, 200);
293
417
  }
294
418
  function generateSelectors(el2) {
295
- const doc = el2.ownerDocument;
296
419
  const out = [];
297
420
  const seen = /* @__PURE__ */ new Set();
298
421
  const push = (s) => {
@@ -303,9 +426,15 @@ function generateSelectors(el2) {
303
426
  }
304
427
  };
305
428
  const tag = el2.tagName.toLowerCase();
429
+ const pushFor = (kind, selector, value, label) => {
430
+ const resolved = selectorFor(el2, selector);
431
+ if (resolved === selector) push({ kind, value, label });
432
+ else if (resolved) push({ kind: "css", value: resolved, label });
433
+ return !!resolved;
434
+ };
306
435
  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}` });
436
+ if (guidelyId) pushFor("guidely-id", attrSelector("data-guidely-id", guidelyId), guidelyId, `Guidely id ${guidelyId}`);
437
+ if (isStableId(el2.id)) pushFor("id", `#${escapeCss(el2.id)}`, `#${el2.id}`, `id ${el2.id}`);
309
438
  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
439
  dataAttrs.sort((a, b) => {
311
440
  const ia = PREFERRED_DATA_ATTRS.indexOf(a.name);
@@ -314,42 +443,75 @@ function generateSelectors(el2) {
314
443
  });
315
444
  for (const a of dataAttrs) {
316
445
  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}` });
446
+ if (!pushFor("data-attr", sel, sel, `${a.name}=${a.value}`)) pushFor("data-attr", tag + sel, tag + sel, `${a.name}=${a.value}`);
319
447
  }
320
448
  const aria = el2.getAttribute("aria-label");
321
449
  if (aria && aria.length <= 120) {
322
450
  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 });
451
+ if (!pushFor("aria", sel, sel, aria)) pushFor("aria", tag + sel, tag + sel, aria);
325
452
  }
326
453
  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}` });
454
+ if (name) pushFor("css", tag + attrSelector("name", name), tag + attrSelector("name", name), `${tag} name=${name}`);
328
455
  const role = el2.getAttribute("role");
329
456
  if (role) {
330
457
  const title = el2.getAttribute("title") || el2.getAttribute("aria-labelledby");
331
458
  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}` });
459
+ pushFor("css", sel, sel, `role ${role}`);
333
460
  }
334
461
  if (tag === "a") {
335
462
  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}` });
463
+ if (href && href.length <= 200) pushFor("css", `a${attrSelector("href", href)}`, `a${attrSelector("href", href)}`, `link ${href}`);
337
464
  }
338
- const path = cssPath(el2, doc);
339
- if (path) push({ kind: "css", value: path, label: "css path" });
465
+ const path = cssPath(el2, shadowRootOf(el2) ?? el2.ownerDocument);
466
+ if (path) pushFor("css", path, path, "css path");
340
467
  if (/^(button|a|h[1-6]|label|summary)$/.test(tag) || role === "button" || role === "link" || role === "tab" || role === "menuitem") {
341
468
  const text = normalizeText(el2.innerText ?? el2.textContent);
342
469
  if (text && text.length <= 80) push({ kind: "text", value: text, label: `text "${text}"` });
343
470
  }
344
471
  return out.slice(0, 10);
345
472
  }
473
+ function visualRect(el2) {
474
+ const own = el2.getBoundingClientRect();
475
+ if (!el2.shadowRoot) return own;
476
+ const boxes = [own, ...Array.from(el2.shadowRoot.children).map((c) => c.getBoundingClientRect())].filter((r) => r.width > 0 && r.height > 0);
477
+ if (boxes.length < 2) return boxes[0] ?? own;
478
+ const left = Math.min(...boxes.map((r) => r.left));
479
+ const top = Math.min(...boxes.map((r) => r.top));
480
+ const right = Math.max(...boxes.map((r) => r.right));
481
+ const bottom = Math.max(...boxes.map((r) => r.bottom));
482
+ return new DOMRect(left, top, right - left, bottom - top);
483
+ }
484
+ var ANCHOR_MAX_AREA_RATIO = 4;
485
+ 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']";
486
+ function componentAnchor(el2) {
487
+ if (!hasLayout(el2.ownerDocument)) return el2;
488
+ let cur = el2;
489
+ for (let root = shadowRootOf(cur); root; root = shadowRootOf(cur)) {
490
+ const host = root.host;
491
+ if (host.id === HOST_ID) break;
492
+ let control = null;
493
+ try {
494
+ control = cur.closest(CONTROL_SELECTOR);
495
+ } catch {
496
+ }
497
+ const r = visualRect(control ?? cur);
498
+ const area = r.width * r.height;
499
+ const h2 = visualRect(host);
500
+ const hostArea = h2.width * h2.height;
501
+ const inside = r.left >= h2.left - 1 && r.top >= h2.top - 1 && r.right <= h2.right + 1 && r.bottom <= h2.bottom + 1;
502
+ if (!area || !inside || hostArea > area * ANCHOR_MAX_AREA_RATIO) break;
503
+ cur = host;
504
+ }
505
+ return cur;
506
+ }
346
507
  function describeElement(el2) {
347
- const r = el2.getBoundingClientRect();
508
+ const target = componentAnchor(el2);
509
+ const r = visualRect(target);
348
510
  return {
349
- selectors: generateSelectors(el2),
350
- textSnapshot: textSnapshotOf(el2),
511
+ selectors: generateSelectors(target),
512
+ textSnapshot: textSnapshotOf(target) || textSnapshotOf(el2),
351
513
  rect: { x: r.x, y: r.y, width: r.width, height: r.height },
352
- tag: el2.tagName.toLowerCase()
514
+ tag: target.tagName.toLowerCase()
353
515
  };
354
516
  }
355
517
 
@@ -458,6 +620,9 @@ function setSanitizedHtml(el2, html) {
458
620
  }
459
621
 
460
622
  // src/pdf-viewer.ts
623
+ var import_runtime5 = require("@guidelytech/shared/runtime");
624
+
625
+ // src/host.ts
461
626
  var import_runtime4 = require("@guidelytech/shared/runtime");
462
627
 
463
628
  // src/styles.ts
@@ -643,6 +808,21 @@ var STYLES = `
643
808
  .g-auth-outline.g-picked .g-auth-label{background:#16a34a}
644
809
  .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}
645
810
  @media (max-width:480px){.g-card.g-modal,.g-panel{width:calc(100vw - 32px)}.g-inner{padding:16px}}
811
+ /* resource center: unread badge + "What's new" */
812
+ .g-launcher-badge{position:absolute;top:-4px;right:-4px;min-width:20px;height:20px;padding:0 6px;border-radius:999px;background:#dc2626;color:#fff;font-size:11px;font-weight:700;line-height:20px;text-align:center;box-shadow:0 0 0 2px var(--g-bg)}
813
+ .g-rc-news{padding:10px 20px 12px;border-bottom:1px solid var(--g-border)}
814
+ .g-rc-news:last-of-type{border-bottom:0}
815
+ .g-rc-news-head{display:flex;align-items:baseline;gap:8px}
816
+ .g-rc-news-title{margin:0;font-size:14px;font-weight:600;line-height:1.35;flex:1}
817
+ .g-rc-news-new{flex:none;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--g-primary-text);background:var(--g-primary);border-radius:999px;padding:2px 7px}
818
+ .g-rc-news-date{display:block;font-size:12px;color:var(--g-muted);margin:2px 0 6px}
819
+ .g-rc-news-body{font-size:13px}
820
+ .g-rc-news-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
821
+ .g-rc-news-actions .g-btn{min-height:32px;padding:6px 12px;font-size:13px}
822
+ /* accessibility */
823
+ .g-sr-only{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
824
+ .g-hotspot:focus-visible,.g-custom:focus-visible,.g-card:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
825
+ @media (forced-colors:active){.g-card,.g-panel,.g-banner{border:1px solid CanvasText}.g-btn{border:1px solid ButtonText}}
646
826
  `;
647
827
 
648
828
  // src/host.ts
@@ -737,6 +917,7 @@ function resolveTheme(theme, appearance) {
737
917
  }
738
918
  function applyTheme(el2, vars) {
739
919
  el2.style.setProperty("--g-primary", vars.primary);
920
+ if (/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(vars.primary)) el2.style.setProperty("--g-primary-text", (0, import_runtime4.readableTextOn)(vars.primary));
740
921
  el2.style.setProperty("--g-bg", vars.bg);
741
922
  el2.style.setProperty("--g-text", vars.text);
742
923
  el2.style.setProperty("--g-radius", `${vars.radius}px`);
@@ -753,6 +934,16 @@ function createScope(theme, appearance, doc = document) {
753
934
  const vars = resolveTheme(theme, appearance);
754
935
  applyTheme(el2, vars);
755
936
  handle.shadow.appendChild(el2);
937
+ const css = [appearance ? theme?.customCss : void 0, appearance?.customCss].filter((c) => !!c && !!c.trim());
938
+ let style = null;
939
+ if (css.length) {
940
+ const id = `c${++cssScopeSeq}`;
941
+ el2.setAttribute("data-g-css", id);
942
+ style = doc.createElement("style");
943
+ style.setAttribute("data-g-custom", id);
944
+ style.textContent = css.map((c) => (0, import_runtime4.scopeCustomCss)(c, `.g-scope[data-g-css="${id}"]`)).join("\n");
945
+ handle.shadow.insertBefore(style, el2);
946
+ }
756
947
  let mq = null;
757
948
  const onChange = () => applyTheme(el2, resolveTheme(theme, appearance));
758
949
  if ((appearance?.theme ?? "auto") === "auto" && typeof window !== "undefined" && typeof window.matchMedia === "function") {
@@ -768,16 +959,18 @@ function createScope(theme, appearance, doc = document) {
768
959
  vars,
769
960
  destroy() {
770
961
  mq?.removeEventListener?.("change", onChange);
962
+ style?.remove();
771
963
  el2.remove();
772
964
  }
773
965
  };
774
966
  }
967
+ var cssScopeSeq = 0;
775
968
 
776
969
  // src/pdf-viewer.ts
777
970
  function enhancePdfViewers(root, doc = root.ownerDocument) {
778
- for (const block of Array.from(root.querySelectorAll(`div.${import_runtime4.PDF_BLOCK_CLASS}`))) {
779
- const link = block.querySelector(`a.${import_runtime4.FILE_LINK_CLASS}`);
780
- const strip = block.querySelector(`.${import_runtime4.PDF_PAGES_CLASS}`);
971
+ for (const block of Array.from(root.querySelectorAll(`div.${import_runtime5.PDF_BLOCK_CLASS}`))) {
972
+ const link = block.querySelector(`a.${import_runtime5.FILE_LINK_CLASS}`);
973
+ const strip = block.querySelector(`.${import_runtime5.PDF_PAGES_CLASS}`);
781
974
  if (!link || !strip || block.hasAttribute("data-g-ready")) continue;
782
975
  for (const img of Array.from(strip.querySelectorAll("img:not([src])"))) img.remove();
783
976
  const pages = Array.from(strip.querySelectorAll("img"));
@@ -904,7 +1097,8 @@ function openReader(doc, block, link, pages, start) {
904
1097
 
905
1098
  // src/position.ts
906
1099
  var import_dom = require("@floating-ui/dom");
907
- function anchor(reference, floating, options = {}) {
1100
+ function anchor(target, floating, options = {}) {
1101
+ const reference = target.shadowRoot ? { getBoundingClientRect: () => visualRect(target), contextElement: target } : target;
908
1102
  const placement = options.placement ?? "auto";
909
1103
  const gap = options.offset ?? 10;
910
1104
  const middleware = [(0, import_dom.offset)(gap + (options.arrowEl ? 4 : 0))];
@@ -957,7 +1151,7 @@ function trackRect(el2, cb) {
957
1151
  let stopped = false;
958
1152
  const run = () => {
959
1153
  if (stopped) return;
960
- cb(el2.getBoundingClientRect());
1154
+ cb(visualRect(el2));
961
1155
  };
962
1156
  const schedule = () => {
963
1157
  if (stopped) return;
@@ -970,6 +1164,9 @@ function trackRect(el2, cb) {
970
1164
  const win = el2.ownerDocument.defaultView;
971
1165
  win?.addEventListener("scroll", schedule, true);
972
1166
  win?.addEventListener("resize", schedule);
1167
+ const shadowRoots = [];
1168
+ for (let root = el2.getRootNode(); root.nodeType === 11 && root.host; root = root.host.getRootNode()) shadowRoots.push(root);
1169
+ for (const root of shadowRoots) root.addEventListener("scroll", schedule, true);
973
1170
  let ro = null;
974
1171
  if (typeof ResizeObserver !== "undefined") {
975
1172
  try {
@@ -985,6 +1182,7 @@ function trackRect(el2, cb) {
985
1182
  stopped = true;
986
1183
  win?.removeEventListener("scroll", schedule, true);
987
1184
  win?.removeEventListener("resize", schedule);
1185
+ for (const root of shadowRoots) root.removeEventListener("scroll", schedule, true);
988
1186
  ro?.disconnect();
989
1187
  clearInterval(interval);
990
1188
  if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(raf);
@@ -1025,7 +1223,7 @@ function buildCard(doc, opts, extraClass) {
1025
1223
  if (step.imageUrl) {
1026
1224
  const img = h(doc, "img", "g-image");
1027
1225
  img.src = step.imageUrl;
1028
- img.alt = "";
1226
+ img.alt = step.imageAlt ?? "";
1029
1227
  card.appendChild(img);
1030
1228
  }
1031
1229
  const inner = h(doc, "div", "g-inner");
@@ -1053,18 +1251,29 @@ function buildCard(doc, opts, extraClass) {
1053
1251
  const buttons = buildButtons(doc, step, opts.canGoBack ?? opts.index > 0, opts.onButton);
1054
1252
  if (buttons.length || step.showProgress && opts.total > 1) {
1055
1253
  const footer = h(doc, "div", "g-footer");
1056
- if (step.showProgress && opts.total > 1) footer.appendChild(h(doc, "span", "g-progress", `${opts.index + 1} of ${opts.total}`));
1254
+ if (step.showProgress && opts.total > 1) {
1255
+ const progress = h(doc, "span", "g-progress", `${opts.index + 1} of ${opts.total}`);
1256
+ progress.setAttribute("aria-label", `Step ${opts.index + 1} of ${opts.total}`);
1257
+ footer.appendChild(progress);
1258
+ }
1057
1259
  buttons.forEach((b) => footer.appendChild(b));
1058
1260
  inner.appendChild(footer);
1059
1261
  }
1060
1262
  card.appendChild(inner);
1061
1263
  if (step.title) card.setAttribute("aria-labelledby", titleId);
1264
+ else card.setAttribute("aria-label", opts.total > 1 ? `Step ${opts.index + 1} of ${opts.total}` : "Guide");
1062
1265
  if (step.body) card.setAttribute("aria-describedby", bodyId);
1063
1266
  return { card, inner, titleId, bodyId };
1064
1267
  }
1065
1268
  function focusables(root) {
1066
1269
  return Array.from(root.querySelectorAll(FOCUSABLE));
1067
1270
  }
1271
+ function isTextEntry(el2) {
1272
+ const t = el2;
1273
+ if (!t || !t.tagName) return false;
1274
+ const tag = t.tagName.toLowerCase();
1275
+ return tag === "input" || tag === "textarea" || tag === "select" || t.isContentEditable === true;
1276
+ }
1068
1277
  function installKeyboard(card, step, trap, onClose) {
1069
1278
  const onKey = (e) => {
1070
1279
  if (e.key === "Escape" && step.showClose) {
@@ -1072,6 +1281,16 @@ function installKeyboard(card, step, trap, onClose) {
1072
1281
  onClose();
1073
1282
  return;
1074
1283
  }
1284
+ if ((e.key === "ArrowRight" || e.key === "ArrowLeft") && !e.altKey && !e.ctrlKey && !e.metaKey && !isTextEntry(e.target)) {
1285
+ const actions = e.key === "ArrowRight" ? ["next", "complete"] : ["back"];
1286
+ const btn = actions.map((a) => card.querySelector(`button[data-action="${a}"]`)).find(Boolean);
1287
+ if (btn) {
1288
+ e.preventDefault();
1289
+ e.stopPropagation();
1290
+ btn.click();
1291
+ }
1292
+ return;
1293
+ }
1075
1294
  if (trap && e.key === "Tab") {
1076
1295
  const items = focusables(card);
1077
1296
  if (!items.length) {
@@ -1096,7 +1315,8 @@ function installKeyboard(card, step, trap, onClose) {
1096
1315
  }
1097
1316
  function focusInto(card) {
1098
1317
  const doc = card.ownerDocument;
1099
- const previous = doc.activeElement;
1318
+ const active = doc.activeElement;
1319
+ const previous = active && active.id !== "guidely-root" ? active : null;
1100
1320
  const primary = card.querySelector(".g-btn-primary") || focusables(card)[0] || card;
1101
1321
  try {
1102
1322
  primary.focus({ preventScroll: true });
@@ -1244,23 +1464,32 @@ function renderHotspot(doc, opts) {
1244
1464
  let card = null;
1245
1465
  let stopAnchor = null;
1246
1466
  let stopKeys = null;
1247
- const close = () => {
1467
+ const close = (returnFocus = false) => {
1468
+ const hadFocus = !!card && card.getRootNode().activeElement !== null && card.contains(card.getRootNode().activeElement);
1248
1469
  stopAnchor?.();
1249
1470
  stopKeys?.();
1250
1471
  card?.remove();
1251
1472
  card = null;
1252
1473
  dot.setAttribute("aria-expanded", "false");
1474
+ if (returnFocus || hadFocus) {
1475
+ try {
1476
+ dot.focus({ preventScroll: true });
1477
+ } catch {
1478
+ }
1479
+ }
1253
1480
  };
1254
1481
  const open = () => {
1255
1482
  if (card) return;
1256
1483
  const built = buildCard(doc, { ...opts, onClose: () => opts.onClose() }, "g-hotspot-card");
1257
1484
  card = built.card;
1258
1485
  card.setAttribute("role", "dialog");
1486
+ card.id = nextId("hotspot");
1487
+ dot.setAttribute("aria-controls", card.id);
1259
1488
  const arrowEl = h(doc, "div", "g-arrow");
1260
1489
  card.appendChild(arrowEl);
1261
1490
  wrapper.appendChild(card);
1262
1491
  stopAnchor = anchor(target, card, { placement: opts.step.placement, offset: opts.step.offset, arrowEl });
1263
- stopKeys = installKeyboard(card, opts.step, false, close);
1492
+ stopKeys = installKeyboard(card, opts.step, false, () => close(true));
1264
1493
  dot.setAttribute("aria-expanded", "true");
1265
1494
  opts.onOpen?.();
1266
1495
  if (cfg.openOn === "click") focusInto(card);
@@ -1282,7 +1511,8 @@ function renderBanner(doc, opts) {
1282
1511
  const cleanups = [];
1283
1512
  const pos = step.position === "top" || step.placement === "top" || step.position.startsWith("top") ? "top" : "bottom";
1284
1513
  const banner = h(doc, "div", `g-banner g-pos-${pos}`);
1285
- banner.setAttribute("role", "status");
1514
+ banner.setAttribute("role", "region");
1515
+ banner.setAttribute("aria-label", step.title || "Announcement");
1286
1516
  banner.setAttribute("data-step-id", step.id);
1287
1517
  const body = h(doc, "div", "g-body");
1288
1518
  if (step.title) body.appendChild(h(doc, "strong", "", `${step.title} `));
@@ -1315,6 +1545,27 @@ function renderSlideout(doc, opts) {
1315
1545
  cleanups.push(focusInto(card));
1316
1546
  return { el: wrapper, card, destroy: () => cleanupAll(cleanups, wrapper) };
1317
1547
  }
1548
+ function runCodeBlock(code, container, opts) {
1549
+ const act = (action) => () => opts.onButton({ id: `code-${action}`, label: action, action, variant: "primary" });
1550
+ const api = Object.freeze({ next: act("next"), back: act("back"), complete: act("complete"), skip: act("skip"), dismiss: () => opts.onClose(), stepId: opts.step.id });
1551
+ try {
1552
+ const fn = new Function("guidely", "container", `"use strict";
1553
+ ${code}`);
1554
+ const result = fn(api, container);
1555
+ return typeof result === "function" ? () => {
1556
+ try {
1557
+ result();
1558
+ } catch (err) {
1559
+ opts.onError?.(err);
1560
+ }
1561
+ } : () => {
1562
+ };
1563
+ } catch (err) {
1564
+ opts.onError?.(err);
1565
+ return () => {
1566
+ };
1567
+ }
1568
+ }
1318
1569
  function renderCustom(doc, opts) {
1319
1570
  const cleanups = [];
1320
1571
  const wrapper = h(doc, "div", "g-step");
@@ -1323,6 +1574,9 @@ function renderCustom(doc, opts) {
1323
1574
  el2.tabIndex = -1;
1324
1575
  el2.setAttribute("data-step-id", opts.step.id);
1325
1576
  setSanitizedHtml(el2, opts.step.customHtml || opts.step.body || "");
1577
+ el2.setAttribute("role", opts.step.showOverlay ? "dialog" : "group");
1578
+ el2.setAttribute("aria-label", opts.step.title || "Guide");
1579
+ if (opts.step.showOverlay) el2.setAttribute("aria-modal", "true");
1326
1580
  const onClick = (e) => {
1327
1581
  const t = e.target?.closest?.("[data-g-action]");
1328
1582
  const action = t?.getAttribute("data-g-action");
@@ -1336,6 +1590,7 @@ function renderCustom(doc, opts) {
1336
1590
  opts.scope.appendChild(wrapper);
1337
1591
  cleanups.push(installKeyboard(el2, opts.step, false, opts.onClose));
1338
1592
  cleanups.push(focusInto(el2));
1593
+ if (opts.runCode && opts.step.code) cleanups.push(runCodeBlock(opts.step.code, el2, opts));
1339
1594
  return { el: wrapper, card: el2, destroy: () => cleanupAll(cleanups, wrapper) };
1340
1595
  }
1341
1596
  function renderStep(opts) {
@@ -1368,7 +1623,7 @@ function isAnchoredStep(step) {
1368
1623
  }
1369
1624
 
1370
1625
  // src/controller.ts
1371
- var import_runtime5 = require("@guidelytech/shared/runtime");
1626
+ var import_runtime6 = require("@guidelytech/shared/runtime");
1372
1627
  function noop() {
1373
1628
  }
1374
1629
  var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
@@ -1433,6 +1688,17 @@ function runGuide(guide, options = {}) {
1433
1688
  view = null;
1434
1689
  stepDone = false;
1435
1690
  };
1691
+ let returnFocusTo = null;
1692
+ const restoreFocus = () => {
1693
+ const el2 = returnFocusTo;
1694
+ returnFocusTo = null;
1695
+ try {
1696
+ const now = doc.activeElement;
1697
+ const lost = !now || now === doc.body || now.id === "guidely-root";
1698
+ if (el2 && lost && el2.isConnected && typeof el2.focus === "function") el2.focus({ preventScroll: true });
1699
+ } catch {
1700
+ }
1701
+ };
1436
1702
  const end = (reason) => {
1437
1703
  if (ended) return;
1438
1704
  ended = true;
@@ -1444,6 +1710,7 @@ function runGuide(guide, options = {}) {
1444
1710
  else if (reason === "skipped") emit("guide_skipped", step, index);
1445
1711
  scope?.destroy();
1446
1712
  scope = null;
1713
+ restoreFocus();
1447
1714
  try {
1448
1715
  options.onEnd?.(reason);
1449
1716
  } catch (err) {
@@ -1461,7 +1728,7 @@ function runGuide(guide, options = {}) {
1461
1728
  const ctx = getCtx();
1462
1729
  if (step && ctx) {
1463
1730
  for (const branch of step.branches) {
1464
- if ((0, import_runtime5.evaluateRules)(branch.when, ctx)) {
1731
+ if ((0, import_runtime6.evaluateRules)(branch.when, ctx)) {
1465
1732
  target = steps.findIndex((s) => s.id === branch.goTo);
1466
1733
  if (target >= 0) break;
1467
1734
  }
@@ -1532,9 +1799,9 @@ function runGuide(guide, options = {}) {
1532
1799
  if (mode === "element_click" && (step.advanceValue || target)) {
1533
1800
  const selector = step.advanceValue;
1534
1801
  const handler = (e) => {
1535
- const t = e.target;
1802
+ const t = composedTarget(e);
1536
1803
  try {
1537
- if (selector ? t && typeof t.closest === "function" && t.closest(selector) : t && target?.contains(t)) advance();
1804
+ if (selector ? t && closestComposed(t, selector) : t && target && containsComposed(target, t)) advance();
1538
1805
  } catch {
1539
1806
  }
1540
1807
  };
@@ -1582,7 +1849,9 @@ function runGuide(guide, options = {}) {
1582
1849
  asModal,
1583
1850
  onButton: handleButton,
1584
1851
  onClose: () => end("dismissed"),
1585
- announce
1852
+ announce,
1853
+ runCode: options.runCode,
1854
+ onError
1586
1855
  });
1587
1856
  } catch (err) {
1588
1857
  onError(err);
@@ -1652,7 +1921,7 @@ function runGuide(guide, options = {}) {
1652
1921
  index = i;
1653
1922
  const step = steps[i];
1654
1923
  const ctx = getCtx();
1655
- if (step.targeting && step.targeting.rules.length && ctx && !(0, import_runtime5.evaluateRules)(step.targeting, ctx)) {
1924
+ if (step.targeting && step.targeting.rules.length && ctx && !(0, import_runtime6.evaluateRules)(step.targeting, ctx)) {
1656
1925
  const nextIndex = i + direction;
1657
1926
  if (nextIndex >= total) return end("completed");
1658
1927
  if (nextIndex < 0) return;
@@ -1694,6 +1963,8 @@ function runGuide(guide, options = {}) {
1694
1963
  guide,
1695
1964
  start() {
1696
1965
  if (status !== "idle") return;
1966
+ const focused = doc.activeElement;
1967
+ if (focused && focused !== doc.body && focused.id !== "guidely-root") returnFocusTo = focused;
1697
1968
  emit("guide_viewed");
1698
1969
  if (total === 0) return end("completed");
1699
1970
  let startIndex = options.startAtStep ? steps.findIndex((s) => s.id === options.startAtStep) : 0;
@@ -1755,7 +2026,7 @@ function runGuide(guide, options = {}) {
1755
2026
  }
1756
2027
 
1757
2028
  // src/checklist.ts
1758
- var import_runtime6 = require("@guidelytech/shared/runtime");
2029
+ var import_runtime7 = require("@guidelytech/shared/runtime");
1759
2030
  function renderChecklist(options) {
1760
2031
  const { scope, config } = options;
1761
2032
  const doc = scope.ownerDocument;
@@ -1830,7 +2101,7 @@ function renderChecklist(options) {
1830
2101
  bar.setAttribute("aria-valuemax", "100");
1831
2102
  body.textContent = "";
1832
2103
  foot.textContent = "";
1833
- const complete = (0, import_runtime6.isChecklistComplete)(config, progress);
2104
+ const complete = (0, import_runtime7.isChecklistComplete)(config, progress);
1834
2105
  if (complete) {
1835
2106
  const wrap = doc.createElement("div");
1836
2107
  wrap.className = "g-complete";
@@ -1918,7 +2189,7 @@ function renderChecklist(options) {
1918
2189
  }
1919
2190
 
1920
2191
  // src/survey.ts
1921
- var import_runtime7 = require("@guidelytech/shared/runtime");
2192
+ var import_runtime8 = require("@guidelytech/shared/runtime");
1922
2193
  function renderSurvey(options) {
1923
2194
  const { scope, config } = options;
1924
2195
  const doc = scope.ownerDocument;
@@ -2110,14 +2381,14 @@ function renderSurvey(options) {
2110
2381
  config.questions.forEach((q) => {
2111
2382
  const el2 = questionEls.get(q.id);
2112
2383
  if (!el2) return;
2113
- const visible = (0, import_runtime7.isQuestionVisible)(q, answers);
2384
+ const visible = (0, import_runtime8.isQuestionVisible)(q, answers);
2114
2385
  el2.hidden = !visible;
2115
2386
  if (!visible && answers[q.id] !== void 0) delete answers[q.id];
2116
2387
  });
2117
2388
  }
2118
2389
  form.addEventListener("submit", (e) => {
2119
2390
  e.preventDefault();
2120
- const missing = config.questions.filter((q) => q.required && (0, import_runtime7.isQuestionVisible)(q, answers) && answers[q.id] === void 0);
2391
+ const missing = config.questions.filter((q) => q.required && (0, import_runtime8.isQuestionVisible)(q, answers) && answers[q.id] === void 0);
2121
2392
  if (missing.length) {
2122
2393
  errorEl.textContent = `Please answer: ${missing.map((q) => q.title).join(", ")}`;
2123
2394
  questionEls.get(missing[0].id)?.querySelector("button,textarea")?.focus();
@@ -2165,8 +2436,11 @@ function renderSurvey(options) {
2165
2436
  };
2166
2437
  }
2167
2438
 
2439
+ // src/resource-center.ts
2440
+ var import_runtime10 = require("@guidelytech/shared/runtime");
2441
+
2168
2442
  // src/help-view.ts
2169
- var import_runtime8 = require("@guidelytech/shared/runtime");
2443
+ var import_runtime9 = require("@guidelytech/shared/runtime");
2170
2444
  var SHOW_ME_KINDS = /* @__PURE__ */ new Set(["flow", "modal", "tooltip", "announcement"]);
2171
2445
  function startableGuide(guides, keyOrId) {
2172
2446
  if (!keyOrId || !guides) return null;
@@ -2185,8 +2459,8 @@ function resultRow(doc, icon, title, summary, onOpen, showMe) {
2185
2459
  return row;
2186
2460
  }
2187
2461
  function enhanceShowMe(root, doc, canStart, start) {
2188
- for (const a of Array.from(root.querySelectorAll(`a[href^="${import_runtime8.SHOW_ME_HREF_PREFIX}"]`))) {
2189
- const key = (0, import_runtime8.showMeKeyFromHref)(a.getAttribute("href"));
2462
+ for (const a of Array.from(root.querySelectorAll(`a[href^="${import_runtime9.SHOW_ME_HREF_PREFIX}"]`))) {
2463
+ const key = (0, import_runtime9.showMeKeyFromHref)(a.getAttribute("href"));
2190
2464
  const holder = a.closest("p.gd-show-me") ?? a;
2191
2465
  if (!key || !canStart(key)) {
2192
2466
  if (holder !== a) holder.remove();
@@ -2301,10 +2575,11 @@ function createHelpView(options) {
2301
2575
  if (active) return renderArticle(active);
2302
2576
  const data = options.data();
2303
2577
  const member = options.audienceIds?.() ?? [];
2304
- const sections = (0, import_runtime8.resourceCenterSections)(data.collections, data.articles, member);
2578
+ searchWrap.hidden = !!data.modules && !data.modules.includes("search");
2579
+ const sections = (0, import_runtime9.resourceCenterSections)(data.collections, data.articles, member);
2305
2580
  const q = query.trim().toLowerCase();
2306
2581
  if (q) {
2307
- const walkthroughs = (data.recommended ?? []).filter((g) => (0, import_runtime8.matchesHelpQuery)(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));
2582
+ const walkthroughs = (data.recommended ?? []).filter((g) => (0, import_runtime9.matchesHelpQuery)(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));
2308
2583
  if (walkthroughs.length) {
2309
2584
  section(`Walkthroughs (${walkthroughs.length})`);
2310
2585
  for (const g of walkthroughs) body.appendChild(resultRow(doc, "\u25B6", g.name, "Show me, step by step", () => options.onStartGuide?.(g.id)));
@@ -2313,10 +2588,10 @@ function createHelpView(options) {
2313
2588
  body.append(...options.extra?.(q) ?? []);
2314
2589
  if (q) {
2315
2590
  const visible = sections.flatMap((s) => s.articles);
2316
- const local = visible.filter((a) => (0, import_runtime8.matchesHelpQuery)(`${a.title} ${a.summary}`, q));
2591
+ const local = visible.filter((a) => (0, import_runtime9.matchesHelpQuery)(`${a.title} ${a.summary}`, q));
2317
2592
  const seen = new Set(local.map((a) => a.id));
2318
2593
  const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
2319
- const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && (0, import_runtime8.isArticleListed)(data.collections, a, member))];
2594
+ const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && (0, import_runtime9.isArticleListed)(data.collections, a, member))];
2320
2595
  section(`Results (${merged.length})`);
2321
2596
  if (!merged.length) body.appendChild(el(doc, "div", "g-rc-empty", "No articles match your search."));
2322
2597
  merged.forEach(articleButton);
@@ -2331,55 +2606,124 @@ function createHelpView(options) {
2331
2606
  body.appendChild(b);
2332
2607
  });
2333
2608
  }
2334
- if (data.recommended?.length) {
2335
- section("Recommended");
2336
- data.recommended.forEach((g) => {
2337
- const b = listItem(doc, "\u25B6", g.name, "Start guide");
2338
- b.addEventListener("click", () => options.onStartGuide?.(g.id));
2339
- body.appendChild(b);
2340
- });
2341
- }
2342
- if (data.checklists?.length) {
2343
- section("Checklists");
2344
- data.checklists.forEach((g) => {
2345
- const b = listItem(doc, "\u2611", g.name, "Open checklist");
2346
- b.addEventListener("click", () => options.onOpenChecklist?.(g.id));
2347
- body.appendChild(b);
2348
- });
2349
- }
2350
- for (const s of sections) {
2351
- section(s.collection ? s.path.join(" \u203A ") : "Articles", s.depth);
2352
- s.articles.forEach(articleButton);
2353
- }
2354
- if (data.faqs?.length) {
2355
- section("FAQ");
2356
- data.faqs.forEach((f) => {
2357
- const b = listItem(doc, "?", f.question);
2358
- b.setAttribute("aria-expanded", "false");
2359
- const a = el(doc, "div", "g-faq-a");
2360
- setSanitizedHtml(a, f.answer);
2361
- a.hidden = true;
2362
- b.addEventListener("click", () => {
2363
- a.hidden = !a.hidden;
2364
- b.setAttribute("aria-expanded", String(!a.hidden));
2609
+ const renderers = {
2610
+ search: () => {
2611
+ },
2612
+ announcements: () => {
2613
+ if (!data.announcements?.length) return;
2614
+ section(import_runtime9.RC_MODULE_LABELS.announcements);
2615
+ const seen = data.announcementsSeenAt ? Date.parse(data.announcementsSeenAt) : NaN;
2616
+ data.announcements.slice(0, 20).forEach((n) => body.appendChild(announcementItem(n, Number.isNaN(seen) || Date.parse(n.publishedAt) > seen)));
2617
+ },
2618
+ guides: () => {
2619
+ if (!data.recommended?.length) return;
2620
+ section("Recommended");
2621
+ data.recommended.forEach((g) => {
2622
+ const b = listItem(doc, "\u25B6", g.name, "Start guide");
2623
+ b.addEventListener("click", () => options.onStartGuide?.(g.id));
2624
+ body.appendChild(b);
2365
2625
  });
2366
- body.append(b, a);
2367
- });
2626
+ },
2627
+ checklists: () => {
2628
+ if (!data.checklists?.length) return;
2629
+ section("Checklists");
2630
+ data.checklists.forEach((g) => {
2631
+ const b = listItem(doc, "\u2611", g.name, "Open checklist");
2632
+ b.addEventListener("click", () => options.onOpenChecklist?.(g.id));
2633
+ body.appendChild(b);
2634
+ });
2635
+ },
2636
+ articles: () => {
2637
+ for (const s of sections) {
2638
+ section(s.collection ? s.path.join(" \u203A ") : "Articles", s.depth);
2639
+ s.articles.forEach(articleButton);
2640
+ }
2641
+ },
2642
+ faqs: () => {
2643
+ if (!data.faqs?.length) return;
2644
+ section("FAQ");
2645
+ data.faqs.forEach((f) => {
2646
+ const b = listItem(doc, "?", f.question);
2647
+ b.setAttribute("aria-expanded", "false");
2648
+ const a = el(doc, "div", "g-faq-a");
2649
+ a.id = `g-faq-${f.id}`;
2650
+ b.setAttribute("aria-controls", a.id);
2651
+ setSanitizedHtml(a, f.answer);
2652
+ a.hidden = true;
2653
+ b.addEventListener("click", () => {
2654
+ a.hidden = !a.hidden;
2655
+ b.setAttribute("aria-expanded", String(!a.hidden));
2656
+ });
2657
+ body.append(b, a);
2658
+ });
2659
+ },
2660
+ links: () => {
2661
+ if (!data.links?.length) return;
2662
+ section("Links");
2663
+ data.links.forEach((l) => {
2664
+ const a = el(doc, "a", "g-rc-item");
2665
+ a.href = l.url;
2666
+ a.target = "_blank";
2667
+ a.rel = "noopener noreferrer";
2668
+ const i = el(doc, "span", "g-rc-icon", "\u2197");
2669
+ i.setAttribute("aria-hidden", "true");
2670
+ a.append(i, el(doc, "span", "g-rc-title", l.label));
2671
+ a.setAttribute("aria-label", `${l.label} (opens in a new tab)`);
2672
+ body.appendChild(a);
2673
+ });
2674
+ }
2675
+ };
2676
+ for (const id of data.modules ?? import_runtime9.RC_MODULE_IDS) renderers[id]?.();
2677
+ if (!body.childNodes.length) body.appendChild(el(doc, "div", "g-rc-empty", "Nothing here yet."));
2678
+ }
2679
+ function announcementItem(n, unread) {
2680
+ const item = el(doc, "article", `g-rc-news${unread ? " g-rc-news-unread" : ""}`);
2681
+ const head = el(doc, "div", "g-rc-news-head");
2682
+ const title = el(doc, "h3", "g-rc-news-title", n.title);
2683
+ head.appendChild(title);
2684
+ if (unread) {
2685
+ const dot = el(doc, "span", "g-rc-news-new", "New");
2686
+ head.appendChild(dot);
2687
+ }
2688
+ const date = el(doc, "time", "g-rc-news-date");
2689
+ date.dateTime = n.publishedAt;
2690
+ try {
2691
+ date.textContent = new Date(n.publishedAt).toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
2692
+ } catch {
2693
+ date.textContent = n.publishedAt.slice(0, 10);
2694
+ }
2695
+ item.append(head, date);
2696
+ if (n.body) {
2697
+ const b = el(doc, "div", "g-body g-rc-news-body");
2698
+ setSanitizedHtml(b, n.body);
2699
+ item.appendChild(b);
2700
+ }
2701
+ const actions = el(doc, "div", "g-rc-news-actions");
2702
+ const guide = n.guideId ? startableGuide(options.guides?.(), n.guideId) : null;
2703
+ if (guide) {
2704
+ const b = el(doc, "button", "g-btn g-btn-primary", n.linkLabel || "Show me");
2705
+ b.type = "button";
2706
+ b.addEventListener("click", () => options.onShowMe ? options.onShowMe(guide.key) : options.onStartGuide?.(guide.id));
2707
+ actions.appendChild(b);
2708
+ }
2709
+ if (n.articleId) {
2710
+ const listed = options.data().articles.find((a) => a.id === n.articleId);
2711
+ if (listed) {
2712
+ const b = el(doc, "button", "g-btn g-btn-secondary", guide ? "Read more" : n.linkLabel || "Read more");
2713
+ b.type = "button";
2714
+ b.addEventListener("click", () => openArticle(listed));
2715
+ actions.appendChild(b);
2716
+ }
2368
2717
  }
2369
- if (data.links?.length) {
2370
- section("Links");
2371
- data.links.forEach((l) => {
2372
- const a = el(doc, "a", "g-rc-item");
2373
- a.href = l.url;
2374
- a.target = "_blank";
2375
- a.rel = "noopener noreferrer";
2376
- const i = el(doc, "span", "g-rc-icon", "\u2197");
2377
- i.setAttribute("aria-hidden", "true");
2378
- a.append(i, el(doc, "span", "g-rc-title", l.label));
2379
- body.appendChild(a);
2380
- });
2718
+ if (n.url && /^https?:\/\//i.test(n.url)) {
2719
+ const a = el(doc, "a", "g-btn g-btn-link", !guide && !n.articleId && n.linkLabel ? n.linkLabel : "Learn more");
2720
+ a.href = n.url;
2721
+ a.target = "_blank";
2722
+ a.rel = "noopener noreferrer";
2723
+ actions.appendChild(a);
2381
2724
  }
2382
- if (!body.childNodes.length) body.appendChild(el(doc, "div", "g-rc-empty", "Nothing here yet."));
2725
+ if (actions.childNodes.length) item.appendChild(actions);
2726
+ return item;
2383
2727
  }
2384
2728
  let debounce = null;
2385
2729
  const onInput = () => {
@@ -2451,6 +2795,19 @@ function renderResourceCenter(options) {
2451
2795
  launcher.appendChild(el(doc, "span", "", config.launcher.label || config.title));
2452
2796
  launcher.setAttribute("aria-label", config.launcher.label || config.title);
2453
2797
  if (options.hideLauncher) launcher.hidden = true;
2798
+ const badge = el(doc, "span", "g-launcher-badge");
2799
+ badge.hidden = true;
2800
+ launcher.appendChild(badge);
2801
+ const modules = () => (0, import_runtime10.orderedModules)(config.modules).filter((m) => m.enabled && (!("targeting" in m) || !m.targeting || !options.evaluate || options.evaluate(m.targeting))).map((m) => m.id);
2802
+ const announcements = () => modules().includes("announcements") ? (0, import_runtime10.visibleAnnouncements)(config.announcements, options.audienceIds?.() ?? []) : [];
2803
+ const updateBadge = () => {
2804
+ const unread = (0, import_runtime10.unreadAnnouncements)(announcements(), options.announcementsSeenAt?.());
2805
+ badge.hidden = unread === 0;
2806
+ badge.textContent = unread > 9 ? "9+" : String(unread);
2807
+ const label = config.launcher.label || config.title;
2808
+ launcher.setAttribute("aria-label", unread ? `${label} (${unread} new update${unread === 1 ? "" : "s"})` : label);
2809
+ };
2810
+ updateBadge();
2454
2811
  const panel = el(doc, "div", `g-panel ${pos}`);
2455
2812
  panel.setAttribute("role", "dialog");
2456
2813
  panel.setAttribute("aria-label", config.title);
@@ -2461,9 +2818,20 @@ function renderResourceCenter(options) {
2461
2818
  close.type = "button";
2462
2819
  close.setAttribute("aria-label", "Close");
2463
2820
  head.appendChild(close);
2821
+ let seenAtOpen = options.announcementsSeenAt?.() ?? null;
2464
2822
  const help = createHelpView({
2465
2823
  doc,
2466
- data: () => ({ collections: config.collections, articles: config.articles, recommended: byId(config.recommendedGuideIds), checklists: byId(config.checklistGuideIds), faqs: config.faqs, links: config.links }),
2824
+ data: () => ({
2825
+ collections: config.collections,
2826
+ articles: config.articles,
2827
+ recommended: byId(config.recommendedGuideIds),
2828
+ checklists: byId(config.checklistGuideIds),
2829
+ faqs: config.faqs,
2830
+ links: config.links,
2831
+ announcements: announcements(),
2832
+ announcementsSeenAt: seenAtOpen,
2833
+ modules: modules()
2834
+ }),
2467
2835
  search: options.search,
2468
2836
  audienceIds: options.audienceIds,
2469
2837
  loadArticle: options.loadArticle,
@@ -2487,8 +2855,13 @@ function renderResourceCenter(options) {
2487
2855
  panel.hidden = !v;
2488
2856
  launcher.setAttribute("aria-expanded", String(v));
2489
2857
  if (v) {
2858
+ seenAtOpen = options.announcementsSeenAt?.() ?? null;
2490
2859
  help.render();
2491
2860
  help.focusSearch();
2861
+ if (announcements().length) {
2862
+ options.onAnnouncementsSeen?.();
2863
+ updateBadge();
2864
+ }
2492
2865
  options.onOpen?.();
2493
2866
  } else {
2494
2867
  help.reset();
@@ -2565,7 +2938,7 @@ function startAuthoring(options) {
2565
2938
  outline.hidden = true;
2566
2939
  return;
2567
2940
  }
2568
- const r = el2.getBoundingClientRect();
2941
+ const r = visualRect(el2);
2569
2942
  outline.hidden = false;
2570
2943
  outline.classList.toggle("g-picked", picked);
2571
2944
  Object.assign(outline.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
@@ -2573,9 +2946,9 @@ function startAuthoring(options) {
2573
2946
  label.style.top = r.top < 30 ? "100%" : "-24px";
2574
2947
  };
2575
2948
  const elementAt = (x, y) => {
2576
- const el2 = doc.elementFromPoint(x, y);
2949
+ const el2 = deepElementFromPoint(doc, x, y);
2577
2950
  if (!el2 || isGuidelyElement(el2) || el2 === doc.documentElement || el2 === doc.body) return null;
2578
- return el2;
2951
+ return componentAnchor(el2);
2579
2952
  };
2580
2953
  const onMove = (e) => {
2581
2954
  if (!picking || stopped) return;
@@ -2590,7 +2963,7 @@ function startAuthoring(options) {
2590
2963
  };
2591
2964
  const onClick = (e) => {
2592
2965
  if (!picking || stopped) return;
2593
- if (e.target && isGuidelyElement(e.target)) return;
2966
+ if (isGuidelyElement(composedTarget(e))) return;
2594
2967
  const el2 = elementAt(e.clientX, e.clientY) ?? hovered;
2595
2968
  if (!el2) return;
2596
2969
  e.preventDefault();
@@ -2603,8 +2976,7 @@ function startAuthoring(options) {
2603
2976
  };
2604
2977
  const swallow = (e) => {
2605
2978
  if (!picking || stopped) return;
2606
- const t = e.target;
2607
- if (t && isGuidelyElement(t)) return;
2979
+ if (isGuidelyElement(composedTarget(e))) return;
2608
2980
  e.preventDefault();
2609
2981
  e.stopPropagation();
2610
2982
  };
@@ -2675,17 +3047,15 @@ function highlightSelectors(selectors, options = {}) {
2675
3047
  const scope = createScope(null, { theme: "light", animation: "none" }, doc);
2676
3048
  const boxes = [];
2677
3049
  const counts = [];
2678
- let resolved = null;
3050
+ const resolved = resolveTarget(selectors, doc);
2679
3051
  const matched = /* @__PURE__ */ new Set();
2680
3052
  selectors.forEach((s) => {
2681
3053
  const els = querySelectorStrategy(s, doc).filter((el2) => !isGuidelyElement(el2));
2682
- const visible = els.filter(isVisible);
2683
3054
  counts.push(els.length);
2684
- if (!resolved && visible.length === 1) resolved = visible[0];
2685
3055
  els.forEach((el2) => matched.add(el2));
2686
3056
  });
2687
3057
  matched.forEach((el2) => {
2688
- const r = el2.getBoundingClientRect();
3058
+ const r = visualRect(el2);
2689
3059
  const box = doc.createElement("div");
2690
3060
  box.className = "g-highlight";
2691
3061
  Object.assign(box.style, { left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
@@ -2706,12 +3076,19 @@ function highlightSelectors(selectors, options = {}) {
2706
3076
  0 && (module.exports = {
2707
3077
  DEFAULT_THEME,
2708
3078
  HOST_ID,
3079
+ SHADOW_SEPARATOR,
2709
3080
  STYLES,
2710
3081
  TargetTimeoutError,
2711
3082
  anchor,
2712
3083
  applyTheme,
2713
3084
  buildCard,
3085
+ closestComposed,
3086
+ componentAnchor,
3087
+ composedParent,
3088
+ composedTarget,
3089
+ containsComposed,
2714
3090
  createScope,
3091
+ deepElementFromPoint,
2715
3092
  describeElement,
2716
3093
  destroyHost,
2717
3094
  enhancePdfViewers,
@@ -2722,6 +3099,7 @@ function highlightSelectors(selectors, options = {}) {
2722
3099
  isChecklistComplete,
2723
3100
  isGuidelyElement,
2724
3101
  isQuestionVisible,
3102
+ isStableClass,
2725
3103
  isStableId,
2726
3104
  isVisible,
2727
3105
  normalizeText,
@@ -2745,10 +3123,12 @@ function highlightSelectors(selectors, options = {}) {
2745
3123
  sanitizeHtml,
2746
3124
  sanitizeToFragment,
2747
3125
  setSanitizedHtml,
3126
+ shadowHostChain,
2748
3127
  startAuthoring,
2749
3128
  textSnapshotOf,
2750
3129
  trackRect,
2751
3130
  urlMatches,
3131
+ visualRect,
2752
3132
  waitForTarget
2753
3133
  });
2754
3134
  //# sourceMappingURL=index.cjs.map