@guidelytech/react-native 0.10.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/README.md +66 -1
- package/dist/index.cjs +1064 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -6
- package/dist/index.d.ts +214 -6
- package/dist/index.js +1032 -109
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -28,14 +28,18 @@ __export(index_exports, {
|
|
|
28
28
|
FeatureValue: () => FeatureValue,
|
|
29
29
|
FlagsProvider: () => FlagsProvider,
|
|
30
30
|
GuidelyClient: () => GuidelyClient,
|
|
31
|
+
GuidelyMask: () => GuidelyMask,
|
|
31
32
|
GuidelyOverlay: () => GuidelyOverlay,
|
|
32
33
|
GuidelyProvider: () => GuidelyProvider,
|
|
33
34
|
GuidelyScreenScope: () => GuidelyScreenScope,
|
|
34
35
|
GuidelyTarget: () => GuidelyTarget,
|
|
36
|
+
HeatmapBuffer: () => HeatmapBuffer,
|
|
35
37
|
InMemoryStickyBucketStore: () => import_flags6.InMemoryStickyBucketStore,
|
|
36
38
|
Messenger: () => Messenger,
|
|
39
|
+
RageClickDetector: () => RageClickDetector,
|
|
37
40
|
SDK_NAME: () => SDK_NAME,
|
|
38
41
|
SDK_VERSION: () => SDK_VERSION,
|
|
42
|
+
TapTracker: () => TapTracker,
|
|
39
43
|
TargetRegistry: () => TargetRegistry,
|
|
40
44
|
createFlagsClient: () => import_flags6.createFlagsClient,
|
|
41
45
|
createSseParser: () => createSseParser,
|
|
@@ -63,8 +67,8 @@ __export(index_exports, {
|
|
|
63
67
|
useGuidelyTarget: () => useGuidelyTarget
|
|
64
68
|
});
|
|
65
69
|
module.exports = __toCommonJS(index_exports);
|
|
66
|
-
var
|
|
67
|
-
var
|
|
70
|
+
var import_react10 = require("react");
|
|
71
|
+
var import_react_native13 = require("react-native");
|
|
68
72
|
var import_flags3 = require("@guidelytech/flags");
|
|
69
73
|
|
|
70
74
|
// src/flags.tsx
|
|
@@ -156,7 +160,7 @@ function FlagsProvider({ children, client: external, payload, ...options }) {
|
|
|
156
160
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlagsContext.Provider, { value, children });
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
// ../shared/dist/chunk-
|
|
163
|
+
// ../shared/dist/chunk-TGIUEYXM.js
|
|
160
164
|
function fnv1a(input) {
|
|
161
165
|
let h = 2166136261;
|
|
162
166
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -336,6 +340,8 @@ function resolveField(rule, ctx) {
|
|
|
336
340
|
return ctx.now.getHours();
|
|
337
341
|
case "rollout.percentage":
|
|
338
342
|
return rolloutBucket(ctx.userId || ctx.anonymousId, ctx.rolloutSalt || "guidely");
|
|
343
|
+
case "experiment.bucket":
|
|
344
|
+
return rule.key ? rolloutBucket(ctx.userId || ctx.anonymousId, rule.key) : void 0;
|
|
339
345
|
case "environment":
|
|
340
346
|
return ctx.environment;
|
|
341
347
|
case "locale":
|
|
@@ -346,6 +352,9 @@ function resolveField(rule, ctx) {
|
|
|
346
352
|
return ctx.screen;
|
|
347
353
|
case "app.version":
|
|
348
354
|
return ctx.appVersion;
|
|
355
|
+
case "page.views":
|
|
356
|
+
case "feature.clicks":
|
|
357
|
+
return rule.key ? ctx.tagUsage?.[rule.key] ?? 0 : 0;
|
|
349
358
|
default:
|
|
350
359
|
return void 0;
|
|
351
360
|
}
|
|
@@ -454,8 +463,8 @@ function sanitizeProperties(props) {
|
|
|
454
463
|
for (const [k, v] of Object.entries(props)) {
|
|
455
464
|
if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
|
|
456
465
|
if (typeof v === "function" || typeof v === "symbol") continue;
|
|
457
|
-
const
|
|
458
|
-
size += k.length + (
|
|
466
|
+
const str2 = typeof v === "string" ? v : JSON.stringify(v ?? null);
|
|
467
|
+
size += k.length + (str2?.length ?? 0);
|
|
459
468
|
if (size > MAX_PROPERTY_BYTES) break;
|
|
460
469
|
out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
|
|
461
470
|
}
|
|
@@ -612,9 +621,151 @@ function isArticleListed(collections, article, member) {
|
|
|
612
621
|
}
|
|
613
622
|
return true;
|
|
614
623
|
}
|
|
624
|
+
var RC_MODULE_IDS = ["announcements", "search", "checklists", "guides", "articles", "links", "faqs"];
|
|
625
|
+
function orderedModules(modules) {
|
|
626
|
+
const known = (modules ?? []).filter((m, i, all) => RC_MODULE_IDS.includes(m.id) && all.findIndex((x) => x.id === m.id) === i);
|
|
627
|
+
const missing2 = RC_MODULE_IDS.filter((id) => !known.some((m) => m.id === id)).map((id) => ({ id, enabled: true }));
|
|
628
|
+
return [...known, ...missing2];
|
|
629
|
+
}
|
|
630
|
+
function visibleAnnouncements(items, member, now = Date.now()) {
|
|
631
|
+
return (items ?? []).filter((a) => {
|
|
632
|
+
const t = Date.parse(a.publishedAt);
|
|
633
|
+
return !Number.isNaN(t) && t <= now && matchesAudiences(a.audienceIds, member);
|
|
634
|
+
}).sort((a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt));
|
|
635
|
+
}
|
|
636
|
+
function unreadAnnouncements(items, lastSeenAt) {
|
|
637
|
+
const seen = lastSeenAt ? Date.parse(lastSeenAt) : NaN;
|
|
638
|
+
if (Number.isNaN(seen)) return items.length;
|
|
639
|
+
return items.filter((a) => Date.parse(a.publishedAt) > seen).length;
|
|
640
|
+
}
|
|
615
641
|
var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
642
|
+
var DEFAULT_LOCALE = "en";
|
|
643
|
+
function normalizeLocale(input) {
|
|
644
|
+
if (typeof input !== "string") return "";
|
|
645
|
+
const parts = input.trim().replace(/_/g, "-").split("-").filter(Boolean);
|
|
646
|
+
if (!parts.length || !/^[a-zA-Z]{2,3}$/.test(parts[0])) return "";
|
|
647
|
+
return parts.slice(0, 3).map((p, i) => i === 0 ? p.toLowerCase() : p.length === 4 ? p[0].toUpperCase() + p.slice(1).toLowerCase() : p.length === 2 ? p.toUpperCase() : p).join("-");
|
|
648
|
+
}
|
|
649
|
+
function baseLanguage(locale) {
|
|
650
|
+
return normalizeLocale(locale).split("-")[0] ?? "";
|
|
651
|
+
}
|
|
652
|
+
function resolveLocale(requested, available, defaultLocale = DEFAULT_LOCALE) {
|
|
653
|
+
const def = normalizeLocale(defaultLocale) || DEFAULT_LOCALE;
|
|
654
|
+
const avail = available.map(normalizeLocale).filter(Boolean);
|
|
655
|
+
const all = [def, ...avail.filter((a) => a.toLowerCase() !== def.toLowerCase())];
|
|
656
|
+
for (const raw of requested) {
|
|
657
|
+
const want = normalizeLocale(raw);
|
|
658
|
+
if (!want) continue;
|
|
659
|
+
const exact = all.find((a) => a.toLowerCase() === want.toLowerCase());
|
|
660
|
+
if (exact) return exact;
|
|
661
|
+
const base = baseLanguage(want);
|
|
662
|
+
const sameBase = all.find((a) => a.toLowerCase() === base) ?? all.find((a) => baseLanguage(a) === base);
|
|
663
|
+
if (sameBase) return sameBase;
|
|
664
|
+
}
|
|
665
|
+
return def;
|
|
666
|
+
}
|
|
667
|
+
function visitorLocales(explicit, traits, platform = []) {
|
|
668
|
+
const out = [];
|
|
669
|
+
const push = (v) => {
|
|
670
|
+
const n = normalizeLocale(v);
|
|
671
|
+
if (n && !out.includes(n)) out.push(n);
|
|
672
|
+
};
|
|
673
|
+
push(explicit);
|
|
674
|
+
push(traits?.language);
|
|
675
|
+
push(traits?.locale);
|
|
676
|
+
for (const p of platform) push(p);
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
function guideLocales(config) {
|
|
680
|
+
const t = config.localization?.translations ?? {};
|
|
681
|
+
return Object.keys(t).filter((l) => Object.values(t[l] ?? {}).some((v) => typeof v === "string" && v.trim() !== ""));
|
|
682
|
+
}
|
|
683
|
+
function pick(map, key, fallback) {
|
|
684
|
+
const v = map?.[key];
|
|
685
|
+
return typeof v === "string" && v.trim() !== "" ? v : fallback;
|
|
686
|
+
}
|
|
687
|
+
function applyGuideTranslations(config, map) {
|
|
688
|
+
if (!map || !Object.keys(map).length) return config;
|
|
689
|
+
return {
|
|
690
|
+
...config,
|
|
691
|
+
steps: config.steps.map((s) => ({
|
|
692
|
+
...s,
|
|
693
|
+
title: pick(map, `steps.${s.id}.title`, s.title),
|
|
694
|
+
body: pick(map, `steps.${s.id}.body`, s.body),
|
|
695
|
+
customHtml: pick(map, `steps.${s.id}.customHtml`, s.customHtml),
|
|
696
|
+
imageAlt: pick(map, `steps.${s.id}.imageAlt`, s.imageAlt),
|
|
697
|
+
buttons: s.buttons.map((b) => ({ ...b, label: pick(map, `steps.${s.id}.buttons.${b.id}.label`, b.label) }))
|
|
698
|
+
})),
|
|
699
|
+
checklist: config.checklist ? {
|
|
700
|
+
...config.checklist,
|
|
701
|
+
launcherLabel: pick(map, "checklist.launcherLabel", config.checklist.launcherLabel),
|
|
702
|
+
completionTitle: pick(map, "checklist.completionTitle", config.checklist.completionTitle),
|
|
703
|
+
completionBody: pick(map, "checklist.completionBody", config.checklist.completionBody),
|
|
704
|
+
tasks: config.checklist.tasks.map((t) => ({ ...t, title: pick(map, `checklist.tasks.${t.id}.title`, t.title), description: pick(map, `checklist.tasks.${t.id}.description`, t.description) }))
|
|
705
|
+
} : config.checklist,
|
|
706
|
+
survey: config.survey ? {
|
|
707
|
+
...config.survey,
|
|
708
|
+
thankYouTitle: pick(map, "survey.thankYouTitle", config.survey.thankYouTitle),
|
|
709
|
+
thankYouBody: pick(map, "survey.thankYouBody", config.survey.thankYouBody),
|
|
710
|
+
questions: config.survey.questions.map((q) => ({
|
|
711
|
+
...q,
|
|
712
|
+
title: pick(map, `survey.questions.${q.id}.title`, q.title),
|
|
713
|
+
description: pick(map, `survey.questions.${q.id}.description`, q.description),
|
|
714
|
+
placeholder: pick(map, `survey.questions.${q.id}.placeholder`, q.placeholder),
|
|
715
|
+
lowLabel: pick(map, `survey.questions.${q.id}.lowLabel`, q.lowLabel),
|
|
716
|
+
highLabel: pick(map, `survey.questions.${q.id}.highLabel`, q.highLabel),
|
|
717
|
+
options: q.options.map((o) => ({ ...o, label: pick(map, `survey.questions.${q.id}.options.${o.id}.label`, o.label) }))
|
|
718
|
+
}))
|
|
719
|
+
} : config.survey
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
function localizeGuide(guide, requested, defaultLocale = DEFAULT_LOCALE) {
|
|
723
|
+
const translations = guide.config.localization?.translations ?? {};
|
|
724
|
+
const locale = resolveLocale(requested, guideLocales(guide.config), defaultLocale);
|
|
725
|
+
const key = Object.keys(translations).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
|
|
726
|
+
if (!key) return { ...guide, locale };
|
|
727
|
+
return { ...guide, locale, config: applyGuideTranslations(guide.config, translations[key]) };
|
|
728
|
+
}
|
|
729
|
+
function resourceCenterLocales(rc) {
|
|
730
|
+
const set = new Set(Object.keys(rc.translations ?? {}));
|
|
731
|
+
for (const a of rc.articles) for (const l of Object.keys(a.translations ?? {})) set.add(l);
|
|
732
|
+
for (const a of rc.announcements ?? []) for (const l of Object.keys(a.translations ?? {})) set.add(l);
|
|
733
|
+
return [...set];
|
|
734
|
+
}
|
|
735
|
+
function translationFor(map, locale) {
|
|
736
|
+
if (!map) return void 0;
|
|
737
|
+
const key = Object.keys(map).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
|
|
738
|
+
return key ? map[key] : void 0;
|
|
739
|
+
}
|
|
740
|
+
function localizeArticle(a, locale) {
|
|
741
|
+
const t = translationFor(a.translations, locale);
|
|
742
|
+
if (!t) return a;
|
|
743
|
+
const body = t.body?.trim() ? t.body : a.body;
|
|
744
|
+
return { ...a, title: t.title?.trim() ? t.title : a.title, summary: t.summary?.trim() ? t.summary : a.summary, body, ...t.bodyOmitted ? { bodyOmitted: true } : {} };
|
|
745
|
+
}
|
|
746
|
+
function localizeAnnouncement(a, locale) {
|
|
747
|
+
const t = translationFor(a.translations, locale);
|
|
748
|
+
if (!t) return a;
|
|
749
|
+
return { ...a, title: t.title?.trim() ? t.title : a.title, body: t.body?.trim() ? t.body : a.body };
|
|
750
|
+
}
|
|
751
|
+
function localizeResourceCenter(rc, requested, defaultLocale = DEFAULT_LOCALE) {
|
|
752
|
+
const locale = resolveLocale(requested, resourceCenterLocales(rc), defaultLocale);
|
|
753
|
+
if (locale.toLowerCase() === normalizeLocale(defaultLocale).toLowerCase()) return { ...rc, locale };
|
|
754
|
+
const map = translationFor(rc.translations, locale);
|
|
755
|
+
return {
|
|
756
|
+
...rc,
|
|
757
|
+
locale,
|
|
758
|
+
title: pick(map, "title", rc.title),
|
|
759
|
+
launcher: { ...rc.launcher, label: pick(map, "launcher.label", rc.launcher.label) },
|
|
760
|
+
collections: rc.collections.map((c) => ({ ...c, name: pick(map, `collections.${c.id}.name`, c.name), description: pick(map, `collections.${c.id}.description`, c.description) })),
|
|
761
|
+
links: rc.links.map((l) => ({ ...l, label: pick(map, `links.${l.id}.label`, l.label) })),
|
|
762
|
+
faqs: rc.faqs.map((f) => ({ ...f, question: pick(map, `faqs.${f.id}.question`, f.question), answer: pick(map, `faqs.${f.id}.answer`, f.answer) })),
|
|
763
|
+
articles: rc.articles.map((a) => localizeArticle(a, locale)),
|
|
764
|
+
announcements: rc.announcements?.map((a) => localizeAnnouncement(a, locale))
|
|
765
|
+
};
|
|
766
|
+
}
|
|
616
767
|
|
|
617
|
-
// ../sdk-core/dist/chunk-
|
|
768
|
+
// ../sdk-core/dist/chunk-JUSM7J5N.js
|
|
618
769
|
var __defProp2 = Object.defineProperty;
|
|
619
770
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
620
771
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -912,6 +1063,8 @@ var KEYS = {
|
|
|
912
1063
|
queue: (key) => `guidely:queue:${key}`,
|
|
913
1064
|
/** Survey answers and guide/checklist progress not yet accepted by the server. */
|
|
914
1065
|
outbox: (key) => `guidely:outbox:${key}`,
|
|
1066
|
+
/** Agent Analytics turns not yet accepted by the server. */
|
|
1067
|
+
agentOutbox: (key) => `guidely:agents:${key}`,
|
|
915
1068
|
session: "guidely:session",
|
|
916
1069
|
active: "guidely:active",
|
|
917
1070
|
authoring: "guidely:authoring",
|
|
@@ -924,7 +1077,7 @@ var KEYS = {
|
|
|
924
1077
|
newsSeen: (key) => `guidely:news:${key}`
|
|
925
1078
|
};
|
|
926
1079
|
|
|
927
|
-
// ../sdk-core/dist/chunk-
|
|
1080
|
+
// ../sdk-core/dist/chunk-ZAF2KRJA.js
|
|
928
1081
|
var EventQueue = class {
|
|
929
1082
|
constructor(opts) {
|
|
930
1083
|
__publicField2(this, "opts", opts);
|
|
@@ -1200,12 +1353,32 @@ function areMutuallyExclusive(a, b) {
|
|
|
1200
1353
|
return la.includes(b.id) || la.includes(b.key) || lb.includes(a.id) || lb.includes(a.key);
|
|
1201
1354
|
}
|
|
1202
1355
|
function isWithinSchedule(guide, now = Date.now()) {
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1356
|
+
for (const w of [guide.config.schedule, guide.config.survey]) {
|
|
1357
|
+
if (!w) continue;
|
|
1358
|
+
if (w.startAt && now < Date.parse(w.startAt)) return false;
|
|
1359
|
+
if (w.endAt && now > Date.parse(w.endAt)) return false;
|
|
1360
|
+
}
|
|
1361
|
+
return true;
|
|
1362
|
+
}
|
|
1363
|
+
var MINUTE = 60 * 1e3;
|
|
1364
|
+
function countsTowardThrottle(guide) {
|
|
1365
|
+
return isExclusiveGuide(guide) && !guide.config.frequency.ignoreThrottling;
|
|
1366
|
+
}
|
|
1367
|
+
function isThrottleEligible(settings, launches, now = Date.now()) {
|
|
1368
|
+
if (!settings || !settings.enabled) return true;
|
|
1369
|
+
const times = launches.map((l) => Date.parse(l)).filter((t) => !Number.isNaN(t));
|
|
1370
|
+
const windowMs = Math.max(1, settings.windowHours) * HOUR;
|
|
1371
|
+
if (times.filter((t) => now - t < windowMs).length >= Math.max(1, settings.maxGuides)) return false;
|
|
1372
|
+
if (settings.minGapMinutes > 0 && times.some((t) => now - t < settings.minGapMinutes * MINUTE)) return false;
|
|
1207
1373
|
return true;
|
|
1208
1374
|
}
|
|
1375
|
+
function pruneLaunches(launches, settings, now = Date.now()) {
|
|
1376
|
+
const keepMs = Math.max(settings?.windowHours ?? 24, 1) * HOUR + (settings?.minGapMinutes ?? 0) * MINUTE;
|
|
1377
|
+
return launches.filter((l) => {
|
|
1378
|
+
const t = Date.parse(l);
|
|
1379
|
+
return !Number.isNaN(t) && now - t < keepMs;
|
|
1380
|
+
}).slice(-100);
|
|
1381
|
+
}
|
|
1209
1382
|
function dismissedAfterMerge(local, server) {
|
|
1210
1383
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1211
1384
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
@@ -1214,6 +1387,115 @@ function dismissedAfterMerge(local, server) {
|
|
|
1214
1387
|
// src/core/client.ts
|
|
1215
1388
|
var import_flags2 = require("@guidelytech/flags");
|
|
1216
1389
|
|
|
1390
|
+
// src/core/autocapture.ts
|
|
1391
|
+
var TAP_SLOP = 10;
|
|
1392
|
+
var TAP_MAX_MS = 1e3;
|
|
1393
|
+
var HEATMAP_MAX_POINTS = 40;
|
|
1394
|
+
function quoteValue(value) {
|
|
1395
|
+
return `"${value.replace(/["\\]/g, "\\$&")}"`;
|
|
1396
|
+
}
|
|
1397
|
+
function attrSelector(name, value) {
|
|
1398
|
+
return `[${name}=${quoteValue(value)}]`.slice(0, 300);
|
|
1399
|
+
}
|
|
1400
|
+
function cleanText(text) {
|
|
1401
|
+
const t = (text ?? "").replace(/\s+/g, " ").trim();
|
|
1402
|
+
return t ? t.slice(0, 100) : void 0;
|
|
1403
|
+
}
|
|
1404
|
+
function tapProperties(el, x, y, platform, maskAll) {
|
|
1405
|
+
const props = { selector: el.selector.slice(0, 300), tag: el.tag || "View", x: Math.round(x) || 0, y: Math.round(y) || 0, platform };
|
|
1406
|
+
const text = maskAll ? void 0 : cleanText(el.text);
|
|
1407
|
+
if (text) props.text = text;
|
|
1408
|
+
return props;
|
|
1409
|
+
}
|
|
1410
|
+
var r2 = (n) => Math.round(Math.min(1, Math.max(0, n)) * 100) / 100;
|
|
1411
|
+
function heatmapPoint(x, y, el, rect, rage) {
|
|
1412
|
+
const p = { k: rage ? "r" : "c", x: Math.round(x) || 0, y: Math.round(y) || 0 };
|
|
1413
|
+
const s = el?.selector.slice(0, 120);
|
|
1414
|
+
if (s) p.s = s;
|
|
1415
|
+
if (s && rect && rect.width > 0 && rect.height > 0) {
|
|
1416
|
+
p.rx = r2((x - rect.x) / rect.width);
|
|
1417
|
+
p.ry = r2((y - rect.y) / rect.height);
|
|
1418
|
+
}
|
|
1419
|
+
return p;
|
|
1420
|
+
}
|
|
1421
|
+
var RageClickDetector = class {
|
|
1422
|
+
constructor(threshold = 3, windowMs = 1e3, radius = 30) {
|
|
1423
|
+
__publicField(this, "threshold", threshold);
|
|
1424
|
+
__publicField(this, "windowMs", windowMs);
|
|
1425
|
+
__publicField(this, "radius", radius);
|
|
1426
|
+
__publicField(this, "clicks", []);
|
|
1427
|
+
__publicField(this, "fired", false);
|
|
1428
|
+
}
|
|
1429
|
+
/** Taps in the current burst once it has fired (rage), otherwise 0. */
|
|
1430
|
+
get burst() {
|
|
1431
|
+
return this.fired ? this.clicks.length : 0;
|
|
1432
|
+
}
|
|
1433
|
+
/** Records a tap; returns the tap count when it completes a burst, otherwise 0. */
|
|
1434
|
+
add(x, y, t) {
|
|
1435
|
+
const first = this.clicks[0];
|
|
1436
|
+
const last = this.clicks[this.clicks.length - 1];
|
|
1437
|
+
if (!first || !last || t - last.t > this.windowMs || Math.hypot(x - first.x, y - first.y) > this.radius) {
|
|
1438
|
+
this.clicks = [];
|
|
1439
|
+
this.fired = false;
|
|
1440
|
+
}
|
|
1441
|
+
this.clicks = this.clicks.filter((c) => t - c.t <= this.windowMs);
|
|
1442
|
+
this.clicks.push({ x, y, t });
|
|
1443
|
+
if (this.fired || this.clicks.length < this.threshold) return 0;
|
|
1444
|
+
this.fired = true;
|
|
1445
|
+
return this.clicks.length;
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
var HeatmapBuffer = class {
|
|
1449
|
+
constructor() {
|
|
1450
|
+
/** Screen path the buffered points belong to. */
|
|
1451
|
+
__publicField(this, "url", "");
|
|
1452
|
+
__publicField(this, "points", []);
|
|
1453
|
+
}
|
|
1454
|
+
/** Adds a tap (`burst` > 0 marks it and the burst's previous taps as rage); true when the batch is full. */
|
|
1455
|
+
add(url, point, burst) {
|
|
1456
|
+
this.url = url;
|
|
1457
|
+
for (let i = Math.max(0, this.points.length - burst + 1); i < this.points.length; i++) this.points[i].k = "r";
|
|
1458
|
+
this.points.push(point);
|
|
1459
|
+
return this.points.length >= HEATMAP_MAX_POINTS;
|
|
1460
|
+
}
|
|
1461
|
+
/** Empties the buffer, returning what was in it. */
|
|
1462
|
+
take() {
|
|
1463
|
+
const points = this.points;
|
|
1464
|
+
this.points = [];
|
|
1465
|
+
return points.length ? { url: this.url, points } : null;
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
var TapTracker = class {
|
|
1469
|
+
constructor() {
|
|
1470
|
+
__publicField(this, "start", null);
|
|
1471
|
+
__publicField(this, "moved", 0);
|
|
1472
|
+
}
|
|
1473
|
+
/** A finger went down. A second finger while one is tracked cancels the gesture (not a tap). */
|
|
1474
|
+
begin(x, y, t, touches, id) {
|
|
1475
|
+
if (this.start || touches > 1) {
|
|
1476
|
+
this.start = null;
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
this.start = { x, y, t, id };
|
|
1480
|
+
this.moved = 0;
|
|
1481
|
+
}
|
|
1482
|
+
move(x, y, id) {
|
|
1483
|
+
const s = this.start;
|
|
1484
|
+
if (s && (id === void 0 || s.id === void 0 || id === s.id)) this.moved = Math.max(this.moved, Math.hypot(x - s.x, y - s.y));
|
|
1485
|
+
}
|
|
1486
|
+
/** The finger lifted: the tap position when it moved ≤ 10pt and lasted < 1s, else null. */
|
|
1487
|
+
end(x, y, t, id) {
|
|
1488
|
+
const s = this.start;
|
|
1489
|
+
if (!s || id !== void 0 && s.id !== void 0 && id !== s.id) return null;
|
|
1490
|
+
this.start = null;
|
|
1491
|
+
const moved = Math.max(this.moved, Math.hypot(x - s.x, y - s.y));
|
|
1492
|
+
return moved <= TAP_SLOP && t - s.t < TAP_MAX_MS ? { x, y } : null;
|
|
1493
|
+
}
|
|
1494
|
+
cancel() {
|
|
1495
|
+
this.start = null;
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1217
1499
|
// src/core/targets.ts
|
|
1218
1500
|
var TargetTimeoutError = class extends Error {
|
|
1219
1501
|
constructor(ids) {
|
|
@@ -1316,6 +1598,35 @@ var TargetRegistry = class {
|
|
|
1316
1598
|
return null;
|
|
1317
1599
|
}
|
|
1318
1600
|
}
|
|
1601
|
+
/**
|
|
1602
|
+
* The registered target on the current screen whose visible frame contains the point (window
|
|
1603
|
+
* coordinates); the smallest one when targets are nested. Frames that do not answer within
|
|
1604
|
+
* `timeoutMs` are skipped.
|
|
1605
|
+
*/
|
|
1606
|
+
async hitTest(x, y, timeoutMs = 500) {
|
|
1607
|
+
const candidates = [];
|
|
1608
|
+
this.targets.forEach((list, id) => {
|
|
1609
|
+
for (const handle of list) if (!(handle.screen && this.screen && handle.screen !== this.screen)) candidates.push({ id, handle });
|
|
1610
|
+
});
|
|
1611
|
+
if (!candidates.length) return null;
|
|
1612
|
+
const rects = await Promise.all(
|
|
1613
|
+
candidates.map(
|
|
1614
|
+
({ handle }) => new Promise((resolve) => {
|
|
1615
|
+
const timer = setTimeout(() => resolve(null), timeoutMs);
|
|
1616
|
+
void this.measureHandle(handle).then((r) => {
|
|
1617
|
+
clearTimeout(timer);
|
|
1618
|
+
resolve(r);
|
|
1619
|
+
});
|
|
1620
|
+
})
|
|
1621
|
+
)
|
|
1622
|
+
);
|
|
1623
|
+
let best = null;
|
|
1624
|
+
rects.forEach((r, i) => {
|
|
1625
|
+
if (!r || x < r.x || y < r.y || x > r.x + r.width || y > r.y + r.height) return;
|
|
1626
|
+
if (!best || r.width * r.height < best.rect.width * best.rect.height) best = { id: candidates[i].id, rect: r };
|
|
1627
|
+
});
|
|
1628
|
+
return best;
|
|
1629
|
+
}
|
|
1319
1630
|
/** First of `ids` that is mounted and visible. */
|
|
1320
1631
|
async resolve(ids) {
|
|
1321
1632
|
for (const id of ids) {
|
|
@@ -2073,7 +2384,8 @@ var Messenger = class {
|
|
|
2073
2384
|
this.stopThreadPolling();
|
|
2074
2385
|
this.set({ open: true, view: { kind: "article", articleId: id, from }, article: null });
|
|
2075
2386
|
try {
|
|
2076
|
-
const
|
|
2387
|
+
const locale = this.deps.locale?.();
|
|
2388
|
+
const res = await this.request("GET", `/articles/${encodeURIComponent(id)}`, locale ? { params: { locale } } : {});
|
|
2077
2389
|
this.set({ article: res.article });
|
|
2078
2390
|
} catch (err) {
|
|
2079
2391
|
this.deps.onError(err);
|
|
@@ -2188,7 +2500,7 @@ var PersistentStore = class {
|
|
|
2188
2500
|
|
|
2189
2501
|
// src/core/client.ts
|
|
2190
2502
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2191
|
-
var SDK_VERSION = "0.
|
|
2503
|
+
var SDK_VERSION = "0.11.0";
|
|
2192
2504
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2193
2505
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2194
2506
|
var CACHED_START_MS = 1500;
|
|
@@ -2202,6 +2514,8 @@ function settlesWithin(promise, ms) {
|
|
|
2202
2514
|
promise.then(done, done);
|
|
2203
2515
|
});
|
|
2204
2516
|
}
|
|
2517
|
+
var appVersionKey = (key) => `guidely:app:${key}`;
|
|
2518
|
+
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
2205
2519
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2206
2520
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
2207
2521
|
var newSession = () => ({ id: `s_${randomId(16)}`, startedAt: nowIso(), lastActiveAt: nowIso(), shownGuides: [], events: [] });
|
|
@@ -2233,6 +2547,7 @@ var GuidelyClient = class {
|
|
|
2233
2547
|
__publicField(this, "activeSurvey", null);
|
|
2234
2548
|
__publicField(this, "queued", []);
|
|
2235
2549
|
__publicField(this, "flows", /* @__PURE__ */ new Map());
|
|
2550
|
+
/** Checklists on screen, with the guide as rendered (in the visitor's language). */
|
|
2236
2551
|
__publicField(this, "checklists", /* @__PURE__ */ new Map());
|
|
2237
2552
|
__publicField(this, "expanded", /* @__PURE__ */ new Set());
|
|
2238
2553
|
__publicField(this, "surveyView", null);
|
|
@@ -2252,6 +2567,27 @@ var GuidelyClient = class {
|
|
|
2252
2567
|
__publicField(this, "flags");
|
|
2253
2568
|
/** Experiments already reported this session (`experiment_viewed` is sent once per session). */
|
|
2254
2569
|
__publicField(this, "exposures", /* @__PURE__ */ new Set());
|
|
2570
|
+
// Product analytics (autocapture).
|
|
2571
|
+
__publicField(this, "rage", new RageClickDetector());
|
|
2572
|
+
__publicField(this, "heatmap", new HeatmapBuffer());
|
|
2573
|
+
/** Taps are processed in order (target frames are measured asynchronously). */
|
|
2574
|
+
__publicField(this, "tapChain", Promise.resolve());
|
|
2575
|
+
/** When the current screen's foreground measurement started; `screenLeft` once it has been reported. */
|
|
2576
|
+
__publicField(this, "screenStartedAt", 0);
|
|
2577
|
+
__publicField(this, "screenLeft", true);
|
|
2578
|
+
/** Last `app_open` (cold start or return to the foreground), for `app_background.foregroundMs`. */
|
|
2579
|
+
__publicField(this, "openedAt", Date.now());
|
|
2580
|
+
__publicField(this, "launchChecked", false);
|
|
2581
|
+
/** An identity was stored before this launch (an existing install adopting lifecycle events). */
|
|
2582
|
+
__publicField(this, "hadIdentity", false);
|
|
2583
|
+
/** Language set with `setLocale()` (or the `locale` option). */
|
|
2584
|
+
__publicField(this, "locale", null);
|
|
2585
|
+
/** Language each shown guide was rendered in; added to its events as `locale`. */
|
|
2586
|
+
__publicField(this, "guideLocales", /* @__PURE__ */ new Map());
|
|
2587
|
+
/** The resource center in the visitor's language, recomputed when the config or language changes. */
|
|
2588
|
+
__publicField(this, "localizedRc", null);
|
|
2589
|
+
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
2590
|
+
__publicField(this, "announcementsSeenAtOpen", null);
|
|
2255
2591
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
2256
2592
|
__publicField(this, "eventBus", {
|
|
2257
2593
|
on: (name, cb) => {
|
|
@@ -2281,6 +2617,7 @@ var GuidelyClient = class {
|
|
|
2281
2617
|
if (!options.storage) this.log("no `storage` passed: identity, guide history, queued events and survey answers last only for this app session, so whatever could not be sent during a Guidely outage is lost when the app restarts. Pass AsyncStorage to persist them.");
|
|
2282
2618
|
this.cleanups.push(this.targets.onTap((id) => this.handleTap(id)));
|
|
2283
2619
|
if (options.user) this.user = { ...options.user };
|
|
2620
|
+
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
2284
2621
|
this.flags = this.createFlags(options.flags);
|
|
2285
2622
|
}
|
|
2286
2623
|
createFlags(opts) {
|
|
@@ -2329,9 +2666,10 @@ var GuidelyClient = class {
|
|
|
2329
2666
|
async bootstrap() {
|
|
2330
2667
|
const key = this.options.projectId;
|
|
2331
2668
|
try {
|
|
2332
|
-
await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key)]);
|
|
2669
|
+
await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key), appVersionKey(key)]);
|
|
2333
2670
|
if (this.destroyed) return;
|
|
2334
2671
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
2672
|
+
this.hadIdentity = !!this.anonymousId;
|
|
2335
2673
|
if (!this.anonymousId) {
|
|
2336
2674
|
this.anonymousId = `anon_${randomId(20)}`;
|
|
2337
2675
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
@@ -2368,6 +2706,7 @@ var GuidelyClient = class {
|
|
|
2368
2706
|
if (!this.config) await fresh;
|
|
2369
2707
|
else if (!await settlesWithin(fresh, CACHED_START_MS)) void fresh.then(() => this.applyConfigChange(before));
|
|
2370
2708
|
if (this.destroyed) return;
|
|
2709
|
+
this.checkLaunch();
|
|
2371
2710
|
this.wireTimers();
|
|
2372
2711
|
if (this.user?.id) await settlesWithin(this.identify(this.user.id, this.user.traits, this.user.account), CACHED_START_MS);
|
|
2373
2712
|
if (this.destroyed) return;
|
|
@@ -2397,6 +2736,7 @@ var GuidelyClient = class {
|
|
|
2397
2736
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
2398
2737
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
2399
2738
|
this.syncMessenger();
|
|
2739
|
+
if (this.hydrated) this.checkLaunch();
|
|
2400
2740
|
this.emitter.emit("config", this.config);
|
|
2401
2741
|
this.log("config loaded", this.config.configVersion, `${this.config.guides.length} guides`);
|
|
2402
2742
|
return true;
|
|
@@ -2417,7 +2757,7 @@ var GuidelyClient = class {
|
|
|
2417
2757
|
if (!this.isReady || !this.config || this.backgroundedAt !== null) return;
|
|
2418
2758
|
const url = this.currentUrl();
|
|
2419
2759
|
const ctx = this.getContext();
|
|
2420
|
-
for (const guide of this.
|
|
2760
|
+
for (const guide of this.candidates()) if (guide.config.trigger.type === "element_visible" && guide.config.trigger.value) this.armVisibility(guide, ctx, url);
|
|
2421
2761
|
this.drainQueue(url, ctx);
|
|
2422
2762
|
}, 1e3);
|
|
2423
2763
|
this.cleanups.push(() => clearInterval(poll));
|
|
@@ -2441,7 +2781,9 @@ var GuidelyClient = class {
|
|
|
2441
2781
|
setAppState(state) {
|
|
2442
2782
|
if (this.destroyed || !this.hydrated) return;
|
|
2443
2783
|
if (state === "background") {
|
|
2784
|
+
const wasForeground = this.backgroundedAt === null;
|
|
2444
2785
|
this.backgroundedAt = Date.now();
|
|
2786
|
+
if (wasForeground) this.wentToBackground();
|
|
2445
2787
|
this.session.lastActiveAt = nowIso();
|
|
2446
2788
|
this.persistSession();
|
|
2447
2789
|
void this.queue?.flush();
|
|
@@ -2458,6 +2800,7 @@ var GuidelyClient = class {
|
|
|
2458
2800
|
this.persistSession();
|
|
2459
2801
|
this.firedDelays.clear();
|
|
2460
2802
|
}
|
|
2803
|
+
this.cameToForeground();
|
|
2461
2804
|
void this.flags.refresh();
|
|
2462
2805
|
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
|
|
2463
2806
|
else if (this.isReady) this.evaluate("ready");
|
|
@@ -2470,6 +2813,7 @@ var GuidelyClient = class {
|
|
|
2470
2813
|
/** Tears everything down. Create a new client to start again. */
|
|
2471
2814
|
destroy() {
|
|
2472
2815
|
if (this.destroyed) return;
|
|
2816
|
+
this.flushHeatmap();
|
|
2473
2817
|
this.destroyed = true;
|
|
2474
2818
|
this.active?.controller.destroy();
|
|
2475
2819
|
this.active = null;
|
|
@@ -2521,6 +2865,7 @@ var GuidelyClient = class {
|
|
|
2521
2865
|
for (const [id, s] of Object.entries(data.guideState || {})) {
|
|
2522
2866
|
const local = guides[id];
|
|
2523
2867
|
guides[id] = {
|
|
2868
|
+
...local,
|
|
2524
2869
|
impressions: Math.max(local?.impressions ?? 0, s.impressions ?? 0),
|
|
2525
2870
|
completedAt: local?.completedAt ?? s.completedAt ?? null,
|
|
2526
2871
|
dismissedAt: local?.dismissedAt ?? s.dismissedAt ?? null,
|
|
@@ -2542,7 +2887,10 @@ var GuidelyClient = class {
|
|
|
2542
2887
|
checklists,
|
|
2543
2888
|
surveysSubmitted: Array.from(/* @__PURE__ */ new Set([...this.state.surveysSubmitted, ...data.surveysSubmitted || []])),
|
|
2544
2889
|
audienceIds: data.audienceIds || [],
|
|
2545
|
-
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt
|
|
2890
|
+
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt,
|
|
2891
|
+
// Device-local only (the server doesn't hold them).
|
|
2892
|
+
...this.state.throttle ? { throttle: this.state.throttle } : {},
|
|
2893
|
+
...this.state.announcementsSeenAt ? { announcementsSeenAt: this.state.announcementsSeenAt } : {}
|
|
2546
2894
|
};
|
|
2547
2895
|
if (data.firstSeenAt && Date.now() - Date.parse(data.firstSeenAt) > 6e4) this.firstVisit = false;
|
|
2548
2896
|
this.persistState();
|
|
@@ -2634,6 +2982,8 @@ var GuidelyClient = class {
|
|
|
2634
2982
|
...extra
|
|
2635
2983
|
};
|
|
2636
2984
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
2985
|
+
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
2986
|
+
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
2637
2987
|
this.queue.enqueue(event);
|
|
2638
2988
|
this.emitter.emit("track", event);
|
|
2639
2989
|
return event;
|
|
@@ -2666,11 +3016,19 @@ var GuidelyClient = class {
|
|
|
2666
3016
|
if (!name) return;
|
|
2667
3017
|
this.whenHydrated(() => {
|
|
2668
3018
|
const changed = name !== this.screenName;
|
|
3019
|
+
if (changed) {
|
|
3020
|
+
this.leaveScreen();
|
|
3021
|
+
this.flushHeatmap();
|
|
3022
|
+
}
|
|
2669
3023
|
this.screenName = name;
|
|
2670
3024
|
this.targets.setScreen(name);
|
|
2671
3025
|
if (changed) this.syncFlagAttributes();
|
|
2672
3026
|
const url = this.currentUrl();
|
|
2673
|
-
if (changed)
|
|
3027
|
+
if (changed) {
|
|
3028
|
+
this.screenStartedAt = Date.now();
|
|
3029
|
+
this.screenLeft = this.backgroundedAt !== null;
|
|
3030
|
+
if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name, platform: this.bridge.device.platform } });
|
|
3031
|
+
}
|
|
2674
3032
|
this.completeChecklistTasksBy("url_visited", url);
|
|
2675
3033
|
this.active?.controller.resume();
|
|
2676
3034
|
this.flows.forEach((f) => f.controller.resume());
|
|
@@ -2698,6 +3056,149 @@ var GuidelyClient = class {
|
|
|
2698
3056
|
if (this.isReady) this.evaluate("click", { targetId });
|
|
2699
3057
|
}
|
|
2700
3058
|
// ---------------------------------------------------------------------------
|
|
3059
|
+
// Product analytics (autocapture)
|
|
3060
|
+
// ---------------------------------------------------------------------------
|
|
3061
|
+
captureAllowed() {
|
|
3062
|
+
return !this.destroyed && this.analyticsEnabled();
|
|
3063
|
+
}
|
|
3064
|
+
/** A capability runs when the project enables it (absent on old servers → off), no option turns it off and analytics are allowed. */
|
|
3065
|
+
autocaptureOn(key) {
|
|
3066
|
+
const o = this.options.autocapture;
|
|
3067
|
+
return !!this.config?.settings?.autocapture?.[key] && o !== false && !(typeof o === "object" && o?.[key] === false) && this.captureAllowed();
|
|
3068
|
+
}
|
|
3069
|
+
/** `page_view` from `screen()` predates autocapture: only an explicit opt-out stops it. */
|
|
3070
|
+
pageviewsOn() {
|
|
3071
|
+
const o = this.options.autocapture;
|
|
3072
|
+
return this.config?.settings?.autocapture?.pageviews !== false && o !== false && !(typeof o === "object" && o?.pageviews === false);
|
|
3073
|
+
}
|
|
3074
|
+
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3075
|
+
isTapCaptureOn() {
|
|
3076
|
+
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps"));
|
|
3077
|
+
}
|
|
3078
|
+
/**
|
|
3079
|
+
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
3080
|
+
* bursts and a heatmap point. A registered `<GuidelyTarget>` whose frame contains the point
|
|
3081
|
+
* (smallest wins) takes precedence over the resolved element and is reported as `#<id>`.
|
|
3082
|
+
*/
|
|
3083
|
+
recordTap(tap) {
|
|
3084
|
+
try {
|
|
3085
|
+
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3086
|
+
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3087
|
+
if (!on.clicks && !on.rage && !on.heat) return;
|
|
3088
|
+
const t = tap.t ?? Date.now();
|
|
3089
|
+
const url = this.screenName ? this.currentUrl() : "";
|
|
3090
|
+
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
3091
|
+
} catch (err) {
|
|
3092
|
+
this.reportError(err);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
async processTap(tap, t, url, on) {
|
|
3096
|
+
let el = tap.element ?? null;
|
|
3097
|
+
let rect = null;
|
|
3098
|
+
const target = this.targets.ids().length ? await this.targets.hitTest(tap.x, tap.y) : null;
|
|
3099
|
+
if (target) {
|
|
3100
|
+
el = { selector: `#${target.id}`, tag: el?.tag ?? tap.hit?.tag ?? "View", text: el?.text };
|
|
3101
|
+
rect = target.rect;
|
|
3102
|
+
}
|
|
3103
|
+
if (this.destroyed) return;
|
|
3104
|
+
const platform = this.bridge.device.platform;
|
|
3105
|
+
const maskAll = !!this.config?.settings?.replay?.maskAllText;
|
|
3106
|
+
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
3107
|
+
if (on.clicks && el) this.enqueue("click", { url: eventUrl, properties: { ...tapProperties(el, tap.x, tap.y, platform, maskAll) } });
|
|
3108
|
+
const count = this.rage.add(tap.x, tap.y, t);
|
|
3109
|
+
if (on.rage && count) this.enqueue("rage_click", { url: eventUrl, properties: { ...tapProperties(el ?? tap.hit ?? UNKNOWN_VIEW, tap.x, tap.y, platform, maskAll), count } });
|
|
3110
|
+
if (!on.heat) return;
|
|
3111
|
+
const burst = this.rage.burst;
|
|
3112
|
+
if (el && !rect && el.measure) rect = await el.measure().catch(() => null);
|
|
3113
|
+
if (this.destroyed) return;
|
|
3114
|
+
if (this.heatmap.url !== url) this.flushHeatmap();
|
|
3115
|
+
if (this.heatmap.add(url, heatmapPoint(tap.x, tap.y, el, rect, burst > 0), burst)) this.flushHeatmap();
|
|
3116
|
+
}
|
|
3117
|
+
/** Sends the buffered heatmap taps as one `heatmap` event attributed to the screen they happened on. */
|
|
3118
|
+
flushHeatmap() {
|
|
3119
|
+
const batch = this.heatmap.take();
|
|
3120
|
+
if (!batch || !this.captureAllowed()) return;
|
|
3121
|
+
const collect = this.collectUrls() && !!batch.url;
|
|
3122
|
+
const { width, height } = this.windowSize();
|
|
3123
|
+
this.enqueue("heatmap", {
|
|
3124
|
+
url: collect ? batch.url : void 0,
|
|
3125
|
+
properties: { ...collect ? { path: batch.url } : {}, viewportWidth: width, viewportHeight: height, pageHeight: height, platform: this.bridge.device.platform, points: batch.points }
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
3128
|
+
windowSize() {
|
|
3129
|
+
try {
|
|
3130
|
+
const s = this.bridge.windowSize?.();
|
|
3131
|
+
if (s && Number.isFinite(s.width) && Number.isFinite(s.height)) return { width: Math.round(s.width), height: Math.round(s.height) };
|
|
3132
|
+
} catch {
|
|
3133
|
+
}
|
|
3134
|
+
return { width: 0, height: 0 };
|
|
3135
|
+
}
|
|
3136
|
+
/** Ends the foreground measurement of the current screen with a `page_leave` (once per measurement). */
|
|
3137
|
+
leaveScreen() {
|
|
3138
|
+
if (!this.screenName || this.screenLeft) return;
|
|
3139
|
+
this.screenLeft = true;
|
|
3140
|
+
if (!this.autocaptureOn("pageLeave")) return;
|
|
3141
|
+
const url = this.currentUrl();
|
|
3142
|
+
const collect = this.collectUrls();
|
|
3143
|
+
this.enqueue("page_leave", {
|
|
3144
|
+
url: collect ? url : void 0,
|
|
3145
|
+
properties: { ...collect ? { path: url } : {}, screen: this.screenName, durationMs: Math.max(0, Date.now() - this.screenStartedAt), scrollDepth: 100, platform: this.bridge.device.platform }
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
/** `{ appVersion, build }` of this launch, without unknown keys. */
|
|
3149
|
+
appInfo() {
|
|
3150
|
+
const appVersion = this.options.appVersion ?? this.bridge.device.appVersion;
|
|
3151
|
+
const build = this.options.appBuild;
|
|
3152
|
+
return { ...appVersion ? { appVersion: String(appVersion) } : {}, ...build ? { build: String(build) } : {} };
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* Once per launch, as soon as a config is known: `app_install` / `app_update` from the stored
|
|
3156
|
+
* version (when the app passes `appVersion` and `storage`), then `app_open` with `coldStart: true`.
|
|
3157
|
+
*/
|
|
3158
|
+
checkLaunch() {
|
|
3159
|
+
if (this.launchChecked || !this.config || this.destroyed || !this.hydrated) return;
|
|
3160
|
+
this.launchChecked = true;
|
|
3161
|
+
try {
|
|
3162
|
+
const on = this.autocaptureOn("appLifecycle");
|
|
3163
|
+
const info = this.appInfo();
|
|
3164
|
+
if (info.appVersion && this.options.storage) {
|
|
3165
|
+
const key = appVersionKey(this.options.projectId);
|
|
3166
|
+
const stored = readJson(this.store, key, null);
|
|
3167
|
+
if (!stored?.appVersion) {
|
|
3168
|
+
if (!this.hadIdentity && on) this.enqueue("app_install", { properties: { ...info } });
|
|
3169
|
+
} else if (stored.appVersion !== info.appVersion || !!stored.build && !!info.build && stored.build !== info.build) {
|
|
3170
|
+
if (on) this.enqueue("app_update", { properties: { ...info, previousVersion: stored.appVersion, ...stored.build ? { previousBuild: stored.build } : {} } });
|
|
3171
|
+
}
|
|
3172
|
+
writeJson(this.store, key, info);
|
|
3173
|
+
}
|
|
3174
|
+
if (on) this.enqueue("app_open", { properties: { ...info, coldStart: true } });
|
|
3175
|
+
} catch (err) {
|
|
3176
|
+
this.reportError(err);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
wentToBackground() {
|
|
3180
|
+
try {
|
|
3181
|
+
this.leaveScreen();
|
|
3182
|
+
this.flushHeatmap();
|
|
3183
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_background", { properties: { foregroundMs: Math.max(0, Date.now() - this.openedAt) } });
|
|
3184
|
+
} catch (err) {
|
|
3185
|
+
this.reportError(err);
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
cameToForeground() {
|
|
3189
|
+
try {
|
|
3190
|
+
this.openedAt = Date.now();
|
|
3191
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_open", { properties: { ...this.appInfo(), coldStart: false } });
|
|
3192
|
+
if (this.screenName && this.screenLeft) {
|
|
3193
|
+
this.screenStartedAt = Date.now();
|
|
3194
|
+
this.screenLeft = false;
|
|
3195
|
+
if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName, platform: this.bridge.device.platform, resumed: true } });
|
|
3196
|
+
}
|
|
3197
|
+
} catch (err) {
|
|
3198
|
+
this.reportError(err);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
// ---------------------------------------------------------------------------
|
|
2701
3202
|
// Targeting + triggers
|
|
2702
3203
|
// ---------------------------------------------------------------------------
|
|
2703
3204
|
/** Builds the targeting context for the current moment. */
|
|
@@ -2744,6 +3245,50 @@ var GuidelyClient = class {
|
|
|
2744
3245
|
if (guide.kind === "checklist") return !this.state.checklists[guide.id]?.dismissedAt;
|
|
2745
3246
|
return isFrequencyEligible(guide.config.frequency, this.state.guides[guide.id], this.session.shownGuides.includes(guide.id));
|
|
2746
3247
|
}
|
|
3248
|
+
/** Guide throttling: may this guide launch automatically now? */
|
|
3249
|
+
throttleAllows(guide) {
|
|
3250
|
+
if (!countsTowardThrottle(guide)) return true;
|
|
3251
|
+
return isThrottleEligible(this.config?.settings.throttling, this.state.throttle ?? []);
|
|
3252
|
+
}
|
|
3253
|
+
recordLaunch(guide, manual) {
|
|
3254
|
+
if (manual || !countsTowardThrottle(guide) || !this.config?.settings.throttling?.enabled) return;
|
|
3255
|
+
this.state.throttle = pruneLaunches([...this.state.throttle ?? [], nowIso()], this.config.settings.throttling);
|
|
3256
|
+
}
|
|
3257
|
+
/** Guides and A/B test control groups, which are evaluated the same way but never render. */
|
|
3258
|
+
candidates() {
|
|
3259
|
+
const guides = this.config?.guides ?? [];
|
|
3260
|
+
const holdouts = this.config?.guideHoldouts ?? [];
|
|
3261
|
+
return holdouts.length ? [...guides, ...holdouts.map((h) => h.guide)] : guides;
|
|
3262
|
+
}
|
|
3263
|
+
holdoutFor(guide) {
|
|
3264
|
+
return this.config?.guideHoldouts?.find((h) => h.guide.id === guide.id) ?? null;
|
|
3265
|
+
}
|
|
3266
|
+
// ---------------------------------------------------------------------------
|
|
3267
|
+
// Localization
|
|
3268
|
+
// ---------------------------------------------------------------------------
|
|
3269
|
+
/** The visitor's language candidates, most preferred first: `setLocale()` / `locale` option, traits, device. */
|
|
3270
|
+
locales() {
|
|
3271
|
+
return visitorLocales(this.locale, this.user?.traits, [this.bridge.device.locale]);
|
|
3272
|
+
}
|
|
3273
|
+
defaultLocale() {
|
|
3274
|
+
return this.config?.settings?.localization?.defaultLocale || "en";
|
|
3275
|
+
}
|
|
3276
|
+
/** The guide in the visitor's language (remembered so its events carry `locale`). */
|
|
3277
|
+
localize(guide) {
|
|
3278
|
+
const localized = localizeGuide(guide, this.locales(), this.defaultLocale());
|
|
3279
|
+
this.guideLocales.set(guide.id, localized.locale);
|
|
3280
|
+
return localized;
|
|
3281
|
+
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Sets the language for guides and the resource center (`null` = back to the user's traits and
|
|
3284
|
+
* the device language). Guides already on screen keep their language; the resource center re-renders.
|
|
3285
|
+
*/
|
|
3286
|
+
setLocale(locale) {
|
|
3287
|
+
const next = locale ? normalizeLocale(locale) || null : null;
|
|
3288
|
+
if (next === this.locale) return;
|
|
3289
|
+
this.locale = next;
|
|
3290
|
+
this.notify();
|
|
3291
|
+
}
|
|
2747
3292
|
triggerFires(guide, reason, detail) {
|
|
2748
3293
|
const t = guide.config.trigger;
|
|
2749
3294
|
switch (t.type) {
|
|
@@ -2779,7 +3324,7 @@ var GuidelyClient = class {
|
|
|
2779
3324
|
this.lastAudiences = current;
|
|
2780
3325
|
}
|
|
2781
3326
|
const candidates = [];
|
|
2782
|
-
for (const guide of this.
|
|
3327
|
+
for (const guide of this.candidates()) {
|
|
2783
3328
|
const t = guide.config.trigger;
|
|
2784
3329
|
if (detail.endedGuide && detail.endedGuide.id === guide.id) continue;
|
|
2785
3330
|
if (reason === "ready" || reason === "navigation") {
|
|
@@ -2795,6 +3340,10 @@ var GuidelyClient = class {
|
|
|
2795
3340
|
if (t.type === "page_load" && t.delayMs > 0 && reason === "timer" && detail.guideId !== guide.id) continue;
|
|
2796
3341
|
if (!this.triggerFires(guide, reason, detail)) continue;
|
|
2797
3342
|
if (!this.isEligible(guide, ctx, url)) continue;
|
|
3343
|
+
if (!this.throttleAllows(guide)) {
|
|
3344
|
+
this.log("throttled", guide.key);
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
2798
3347
|
candidates.push(guide);
|
|
2799
3348
|
}
|
|
2800
3349
|
if (reason === "after_guide" || reason === "navigation") this.drainQueue(url, ctx);
|
|
@@ -2840,6 +3389,12 @@ var GuidelyClient = class {
|
|
|
2840
3389
|
}
|
|
2841
3390
|
tryShow(guide, url, manual) {
|
|
2842
3391
|
if (this.destroyed) return false;
|
|
3392
|
+
if (!manual && !this.throttleAllows(guide)) return false;
|
|
3393
|
+
if (this.holdoutFor(guide)) {
|
|
3394
|
+
if (this.hasExclusive() && !manual) return false;
|
|
3395
|
+
this.recordImpression(guide, manual);
|
|
3396
|
+
return true;
|
|
3397
|
+
}
|
|
2843
3398
|
if (!isExclusiveGuide(guide)) {
|
|
2844
3399
|
if (this.flows.has(guide.id) || this.checklists.has(guide.id)) return false;
|
|
2845
3400
|
this.show(guide, manual);
|
|
@@ -2881,14 +3436,18 @@ var GuidelyClient = class {
|
|
|
2881
3436
|
guideState(id) {
|
|
2882
3437
|
return this.state.guides[id] ?? (this.state.guides[id] = { impressions: 0 });
|
|
2883
3438
|
}
|
|
2884
|
-
recordImpression(guide) {
|
|
3439
|
+
recordImpression(guide, manual = false) {
|
|
2885
3440
|
const s = this.guideState(guide.id);
|
|
2886
3441
|
s.impressions += 1;
|
|
2887
3442
|
s.lastShownAt = nowIso();
|
|
2888
3443
|
if (!this.session.shownGuides.includes(guide.id)) this.session.shownGuides.push(guide.id);
|
|
3444
|
+
this.recordLaunch(guide, manual);
|
|
2889
3445
|
this.persistState();
|
|
2890
3446
|
this.persistSession();
|
|
2891
|
-
this.
|
|
3447
|
+
const holdout = this.holdoutFor(guide);
|
|
3448
|
+
const experiment = holdout ? { id: holdout.experimentId, variantId: holdout.variantId } : guide.experiment;
|
|
3449
|
+
if (experiment) this.enqueue("experiment_viewed", { properties: { guideExperimentId: experiment.id, variantId: experiment.variantId, ...holdout ? {} : { guideId: guide.id } } });
|
|
3450
|
+
if (!holdout) this.syncGuideState(guide.id, { impression: true });
|
|
2892
3451
|
}
|
|
2893
3452
|
recordEnd(guide, reason) {
|
|
2894
3453
|
const s = this.guideState(guide.id);
|
|
@@ -2916,7 +3475,8 @@ var GuidelyClient = class {
|
|
|
2916
3475
|
this.reportError(err);
|
|
2917
3476
|
}
|
|
2918
3477
|
}
|
|
2919
|
-
showFlow(
|
|
3478
|
+
showFlow(raw, manual, stepId) {
|
|
3479
|
+
const guide = this.localize(raw);
|
|
2920
3480
|
const exclusive = isExclusiveGuide(guide);
|
|
2921
3481
|
const flow = { guide, manual, view: null, controller: null };
|
|
2922
3482
|
let renders = 0;
|
|
@@ -2931,7 +3491,7 @@ var GuidelyClient = class {
|
|
|
2931
3491
|
const step = e.stepId ? guide.config.steps.find((s) => s.id === e.stepId) : void 0;
|
|
2932
3492
|
this.enqueue(e.name, { guideId: guide.id, guideVersion: guide.version, stepId: e.stepId, stepIndex: e.stepIndex, properties: e.properties });
|
|
2933
3493
|
if (e.name === "guide_started") {
|
|
2934
|
-
this.recordImpression(guide);
|
|
3494
|
+
this.recordImpression(guide, manual);
|
|
2935
3495
|
this.emitter.emit("guide:start", guide);
|
|
2936
3496
|
} else if (e.name === "step_viewed" && step) {
|
|
2937
3497
|
this.emitter.emit("guide:step", { guide, stepId: step.id, stepIndex: e.stepIndex ?? 0 });
|
|
@@ -2978,21 +3538,24 @@ var GuidelyClient = class {
|
|
|
2978
3538
|
if (this.flows.get(guide.id) === flow) this.flows.delete(guide.id);
|
|
2979
3539
|
}
|
|
2980
3540
|
}
|
|
2981
|
-
showChecklist(
|
|
3541
|
+
showChecklist(raw, open) {
|
|
3542
|
+
if (!raw.config.checklist || this.checklists.has(raw.id)) return;
|
|
3543
|
+
const guide = this.localize(raw);
|
|
2982
3544
|
const config = guide.config.checklist;
|
|
2983
|
-
if (!config
|
|
3545
|
+
if (!config) return;
|
|
2984
3546
|
const progress = this.getChecklistProgress(guide.id);
|
|
2985
3547
|
const first = !this.state.guides[guide.id]?.impressions;
|
|
2986
|
-
this.checklists.set(guide.id, { open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
3548
|
+
this.checklists.set(guide.id, { guide, open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
2987
3549
|
if (first) this.recordImpression(guide);
|
|
2988
3550
|
if (this.checklists.get(guide.id)?.open) this.enqueue("guide_viewed", { guideId: guide.id, guideVersion: guide.version });
|
|
2989
3551
|
this.notify();
|
|
2990
3552
|
}
|
|
2991
|
-
showSurvey(
|
|
2992
|
-
if (!
|
|
3553
|
+
showSurvey(raw, manual) {
|
|
3554
|
+
if (!raw.config.survey) return;
|
|
3555
|
+
const guide = this.localize(raw);
|
|
2993
3556
|
this.activeSurvey = { guide, manual };
|
|
2994
3557
|
this.surveyView = { key: `${guide.id}:${randomId(6)}`, guide, submitted: false };
|
|
2995
|
-
this.recordImpression(guide);
|
|
3558
|
+
this.recordImpression(guide, manual);
|
|
2996
3559
|
this.enqueue("survey_displayed", { guideId: guide.id, guideVersion: guide.version });
|
|
2997
3560
|
this.emitter.emit("guide:start", guide);
|
|
2998
3561
|
this.notify();
|
|
@@ -3052,10 +3615,7 @@ var GuidelyClient = class {
|
|
|
3052
3615
|
const steps = [];
|
|
3053
3616
|
for (const f of [...this.flows.values(), ...this.active ? [this.active] : []]) if (f.view) steps.push({ ...f.view, expanded: this.expanded.has(f.guide.id) });
|
|
3054
3617
|
const checklists = [];
|
|
3055
|
-
this.checklists.forEach((w, id) => {
|
|
3056
|
-
const guide = this.findGuide(id);
|
|
3057
|
-
if (guide) checklists.push({ guide, progress: this.getChecklistProgress(id), open: w.open });
|
|
3058
|
-
});
|
|
3618
|
+
this.checklists.forEach((w, id) => checklists.push({ guide: w.guide, progress: this.getChecklistProgress(id), open: w.open }));
|
|
3059
3619
|
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, theme: this.themeFor(), messenger: !!this.messenger };
|
|
3060
3620
|
for (const cb of Array.from(this.uiListeners)) {
|
|
3061
3621
|
try {
|
|
@@ -3140,10 +3700,58 @@ var GuidelyClient = class {
|
|
|
3140
3700
|
getAudienceIds() {
|
|
3141
3701
|
return this.state.audienceIds;
|
|
3142
3702
|
}
|
|
3703
|
+
/** The published resource center in the visitor's language, or null when it is off. */
|
|
3143
3704
|
getResourceCenter() {
|
|
3144
|
-
const rc = this.
|
|
3705
|
+
const rc = this.localizedResourceCenter();
|
|
3145
3706
|
return rc && rc.enabled ? rc : null;
|
|
3146
3707
|
}
|
|
3708
|
+
/** The resource center in the visitor's language (cached until the config or language changes). */
|
|
3709
|
+
localizedResourceCenter() {
|
|
3710
|
+
const rc = this.config?.resourceCenter;
|
|
3711
|
+
if (!rc) return null;
|
|
3712
|
+
const locales = this.locales();
|
|
3713
|
+
const key = `${locales.join(",")}|${this.defaultLocale()}`;
|
|
3714
|
+
if (this.localizedRc?.source !== rc || this.localizedRc.locales !== key) this.localizedRc = { source: rc, locales: key, value: localizeResourceCenter(rc, locales, this.defaultLocale()) };
|
|
3715
|
+
return this.localizedRc.value;
|
|
3716
|
+
}
|
|
3717
|
+
/** Evaluates targeting rules (e.g. a resource-center section's) for the current visitor and screen. */
|
|
3718
|
+
matchesTargeting(rules) {
|
|
3719
|
+
if (!rules) return true;
|
|
3720
|
+
try {
|
|
3721
|
+
return evaluateRules(rules, this.getContext());
|
|
3722
|
+
} catch (err) {
|
|
3723
|
+
this.reportError(err);
|
|
3724
|
+
return false;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
/** Resource-center sections this visitor sees, in order (disabled and non-matching ones left out). */
|
|
3728
|
+
getResourceCenterModules() {
|
|
3729
|
+
const rc = this.getResourceCenter();
|
|
3730
|
+
if (!rc) return [];
|
|
3731
|
+
return orderedModules(rc.modules).filter((m) => m.enabled && (!("targeting" in m) || this.matchesTargeting(m.targeting))).map((m) => m.id);
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Sections of the messenger's Help tab, in order: the resource center's modules without "What's
|
|
3735
|
+
* new" (the messenger has its own News tab); every section when no resource center is published.
|
|
3736
|
+
*/
|
|
3737
|
+
getHelpModules() {
|
|
3738
|
+
const modules = this.getResourceCenter() ? this.getResourceCenterModules() : [...RC_MODULE_IDS];
|
|
3739
|
+
return modules.filter((id) => id !== "announcements");
|
|
3740
|
+
}
|
|
3741
|
+
/** "What's new" entries this visitor sees (newest first); empty when the section is off. */
|
|
3742
|
+
getAnnouncements() {
|
|
3743
|
+
const rc = this.getResourceCenter();
|
|
3744
|
+
if (!rc || !this.getResourceCenterModules().includes("announcements")) return [];
|
|
3745
|
+
return visibleAnnouncements(rc.announcements, this.state.audienceIds);
|
|
3746
|
+
}
|
|
3747
|
+
/** "What's new" entries published since the visitor last opened the resource center. */
|
|
3748
|
+
getUnreadAnnouncements() {
|
|
3749
|
+
return unreadAnnouncements(this.getAnnouncements(), this.state.announcementsSeenAt);
|
|
3750
|
+
}
|
|
3751
|
+
/** Whether an entry is new to the visitor in the resource center that is open now. */
|
|
3752
|
+
isAnnouncementNew(a) {
|
|
3753
|
+
return unreadAnnouncements([a], this.announcementsSeenAtOpen) > 0;
|
|
3754
|
+
}
|
|
3147
3755
|
async searchResourceCenter(q) {
|
|
3148
3756
|
try {
|
|
3149
3757
|
const res = await this.api.get("/resource-center/search", { params: { q } });
|
|
@@ -3152,6 +3760,11 @@ var GuidelyClient = class {
|
|
|
3152
3760
|
return [];
|
|
3153
3761
|
}
|
|
3154
3762
|
}
|
|
3763
|
+
/** The language of the resource center as shown, when it isn't the default (sent with article fetches). */
|
|
3764
|
+
articleLocale() {
|
|
3765
|
+
const locale = this.localizedResourceCenter()?.locale;
|
|
3766
|
+
return locale && locale.toLowerCase() !== this.defaultLocale().toLowerCase() ? locale : void 0;
|
|
3767
|
+
}
|
|
3155
3768
|
articleViewed(article) {
|
|
3156
3769
|
this.enqueue("article_viewed", { properties: { articleId: article.id, slug: article.slug } });
|
|
3157
3770
|
}
|
|
@@ -3286,6 +3899,11 @@ var GuidelyClient = class {
|
|
|
3286
3899
|
}
|
|
3287
3900
|
if (this.resourceCenterOpen) return;
|
|
3288
3901
|
this.resourceCenterOpen = true;
|
|
3902
|
+
this.announcementsSeenAtOpen = this.state.announcementsSeenAt ?? null;
|
|
3903
|
+
if (this.getAnnouncements().length) {
|
|
3904
|
+
this.state.announcementsSeenAt = nowIso();
|
|
3905
|
+
this.persistState();
|
|
3906
|
+
}
|
|
3289
3907
|
this.enqueue("resource_center_opened");
|
|
3290
3908
|
this.emitter.emit("resource-center:open");
|
|
3291
3909
|
this.notify();
|
|
@@ -3321,7 +3939,8 @@ var GuidelyClient = class {
|
|
|
3321
3939
|
onMessage: (message, conversation) => this.emitter.emit("conversation:message", { message, conversation }),
|
|
3322
3940
|
onStartGuide: (key) => this.setTimer(() => this.startGuide(key), 0),
|
|
3323
3941
|
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err),
|
|
3324
|
-
health: this.api.health
|
|
3942
|
+
health: this.api.health,
|
|
3943
|
+
locale: () => this.articleLocale()
|
|
3325
3944
|
});
|
|
3326
3945
|
this.resourceCenterOpen = false;
|
|
3327
3946
|
}
|
|
@@ -3455,7 +4074,191 @@ var GuidelyClient = class {
|
|
|
3455
4074
|
}
|
|
3456
4075
|
};
|
|
3457
4076
|
function forPlatform(config, platform) {
|
|
3458
|
-
|
|
4077
|
+
const keep = (g) => supportsPlatform(g.config, platform);
|
|
4078
|
+
const holdouts = config.guideHoldouts?.filter((h) => keep(h.guide)).map((h) => ({ ...h, guide: withoutCode(h.guide) }));
|
|
4079
|
+
return { ...config, guides: config.guides.filter(keep).map(withoutCode), ...holdouts ? { guideHoldouts: holdouts } : {} };
|
|
4080
|
+
}
|
|
4081
|
+
function withoutCode(guide) {
|
|
4082
|
+
if (!guide.config.steps.some((s) => s.code)) return guide;
|
|
4083
|
+
return { ...guide, config: { ...guide.config, steps: guide.config.steps.map(({ code: _code, ...step }) => step) } };
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
// src/ui/Autocapture.tsx
|
|
4087
|
+
var import_react8 = require("react");
|
|
4088
|
+
var import_react_native11 = require("react-native");
|
|
4089
|
+
|
|
4090
|
+
// src/core/viewTree.ts
|
|
4091
|
+
var HOST_TEXT = 6;
|
|
4092
|
+
var MAX_ANCESTORS = 500;
|
|
4093
|
+
var MAX_TEXT_NODES = 300;
|
|
4094
|
+
var INTERACTIVE_ROLES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem", "checkbox", "switch", "radio", "togglebutton", "imagebutton", "option", "combobox", "adjustable"]);
|
|
4095
|
+
var TEXT_INPUT_HOST = /TextInput/i;
|
|
4096
|
+
var NOISE = /^(Anonymous|Unknown|ForwardRef|Memo|Context|Provider|Consumer|Fragment|StrictMode|Suspense|Profiler)$|Context$|Provider$|Consumer$/;
|
|
4097
|
+
var isFiber = (v) => !!v && typeof v === "object" && "return" in v && "memoizedProps" in v;
|
|
4098
|
+
function fiberFromEvent(event) {
|
|
4099
|
+
try {
|
|
4100
|
+
const e = event;
|
|
4101
|
+
if (isFiber(e?._targetInst)) return e._targetInst;
|
|
4102
|
+
const target = e?.target;
|
|
4103
|
+
if (!target || typeof target !== "object") return null;
|
|
4104
|
+
for (const key of ["__internalInstanceHandle", "_internalInstanceHandle", "_internalFiberInstanceHandleDEV"]) if (isFiber(target[key])) return target[key];
|
|
4105
|
+
for (const sym of Object.getOwnPropertySymbols(target)) if (sym.description === "internalInstanceHandle" && isFiber(target[sym])) return target[sym];
|
|
4106
|
+
} catch {
|
|
4107
|
+
}
|
|
4108
|
+
return null;
|
|
4109
|
+
}
|
|
4110
|
+
function componentName(type, depth = 0) {
|
|
4111
|
+
if (!type || depth > 3) return null;
|
|
4112
|
+
if (typeof type === "function") {
|
|
4113
|
+
const f = type;
|
|
4114
|
+
const n = typeof f.displayName === "string" && f.displayName ? f.displayName : typeof f.name === "string" ? f.name : "";
|
|
4115
|
+
return n || null;
|
|
4116
|
+
}
|
|
4117
|
+
if (typeof type === "object") {
|
|
4118
|
+
const o = type;
|
|
4119
|
+
if (typeof o.displayName === "string" && o.displayName) return o.displayName;
|
|
4120
|
+
return componentName(o.render, depth + 1) ?? componentName(o.type, depth + 1);
|
|
4121
|
+
}
|
|
4122
|
+
return null;
|
|
4123
|
+
}
|
|
4124
|
+
var isHost = (f) => typeof f.type === "string";
|
|
4125
|
+
var isTextInput = (f) => typeof f.type === "string" ? TEXT_INPUT_HOST.test(f.type) : componentName(f.type) === "TextInput";
|
|
4126
|
+
function isInteractive(props) {
|
|
4127
|
+
if (!props || typeof props !== "object") return null;
|
|
4128
|
+
if (typeof props.onPress === "function" || typeof props.onLongPress === "function") return "press";
|
|
4129
|
+
const role = props.accessibilityRole ?? props.role;
|
|
4130
|
+
return typeof role === "string" && INTERACTIVE_ROLES.has(role) ? "role" : null;
|
|
4131
|
+
}
|
|
4132
|
+
function firstHost(f) {
|
|
4133
|
+
let cur = f;
|
|
4134
|
+
for (let i = 0; cur && i < 30; i++, cur = cur.child) if (isHost(cur)) return cur;
|
|
4135
|
+
return null;
|
|
4136
|
+
}
|
|
4137
|
+
function idSelector(f) {
|
|
4138
|
+
const host = firstHost(f);
|
|
4139
|
+
const sources = [f.memoizedProps, host?.memoizedProps];
|
|
4140
|
+
for (const key of ["nativeID", "testID"]) {
|
|
4141
|
+
for (const p of sources) {
|
|
4142
|
+
const v = p && typeof p === "object" ? p[key] : void 0;
|
|
4143
|
+
if (typeof v === "string" && v) return attrSelector(key, v);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
return null;
|
|
4147
|
+
}
|
|
4148
|
+
function pathSelector(f) {
|
|
4149
|
+
const parts = [];
|
|
4150
|
+
let cur = f;
|
|
4151
|
+
for (let i = 0; cur && parts.length < 4 && i < MAX_ANCESTORS; i++, cur = cur.return) {
|
|
4152
|
+
if (isHost(cur)) continue;
|
|
4153
|
+
const name = componentName(cur.type);
|
|
4154
|
+
if (!name || NOISE.test(name)) continue;
|
|
4155
|
+
let same = 0;
|
|
4156
|
+
let index = 0;
|
|
4157
|
+
for (let s = cur.return?.child, n = 0; s && n < 500; s = s.sibling, n++) {
|
|
4158
|
+
if (s.type !== cur.type) continue;
|
|
4159
|
+
same += 1;
|
|
4160
|
+
if (s === cur) index = same;
|
|
4161
|
+
}
|
|
4162
|
+
parts.unshift(same > 1 && index ? `${name}:nth-of-type(${index})` : name);
|
|
4163
|
+
}
|
|
4164
|
+
return parts.join(" > ").slice(0, 300) || "View";
|
|
4165
|
+
}
|
|
4166
|
+
function subtreeText(f, opts) {
|
|
4167
|
+
const out = [];
|
|
4168
|
+
let visited = 0;
|
|
4169
|
+
let masked = false;
|
|
4170
|
+
const walk = (node) => {
|
|
4171
|
+
for (let n = node; n && visited < MAX_TEXT_NODES && !masked; n = n.sibling) {
|
|
4172
|
+
visited += 1;
|
|
4173
|
+
if (opts.isMask?.(n.type)) {
|
|
4174
|
+
masked = true;
|
|
4175
|
+
return;
|
|
4176
|
+
}
|
|
4177
|
+
if (isTextInput(n)) continue;
|
|
4178
|
+
if (n.tag === HOST_TEXT && (typeof n.memoizedProps === "string" || typeof n.memoizedProps === "number")) out.push(String(n.memoizedProps));
|
|
4179
|
+
else walk(n.child);
|
|
4180
|
+
}
|
|
4181
|
+
};
|
|
4182
|
+
if (opts.isMask?.(f.type)) return { text: "", masked: true };
|
|
4183
|
+
walk(f.child);
|
|
4184
|
+
return { text: out.join(" "), masked };
|
|
4185
|
+
}
|
|
4186
|
+
function measureHost(host) {
|
|
4187
|
+
return new Promise((resolve) => {
|
|
4188
|
+
let settled = false;
|
|
4189
|
+
const done = (r) => {
|
|
4190
|
+
if (settled) return;
|
|
4191
|
+
settled = true;
|
|
4192
|
+
clearTimeout(timer);
|
|
4193
|
+
resolve(r);
|
|
4194
|
+
};
|
|
4195
|
+
const timer = setTimeout(() => done(null), 500);
|
|
4196
|
+
const cb = (x, y, width, height) => done(Number.isFinite(x) && Number.isFinite(y) && width > 0 && height > 0 ? { x, y, width, height } : null);
|
|
4197
|
+
try {
|
|
4198
|
+
const sn = host.stateNode;
|
|
4199
|
+
const pub = sn?.canonical?.publicInstance ?? sn;
|
|
4200
|
+
if (pub && typeof pub.measureInWindow === "function") return pub.measureInWindow(cb);
|
|
4201
|
+
const ui = globalThis.nativeFabricUIManager;
|
|
4202
|
+
if (sn?.node && typeof ui?.measureInWindow === "function") return ui.measureInWindow(sn.node, cb);
|
|
4203
|
+
} catch {
|
|
4204
|
+
}
|
|
4205
|
+
done(null);
|
|
4206
|
+
});
|
|
4207
|
+
}
|
|
4208
|
+
function describe(f, masked, opts, withText) {
|
|
4209
|
+
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4210
|
+
const host = firstHost(f);
|
|
4211
|
+
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
4212
|
+
if (withText && !masked) {
|
|
4213
|
+
const sub = subtreeText(f, opts);
|
|
4214
|
+
if (!sub.masked) {
|
|
4215
|
+
const hostProps = host?.memoizedProps && typeof host.memoizedProps === "object" ? host.memoizedProps : {};
|
|
4216
|
+
const label = props.accessibilityLabel ?? props["aria-label"] ?? hostProps.accessibilityLabel ?? hostProps["aria-label"];
|
|
4217
|
+
const text = cleanText(sub.text) ?? (typeof label === "string" ? cleanText(label) : void 0);
|
|
4218
|
+
if (text) el.text = text;
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
if (host) el.measure = () => measureHost(host);
|
|
4222
|
+
return el;
|
|
4223
|
+
}
|
|
4224
|
+
function resolveFiber(target, opts = {}) {
|
|
4225
|
+
const none = { ignored: false, element: null, hit: null };
|
|
4226
|
+
if (!target) return none;
|
|
4227
|
+
try {
|
|
4228
|
+
let press = null;
|
|
4229
|
+
let role = null;
|
|
4230
|
+
let hitFiber = null;
|
|
4231
|
+
let stopped = false;
|
|
4232
|
+
let masked = false;
|
|
4233
|
+
for (let f = target, i = 0; f && i < MAX_ANCESTORS; f = f.return, i++) {
|
|
4234
|
+
if (opts.isIgnored?.(f.type)) return { ignored: true, element: null, hit: null };
|
|
4235
|
+
if (opts.isMask?.(f.type)) masked = true;
|
|
4236
|
+
if (stopped) continue;
|
|
4237
|
+
if (isTextInput(f)) {
|
|
4238
|
+
stopped = true;
|
|
4239
|
+
continue;
|
|
4240
|
+
}
|
|
4241
|
+
if (isHost(f)) continue;
|
|
4242
|
+
const name = componentName(f.type);
|
|
4243
|
+
if (!hitFiber && name && !NOISE.test(name)) hitFiber = f;
|
|
4244
|
+
const kind = isInteractive(f.memoizedProps);
|
|
4245
|
+
if (kind === "press") {
|
|
4246
|
+
press = f;
|
|
4247
|
+
stopped = true;
|
|
4248
|
+
} else if (kind === "role" && !role) role = f;
|
|
4249
|
+
}
|
|
4250
|
+
const element = press ?? role;
|
|
4251
|
+
return {
|
|
4252
|
+
ignored: false,
|
|
4253
|
+
element: element ? describe(element, masked, opts, true) : null,
|
|
4254
|
+
hit: hitFiber ? describe(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4255
|
+
};
|
|
4256
|
+
} catch {
|
|
4257
|
+
return none;
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
function resolveTapTarget(event, opts = {}) {
|
|
4261
|
+
return resolveFiber(fiberFromEvent(event), opts);
|
|
3459
4262
|
}
|
|
3460
4263
|
|
|
3461
4264
|
// src/ui/Overlay.tsx
|
|
@@ -4215,25 +5018,41 @@ function Help({ store, client, palette: p }) {
|
|
|
4215
5018
|
] }, title);
|
|
4216
5019
|
const audiences = client.getAudienceIds();
|
|
4217
5020
|
const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
|
|
4218
|
-
const
|
|
5021
|
+
const modules = client.getHelpModules();
|
|
5022
|
+
const searchOn = modules.includes("search");
|
|
5023
|
+
const listed = searchOn ? results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences)) : void 0;
|
|
5024
|
+
const checklists = (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist");
|
|
5025
|
+
const renderModule = (id) => {
|
|
5026
|
+
switch (id) {
|
|
5027
|
+
case "guides":
|
|
5028
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null;
|
|
5029
|
+
case "checklists":
|
|
5030
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u2611"))) : null;
|
|
5031
|
+
case "articles":
|
|
5032
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
5033
|
+
!articles ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.ActivityIndicator, { color: p.muted }) : null,
|
|
5034
|
+
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a)))))
|
|
5035
|
+
] });
|
|
5036
|
+
case "faqs":
|
|
5037
|
+
return rc?.faqs.length ? section(
|
|
5038
|
+
"FAQ",
|
|
5039
|
+
rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
|
|
5040
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
|
|
5041
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
|
|
5042
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
5043
|
+
] }),
|
|
5044
|
+
faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
5045
|
+
] }, f.id))
|
|
5046
|
+
) : null;
|
|
5047
|
+
case "links":
|
|
5048
|
+
return rc?.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
5049
|
+
default:
|
|
5050
|
+
return null;
|
|
5051
|
+
}
|
|
5052
|
+
};
|
|
4219
5053
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
|
|
4220
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.TextInput, { value: q, onChangeText: setQ, placeholder: "Search articles", placeholderTextColor: p.muted, accessibilityLabel: "Search help articles", returnKeyType: "search", style: [styles4.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }] }),
|
|
4221
|
-
listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ (0, import_jsx_runtime5.
|
|
4222
|
-
recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null,
|
|
4223
|
-
!articles ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.ActivityIndicator, { color: p.muted }) : null,
|
|
4224
|
-
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a))))),
|
|
4225
|
-
rc?.faqs.length ? section(
|
|
4226
|
-
"FAQ",
|
|
4227
|
-
rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
|
|
4228
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
|
|
4229
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
|
|
4230
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
4231
|
-
] }),
|
|
4232
|
-
faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
4233
|
-
] }, f.id))
|
|
4234
|
-
) : null,
|
|
4235
|
-
rc?.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null
|
|
4236
|
-
] })
|
|
5054
|
+
searchOn ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.TextInput, { value: q, onChangeText: setQ, placeholder: "Search articles", placeholderTextColor: p.muted, accessibilityLabel: "Search help articles", returnKeyType: "search", style: [styles4.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }] }) : null,
|
|
5055
|
+
listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: modules.filter((id) => id !== "search").map((id) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react3.Fragment, { children: renderModule(id) }, id)) })
|
|
4237
5056
|
] });
|
|
4238
5057
|
}
|
|
4239
5058
|
function Article({ article, palette: p, client }) {
|
|
@@ -4336,6 +5155,8 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4336
5155
|
const checklists = (0, import_react4.useMemo)(() => (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist"), [rc, guides]);
|
|
4337
5156
|
if (!rc) return null;
|
|
4338
5157
|
const close = () => client.closeResourceCenter();
|
|
5158
|
+
const modules = client.getResourceCenterModules();
|
|
5159
|
+
const announcements = client.getAnnouncements();
|
|
4339
5160
|
const openArticle = (a) => {
|
|
4340
5161
|
if (a.externalUrl) return client.navigate(a.externalUrl);
|
|
4341
5162
|
client.articleViewed(a);
|
|
@@ -4354,7 +5175,51 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4354
5175
|
] }, title);
|
|
4355
5176
|
const audiences = client.getAudienceIds();
|
|
4356
5177
|
const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
|
|
4357
|
-
const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
|
|
5178
|
+
const listed = modules.includes("search") ? results?.filter((a) => isArticleListed(rc.collections, a, audiences)) : void 0;
|
|
5179
|
+
const link = (label, onPress) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 8, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.link, { color: p.primary }], children: label }) }, label);
|
|
5180
|
+
const announcement2 = (a) => {
|
|
5181
|
+
const linked = a.articleId ? rc.articles.find((x) => x.id === a.articleId) : void 0;
|
|
5182
|
+
const isNew = client.isAnnouncementNew(a);
|
|
5183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5184
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles5.faqHead, children: [
|
|
5185
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { accessibilityRole: "header", style: [styles5.rowTitle, styles5.flex, { color: p.text, fontFamily: p.fontFamily }], children: a.title }),
|
|
5186
|
+
isNew ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.badge, { color: p.onPrimary, backgroundColor: p.primary }], children: "NEW" }) : null
|
|
5187
|
+
] }),
|
|
5188
|
+
a.body ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: a.body, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null,
|
|
5189
|
+
a.guideId || linked || a.url ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles5.links, children: [
|
|
5190
|
+
a.guideId ? link("Show me", () => (close(), client.startGuide(a.guideId))) : null,
|
|
5191
|
+
linked ? link("Read more", () => openArticle(linked)) : null,
|
|
5192
|
+
a.url ? link(a.linkLabel || "Learn more", () => client.navigate(a.url)) : null
|
|
5193
|
+
] }) : null
|
|
5194
|
+
] }, a.id);
|
|
5195
|
+
};
|
|
5196
|
+
const renderModule = (id) => {
|
|
5197
|
+
switch (id) {
|
|
5198
|
+
case "announcements":
|
|
5199
|
+
return announcements.length ? section("What's new", announcements.map(announcement2)) : null;
|
|
5200
|
+
case "guides":
|
|
5201
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null;
|
|
5202
|
+
case "checklists":
|
|
5203
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null;
|
|
5204
|
+
case "articles":
|
|
5205
|
+
return sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => openArticle(a)))));
|
|
5206
|
+
case "faqs":
|
|
5207
|
+
return rc.faqs.length ? section(
|
|
5208
|
+
"FAQ",
|
|
5209
|
+
rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5210
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
|
|
5211
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
|
|
5212
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
5213
|
+
] }),
|
|
5214
|
+
faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
5215
|
+
] }, f.id))
|
|
5216
|
+
) : null;
|
|
5217
|
+
case "links":
|
|
5218
|
+
return rc.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
5219
|
+
default:
|
|
5220
|
+
return null;
|
|
5221
|
+
}
|
|
5222
|
+
};
|
|
4358
5223
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Modal, { visible: open, animationType: "slide", presentationStyle: import_react_native6.Platform.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => article ? setArticle(null) : close(), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.sheet, { backgroundColor: p.background, paddingTop: import_react_native6.Platform.OS === "ios" ? 8 : topInset }], children: [
|
|
4359
5224
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.header, { borderColor: p.border }], children: [
|
|
4360
5225
|
article ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Pressable, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => setArticle(null), hitSlop: 10, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
|
|
@@ -4365,7 +5230,7 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4365
5230
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { accessibilityRole: "header", style: [styles5.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
|
|
4366
5231
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
|
|
4367
5232
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.ScrollView, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], keyboardShouldPersistTaps: "handled", children: [
|
|
4368
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
5233
|
+
modules.includes("search") ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4369
5234
|
import_react_native6.TextInput,
|
|
4370
5235
|
{
|
|
4371
5236
|
value: query,
|
|
@@ -4377,26 +5242,11 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4377
5242
|
returnKeyType: "search",
|
|
4378
5243
|
style: [styles5.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }]
|
|
4379
5244
|
}
|
|
4380
|
-
),
|
|
5245
|
+
) : null,
|
|
4381
5246
|
listed ? section(
|
|
4382
5247
|
`Results for \u201C${query.trim()}\u201D`,
|
|
4383
5248
|
listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
|
|
4384
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime6.
|
|
4385
|
-
recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null,
|
|
4386
|
-
checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null,
|
|
4387
|
-
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))))),
|
|
4388
|
-
rc.faqs.length ? section(
|
|
4389
|
-
"FAQ",
|
|
4390
|
-
rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
4391
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
|
|
4392
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
|
|
4393
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
4394
|
-
] }),
|
|
4395
|
-
faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
4396
|
-
] }, f.id))
|
|
4397
|
-
) : null,
|
|
4398
|
-
rc.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null
|
|
4399
|
-
] })
|
|
5249
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: modules.filter((id) => id !== "search").map((id) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react4.Fragment, { children: renderModule(id) }, id)) })
|
|
4400
5250
|
] })
|
|
4401
5251
|
] }) });
|
|
4402
5252
|
}
|
|
@@ -4416,7 +5266,10 @@ var styles5 = import_react_native6.StyleSheet.create({
|
|
|
4416
5266
|
chev: { fontSize: 18 },
|
|
4417
5267
|
faq: { flexDirection: "column", alignItems: "stretch" },
|
|
4418
5268
|
faqHead: { flexDirection: "row", alignItems: "center", gap: 10 },
|
|
4419
|
-
articleTitle: { fontSize: 22, fontWeight: "700" }
|
|
5269
|
+
articleTitle: { fontSize: 22, fontWeight: "700" },
|
|
5270
|
+
badge: { fontSize: 10, fontWeight: "800", letterSpacing: 0.5, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 6, overflow: "hidden" },
|
|
5271
|
+
links: { flexDirection: "row", flexWrap: "wrap", gap: 16, marginTop: 4 },
|
|
5272
|
+
link: { fontSize: 14, fontWeight: "600" }
|
|
4420
5273
|
});
|
|
4421
5274
|
|
|
4422
5275
|
// src/ui/StepLayer.tsx
|
|
@@ -4462,7 +5315,10 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
4462
5315
|
|
|
4463
5316
|
// src/ui/StepCard.tsx
|
|
4464
5317
|
var import_react_native7 = require("react-native");
|
|
4465
|
-
var import_jsx_runtime7 =
|
|
5318
|
+
var import_jsx_runtime7 = (
|
|
5319
|
+
// An empty description marks the image as decorative: screen readers skip it.
|
|
5320
|
+
require("react/jsx-runtime")
|
|
5321
|
+
);
|
|
4466
5322
|
function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
4467
5323
|
const { step, guide } = view;
|
|
4468
5324
|
const buttons = visibleButtons(step, view.canGoBack);
|
|
@@ -4470,7 +5326,16 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
|
4470
5326
|
const showProgress = step.showProgress && view.total > 1;
|
|
4471
5327
|
const onLink = (url) => client.navigate(url);
|
|
4472
5328
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Surface, { palette: p, style: [{ width }, style], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { accessible: false, accessibilityViewIsModal: view.asModal || step.type === "modal", style: compact ? styles6.compact : styles6.inner, children: [
|
|
4473
|
-
step.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
5329
|
+
step.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
5330
|
+
import_react_native7.Image,
|
|
5331
|
+
{
|
|
5332
|
+
source: { uri: step.imageUrl },
|
|
5333
|
+
style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }],
|
|
5334
|
+
resizeMode: "cover",
|
|
5335
|
+
accessibilityIgnoresInvertColors: true,
|
|
5336
|
+
...step.imageAlt?.trim() ? { accessible: true, accessibilityRole: "image", accessibilityLabel: step.imageAlt.trim() } : { accessible: false, importantForAccessibility: "no" }
|
|
5337
|
+
}
|
|
5338
|
+
) : null,
|
|
4474
5339
|
step.title ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: styles6.header, children: [
|
|
4475
5340
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Text, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
|
|
4476
5341
|
step.showClose ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
|
|
@@ -5058,18 +5923,22 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5058
5923
|
const basePalette = palette(ui.theme, scheme);
|
|
5059
5924
|
const messenger = ui.messenger ? client.getMessenger() : null;
|
|
5060
5925
|
const showRcLauncher = !messenger && !!client.options.showResourceCenterLauncher && !!client.getResourceCenter();
|
|
5926
|
+
const unread = showRcLauncher ? client.getUnreadAnnouncements() : 0;
|
|
5061
5927
|
const messengerLeft = messenger?.getSnapshot().config.launcher.position === "bottom-left";
|
|
5062
5928
|
const checklistOnSameSide = ui.checklists.some((c) => c.guide.config.checklist?.position === "bottom-left" === messengerLeft) && client.options.showChecklistLauncher !== false;
|
|
5063
5929
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { ref, collapsable: false, pointerEvents: "box-none", style: import_react_native10.StyleSheet.absoluteFill, onLayout: measureOrigin, children: [
|
|
5064
5930
|
ui.checklists.map((c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Checklist, { view: c, client, palette: palette(client.themeFor(c.guide), scheme), geometry, showLauncher: client.options.showChecklistLauncher !== false, launcherBottom: launcherOffset }, c.guide.id)),
|
|
5065
|
-
showRcLauncher ? /* @__PURE__ */ (0, import_jsx_runtime10.
|
|
5931
|
+
showRcLauncher ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
5066
5932
|
import_react_native10.Pressable,
|
|
5067
5933
|
{
|
|
5068
5934
|
accessibilityRole: "button",
|
|
5069
|
-
accessibilityLabel: client
|
|
5935
|
+
accessibilityLabel: rcLauncherLabel(client),
|
|
5070
5936
|
onPress: () => client.openResourceCenter(),
|
|
5071
5937
|
style: [styles9.rc, client.getResourceCenter()?.launcher.position === "bottom-left" ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: insets.bottom + launcherOffset + (ui.checklists.length ? 56 : 0), backgroundColor: basePalette.primary }],
|
|
5072
|
-
children:
|
|
5938
|
+
children: [
|
|
5939
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: [styles9.rcText, { color: basePalette.onPrimary }], children: "?" }),
|
|
5940
|
+
unread ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.View, { pointerEvents: "none", style: [styles9.rcBadge, { borderColor: basePalette.primary }], children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles9.rcBadgeText, children: unread > 9 ? "9+" : unread }) }) : null
|
|
5941
|
+
]
|
|
5073
5942
|
}
|
|
5074
5943
|
) : null,
|
|
5075
5944
|
messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessengerLauncher, { store: messenger, palette: basePalette, style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0) }] }) : null,
|
|
@@ -5080,28 +5949,76 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5080
5949
|
}
|
|
5081
5950
|
var styles9 = import_react_native10.StyleSheet.create({
|
|
5082
5951
|
rc: { position: "absolute", width: 48, height: 48, borderRadius: 24, alignItems: "center", justifyContent: "center", elevation: 8, shadowColor: "#000", shadowOpacity: 0.2, shadowRadius: 10, shadowOffset: { width: 0, height: 4 } },
|
|
5083
|
-
rcText: { fontSize: 22, fontWeight: "800" }
|
|
5952
|
+
rcText: { fontSize: 22, fontWeight: "800" },
|
|
5953
|
+
rcBadge: { position: "absolute", top: -4, right: -4, minWidth: 20, height: 20, paddingHorizontal: 5, borderRadius: 10, borderWidth: 2, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center" },
|
|
5954
|
+
rcBadgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" }
|
|
5084
5955
|
});
|
|
5956
|
+
function rcLauncherLabel(client) {
|
|
5957
|
+
const label = client.getResourceCenter()?.launcher.label || "Help";
|
|
5958
|
+
const unread = client.getUnreadAnnouncements();
|
|
5959
|
+
return unread ? `${label} (${unread} new update${unread === 1 ? "" : "s"})` : label;
|
|
5960
|
+
}
|
|
5085
5961
|
|
|
5086
|
-
// src/ui/
|
|
5087
|
-
var import_react8 = require("react");
|
|
5088
|
-
var import_react_native11 = require("react-native");
|
|
5962
|
+
// src/ui/Autocapture.tsx
|
|
5089
5963
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
5090
|
-
|
|
5091
|
-
|
|
5964
|
+
function GuidelyMask({ children, ...rest }) {
|
|
5965
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.View, { ...rest, children });
|
|
5966
|
+
}
|
|
5967
|
+
var RESOLVE = {
|
|
5968
|
+
isIgnored: (type) => type === GuidelyOverlay,
|
|
5969
|
+
isMask: (type) => type === GuidelyMask
|
|
5970
|
+
};
|
|
5971
|
+
function useTapObserver(client) {
|
|
5972
|
+
const clientRef = (0, import_react8.useRef)(client);
|
|
5973
|
+
clientRef.current = client;
|
|
5974
|
+
return (0, import_react8.useMemo)(() => {
|
|
5975
|
+
const tracker = new TapTracker();
|
|
5976
|
+
const guard = (fn) => (e) => {
|
|
5977
|
+
try {
|
|
5978
|
+
fn(e);
|
|
5979
|
+
} catch {
|
|
5980
|
+
tracker.cancel();
|
|
5981
|
+
}
|
|
5982
|
+
};
|
|
5983
|
+
return {
|
|
5984
|
+
onTouchStartCapture: guard((e) => {
|
|
5985
|
+
const n = e.nativeEvent;
|
|
5986
|
+
if (!clientRef.current?.isTapCaptureOn()) return tracker.cancel();
|
|
5987
|
+
tracker.begin(n.pageX, n.pageY, Date.now(), Array.isArray(n.touches) ? n.touches.length : 1, n.identifier);
|
|
5988
|
+
}),
|
|
5989
|
+
onTouchMoveCapture: guard((e) => tracker.move(e.nativeEvent.pageX, e.nativeEvent.pageY, e.nativeEvent.identifier)),
|
|
5990
|
+
onTouchEndCapture: guard((e) => {
|
|
5991
|
+
const n = e.nativeEvent;
|
|
5992
|
+
const tap = tracker.end(n.pageX, n.pageY, Date.now(), n.identifier);
|
|
5993
|
+
const c = clientRef.current;
|
|
5994
|
+
if (!tap || !c) return;
|
|
5995
|
+
const r = resolveTapTarget(e, RESOLVE);
|
|
5996
|
+
if (!r.ignored) c.recordTap({ x: tap.x, y: tap.y, t: Date.now(), element: r.element, hit: r.hit });
|
|
5997
|
+
}),
|
|
5998
|
+
onTouchCancelCapture: guard(() => tracker.cancel())
|
|
5999
|
+
};
|
|
6000
|
+
}, []);
|
|
6001
|
+
}
|
|
6002
|
+
|
|
6003
|
+
// src/ui/Target.tsx
|
|
6004
|
+
var import_react9 = require("react");
|
|
6005
|
+
var import_react_native12 = require("react-native");
|
|
6006
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
6007
|
+
var GuidelyContext = (0, import_react9.createContext)(null);
|
|
6008
|
+
var GuidelyScreenContext = (0, import_react9.createContext)(null);
|
|
5092
6009
|
function GuidelyScreenScope({ name, children }) {
|
|
5093
|
-
return /* @__PURE__ */ (0,
|
|
6010
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(GuidelyScreenContext.Provider, { value: name, children });
|
|
5094
6011
|
}
|
|
5095
6012
|
function guidelyScreenLayout({ route, children }) {
|
|
5096
|
-
return /* @__PURE__ */ (0,
|
|
6013
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(GuidelyScreenScope, { name: route.name, children });
|
|
5097
6014
|
}
|
|
5098
|
-
var
|
|
6015
|
+
var TAP_SLOP2 = 10;
|
|
5099
6016
|
function useGuidelyTarget(id) {
|
|
5100
|
-
const client = (0,
|
|
5101
|
-
const screen = (0,
|
|
5102
|
-
const ref = (0,
|
|
5103
|
-
const start = (0,
|
|
5104
|
-
(0,
|
|
6017
|
+
const client = (0, import_react9.useContext)(GuidelyContext);
|
|
6018
|
+
const screen = (0, import_react9.useContext)(GuidelyScreenContext);
|
|
6019
|
+
const ref = (0, import_react9.useRef)(null);
|
|
6020
|
+
const start = (0, import_react9.useRef)(null);
|
|
6021
|
+
(0, import_react9.useEffect)(() => {
|
|
5105
6022
|
if (!client || !id) return;
|
|
5106
6023
|
return client.targets.register(id, {
|
|
5107
6024
|
screen,
|
|
@@ -5112,15 +6029,15 @@ function useGuidelyTarget(id) {
|
|
|
5112
6029
|
})
|
|
5113
6030
|
});
|
|
5114
6031
|
}, [client, id, screen]);
|
|
5115
|
-
const onLayout = (0,
|
|
5116
|
-
const onTouchStart = (0,
|
|
6032
|
+
const onLayout = (0, import_react9.useCallback)(() => client?.targets.changed(), [client]);
|
|
6033
|
+
const onTouchStart = (0, import_react9.useCallback)((e) => {
|
|
5117
6034
|
start.current = { x: e.nativeEvent.pageX, y: e.nativeEvent.pageY };
|
|
5118
6035
|
}, []);
|
|
5119
|
-
const onTouchEnd = (0,
|
|
6036
|
+
const onTouchEnd = (0, import_react9.useCallback)(
|
|
5120
6037
|
(e) => {
|
|
5121
6038
|
const s = start.current;
|
|
5122
6039
|
start.current = null;
|
|
5123
|
-
if (s && Math.abs(e.nativeEvent.pageX - s.x) <
|
|
6040
|
+
if (s && Math.abs(e.nativeEvent.pageX - s.x) < TAP_SLOP2 && Math.abs(e.nativeEvent.pageY - s.y) < TAP_SLOP2) client?.targets.tapped(id);
|
|
5124
6041
|
},
|
|
5125
6042
|
[client, id]
|
|
5126
6043
|
);
|
|
@@ -5128,8 +6045,8 @@ function useGuidelyTarget(id) {
|
|
|
5128
6045
|
}
|
|
5129
6046
|
function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...rest }) {
|
|
5130
6047
|
const t = useGuidelyTarget(id);
|
|
5131
|
-
return /* @__PURE__ */ (0,
|
|
5132
|
-
|
|
6048
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6049
|
+
import_react_native12.View,
|
|
5133
6050
|
{
|
|
5134
6051
|
...rest,
|
|
5135
6052
|
ref: t.ref,
|
|
@@ -5153,7 +6070,7 @@ function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...re
|
|
|
5153
6070
|
|
|
5154
6071
|
// src/index.tsx
|
|
5155
6072
|
var import_flags6 = require("@guidelytech/flags");
|
|
5156
|
-
var
|
|
6073
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
5157
6074
|
function deviceLocale() {
|
|
5158
6075
|
try {
|
|
5159
6076
|
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
@@ -5162,33 +6079,34 @@ function deviceLocale() {
|
|
|
5162
6079
|
}
|
|
5163
6080
|
}
|
|
5164
6081
|
function defaultBridge(appVersion) {
|
|
5165
|
-
const { width, height } =
|
|
6082
|
+
const { width, height } = import_react_native13.Dimensions.get("window");
|
|
5166
6083
|
return {
|
|
5167
6084
|
device: {
|
|
5168
|
-
platform:
|
|
5169
|
-
deviceType:
|
|
6085
|
+
platform: import_react_native13.Platform.OS === "android" ? "android" : "ios",
|
|
6086
|
+
deviceType: import_react_native13.Platform.OS === "ios" && import_react_native13.Platform.isPad || Math.min(width, height) >= 600 ? "tablet" : "mobile",
|
|
5170
6087
|
locale: deviceLocale(),
|
|
5171
6088
|
appVersion
|
|
5172
6089
|
},
|
|
5173
|
-
openExternal: (url) => void
|
|
5174
|
-
})
|
|
6090
|
+
openExternal: (url) => void import_react_native13.Linking.openURL(url).catch(() => {
|
|
6091
|
+
}),
|
|
6092
|
+
windowSize: () => import_react_native13.Dimensions.get("window")
|
|
5175
6093
|
};
|
|
5176
6094
|
}
|
|
5177
6095
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
5178
|
-
const [client, setClient] = (0,
|
|
5179
|
-
const optionsRef = (0,
|
|
6096
|
+
const [client, setClient] = (0, import_react10.useState)(null);
|
|
6097
|
+
const optionsRef = (0, import_react10.useRef)(options);
|
|
5180
6098
|
optionsRef.current = options;
|
|
5181
|
-
const userRef = (0,
|
|
6099
|
+
const userRef = (0, import_react10.useRef)(user);
|
|
5182
6100
|
userRef.current = user;
|
|
5183
6101
|
const flagsOpts = options.flags === false ? null : options.flags ?? {};
|
|
5184
6102
|
const payload = flagsPayload ?? flagsOpts?.payload ?? null;
|
|
5185
|
-
const payloadRef = (0,
|
|
6103
|
+
const payloadRef = (0, import_react10.useRef)(payload);
|
|
5186
6104
|
payloadRef.current = payload;
|
|
5187
|
-
const appAttributes = (0,
|
|
5188
|
-
const appAttributesRef = (0,
|
|
6105
|
+
const appAttributes = (0, import_react10.useMemo)(() => ({ ...flagsOpts?.attributes ?? {}, ...flagAttributes ?? {} }), [(0, import_flags3.stableStringify)(flagsOpts?.attributes ?? {}), (0, import_flags3.stableStringify)(flagAttributes ?? {})]);
|
|
6106
|
+
const appAttributesRef = (0, import_react10.useRef)(appAttributes);
|
|
5189
6107
|
appAttributesRef.current = appAttributes;
|
|
5190
6108
|
const { projectId, environment, apiUrl, storage } = options;
|
|
5191
|
-
(0,
|
|
6109
|
+
(0, import_react10.useEffect)(() => {
|
|
5192
6110
|
if (!projectId) return;
|
|
5193
6111
|
let c;
|
|
5194
6112
|
try {
|
|
@@ -5214,7 +6132,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5214
6132
|
}
|
|
5215
6133
|
setClient(c);
|
|
5216
6134
|
void c.init();
|
|
5217
|
-
const sub =
|
|
6135
|
+
const sub = import_react_native13.AppState.addEventListener("change", (s) => c.setAppState(s));
|
|
5218
6136
|
return () => {
|
|
5219
6137
|
sub.remove();
|
|
5220
6138
|
c.destroy();
|
|
@@ -5222,8 +6140,8 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5222
6140
|
};
|
|
5223
6141
|
}, [projectId, environment, apiUrl, storage]);
|
|
5224
6142
|
const userKey = user ? JSON.stringify([user.id, user.traits ?? null, user.account ?? null]) : "";
|
|
5225
|
-
const hadUser = (0,
|
|
5226
|
-
(0,
|
|
6143
|
+
const hadUser = (0, import_react10.useRef)(false);
|
|
6144
|
+
(0, import_react10.useEffect)(() => {
|
|
5227
6145
|
if (!client) return;
|
|
5228
6146
|
if (user && user.id) {
|
|
5229
6147
|
hadUser.current = true;
|
|
@@ -5233,16 +6151,21 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5233
6151
|
client.reset();
|
|
5234
6152
|
}
|
|
5235
6153
|
}, [client, userKey]);
|
|
5236
|
-
(0,
|
|
6154
|
+
(0, import_react10.useEffect)(() => {
|
|
5237
6155
|
client?.flags.setAttributes(appAttributes);
|
|
5238
6156
|
}, [client, appAttributes]);
|
|
6157
|
+
(0, import_react10.useEffect)(() => {
|
|
6158
|
+
client?.setLocale(options.locale ?? null);
|
|
6159
|
+
}, [client, options.locale]);
|
|
5239
6160
|
const bootstrap = useBootstrap(payload, { ...user?.id ? { ...user.traits ?? {}, id: user.id } : {}, ...appAttributes });
|
|
5240
|
-
const flags = (0,
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
6161
|
+
const flags = (0, import_react10.useMemo)(() => ({ client: client?.flags ?? null, bootstrap }), [client, bootstrap]);
|
|
6162
|
+
const tapObserver = useTapObserver(client);
|
|
6163
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GuidelyContext.Provider, { value: client, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(FlagsContext.Provider, { value: flags, children: [
|
|
6164
|
+
options.autocapture === false ? children : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native13.View, { style: styles10.root, ...tapObserver, children }),
|
|
6165
|
+
client ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GuidelyOverlay, { client, insets, launcherOffset }) : null
|
|
5244
6166
|
] }) });
|
|
5245
6167
|
}
|
|
6168
|
+
var styles10 = import_react_native13.StyleSheet.create({ root: { flex: 1 } });
|
|
5246
6169
|
var NOOP_RESULTS = {
|
|
5247
6170
|
init: Promise.resolve(),
|
|
5248
6171
|
ready: Promise.resolve(),
|
|
@@ -5291,12 +6214,12 @@ var noopClient = new Proxy({}, {
|
|
|
5291
6214
|
}
|
|
5292
6215
|
});
|
|
5293
6216
|
function useGuidely() {
|
|
5294
|
-
return (0,
|
|
6217
|
+
return (0, import_react10.useContext)(GuidelyContext) ?? noopClient;
|
|
5295
6218
|
}
|
|
5296
6219
|
function useGuidelyReady() {
|
|
5297
|
-
const client = (0,
|
|
5298
|
-
const [ready, setReady] = (0,
|
|
5299
|
-
(0,
|
|
6220
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6221
|
+
const [ready, setReady] = (0, import_react10.useState)(() => !!client?.isClientReady());
|
|
6222
|
+
(0, import_react10.useEffect)(() => {
|
|
5300
6223
|
if (!client) return setReady(false);
|
|
5301
6224
|
setReady(client.isClientReady());
|
|
5302
6225
|
const off = client.on("ready", () => setReady(true));
|
|
@@ -5306,44 +6229,44 @@ function useGuidelyReady() {
|
|
|
5306
6229
|
return ready;
|
|
5307
6230
|
}
|
|
5308
6231
|
function useGuidelyIdentify(userId, traits, account) {
|
|
5309
|
-
const client = (0,
|
|
6232
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
5310
6233
|
const key = JSON.stringify([userId ?? null, traits ?? null, account ?? null]);
|
|
5311
|
-
(0,
|
|
6234
|
+
(0, import_react10.useEffect)(() => {
|
|
5312
6235
|
if (client && userId) void client.identify(userId, traits, account);
|
|
5313
6236
|
}, [client, key]);
|
|
5314
6237
|
}
|
|
5315
6238
|
function useGuidelyEvent(event, handler) {
|
|
5316
|
-
const client = (0,
|
|
5317
|
-
const ref = (0,
|
|
6239
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6240
|
+
const ref = (0, import_react10.useRef)(handler);
|
|
5318
6241
|
ref.current = handler;
|
|
5319
|
-
(0,
|
|
6242
|
+
(0, import_react10.useEffect)(() => {
|
|
5320
6243
|
if (!client) return;
|
|
5321
6244
|
return client.on(event, ((...args) => ref.current(...args)));
|
|
5322
6245
|
}, [client, event]);
|
|
5323
6246
|
}
|
|
5324
6247
|
function useGuidelyScreen(name, properties) {
|
|
5325
|
-
const client = (0,
|
|
5326
|
-
(0,
|
|
6248
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6249
|
+
(0, import_react10.useEffect)(() => {
|
|
5327
6250
|
if (client && name) client.screen(name, properties);
|
|
5328
6251
|
}, [client, name]);
|
|
5329
6252
|
}
|
|
5330
6253
|
function useGuidelyNavigation(navigationRef, options = {}) {
|
|
5331
|
-
const client = (0,
|
|
5332
|
-
const nameRef = (0,
|
|
6254
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6255
|
+
const nameRef = (0, import_react10.useRef)(options.screenName);
|
|
5333
6256
|
nameRef.current = options.screenName;
|
|
5334
6257
|
const report = () => {
|
|
5335
6258
|
const route = navigationRef.getCurrentRoute?.();
|
|
5336
6259
|
if (client && route?.name) client.screen(nameRef.current ? nameRef.current(route) : route.name);
|
|
5337
6260
|
};
|
|
5338
|
-
(0,
|
|
6261
|
+
(0, import_react10.useEffect)(report, [client]);
|
|
5339
6262
|
return { onReady: report, onStateChange: report };
|
|
5340
6263
|
}
|
|
5341
6264
|
function useUiSelector(select, fallback) {
|
|
5342
|
-
const client = (0,
|
|
5343
|
-
const selectRef = (0,
|
|
6265
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6266
|
+
const selectRef = (0, import_react10.useRef)(select);
|
|
5344
6267
|
selectRef.current = select;
|
|
5345
|
-
const [value, setValue] = (0,
|
|
5346
|
-
(0,
|
|
6268
|
+
const [value, setValue] = (0, import_react10.useState)(() => client ? select(client) : fallback);
|
|
6269
|
+
(0, import_react10.useEffect)(() => {
|
|
5347
6270
|
if (!client) return;
|
|
5348
6271
|
let last = JSON.stringify(selectRef.current(client));
|
|
5349
6272
|
setValue(selectRef.current(client));
|
|
@@ -5383,14 +6306,18 @@ function useActiveGuide() {
|
|
|
5383
6306
|
FeatureValue,
|
|
5384
6307
|
FlagsProvider,
|
|
5385
6308
|
GuidelyClient,
|
|
6309
|
+
GuidelyMask,
|
|
5386
6310
|
GuidelyOverlay,
|
|
5387
6311
|
GuidelyProvider,
|
|
5388
6312
|
GuidelyScreenScope,
|
|
5389
6313
|
GuidelyTarget,
|
|
6314
|
+
HeatmapBuffer,
|
|
5390
6315
|
InMemoryStickyBucketStore,
|
|
5391
6316
|
Messenger,
|
|
6317
|
+
RageClickDetector,
|
|
5392
6318
|
SDK_NAME,
|
|
5393
6319
|
SDK_VERSION,
|
|
6320
|
+
TapTracker,
|
|
5394
6321
|
TargetRegistry,
|
|
5395
6322
|
createFlagsClient,
|
|
5396
6323
|
createSseParser,
|