@guidelytech/react-native 0.10.0 → 0.12.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 +71 -2
- package/dist/index.cjs +1222 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +255 -32
- package/dist/index.d.ts +255 -32
- package/dist/index.js +1206 -138
- 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-XJXXVV3C.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,14 +463,14 @@ 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
|
}
|
|
462
471
|
return out;
|
|
463
472
|
}
|
|
464
|
-
var SOURCEMAP_MAX_FILE_BYTES =
|
|
473
|
+
var SOURCEMAP_MAX_FILE_BYTES = 100 * 1024 * 1024;
|
|
465
474
|
var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
466
475
|
var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
467
476
|
var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
|
|
@@ -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-XML74K2P.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",
|
|
@@ -921,10 +1074,12 @@ var KEYS = {
|
|
|
921
1074
|
/** Messenger conversations this browser can read: [{ id, token }]. */
|
|
922
1075
|
conversations: (key) => `guidely:conversations:${key}`,
|
|
923
1076
|
/** Announcement ids opened in the messenger's News tab. */
|
|
924
|
-
newsSeen: (key) => `guidely:news:${key}
|
|
1077
|
+
newsSeen: (key) => `guidely:news:${key}`,
|
|
1078
|
+
/** Where the visitor dragged the messenger launcher ({ side, y }). */
|
|
1079
|
+
launcher: (key) => `guidely:launcher:${key}`
|
|
925
1080
|
};
|
|
926
1081
|
|
|
927
|
-
// ../sdk-core/dist/chunk-
|
|
1082
|
+
// ../sdk-core/dist/chunk-2E2YUQAG.js
|
|
928
1083
|
var EventQueue = class {
|
|
929
1084
|
constructor(opts) {
|
|
930
1085
|
__publicField2(this, "opts", opts);
|
|
@@ -1200,12 +1355,32 @@ function areMutuallyExclusive(a, b) {
|
|
|
1200
1355
|
return la.includes(b.id) || la.includes(b.key) || lb.includes(a.id) || lb.includes(a.key);
|
|
1201
1356
|
}
|
|
1202
1357
|
function isWithinSchedule(guide, now = Date.now()) {
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1358
|
+
for (const w of [guide.config.schedule, guide.config.survey]) {
|
|
1359
|
+
if (!w) continue;
|
|
1360
|
+
if (w.startAt && now < Date.parse(w.startAt)) return false;
|
|
1361
|
+
if (w.endAt && now > Date.parse(w.endAt)) return false;
|
|
1362
|
+
}
|
|
1207
1363
|
return true;
|
|
1208
1364
|
}
|
|
1365
|
+
var MINUTE = 60 * 1e3;
|
|
1366
|
+
function countsTowardThrottle(guide) {
|
|
1367
|
+
return isExclusiveGuide(guide) && !guide.config.frequency.ignoreThrottling;
|
|
1368
|
+
}
|
|
1369
|
+
function isThrottleEligible(settings, launches, now = Date.now()) {
|
|
1370
|
+
if (!settings || !settings.enabled) return true;
|
|
1371
|
+
const times = launches.map((l) => Date.parse(l)).filter((t) => !Number.isNaN(t));
|
|
1372
|
+
const windowMs = Math.max(1, settings.windowHours) * HOUR;
|
|
1373
|
+
if (times.filter((t) => now - t < windowMs).length >= Math.max(1, settings.maxGuides)) return false;
|
|
1374
|
+
if (settings.minGapMinutes > 0 && times.some((t) => now - t < settings.minGapMinutes * MINUTE)) return false;
|
|
1375
|
+
return true;
|
|
1376
|
+
}
|
|
1377
|
+
function pruneLaunches(launches, settings, now = Date.now()) {
|
|
1378
|
+
const keepMs = Math.max(settings?.windowHours ?? 24, 1) * HOUR + (settings?.minGapMinutes ?? 0) * MINUTE;
|
|
1379
|
+
return launches.filter((l) => {
|
|
1380
|
+
const t = Date.parse(l);
|
|
1381
|
+
return !Number.isNaN(t) && now - t < keepMs;
|
|
1382
|
+
}).slice(-100);
|
|
1383
|
+
}
|
|
1209
1384
|
function dismissedAfterMerge(local, server) {
|
|
1210
1385
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1211
1386
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
@@ -1214,6 +1389,158 @@ function dismissedAfterMerge(local, server) {
|
|
|
1214
1389
|
// src/core/client.ts
|
|
1215
1390
|
var import_flags2 = require("@guidelytech/flags");
|
|
1216
1391
|
|
|
1392
|
+
// src/core/launcherPlacement.ts
|
|
1393
|
+
var LAUNCHER_PLACEMENT_KEY = "guidely:messenger.launcher";
|
|
1394
|
+
var LAUNCHER_MARGIN = 16;
|
|
1395
|
+
var LAUNCHER_DRAG_THRESHOLD = 8;
|
|
1396
|
+
var clamp2 = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
1397
|
+
function sideX(f, side) {
|
|
1398
|
+
return side === "left" ? f.insets.left + LAUNCHER_MARGIN : f.width - f.insets.right - LAUNCHER_MARGIN - f.size;
|
|
1399
|
+
}
|
|
1400
|
+
function verticalRange(f) {
|
|
1401
|
+
const max = f.height - f.defaultBottom - f.size;
|
|
1402
|
+
return { min: Math.min(max, f.insets.top + LAUNCHER_MARGIN), max };
|
|
1403
|
+
}
|
|
1404
|
+
function clampToSafeArea(f, p) {
|
|
1405
|
+
const left = f.insets.left + LAUNCHER_MARGIN;
|
|
1406
|
+
const top = f.insets.top + LAUNCHER_MARGIN;
|
|
1407
|
+
return {
|
|
1408
|
+
x: clamp2(p.x, left, Math.max(left, f.width - f.insets.right - LAUNCHER_MARGIN - f.size)),
|
|
1409
|
+
y: clamp2(p.y, top, Math.max(top, f.height - f.insets.bottom - LAUNCHER_MARGIN - f.size))
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
function snapPlacement(f, p) {
|
|
1413
|
+
const { min, max } = verticalRange(f);
|
|
1414
|
+
const side = p.x + f.size / 2 < f.width / 2 ? "left" : "right";
|
|
1415
|
+
return { side, fraction: max > min ? (max - clamp2(p.y, min, max)) / (max - min) : 0 };
|
|
1416
|
+
}
|
|
1417
|
+
function placementPoint(f, placement) {
|
|
1418
|
+
const { min, max } = verticalRange(f);
|
|
1419
|
+
return { x: sideX(f, placement.side), y: max - clamp2(placement.fraction, 0, 1) * (max - min) };
|
|
1420
|
+
}
|
|
1421
|
+
function parsePlacement(raw) {
|
|
1422
|
+
if (!raw) return null;
|
|
1423
|
+
try {
|
|
1424
|
+
const v = JSON.parse(raw);
|
|
1425
|
+
if (!v || typeof v !== "object" || v.side !== "left" && v.side !== "right" || typeof v.fraction !== "number" || !Number.isFinite(v.fraction)) return null;
|
|
1426
|
+
return { side: v.side, fraction: clamp2(v.fraction, 0, 1) };
|
|
1427
|
+
} catch {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function serializePlacement(p) {
|
|
1432
|
+
return JSON.stringify({ side: p.side, fraction: Math.round(clamp2(p.fraction, 0, 1) * 1e4) / 1e4 });
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/core/autocapture.ts
|
|
1436
|
+
var TAP_SLOP = 10;
|
|
1437
|
+
var TAP_MAX_MS = 1e3;
|
|
1438
|
+
var HEATMAP_MAX_POINTS = 40;
|
|
1439
|
+
function quoteValue(value) {
|
|
1440
|
+
return `"${value.replace(/["\\]/g, "\\$&")}"`;
|
|
1441
|
+
}
|
|
1442
|
+
function attrSelector(name, value) {
|
|
1443
|
+
return `[${name}=${quoteValue(value)}]`.slice(0, 300);
|
|
1444
|
+
}
|
|
1445
|
+
function cleanText(text) {
|
|
1446
|
+
const t = (text ?? "").replace(/\s+/g, " ").trim();
|
|
1447
|
+
return t ? t.slice(0, 100) : void 0;
|
|
1448
|
+
}
|
|
1449
|
+
function tapProperties(el, x, y, platform, maskAll) {
|
|
1450
|
+
const props = { selector: el.selector.slice(0, 300), tag: el.tag || "View", x: Math.round(x) || 0, y: Math.round(y) || 0, platform };
|
|
1451
|
+
const text = maskAll ? void 0 : cleanText(el.text);
|
|
1452
|
+
if (text) props.text = text;
|
|
1453
|
+
return props;
|
|
1454
|
+
}
|
|
1455
|
+
var r2 = (n) => Math.round(Math.min(1, Math.max(0, n)) * 100) / 100;
|
|
1456
|
+
function heatmapPoint(x, y, el, rect, rage) {
|
|
1457
|
+
const p = { k: rage ? "r" : "c", x: Math.round(x) || 0, y: Math.round(y) || 0 };
|
|
1458
|
+
const s = el?.selector.slice(0, 120);
|
|
1459
|
+
if (s) p.s = s;
|
|
1460
|
+
if (s && rect && rect.width > 0 && rect.height > 0) {
|
|
1461
|
+
p.rx = r2((x - rect.x) / rect.width);
|
|
1462
|
+
p.ry = r2((y - rect.y) / rect.height);
|
|
1463
|
+
}
|
|
1464
|
+
return p;
|
|
1465
|
+
}
|
|
1466
|
+
var RageClickDetector = class {
|
|
1467
|
+
constructor(threshold = 3, windowMs = 1e3, radius = 30) {
|
|
1468
|
+
__publicField(this, "threshold", threshold);
|
|
1469
|
+
__publicField(this, "windowMs", windowMs);
|
|
1470
|
+
__publicField(this, "radius", radius);
|
|
1471
|
+
__publicField(this, "clicks", []);
|
|
1472
|
+
__publicField(this, "fired", false);
|
|
1473
|
+
}
|
|
1474
|
+
/** Taps in the current burst once it has fired (rage), otherwise 0. */
|
|
1475
|
+
get burst() {
|
|
1476
|
+
return this.fired ? this.clicks.length : 0;
|
|
1477
|
+
}
|
|
1478
|
+
/** Records a tap; returns the tap count when it completes a burst, otherwise 0. */
|
|
1479
|
+
add(x, y, t) {
|
|
1480
|
+
const first = this.clicks[0];
|
|
1481
|
+
const last = this.clicks[this.clicks.length - 1];
|
|
1482
|
+
if (!first || !last || t - last.t > this.windowMs || Math.hypot(x - first.x, y - first.y) > this.radius) {
|
|
1483
|
+
this.clicks = [];
|
|
1484
|
+
this.fired = false;
|
|
1485
|
+
}
|
|
1486
|
+
this.clicks = this.clicks.filter((c) => t - c.t <= this.windowMs);
|
|
1487
|
+
this.clicks.push({ x, y, t });
|
|
1488
|
+
if (this.fired || this.clicks.length < this.threshold) return 0;
|
|
1489
|
+
this.fired = true;
|
|
1490
|
+
return this.clicks.length;
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
var HeatmapBuffer = class {
|
|
1494
|
+
constructor() {
|
|
1495
|
+
/** Screen path the buffered points belong to. */
|
|
1496
|
+
__publicField(this, "url", "");
|
|
1497
|
+
__publicField(this, "points", []);
|
|
1498
|
+
}
|
|
1499
|
+
/** Adds a tap (`burst` > 0 marks it and the burst's previous taps as rage); true when the batch is full. */
|
|
1500
|
+
add(url, point, burst) {
|
|
1501
|
+
this.url = url;
|
|
1502
|
+
for (let i = Math.max(0, this.points.length - burst + 1); i < this.points.length; i++) this.points[i].k = "r";
|
|
1503
|
+
this.points.push(point);
|
|
1504
|
+
return this.points.length >= HEATMAP_MAX_POINTS;
|
|
1505
|
+
}
|
|
1506
|
+
/** Empties the buffer, returning what was in it. */
|
|
1507
|
+
take() {
|
|
1508
|
+
const points = this.points;
|
|
1509
|
+
this.points = [];
|
|
1510
|
+
return points.length ? { url: this.url, points } : null;
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
var TapTracker = class {
|
|
1514
|
+
constructor() {
|
|
1515
|
+
__publicField(this, "start", null);
|
|
1516
|
+
__publicField(this, "moved", 0);
|
|
1517
|
+
}
|
|
1518
|
+
/** A finger went down. A second finger while one is tracked cancels the gesture (not a tap). */
|
|
1519
|
+
begin(x, y, t, touches, id) {
|
|
1520
|
+
if (this.start || touches > 1) {
|
|
1521
|
+
this.start = null;
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
this.start = { x, y, t, id };
|
|
1525
|
+
this.moved = 0;
|
|
1526
|
+
}
|
|
1527
|
+
move(x, y, id) {
|
|
1528
|
+
const s = this.start;
|
|
1529
|
+
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));
|
|
1530
|
+
}
|
|
1531
|
+
/** The finger lifted: the tap position when it moved ≤ 10pt and lasted < 1s, else null. */
|
|
1532
|
+
end(x, y, t, id) {
|
|
1533
|
+
const s = this.start;
|
|
1534
|
+
if (!s || id !== void 0 && s.id !== void 0 && id !== s.id) return null;
|
|
1535
|
+
this.start = null;
|
|
1536
|
+
const moved = Math.max(this.moved, Math.hypot(x - s.x, y - s.y));
|
|
1537
|
+
return moved <= TAP_SLOP && t - s.t < TAP_MAX_MS ? { x, y } : null;
|
|
1538
|
+
}
|
|
1539
|
+
cancel() {
|
|
1540
|
+
this.start = null;
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1217
1544
|
// src/core/targets.ts
|
|
1218
1545
|
var TargetTimeoutError = class extends Error {
|
|
1219
1546
|
constructor(ids) {
|
|
@@ -1316,6 +1643,35 @@ var TargetRegistry = class {
|
|
|
1316
1643
|
return null;
|
|
1317
1644
|
}
|
|
1318
1645
|
}
|
|
1646
|
+
/**
|
|
1647
|
+
* The registered target on the current screen whose visible frame contains the point (window
|
|
1648
|
+
* coordinates); the smallest one when targets are nested. Frames that do not answer within
|
|
1649
|
+
* `timeoutMs` are skipped.
|
|
1650
|
+
*/
|
|
1651
|
+
async hitTest(x, y, timeoutMs = 500) {
|
|
1652
|
+
const candidates = [];
|
|
1653
|
+
this.targets.forEach((list, id) => {
|
|
1654
|
+
for (const handle of list) if (!(handle.screen && this.screen && handle.screen !== this.screen)) candidates.push({ id, handle });
|
|
1655
|
+
});
|
|
1656
|
+
if (!candidates.length) return null;
|
|
1657
|
+
const rects = await Promise.all(
|
|
1658
|
+
candidates.map(
|
|
1659
|
+
({ handle }) => new Promise((resolve) => {
|
|
1660
|
+
const timer = setTimeout(() => resolve(null), timeoutMs);
|
|
1661
|
+
void this.measureHandle(handle).then((r) => {
|
|
1662
|
+
clearTimeout(timer);
|
|
1663
|
+
resolve(r);
|
|
1664
|
+
});
|
|
1665
|
+
})
|
|
1666
|
+
)
|
|
1667
|
+
);
|
|
1668
|
+
let best = null;
|
|
1669
|
+
rects.forEach((r, i) => {
|
|
1670
|
+
if (!r || x < r.x || y < r.y || x > r.x + r.width || y > r.y + r.height) return;
|
|
1671
|
+
if (!best || r.width * r.height < best.rect.width * best.rect.height) best = { id: candidates[i].id, rect: r };
|
|
1672
|
+
});
|
|
1673
|
+
return best;
|
|
1674
|
+
}
|
|
1319
1675
|
/** First of `ids` that is mounted and visible. */
|
|
1320
1676
|
async resolve(ids) {
|
|
1321
1677
|
for (const id of ids) {
|
|
@@ -2073,7 +2429,8 @@ var Messenger = class {
|
|
|
2073
2429
|
this.stopThreadPolling();
|
|
2074
2430
|
this.set({ open: true, view: { kind: "article", articleId: id, from }, article: null });
|
|
2075
2431
|
try {
|
|
2076
|
-
const
|
|
2432
|
+
const locale = this.deps.locale?.();
|
|
2433
|
+
const res = await this.request("GET", `/articles/${encodeURIComponent(id)}`, locale ? { params: { locale } } : {});
|
|
2077
2434
|
this.set({ article: res.article });
|
|
2078
2435
|
} catch (err) {
|
|
2079
2436
|
this.deps.onError(err);
|
|
@@ -2188,7 +2545,7 @@ var PersistentStore = class {
|
|
|
2188
2545
|
|
|
2189
2546
|
// src/core/client.ts
|
|
2190
2547
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2191
|
-
var SDK_VERSION = "0.
|
|
2548
|
+
var SDK_VERSION = "0.12.0";
|
|
2192
2549
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2193
2550
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2194
2551
|
var CACHED_START_MS = 1500;
|
|
@@ -2202,6 +2559,8 @@ function settlesWithin(promise, ms) {
|
|
|
2202
2559
|
promise.then(done, done);
|
|
2203
2560
|
});
|
|
2204
2561
|
}
|
|
2562
|
+
var appVersionKey = (key) => `guidely:app:${key}`;
|
|
2563
|
+
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
2205
2564
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2206
2565
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
2207
2566
|
var newSession = () => ({ id: `s_${randomId(16)}`, startedAt: nowIso(), lastActiveAt: nowIso(), shownGuides: [], events: [] });
|
|
@@ -2233,6 +2592,7 @@ var GuidelyClient = class {
|
|
|
2233
2592
|
__publicField(this, "activeSurvey", null);
|
|
2234
2593
|
__publicField(this, "queued", []);
|
|
2235
2594
|
__publicField(this, "flows", /* @__PURE__ */ new Map());
|
|
2595
|
+
/** Checklists on screen, with the guide as rendered (in the visitor's language). */
|
|
2236
2596
|
__publicField(this, "checklists", /* @__PURE__ */ new Map());
|
|
2237
2597
|
__publicField(this, "expanded", /* @__PURE__ */ new Set());
|
|
2238
2598
|
__publicField(this, "surveyView", null);
|
|
@@ -2252,6 +2612,27 @@ var GuidelyClient = class {
|
|
|
2252
2612
|
__publicField(this, "flags");
|
|
2253
2613
|
/** Experiments already reported this session (`experiment_viewed` is sent once per session). */
|
|
2254
2614
|
__publicField(this, "exposures", /* @__PURE__ */ new Set());
|
|
2615
|
+
// Product analytics (autocapture).
|
|
2616
|
+
__publicField(this, "rage", new RageClickDetector());
|
|
2617
|
+
__publicField(this, "heatmap", new HeatmapBuffer());
|
|
2618
|
+
/** Taps are processed in order (target frames are measured asynchronously). */
|
|
2619
|
+
__publicField(this, "tapChain", Promise.resolve());
|
|
2620
|
+
/** When the current screen's foreground measurement started; `screenLeft` once it has been reported. */
|
|
2621
|
+
__publicField(this, "screenStartedAt", 0);
|
|
2622
|
+
__publicField(this, "screenLeft", true);
|
|
2623
|
+
/** Last `app_open` (cold start or return to the foreground), for `app_background.foregroundMs`. */
|
|
2624
|
+
__publicField(this, "openedAt", Date.now());
|
|
2625
|
+
__publicField(this, "launchChecked", false);
|
|
2626
|
+
/** An identity was stored before this launch (an existing install adopting lifecycle events). */
|
|
2627
|
+
__publicField(this, "hadIdentity", false);
|
|
2628
|
+
/** Language set with `setLocale()` (or the `locale` option). */
|
|
2629
|
+
__publicField(this, "locale", null);
|
|
2630
|
+
/** Language each shown guide was rendered in; added to its events as `locale`. */
|
|
2631
|
+
__publicField(this, "guideLocales", /* @__PURE__ */ new Map());
|
|
2632
|
+
/** The resource center in the visitor's language, recomputed when the config or language changes. */
|
|
2633
|
+
__publicField(this, "localizedRc", null);
|
|
2634
|
+
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
2635
|
+
__publicField(this, "announcementsSeenAtOpen", null);
|
|
2255
2636
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
2256
2637
|
__publicField(this, "eventBus", {
|
|
2257
2638
|
on: (name, cb) => {
|
|
@@ -2281,6 +2662,7 @@ var GuidelyClient = class {
|
|
|
2281
2662
|
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
2663
|
this.cleanups.push(this.targets.onTap((id) => this.handleTap(id)));
|
|
2283
2664
|
if (options.user) this.user = { ...options.user };
|
|
2665
|
+
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
2284
2666
|
this.flags = this.createFlags(options.flags);
|
|
2285
2667
|
}
|
|
2286
2668
|
createFlags(opts) {
|
|
@@ -2329,9 +2711,10 @@ var GuidelyClient = class {
|
|
|
2329
2711
|
async bootstrap() {
|
|
2330
2712
|
const key = this.options.projectId;
|
|
2331
2713
|
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)]);
|
|
2714
|
+
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), LAUNCHER_PLACEMENT_KEY]);
|
|
2333
2715
|
if (this.destroyed) return;
|
|
2334
2716
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
2717
|
+
this.hadIdentity = !!this.anonymousId;
|
|
2335
2718
|
if (!this.anonymousId) {
|
|
2336
2719
|
this.anonymousId = `anon_${randomId(20)}`;
|
|
2337
2720
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
@@ -2368,6 +2751,7 @@ var GuidelyClient = class {
|
|
|
2368
2751
|
if (!this.config) await fresh;
|
|
2369
2752
|
else if (!await settlesWithin(fresh, CACHED_START_MS)) void fresh.then(() => this.applyConfigChange(before));
|
|
2370
2753
|
if (this.destroyed) return;
|
|
2754
|
+
this.checkLaunch();
|
|
2371
2755
|
this.wireTimers();
|
|
2372
2756
|
if (this.user?.id) await settlesWithin(this.identify(this.user.id, this.user.traits, this.user.account), CACHED_START_MS);
|
|
2373
2757
|
if (this.destroyed) return;
|
|
@@ -2397,6 +2781,7 @@ var GuidelyClient = class {
|
|
|
2397
2781
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
2398
2782
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
2399
2783
|
this.syncMessenger();
|
|
2784
|
+
if (this.hydrated) this.checkLaunch();
|
|
2400
2785
|
this.emitter.emit("config", this.config);
|
|
2401
2786
|
this.log("config loaded", this.config.configVersion, `${this.config.guides.length} guides`);
|
|
2402
2787
|
return true;
|
|
@@ -2417,7 +2802,7 @@ var GuidelyClient = class {
|
|
|
2417
2802
|
if (!this.isReady || !this.config || this.backgroundedAt !== null) return;
|
|
2418
2803
|
const url = this.currentUrl();
|
|
2419
2804
|
const ctx = this.getContext();
|
|
2420
|
-
for (const guide of this.
|
|
2805
|
+
for (const guide of this.candidates()) if (guide.config.trigger.type === "element_visible" && guide.config.trigger.value) this.armVisibility(guide, ctx, url);
|
|
2421
2806
|
this.drainQueue(url, ctx);
|
|
2422
2807
|
}, 1e3);
|
|
2423
2808
|
this.cleanups.push(() => clearInterval(poll));
|
|
@@ -2441,7 +2826,9 @@ var GuidelyClient = class {
|
|
|
2441
2826
|
setAppState(state) {
|
|
2442
2827
|
if (this.destroyed || !this.hydrated) return;
|
|
2443
2828
|
if (state === "background") {
|
|
2829
|
+
const wasForeground = this.backgroundedAt === null;
|
|
2444
2830
|
this.backgroundedAt = Date.now();
|
|
2831
|
+
if (wasForeground) this.wentToBackground();
|
|
2445
2832
|
this.session.lastActiveAt = nowIso();
|
|
2446
2833
|
this.persistSession();
|
|
2447
2834
|
void this.queue?.flush();
|
|
@@ -2458,6 +2845,7 @@ var GuidelyClient = class {
|
|
|
2458
2845
|
this.persistSession();
|
|
2459
2846
|
this.firedDelays.clear();
|
|
2460
2847
|
}
|
|
2848
|
+
this.cameToForeground();
|
|
2461
2849
|
void this.flags.refresh();
|
|
2462
2850
|
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
|
|
2463
2851
|
else if (this.isReady) this.evaluate("ready");
|
|
@@ -2470,6 +2858,7 @@ var GuidelyClient = class {
|
|
|
2470
2858
|
/** Tears everything down. Create a new client to start again. */
|
|
2471
2859
|
destroy() {
|
|
2472
2860
|
if (this.destroyed) return;
|
|
2861
|
+
this.flushHeatmap();
|
|
2473
2862
|
this.destroyed = true;
|
|
2474
2863
|
this.active?.controller.destroy();
|
|
2475
2864
|
this.active = null;
|
|
@@ -2521,6 +2910,7 @@ var GuidelyClient = class {
|
|
|
2521
2910
|
for (const [id, s] of Object.entries(data.guideState || {})) {
|
|
2522
2911
|
const local = guides[id];
|
|
2523
2912
|
guides[id] = {
|
|
2913
|
+
...local,
|
|
2524
2914
|
impressions: Math.max(local?.impressions ?? 0, s.impressions ?? 0),
|
|
2525
2915
|
completedAt: local?.completedAt ?? s.completedAt ?? null,
|
|
2526
2916
|
dismissedAt: local?.dismissedAt ?? s.dismissedAt ?? null,
|
|
@@ -2542,7 +2932,10 @@ var GuidelyClient = class {
|
|
|
2542
2932
|
checklists,
|
|
2543
2933
|
surveysSubmitted: Array.from(/* @__PURE__ */ new Set([...this.state.surveysSubmitted, ...data.surveysSubmitted || []])),
|
|
2544
2934
|
audienceIds: data.audienceIds || [],
|
|
2545
|
-
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt
|
|
2935
|
+
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt,
|
|
2936
|
+
// Device-local only (the server doesn't hold them).
|
|
2937
|
+
...this.state.throttle ? { throttle: this.state.throttle } : {},
|
|
2938
|
+
...this.state.announcementsSeenAt ? { announcementsSeenAt: this.state.announcementsSeenAt } : {}
|
|
2546
2939
|
};
|
|
2547
2940
|
if (data.firstSeenAt && Date.now() - Date.parse(data.firstSeenAt) > 6e4) this.firstVisit = false;
|
|
2548
2941
|
this.persistState();
|
|
@@ -2634,6 +3027,8 @@ var GuidelyClient = class {
|
|
|
2634
3027
|
...extra
|
|
2635
3028
|
};
|
|
2636
3029
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
3030
|
+
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
3031
|
+
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
2637
3032
|
this.queue.enqueue(event);
|
|
2638
3033
|
this.emitter.emit("track", event);
|
|
2639
3034
|
return event;
|
|
@@ -2666,11 +3061,19 @@ var GuidelyClient = class {
|
|
|
2666
3061
|
if (!name) return;
|
|
2667
3062
|
this.whenHydrated(() => {
|
|
2668
3063
|
const changed = name !== this.screenName;
|
|
3064
|
+
if (changed) {
|
|
3065
|
+
this.leaveScreen();
|
|
3066
|
+
this.flushHeatmap();
|
|
3067
|
+
}
|
|
2669
3068
|
this.screenName = name;
|
|
2670
3069
|
this.targets.setScreen(name);
|
|
2671
3070
|
if (changed) this.syncFlagAttributes();
|
|
2672
3071
|
const url = this.currentUrl();
|
|
2673
|
-
if (changed)
|
|
3072
|
+
if (changed) {
|
|
3073
|
+
this.screenStartedAt = Date.now();
|
|
3074
|
+
this.screenLeft = this.backgroundedAt !== null;
|
|
3075
|
+
if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name, ...this.deviceProperties() } });
|
|
3076
|
+
}
|
|
2674
3077
|
this.completeChecklistTasksBy("url_visited", url);
|
|
2675
3078
|
this.active?.controller.resume();
|
|
2676
3079
|
this.flows.forEach((f) => f.controller.resume());
|
|
@@ -2698,6 +3101,149 @@ var GuidelyClient = class {
|
|
|
2698
3101
|
if (this.isReady) this.evaluate("click", { targetId });
|
|
2699
3102
|
}
|
|
2700
3103
|
// ---------------------------------------------------------------------------
|
|
3104
|
+
// Product analytics (autocapture)
|
|
3105
|
+
// ---------------------------------------------------------------------------
|
|
3106
|
+
captureAllowed() {
|
|
3107
|
+
return !this.destroyed && this.analyticsEnabled();
|
|
3108
|
+
}
|
|
3109
|
+
/** A capability runs when the project enables it (absent on old servers → off), no option turns it off and analytics are allowed. */
|
|
3110
|
+
autocaptureOn(key) {
|
|
3111
|
+
const o = this.options.autocapture;
|
|
3112
|
+
return !!this.config?.settings?.autocapture?.[key] && o !== false && !(typeof o === "object" && o?.[key] === false) && this.captureAllowed();
|
|
3113
|
+
}
|
|
3114
|
+
/** `page_view` from `screen()` predates autocapture: only an explicit opt-out stops it. */
|
|
3115
|
+
pageviewsOn() {
|
|
3116
|
+
const o = this.options.autocapture;
|
|
3117
|
+
return this.config?.settings?.autocapture?.pageviews !== false && o !== false && !(typeof o === "object" && o?.pageviews === false);
|
|
3118
|
+
}
|
|
3119
|
+
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3120
|
+
isTapCaptureOn() {
|
|
3121
|
+
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps"));
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
3125
|
+
* bursts and a heatmap point. A registered `<GuidelyTarget>` whose frame contains the point
|
|
3126
|
+
* (smallest wins) takes precedence over the resolved element and is reported as `#<id>`.
|
|
3127
|
+
*/
|
|
3128
|
+
recordTap(tap) {
|
|
3129
|
+
try {
|
|
3130
|
+
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3131
|
+
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3132
|
+
if (!on.clicks && !on.rage && !on.heat) return;
|
|
3133
|
+
const t = tap.t ?? Date.now();
|
|
3134
|
+
const url = this.screenName ? this.currentUrl() : "";
|
|
3135
|
+
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
this.reportError(err);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
async processTap(tap, t, url, on) {
|
|
3141
|
+
let el = tap.element ?? null;
|
|
3142
|
+
let rect = null;
|
|
3143
|
+
const target = this.targets.ids().length ? await this.targets.hitTest(tap.x, tap.y) : null;
|
|
3144
|
+
if (target) {
|
|
3145
|
+
el = { selector: `#${target.id}`, tag: el?.tag ?? tap.hit?.tag ?? "View", text: el?.text };
|
|
3146
|
+
rect = target.rect;
|
|
3147
|
+
}
|
|
3148
|
+
if (this.destroyed) return;
|
|
3149
|
+
const platform = this.bridge.device.platform;
|
|
3150
|
+
const maskAll = !!this.config?.settings?.replay?.maskAllText;
|
|
3151
|
+
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
3152
|
+
if (on.clicks && el) this.enqueue("click", { url: eventUrl, properties: { ...tapProperties(el, tap.x, tap.y, platform, maskAll) } });
|
|
3153
|
+
const count = this.rage.add(tap.x, tap.y, t);
|
|
3154
|
+
if (on.rage && count) this.enqueue("rage_click", { url: eventUrl, properties: { ...tapProperties(el ?? tap.hit ?? UNKNOWN_VIEW, tap.x, tap.y, platform, maskAll), count } });
|
|
3155
|
+
if (!on.heat) return;
|
|
3156
|
+
const burst = this.rage.burst;
|
|
3157
|
+
if (el && !rect && el.measure) rect = await el.measure().catch(() => null);
|
|
3158
|
+
if (this.destroyed) return;
|
|
3159
|
+
if (this.heatmap.url !== url) this.flushHeatmap();
|
|
3160
|
+
if (this.heatmap.add(url, heatmapPoint(tap.x, tap.y, el, rect, burst > 0), burst)) this.flushHeatmap();
|
|
3161
|
+
}
|
|
3162
|
+
/** Sends the buffered heatmap taps as one `heatmap` event attributed to the screen they happened on. */
|
|
3163
|
+
flushHeatmap() {
|
|
3164
|
+
const batch = this.heatmap.take();
|
|
3165
|
+
if (!batch || !this.captureAllowed()) return;
|
|
3166
|
+
const collect = this.collectUrls() && !!batch.url;
|
|
3167
|
+
const { width, height } = this.windowSize();
|
|
3168
|
+
this.enqueue("heatmap", {
|
|
3169
|
+
url: collect ? batch.url : void 0,
|
|
3170
|
+
properties: { ...collect ? { path: batch.url } : {}, viewportWidth: width, viewportHeight: height, pageHeight: height, platform: this.bridge.device.platform, points: batch.points }
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
windowSize() {
|
|
3174
|
+
try {
|
|
3175
|
+
const s = this.bridge.windowSize?.();
|
|
3176
|
+
if (s && Number.isFinite(s.width) && Number.isFinite(s.height)) return { width: Math.round(s.width), height: Math.round(s.height) };
|
|
3177
|
+
} catch {
|
|
3178
|
+
}
|
|
3179
|
+
return { width: 0, height: 0 };
|
|
3180
|
+
}
|
|
3181
|
+
/** Ends the foreground measurement of the current screen with a `page_leave` (once per measurement). */
|
|
3182
|
+
leaveScreen() {
|
|
3183
|
+
if (!this.screenName || this.screenLeft) return;
|
|
3184
|
+
this.screenLeft = true;
|
|
3185
|
+
if (!this.autocaptureOn("pageLeave")) return;
|
|
3186
|
+
const url = this.currentUrl();
|
|
3187
|
+
const collect = this.collectUrls();
|
|
3188
|
+
this.enqueue("page_leave", {
|
|
3189
|
+
url: collect ? url : void 0,
|
|
3190
|
+
properties: { ...collect ? { path: url } : {}, screen: this.screenName, durationMs: Math.max(0, Date.now() - this.screenStartedAt), scrollDepth: 100, platform: this.bridge.device.platform }
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
/** `{ appVersion, build }` of this launch, without unknown keys. */
|
|
3194
|
+
appInfo() {
|
|
3195
|
+
const appVersion = this.options.appVersion ?? this.bridge.device.appVersion;
|
|
3196
|
+
const build = this.options.appBuild;
|
|
3197
|
+
return { ...appVersion ? { appVersion: String(appVersion) } : {}, ...build ? { build: String(build) } : {} };
|
|
3198
|
+
}
|
|
3199
|
+
/**
|
|
3200
|
+
* Once per launch, as soon as a config is known: `app_install` / `app_update` from the stored
|
|
3201
|
+
* version (when the app passes `appVersion` and `storage`), then `app_open` with `coldStart: true`.
|
|
3202
|
+
*/
|
|
3203
|
+
checkLaunch() {
|
|
3204
|
+
if (this.launchChecked || !this.config || this.destroyed || !this.hydrated) return;
|
|
3205
|
+
this.launchChecked = true;
|
|
3206
|
+
try {
|
|
3207
|
+
const on = this.autocaptureOn("appLifecycle");
|
|
3208
|
+
const info = this.appInfo();
|
|
3209
|
+
if (info.appVersion && this.options.storage) {
|
|
3210
|
+
const key = appVersionKey(this.options.projectId);
|
|
3211
|
+
const stored = readJson(this.store, key, null);
|
|
3212
|
+
if (!stored?.appVersion) {
|
|
3213
|
+
if (!this.hadIdentity && on) this.enqueue("app_install", { properties: { ...info } });
|
|
3214
|
+
} else if (stored.appVersion !== info.appVersion || !!stored.build && !!info.build && stored.build !== info.build) {
|
|
3215
|
+
if (on) this.enqueue("app_update", { properties: { ...info, previousVersion: stored.appVersion, ...stored.build ? { previousBuild: stored.build } : {} } });
|
|
3216
|
+
}
|
|
3217
|
+
writeJson(this.store, key, info);
|
|
3218
|
+
}
|
|
3219
|
+
if (on) this.enqueue("app_open", { properties: { ...info, coldStart: true } });
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
this.reportError(err);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
wentToBackground() {
|
|
3225
|
+
try {
|
|
3226
|
+
this.leaveScreen();
|
|
3227
|
+
this.flushHeatmap();
|
|
3228
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_background", { properties: { foregroundMs: Math.max(0, Date.now() - this.openedAt) } });
|
|
3229
|
+
} catch (err) {
|
|
3230
|
+
this.reportError(err);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
cameToForeground() {
|
|
3234
|
+
try {
|
|
3235
|
+
this.openedAt = Date.now();
|
|
3236
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_open", { properties: { ...this.appInfo(), coldStart: false } });
|
|
3237
|
+
if (this.screenName && this.screenLeft) {
|
|
3238
|
+
this.screenStartedAt = Date.now();
|
|
3239
|
+
this.screenLeft = false;
|
|
3240
|
+
if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName, ...this.deviceProperties(), resumed: true } });
|
|
3241
|
+
}
|
|
3242
|
+
} catch (err) {
|
|
3243
|
+
this.reportError(err);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
// ---------------------------------------------------------------------------
|
|
2701
3247
|
// Targeting + triggers
|
|
2702
3248
|
// ---------------------------------------------------------------------------
|
|
2703
3249
|
/** Builds the targeting context for the current moment. */
|
|
@@ -2744,6 +3290,50 @@ var GuidelyClient = class {
|
|
|
2744
3290
|
if (guide.kind === "checklist") return !this.state.checklists[guide.id]?.dismissedAt;
|
|
2745
3291
|
return isFrequencyEligible(guide.config.frequency, this.state.guides[guide.id], this.session.shownGuides.includes(guide.id));
|
|
2746
3292
|
}
|
|
3293
|
+
/** Guide throttling: may this guide launch automatically now? */
|
|
3294
|
+
throttleAllows(guide) {
|
|
3295
|
+
if (!countsTowardThrottle(guide)) return true;
|
|
3296
|
+
return isThrottleEligible(this.config?.settings.throttling, this.state.throttle ?? []);
|
|
3297
|
+
}
|
|
3298
|
+
recordLaunch(guide, manual) {
|
|
3299
|
+
if (manual || !countsTowardThrottle(guide) || !this.config?.settings.throttling?.enabled) return;
|
|
3300
|
+
this.state.throttle = pruneLaunches([...this.state.throttle ?? [], nowIso()], this.config.settings.throttling);
|
|
3301
|
+
}
|
|
3302
|
+
/** Guides and A/B test control groups, which are evaluated the same way but never render. */
|
|
3303
|
+
candidates() {
|
|
3304
|
+
const guides = this.config?.guides ?? [];
|
|
3305
|
+
const holdouts = this.config?.guideHoldouts ?? [];
|
|
3306
|
+
return holdouts.length ? [...guides, ...holdouts.map((h) => h.guide)] : guides;
|
|
3307
|
+
}
|
|
3308
|
+
holdoutFor(guide) {
|
|
3309
|
+
return this.config?.guideHoldouts?.find((h) => h.guide.id === guide.id) ?? null;
|
|
3310
|
+
}
|
|
3311
|
+
// ---------------------------------------------------------------------------
|
|
3312
|
+
// Localization
|
|
3313
|
+
// ---------------------------------------------------------------------------
|
|
3314
|
+
/** The visitor's language candidates, most preferred first: `setLocale()` / `locale` option, traits, device. */
|
|
3315
|
+
locales() {
|
|
3316
|
+
return visitorLocales(this.locale, this.user?.traits, [this.bridge.device.locale]);
|
|
3317
|
+
}
|
|
3318
|
+
defaultLocale() {
|
|
3319
|
+
return this.config?.settings?.localization?.defaultLocale || "en";
|
|
3320
|
+
}
|
|
3321
|
+
/** The guide in the visitor's language (remembered so its events carry `locale`). */
|
|
3322
|
+
localize(guide) {
|
|
3323
|
+
const localized = localizeGuide(guide, this.locales(), this.defaultLocale());
|
|
3324
|
+
this.guideLocales.set(guide.id, localized.locale);
|
|
3325
|
+
return localized;
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Sets the language for guides and the resource center (`null` = back to the user's traits and
|
|
3329
|
+
* the device language). Guides already on screen keep their language; the resource center re-renders.
|
|
3330
|
+
*/
|
|
3331
|
+
setLocale(locale) {
|
|
3332
|
+
const next = locale ? normalizeLocale(locale) || null : null;
|
|
3333
|
+
if (next === this.locale) return;
|
|
3334
|
+
this.locale = next;
|
|
3335
|
+
this.notify();
|
|
3336
|
+
}
|
|
2747
3337
|
triggerFires(guide, reason, detail) {
|
|
2748
3338
|
const t = guide.config.trigger;
|
|
2749
3339
|
switch (t.type) {
|
|
@@ -2779,7 +3369,7 @@ var GuidelyClient = class {
|
|
|
2779
3369
|
this.lastAudiences = current;
|
|
2780
3370
|
}
|
|
2781
3371
|
const candidates = [];
|
|
2782
|
-
for (const guide of this.
|
|
3372
|
+
for (const guide of this.candidates()) {
|
|
2783
3373
|
const t = guide.config.trigger;
|
|
2784
3374
|
if (detail.endedGuide && detail.endedGuide.id === guide.id) continue;
|
|
2785
3375
|
if (reason === "ready" || reason === "navigation") {
|
|
@@ -2795,6 +3385,10 @@ var GuidelyClient = class {
|
|
|
2795
3385
|
if (t.type === "page_load" && t.delayMs > 0 && reason === "timer" && detail.guideId !== guide.id) continue;
|
|
2796
3386
|
if (!this.triggerFires(guide, reason, detail)) continue;
|
|
2797
3387
|
if (!this.isEligible(guide, ctx, url)) continue;
|
|
3388
|
+
if (!this.throttleAllows(guide)) {
|
|
3389
|
+
this.log("throttled", guide.key);
|
|
3390
|
+
continue;
|
|
3391
|
+
}
|
|
2798
3392
|
candidates.push(guide);
|
|
2799
3393
|
}
|
|
2800
3394
|
if (reason === "after_guide" || reason === "navigation") this.drainQueue(url, ctx);
|
|
@@ -2840,6 +3434,12 @@ var GuidelyClient = class {
|
|
|
2840
3434
|
}
|
|
2841
3435
|
tryShow(guide, url, manual) {
|
|
2842
3436
|
if (this.destroyed) return false;
|
|
3437
|
+
if (!manual && !this.throttleAllows(guide)) return false;
|
|
3438
|
+
if (this.holdoutFor(guide)) {
|
|
3439
|
+
if (this.hasExclusive() && !manual) return false;
|
|
3440
|
+
this.recordImpression(guide, manual);
|
|
3441
|
+
return true;
|
|
3442
|
+
}
|
|
2843
3443
|
if (!isExclusiveGuide(guide)) {
|
|
2844
3444
|
if (this.flows.has(guide.id) || this.checklists.has(guide.id)) return false;
|
|
2845
3445
|
this.show(guide, manual);
|
|
@@ -2881,14 +3481,18 @@ var GuidelyClient = class {
|
|
|
2881
3481
|
guideState(id) {
|
|
2882
3482
|
return this.state.guides[id] ?? (this.state.guides[id] = { impressions: 0 });
|
|
2883
3483
|
}
|
|
2884
|
-
recordImpression(guide) {
|
|
3484
|
+
recordImpression(guide, manual = false) {
|
|
2885
3485
|
const s = this.guideState(guide.id);
|
|
2886
3486
|
s.impressions += 1;
|
|
2887
3487
|
s.lastShownAt = nowIso();
|
|
2888
3488
|
if (!this.session.shownGuides.includes(guide.id)) this.session.shownGuides.push(guide.id);
|
|
3489
|
+
this.recordLaunch(guide, manual);
|
|
2889
3490
|
this.persistState();
|
|
2890
3491
|
this.persistSession();
|
|
2891
|
-
this.
|
|
3492
|
+
const holdout = this.holdoutFor(guide);
|
|
3493
|
+
const experiment = holdout ? { id: holdout.experimentId, variantId: holdout.variantId } : guide.experiment;
|
|
3494
|
+
if (experiment) this.enqueue("experiment_viewed", { properties: { guideExperimentId: experiment.id, variantId: experiment.variantId, ...holdout ? {} : { guideId: guide.id } } });
|
|
3495
|
+
if (!holdout) this.syncGuideState(guide.id, { impression: true });
|
|
2892
3496
|
}
|
|
2893
3497
|
recordEnd(guide, reason) {
|
|
2894
3498
|
const s = this.guideState(guide.id);
|
|
@@ -2916,7 +3520,8 @@ var GuidelyClient = class {
|
|
|
2916
3520
|
this.reportError(err);
|
|
2917
3521
|
}
|
|
2918
3522
|
}
|
|
2919
|
-
showFlow(
|
|
3523
|
+
showFlow(raw, manual, stepId) {
|
|
3524
|
+
const guide = this.localize(raw);
|
|
2920
3525
|
const exclusive = isExclusiveGuide(guide);
|
|
2921
3526
|
const flow = { guide, manual, view: null, controller: null };
|
|
2922
3527
|
let renders = 0;
|
|
@@ -2931,7 +3536,7 @@ var GuidelyClient = class {
|
|
|
2931
3536
|
const step = e.stepId ? guide.config.steps.find((s) => s.id === e.stepId) : void 0;
|
|
2932
3537
|
this.enqueue(e.name, { guideId: guide.id, guideVersion: guide.version, stepId: e.stepId, stepIndex: e.stepIndex, properties: e.properties });
|
|
2933
3538
|
if (e.name === "guide_started") {
|
|
2934
|
-
this.recordImpression(guide);
|
|
3539
|
+
this.recordImpression(guide, manual);
|
|
2935
3540
|
this.emitter.emit("guide:start", guide);
|
|
2936
3541
|
} else if (e.name === "step_viewed" && step) {
|
|
2937
3542
|
this.emitter.emit("guide:step", { guide, stepId: step.id, stepIndex: e.stepIndex ?? 0 });
|
|
@@ -2978,21 +3583,24 @@ var GuidelyClient = class {
|
|
|
2978
3583
|
if (this.flows.get(guide.id) === flow) this.flows.delete(guide.id);
|
|
2979
3584
|
}
|
|
2980
3585
|
}
|
|
2981
|
-
showChecklist(
|
|
3586
|
+
showChecklist(raw, open) {
|
|
3587
|
+
if (!raw.config.checklist || this.checklists.has(raw.id)) return;
|
|
3588
|
+
const guide = this.localize(raw);
|
|
2982
3589
|
const config = guide.config.checklist;
|
|
2983
|
-
if (!config
|
|
3590
|
+
if (!config) return;
|
|
2984
3591
|
const progress = this.getChecklistProgress(guide.id);
|
|
2985
3592
|
const first = !this.state.guides[guide.id]?.impressions;
|
|
2986
|
-
this.checklists.set(guide.id, { open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
3593
|
+
this.checklists.set(guide.id, { guide, open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
2987
3594
|
if (first) this.recordImpression(guide);
|
|
2988
3595
|
if (this.checklists.get(guide.id)?.open) this.enqueue("guide_viewed", { guideId: guide.id, guideVersion: guide.version });
|
|
2989
3596
|
this.notify();
|
|
2990
3597
|
}
|
|
2991
|
-
showSurvey(
|
|
2992
|
-
if (!
|
|
3598
|
+
showSurvey(raw, manual) {
|
|
3599
|
+
if (!raw.config.survey) return;
|
|
3600
|
+
const guide = this.localize(raw);
|
|
2993
3601
|
this.activeSurvey = { guide, manual };
|
|
2994
3602
|
this.surveyView = { key: `${guide.id}:${randomId(6)}`, guide, submitted: false };
|
|
2995
|
-
this.recordImpression(guide);
|
|
3603
|
+
this.recordImpression(guide, manual);
|
|
2996
3604
|
this.enqueue("survey_displayed", { guideId: guide.id, guideVersion: guide.version });
|
|
2997
3605
|
this.emitter.emit("guide:start", guide);
|
|
2998
3606
|
this.notify();
|
|
@@ -3052,10 +3660,7 @@ var GuidelyClient = class {
|
|
|
3052
3660
|
const steps = [];
|
|
3053
3661
|
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
3662
|
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
|
-
});
|
|
3663
|
+
this.checklists.forEach((w, id) => checklists.push({ guide: w.guide, progress: this.getChecklistProgress(id), open: w.open }));
|
|
3059
3664
|
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, theme: this.themeFor(), messenger: !!this.messenger };
|
|
3060
3665
|
for (const cb of Array.from(this.uiListeners)) {
|
|
3061
3666
|
try {
|
|
@@ -3140,10 +3745,58 @@ var GuidelyClient = class {
|
|
|
3140
3745
|
getAudienceIds() {
|
|
3141
3746
|
return this.state.audienceIds;
|
|
3142
3747
|
}
|
|
3748
|
+
/** The published resource center in the visitor's language, or null when it is off. */
|
|
3143
3749
|
getResourceCenter() {
|
|
3144
|
-
const rc = this.
|
|
3750
|
+
const rc = this.localizedResourceCenter();
|
|
3145
3751
|
return rc && rc.enabled ? rc : null;
|
|
3146
3752
|
}
|
|
3753
|
+
/** The resource center in the visitor's language (cached until the config or language changes). */
|
|
3754
|
+
localizedResourceCenter() {
|
|
3755
|
+
const rc = this.config?.resourceCenter;
|
|
3756
|
+
if (!rc) return null;
|
|
3757
|
+
const locales = this.locales();
|
|
3758
|
+
const key = `${locales.join(",")}|${this.defaultLocale()}`;
|
|
3759
|
+
if (this.localizedRc?.source !== rc || this.localizedRc.locales !== key) this.localizedRc = { source: rc, locales: key, value: localizeResourceCenter(rc, locales, this.defaultLocale()) };
|
|
3760
|
+
return this.localizedRc.value;
|
|
3761
|
+
}
|
|
3762
|
+
/** Evaluates targeting rules (e.g. a resource-center section's) for the current visitor and screen. */
|
|
3763
|
+
matchesTargeting(rules) {
|
|
3764
|
+
if (!rules) return true;
|
|
3765
|
+
try {
|
|
3766
|
+
return evaluateRules(rules, this.getContext());
|
|
3767
|
+
} catch (err) {
|
|
3768
|
+
this.reportError(err);
|
|
3769
|
+
return false;
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
/** Resource-center sections this visitor sees, in order (disabled and non-matching ones left out). */
|
|
3773
|
+
getResourceCenterModules() {
|
|
3774
|
+
const rc = this.getResourceCenter();
|
|
3775
|
+
if (!rc) return [];
|
|
3776
|
+
return orderedModules(rc.modules).filter((m) => m.enabled && (!("targeting" in m) || this.matchesTargeting(m.targeting))).map((m) => m.id);
|
|
3777
|
+
}
|
|
3778
|
+
/**
|
|
3779
|
+
* Sections of the messenger's Help tab, in order: the resource center's modules without "What's
|
|
3780
|
+
* new" (the messenger has its own News tab); every section when no resource center is published.
|
|
3781
|
+
*/
|
|
3782
|
+
getHelpModules() {
|
|
3783
|
+
const modules = this.getResourceCenter() ? this.getResourceCenterModules() : [...RC_MODULE_IDS];
|
|
3784
|
+
return modules.filter((id) => id !== "announcements");
|
|
3785
|
+
}
|
|
3786
|
+
/** "What's new" entries this visitor sees (newest first); empty when the section is off. */
|
|
3787
|
+
getAnnouncements() {
|
|
3788
|
+
const rc = this.getResourceCenter();
|
|
3789
|
+
if (!rc || !this.getResourceCenterModules().includes("announcements")) return [];
|
|
3790
|
+
return visibleAnnouncements(rc.announcements, this.state.audienceIds);
|
|
3791
|
+
}
|
|
3792
|
+
/** "What's new" entries published since the visitor last opened the resource center. */
|
|
3793
|
+
getUnreadAnnouncements() {
|
|
3794
|
+
return unreadAnnouncements(this.getAnnouncements(), this.state.announcementsSeenAt);
|
|
3795
|
+
}
|
|
3796
|
+
/** Whether an entry is new to the visitor in the resource center that is open now. */
|
|
3797
|
+
isAnnouncementNew(a) {
|
|
3798
|
+
return unreadAnnouncements([a], this.announcementsSeenAtOpen) > 0;
|
|
3799
|
+
}
|
|
3147
3800
|
async searchResourceCenter(q) {
|
|
3148
3801
|
try {
|
|
3149
3802
|
const res = await this.api.get("/resource-center/search", { params: { q } });
|
|
@@ -3152,6 +3805,11 @@ var GuidelyClient = class {
|
|
|
3152
3805
|
return [];
|
|
3153
3806
|
}
|
|
3154
3807
|
}
|
|
3808
|
+
/** The language of the resource center as shown, when it isn't the default (sent with article fetches). */
|
|
3809
|
+
articleLocale() {
|
|
3810
|
+
const locale = this.localizedResourceCenter()?.locale;
|
|
3811
|
+
return locale && locale.toLowerCase() !== this.defaultLocale().toLowerCase() ? locale : void 0;
|
|
3812
|
+
}
|
|
3155
3813
|
articleViewed(article) {
|
|
3156
3814
|
this.enqueue("article_viewed", { properties: { articleId: article.id, slug: article.slug } });
|
|
3157
3815
|
}
|
|
@@ -3286,6 +3944,11 @@ var GuidelyClient = class {
|
|
|
3286
3944
|
}
|
|
3287
3945
|
if (this.resourceCenterOpen) return;
|
|
3288
3946
|
this.resourceCenterOpen = true;
|
|
3947
|
+
this.announcementsSeenAtOpen = this.state.announcementsSeenAt ?? null;
|
|
3948
|
+
if (this.getAnnouncements().length) {
|
|
3949
|
+
this.state.announcementsSeenAt = nowIso();
|
|
3950
|
+
this.persistState();
|
|
3951
|
+
}
|
|
3289
3952
|
this.enqueue("resource_center_opened");
|
|
3290
3953
|
this.emitter.emit("resource-center:open");
|
|
3291
3954
|
this.notify();
|
|
@@ -3321,7 +3984,8 @@ var GuidelyClient = class {
|
|
|
3321
3984
|
onMessage: (message, conversation) => this.emitter.emit("conversation:message", { message, conversation }),
|
|
3322
3985
|
onStartGuide: (key) => this.setTimer(() => this.startGuide(key), 0),
|
|
3323
3986
|
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err),
|
|
3324
|
-
health: this.api.health
|
|
3987
|
+
health: this.api.health,
|
|
3988
|
+
locale: () => this.articleLocale()
|
|
3325
3989
|
});
|
|
3326
3990
|
this.resourceCenterOpen = false;
|
|
3327
3991
|
}
|
|
@@ -3335,6 +3999,14 @@ var GuidelyClient = class {
|
|
|
3335
3999
|
getMessenger() {
|
|
3336
4000
|
return this.messenger;
|
|
3337
4001
|
}
|
|
4002
|
+
/** Where the visitor dragged the messenger bubble on this device, or null to use the configured spot. */
|
|
4003
|
+
getMessengerLauncherPlacement() {
|
|
4004
|
+
return parsePlacement(this.store.get(LAUNCHER_PLACEMENT_KEY));
|
|
4005
|
+
}
|
|
4006
|
+
/** Remembers the bubble's spot on this device (in memory only when no `storage` was passed). */
|
|
4007
|
+
setMessengerLauncherPlacement(placement) {
|
|
4008
|
+
this.store.set(LAUNCHER_PLACEMENT_KEY, serializePlacement(placement));
|
|
4009
|
+
}
|
|
3338
4010
|
/** Opens the Scout messenger, optionally on a tab. No-op when Scout is off for this platform. */
|
|
3339
4011
|
openMessenger(tab) {
|
|
3340
4012
|
this.whenHydrated(() => this.messenger?.open(tab));
|
|
@@ -3365,6 +4037,11 @@ var GuidelyClient = class {
|
|
|
3365
4037
|
onFlagsChange(cb) {
|
|
3366
4038
|
return this.flags.subscribe(cb);
|
|
3367
4039
|
}
|
|
4040
|
+
/** Device facts on every screen view, read by the Pages devices / browsers breakdowns. */
|
|
4041
|
+
deviceProperties() {
|
|
4042
|
+
const d = this.bridge.device;
|
|
4043
|
+
return { platform: d.platform, deviceType: d.deviceType, os: d.platform };
|
|
4044
|
+
}
|
|
3368
4045
|
/** Attributes flags are evaluated with: identity, traits, account, audiences, screen and device. */
|
|
3369
4046
|
flagContext() {
|
|
3370
4047
|
const d = this.bridge.device;
|
|
@@ -3455,7 +4132,191 @@ var GuidelyClient = class {
|
|
|
3455
4132
|
}
|
|
3456
4133
|
};
|
|
3457
4134
|
function forPlatform(config, platform) {
|
|
3458
|
-
|
|
4135
|
+
const keep = (g) => supportsPlatform(g.config, platform);
|
|
4136
|
+
const holdouts = config.guideHoldouts?.filter((h) => keep(h.guide)).map((h) => ({ ...h, guide: withoutCode(h.guide) }));
|
|
4137
|
+
return { ...config, guides: config.guides.filter(keep).map(withoutCode), ...holdouts ? { guideHoldouts: holdouts } : {} };
|
|
4138
|
+
}
|
|
4139
|
+
function withoutCode(guide) {
|
|
4140
|
+
if (!guide.config.steps.some((s) => s.code)) return guide;
|
|
4141
|
+
return { ...guide, config: { ...guide.config, steps: guide.config.steps.map(({ code: _code, ...step }) => step) } };
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
// src/ui/Autocapture.tsx
|
|
4145
|
+
var import_react8 = require("react");
|
|
4146
|
+
var import_react_native11 = require("react-native");
|
|
4147
|
+
|
|
4148
|
+
// src/core/viewTree.ts
|
|
4149
|
+
var HOST_TEXT = 6;
|
|
4150
|
+
var MAX_ANCESTORS = 500;
|
|
4151
|
+
var MAX_TEXT_NODES = 300;
|
|
4152
|
+
var INTERACTIVE_ROLES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem", "checkbox", "switch", "radio", "togglebutton", "imagebutton", "option", "combobox", "adjustable"]);
|
|
4153
|
+
var TEXT_INPUT_HOST = /TextInput/i;
|
|
4154
|
+
var NOISE = /^(Anonymous|Unknown|ForwardRef|Memo|Context|Provider|Consumer|Fragment|StrictMode|Suspense|Profiler)$|Context$|Provider$|Consumer$/;
|
|
4155
|
+
var isFiber = (v) => !!v && typeof v === "object" && "return" in v && "memoizedProps" in v;
|
|
4156
|
+
function fiberFromEvent(event) {
|
|
4157
|
+
try {
|
|
4158
|
+
const e = event;
|
|
4159
|
+
if (isFiber(e?._targetInst)) return e._targetInst;
|
|
4160
|
+
const target = e?.target;
|
|
4161
|
+
if (!target || typeof target !== "object") return null;
|
|
4162
|
+
for (const key of ["__internalInstanceHandle", "_internalInstanceHandle", "_internalFiberInstanceHandleDEV"]) if (isFiber(target[key])) return target[key];
|
|
4163
|
+
for (const sym of Object.getOwnPropertySymbols(target)) if (sym.description === "internalInstanceHandle" && isFiber(target[sym])) return target[sym];
|
|
4164
|
+
} catch {
|
|
4165
|
+
}
|
|
4166
|
+
return null;
|
|
4167
|
+
}
|
|
4168
|
+
function componentName(type, depth = 0) {
|
|
4169
|
+
if (!type || depth > 3) return null;
|
|
4170
|
+
if (typeof type === "function") {
|
|
4171
|
+
const f = type;
|
|
4172
|
+
const n = typeof f.displayName === "string" && f.displayName ? f.displayName : typeof f.name === "string" ? f.name : "";
|
|
4173
|
+
return n || null;
|
|
4174
|
+
}
|
|
4175
|
+
if (typeof type === "object") {
|
|
4176
|
+
const o = type;
|
|
4177
|
+
if (typeof o.displayName === "string" && o.displayName) return o.displayName;
|
|
4178
|
+
return componentName(o.render, depth + 1) ?? componentName(o.type, depth + 1);
|
|
4179
|
+
}
|
|
4180
|
+
return null;
|
|
4181
|
+
}
|
|
4182
|
+
var isHost = (f) => typeof f.type === "string";
|
|
4183
|
+
var isTextInput = (f) => typeof f.type === "string" ? TEXT_INPUT_HOST.test(f.type) : componentName(f.type) === "TextInput";
|
|
4184
|
+
function isInteractive(props) {
|
|
4185
|
+
if (!props || typeof props !== "object") return null;
|
|
4186
|
+
if (typeof props.onPress === "function" || typeof props.onLongPress === "function") return "press";
|
|
4187
|
+
const role = props.accessibilityRole ?? props.role;
|
|
4188
|
+
return typeof role === "string" && INTERACTIVE_ROLES.has(role) ? "role" : null;
|
|
4189
|
+
}
|
|
4190
|
+
function firstHost(f) {
|
|
4191
|
+
let cur = f;
|
|
4192
|
+
for (let i = 0; cur && i < 30; i++, cur = cur.child) if (isHost(cur)) return cur;
|
|
4193
|
+
return null;
|
|
4194
|
+
}
|
|
4195
|
+
function idSelector(f) {
|
|
4196
|
+
const host = firstHost(f);
|
|
4197
|
+
const sources = [f.memoizedProps, host?.memoizedProps];
|
|
4198
|
+
for (const key of ["nativeID", "testID"]) {
|
|
4199
|
+
for (const p of sources) {
|
|
4200
|
+
const v = p && typeof p === "object" ? p[key] : void 0;
|
|
4201
|
+
if (typeof v === "string" && v) return attrSelector(key, v);
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
return null;
|
|
4205
|
+
}
|
|
4206
|
+
function pathSelector(f) {
|
|
4207
|
+
const parts = [];
|
|
4208
|
+
let cur = f;
|
|
4209
|
+
for (let i = 0; cur && parts.length < 4 && i < MAX_ANCESTORS; i++, cur = cur.return) {
|
|
4210
|
+
if (isHost(cur)) continue;
|
|
4211
|
+
const name = componentName(cur.type);
|
|
4212
|
+
if (!name || NOISE.test(name)) continue;
|
|
4213
|
+
let same = 0;
|
|
4214
|
+
let index = 0;
|
|
4215
|
+
for (let s = cur.return?.child, n = 0; s && n < 500; s = s.sibling, n++) {
|
|
4216
|
+
if (s.type !== cur.type) continue;
|
|
4217
|
+
same += 1;
|
|
4218
|
+
if (s === cur) index = same;
|
|
4219
|
+
}
|
|
4220
|
+
parts.unshift(same > 1 && index ? `${name}:nth-of-type(${index})` : name);
|
|
4221
|
+
}
|
|
4222
|
+
return parts.join(" > ").slice(0, 300) || "View";
|
|
4223
|
+
}
|
|
4224
|
+
function subtreeText(f, opts) {
|
|
4225
|
+
const out = [];
|
|
4226
|
+
let visited = 0;
|
|
4227
|
+
let masked = false;
|
|
4228
|
+
const walk = (node) => {
|
|
4229
|
+
for (let n = node; n && visited < MAX_TEXT_NODES && !masked; n = n.sibling) {
|
|
4230
|
+
visited += 1;
|
|
4231
|
+
if (opts.isMask?.(n.type)) {
|
|
4232
|
+
masked = true;
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
if (isTextInput(n)) continue;
|
|
4236
|
+
if (n.tag === HOST_TEXT && (typeof n.memoizedProps === "string" || typeof n.memoizedProps === "number")) out.push(String(n.memoizedProps));
|
|
4237
|
+
else walk(n.child);
|
|
4238
|
+
}
|
|
4239
|
+
};
|
|
4240
|
+
if (opts.isMask?.(f.type)) return { text: "", masked: true };
|
|
4241
|
+
walk(f.child);
|
|
4242
|
+
return { text: out.join(" "), masked };
|
|
4243
|
+
}
|
|
4244
|
+
function measureHost(host) {
|
|
4245
|
+
return new Promise((resolve) => {
|
|
4246
|
+
let settled = false;
|
|
4247
|
+
const done = (r) => {
|
|
4248
|
+
if (settled) return;
|
|
4249
|
+
settled = true;
|
|
4250
|
+
clearTimeout(timer);
|
|
4251
|
+
resolve(r);
|
|
4252
|
+
};
|
|
4253
|
+
const timer = setTimeout(() => done(null), 500);
|
|
4254
|
+
const cb = (x, y, width, height) => done(Number.isFinite(x) && Number.isFinite(y) && width > 0 && height > 0 ? { x, y, width, height } : null);
|
|
4255
|
+
try {
|
|
4256
|
+
const sn = host.stateNode;
|
|
4257
|
+
const pub = sn?.canonical?.publicInstance ?? sn;
|
|
4258
|
+
if (pub && typeof pub.measureInWindow === "function") return pub.measureInWindow(cb);
|
|
4259
|
+
const ui = globalThis.nativeFabricUIManager;
|
|
4260
|
+
if (sn?.node && typeof ui?.measureInWindow === "function") return ui.measureInWindow(sn.node, cb);
|
|
4261
|
+
} catch {
|
|
4262
|
+
}
|
|
4263
|
+
done(null);
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
4266
|
+
function describe(f, masked, opts, withText) {
|
|
4267
|
+
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4268
|
+
const host = firstHost(f);
|
|
4269
|
+
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
4270
|
+
if (withText && !masked) {
|
|
4271
|
+
const sub = subtreeText(f, opts);
|
|
4272
|
+
if (!sub.masked) {
|
|
4273
|
+
const hostProps = host?.memoizedProps && typeof host.memoizedProps === "object" ? host.memoizedProps : {};
|
|
4274
|
+
const label = props.accessibilityLabel ?? props["aria-label"] ?? hostProps.accessibilityLabel ?? hostProps["aria-label"];
|
|
4275
|
+
const text = cleanText(sub.text) ?? (typeof label === "string" ? cleanText(label) : void 0);
|
|
4276
|
+
if (text) el.text = text;
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
if (host) el.measure = () => measureHost(host);
|
|
4280
|
+
return el;
|
|
4281
|
+
}
|
|
4282
|
+
function resolveFiber(target, opts = {}) {
|
|
4283
|
+
const none = { ignored: false, element: null, hit: null };
|
|
4284
|
+
if (!target) return none;
|
|
4285
|
+
try {
|
|
4286
|
+
let press = null;
|
|
4287
|
+
let role = null;
|
|
4288
|
+
let hitFiber = null;
|
|
4289
|
+
let stopped = false;
|
|
4290
|
+
let masked = false;
|
|
4291
|
+
for (let f = target, i = 0; f && i < MAX_ANCESTORS; f = f.return, i++) {
|
|
4292
|
+
if (opts.isIgnored?.(f.type)) return { ignored: true, element: null, hit: null };
|
|
4293
|
+
if (opts.isMask?.(f.type)) masked = true;
|
|
4294
|
+
if (stopped) continue;
|
|
4295
|
+
if (isTextInput(f)) {
|
|
4296
|
+
stopped = true;
|
|
4297
|
+
continue;
|
|
4298
|
+
}
|
|
4299
|
+
if (isHost(f)) continue;
|
|
4300
|
+
const name = componentName(f.type);
|
|
4301
|
+
if (!hitFiber && name && !NOISE.test(name)) hitFiber = f;
|
|
4302
|
+
const kind = isInteractive(f.memoizedProps);
|
|
4303
|
+
if (kind === "press") {
|
|
4304
|
+
press = f;
|
|
4305
|
+
stopped = true;
|
|
4306
|
+
} else if (kind === "role" && !role) role = f;
|
|
4307
|
+
}
|
|
4308
|
+
const element = press ?? role;
|
|
4309
|
+
return {
|
|
4310
|
+
ignored: false,
|
|
4311
|
+
element: element ? describe(element, masked, opts, true) : null,
|
|
4312
|
+
hit: hitFiber ? describe(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4313
|
+
};
|
|
4314
|
+
} catch {
|
|
4315
|
+
return none;
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
function resolveTapTarget(event, opts = {}) {
|
|
4319
|
+
return resolveFiber(fiberFromEvent(event), opts);
|
|
3459
4320
|
}
|
|
3460
4321
|
|
|
3461
4322
|
// src/ui/Overlay.tsx
|
|
@@ -4009,14 +4870,89 @@ function ago(iso) {
|
|
|
4009
4870
|
if (s < 86400) return `${Math.floor(s / 3600)}h`;
|
|
4010
4871
|
return `${Math.floor(s / 86400)}d`;
|
|
4011
4872
|
}
|
|
4012
|
-
|
|
4873
|
+
var LAUNCHER_SIZE = 54;
|
|
4874
|
+
function useReduceMotion() {
|
|
4875
|
+
const [on, setOn] = (0, import_react3.useState)(false);
|
|
4876
|
+
(0, import_react3.useEffect)(() => {
|
|
4877
|
+
let live = true;
|
|
4878
|
+
import_react_native5.AccessibilityInfo.isReduceMotionEnabled?.().then((v) => live && setOn(v)).catch(() => {
|
|
4879
|
+
});
|
|
4880
|
+
const sub = import_react_native5.AccessibilityInfo.addEventListener?.("reduceMotionChanged", setOn);
|
|
4881
|
+
return () => {
|
|
4882
|
+
live = false;
|
|
4883
|
+
sub?.remove();
|
|
4884
|
+
};
|
|
4885
|
+
}, []);
|
|
4886
|
+
return on;
|
|
4887
|
+
}
|
|
4888
|
+
function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
|
|
4013
4889
|
const snap = useMessenger(store);
|
|
4890
|
+
const reduceMotion = useReduceMotion();
|
|
4891
|
+
const [placement, setPlacement] = (0, import_react3.useState)(() => client.getMessengerLauncherPlacement());
|
|
4892
|
+
const pan = (0, import_react3.useRef)(new import_react_native5.Animated.ValueXY()).current;
|
|
4893
|
+
const offset = (0, import_react3.useRef)({ x: 0, y: 0 });
|
|
4894
|
+
const dragging = (0, import_react3.useRef)(false);
|
|
4895
|
+
const live = (0, import_react3.useRef)({ frame, defaultSide, reduceMotion });
|
|
4896
|
+
live.current = { frame, defaultSide, reduceMotion };
|
|
4897
|
+
const { width, height, size, defaultBottom } = frame;
|
|
4898
|
+
const { top, bottom, left, right } = frame.insets;
|
|
4899
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
4900
|
+
if (dragging.current) return;
|
|
4901
|
+
const f = live.current.frame;
|
|
4902
|
+
const anchor = placementPoint(f, { side: defaultSide, fraction: 0 });
|
|
4903
|
+
const at = placement ? placementPoint(f, placement) : anchor;
|
|
4904
|
+
offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
|
|
4905
|
+
pan.setValue(offset.current);
|
|
4906
|
+
}, [pan, placement, defaultSide, width, height, size, defaultBottom, top, bottom, left, right]);
|
|
4907
|
+
const responder = (0, import_react3.useMemo)(() => {
|
|
4908
|
+
const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
|
|
4909
|
+
const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
|
|
4910
|
+
let start = { x: 0, y: 0 };
|
|
4911
|
+
const follow = (g) => {
|
|
4912
|
+
const a = anchor();
|
|
4913
|
+
const at = clampToSafeArea(live.current.frame, { x: a.x + start.x + g.dx, y: a.y + start.y + g.dy });
|
|
4914
|
+
return { x: at.x - a.x, y: at.y - a.y };
|
|
4915
|
+
};
|
|
4916
|
+
const drop = (g) => {
|
|
4917
|
+
const a = anchor();
|
|
4918
|
+
const d = follow(g);
|
|
4919
|
+
const next = snapPlacement(live.current.frame, { x: a.x + d.x, y: a.y + d.y });
|
|
4920
|
+
const at = placementPoint(live.current.frame, next);
|
|
4921
|
+
const to = { x: at.x - a.x, y: at.y - a.y };
|
|
4922
|
+
offset.current = to;
|
|
4923
|
+
client.setMessengerLauncherPlacement(next);
|
|
4924
|
+
const done = ({ finished }) => {
|
|
4925
|
+
if (finished) dragging.current = false;
|
|
4926
|
+
setPlacement(next);
|
|
4927
|
+
};
|
|
4928
|
+
if (live.current.reduceMotion) {
|
|
4929
|
+
pan.setValue(to);
|
|
4930
|
+
done({ finished: true });
|
|
4931
|
+
} else import_react_native5.Animated.timing(pan, { toValue: to, duration: 300, easing: import_react_native5.Easing.out(import_react_native5.Easing.cubic), useNativeDriver: true }).start(done);
|
|
4932
|
+
};
|
|
4933
|
+
return import_react_native5.PanResponder.create({
|
|
4934
|
+
// Taps stay with the Pressable; only a real drag is taken (which cancels its press).
|
|
4935
|
+
onMoveShouldSetPanResponderCapture: (_, g) => moved(g),
|
|
4936
|
+
onMoveShouldSetPanResponder: (_, g) => moved(g),
|
|
4937
|
+
onPanResponderGrant: () => {
|
|
4938
|
+
dragging.current = true;
|
|
4939
|
+
start = offset.current;
|
|
4940
|
+
pan.stopAnimation((v) => {
|
|
4941
|
+
if (Number.isFinite(v?.x) && Number.isFinite(v?.y)) start = v;
|
|
4942
|
+
});
|
|
4943
|
+
},
|
|
4944
|
+
onPanResponderMove: (_, g) => pan.setValue(follow(g)),
|
|
4945
|
+
onPanResponderRelease: (_, g) => drop(g),
|
|
4946
|
+
onPanResponderTerminate: (_, g) => drop(g),
|
|
4947
|
+
onPanResponderTerminationRequest: () => false
|
|
4948
|
+
});
|
|
4949
|
+
}, [client, pan]);
|
|
4014
4950
|
if (snap.open) return null;
|
|
4015
4951
|
const label = snap.unread ? `Open ${snap.config.name}, ${snap.unread} unread` : `Open ${snap.config.name}`;
|
|
4016
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher,
|
|
4952
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Animated.View, { ...responder.panHandlers, style: [styles4.launcherWrap, style, { transform: pan.getTranslateTransform() }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => !dragging.current && store.open(), style: ({ pressed }) => [styles4.launcher, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
|
|
4017
4953
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
|
|
4018
4954
|
snap.unread ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: styles4.badge, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: styles4.badgeText, children: snap.unread }) }) : null
|
|
4019
|
-
] });
|
|
4955
|
+
] }) });
|
|
4020
4956
|
}
|
|
4021
4957
|
function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
|
|
4022
4958
|
const snap = useMessenger(store);
|
|
@@ -4215,25 +5151,41 @@ function Help({ store, client, palette: p }) {
|
|
|
4215
5151
|
] }, title);
|
|
4216
5152
|
const audiences = client.getAudienceIds();
|
|
4217
5153
|
const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
|
|
4218
|
-
const
|
|
5154
|
+
const modules = client.getHelpModules();
|
|
5155
|
+
const searchOn = modules.includes("search");
|
|
5156
|
+
const listed = searchOn ? results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences)) : void 0;
|
|
5157
|
+
const checklists = (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist");
|
|
5158
|
+
const renderModule = (id) => {
|
|
5159
|
+
switch (id) {
|
|
5160
|
+
case "guides":
|
|
5161
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null;
|
|
5162
|
+
case "checklists":
|
|
5163
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u2611"))) : null;
|
|
5164
|
+
case "articles":
|
|
5165
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
5166
|
+
!articles ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.ActivityIndicator, { color: p.muted }) : null,
|
|
5167
|
+
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a)))))
|
|
5168
|
+
] });
|
|
5169
|
+
case "faqs":
|
|
5170
|
+
return rc?.faqs.length ? section(
|
|
5171
|
+
"FAQ",
|
|
5172
|
+
rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
|
|
5173
|
+
/* @__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: [
|
|
5174
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
|
|
5175
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
5176
|
+
] }),
|
|
5177
|
+
faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
5178
|
+
] }, f.id))
|
|
5179
|
+
) : null;
|
|
5180
|
+
case "links":
|
|
5181
|
+
return rc?.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
5182
|
+
default:
|
|
5183
|
+
return null;
|
|
5184
|
+
}
|
|
5185
|
+
};
|
|
4219
5186
|
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
|
-
] })
|
|
5187
|
+
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,
|
|
5188
|
+
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
5189
|
] });
|
|
4238
5190
|
}
|
|
4239
5191
|
function Article({ article, palette: p, client }) {
|
|
@@ -4245,7 +5197,8 @@ function Article({ article, palette: p, client }) {
|
|
|
4245
5197
|
}
|
|
4246
5198
|
var styles4 = import_react_native5.StyleSheet.create({
|
|
4247
5199
|
flex: { flex: 1 },
|
|
4248
|
-
|
|
5200
|
+
launcherWrap: { position: "absolute", width: LAUNCHER_SIZE, height: LAUNCHER_SIZE, elevation: 8 },
|
|
5201
|
+
launcher: { width: LAUNCHER_SIZE, height: LAUNCHER_SIZE, borderRadius: 27, alignItems: "center", justifyContent: "center", elevation: 8, shadowColor: "#000", shadowOpacity: 0.22, shadowRadius: 12, shadowOffset: { width: 0, height: 4 } },
|
|
4249
5202
|
launcherIcon: { fontSize: 24 },
|
|
4250
5203
|
badge: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center", paddingHorizontal: 5, borderWidth: 2, borderColor: "#ffffff" },
|
|
4251
5204
|
badgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" },
|
|
@@ -4336,6 +5289,8 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4336
5289
|
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
5290
|
if (!rc) return null;
|
|
4338
5291
|
const close = () => client.closeResourceCenter();
|
|
5292
|
+
const modules = client.getResourceCenterModules();
|
|
5293
|
+
const announcements = client.getAnnouncements();
|
|
4339
5294
|
const openArticle = (a) => {
|
|
4340
5295
|
if (a.externalUrl) return client.navigate(a.externalUrl);
|
|
4341
5296
|
client.articleViewed(a);
|
|
@@ -4354,7 +5309,51 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4354
5309
|
] }, title);
|
|
4355
5310
|
const audiences = client.getAudienceIds();
|
|
4356
5311
|
const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
|
|
4357
|
-
const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
|
|
5312
|
+
const listed = modules.includes("search") ? results?.filter((a) => isArticleListed(rc.collections, a, audiences)) : void 0;
|
|
5313
|
+
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);
|
|
5314
|
+
const announcement2 = (a) => {
|
|
5315
|
+
const linked = a.articleId ? rc.articles.find((x) => x.id === a.articleId) : void 0;
|
|
5316
|
+
const isNew = client.isAnnouncementNew(a);
|
|
5317
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5318
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles5.faqHead, children: [
|
|
5319
|
+
/* @__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 }),
|
|
5320
|
+
isNew ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.badge, { color: p.onPrimary, backgroundColor: p.primary }], children: "NEW" }) : null
|
|
5321
|
+
] }),
|
|
5322
|
+
a.body ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: a.body, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null,
|
|
5323
|
+
a.guideId || linked || a.url ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles5.links, children: [
|
|
5324
|
+
a.guideId ? link("Show me", () => (close(), client.startGuide(a.guideId))) : null,
|
|
5325
|
+
linked ? link("Read more", () => openArticle(linked)) : null,
|
|
5326
|
+
a.url ? link(a.linkLabel || "Learn more", () => client.navigate(a.url)) : null
|
|
5327
|
+
] }) : null
|
|
5328
|
+
] }, a.id);
|
|
5329
|
+
};
|
|
5330
|
+
const renderModule = (id) => {
|
|
5331
|
+
switch (id) {
|
|
5332
|
+
case "announcements":
|
|
5333
|
+
return announcements.length ? section("What's new", announcements.map(announcement2)) : null;
|
|
5334
|
+
case "guides":
|
|
5335
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null;
|
|
5336
|
+
case "checklists":
|
|
5337
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null;
|
|
5338
|
+
case "articles":
|
|
5339
|
+
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)))));
|
|
5340
|
+
case "faqs":
|
|
5341
|
+
return rc.faqs.length ? section(
|
|
5342
|
+
"FAQ",
|
|
5343
|
+
rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5344
|
+
/* @__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: [
|
|
5345
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
|
|
5346
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
5347
|
+
] }),
|
|
5348
|
+
faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
5349
|
+
] }, f.id))
|
|
5350
|
+
) : null;
|
|
5351
|
+
case "links":
|
|
5352
|
+
return rc.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
5353
|
+
default:
|
|
5354
|
+
return null;
|
|
5355
|
+
}
|
|
5356
|
+
};
|
|
4358
5357
|
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
5358
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.header, { borderColor: p.border }], children: [
|
|
4360
5359
|
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 +5364,7 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4365
5364
|
/* @__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
5365
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
|
|
4367
5366
|
] }) : /* @__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)(
|
|
5367
|
+
modules.includes("search") ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4369
5368
|
import_react_native6.TextInput,
|
|
4370
5369
|
{
|
|
4371
5370
|
value: query,
|
|
@@ -4377,26 +5376,11 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4377
5376
|
returnKeyType: "search",
|
|
4378
5377
|
style: [styles5.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }]
|
|
4379
5378
|
}
|
|
4380
|
-
),
|
|
5379
|
+
) : null,
|
|
4381
5380
|
listed ? section(
|
|
4382
5381
|
`Results for \u201C${query.trim()}\u201D`,
|
|
4383
5382
|
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
|
-
] })
|
|
5383
|
+
) : /* @__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
5384
|
] })
|
|
4401
5385
|
] }) });
|
|
4402
5386
|
}
|
|
@@ -4416,7 +5400,10 @@ var styles5 = import_react_native6.StyleSheet.create({
|
|
|
4416
5400
|
chev: { fontSize: 18 },
|
|
4417
5401
|
faq: { flexDirection: "column", alignItems: "stretch" },
|
|
4418
5402
|
faqHead: { flexDirection: "row", alignItems: "center", gap: 10 },
|
|
4419
|
-
articleTitle: { fontSize: 22, fontWeight: "700" }
|
|
5403
|
+
articleTitle: { fontSize: 22, fontWeight: "700" },
|
|
5404
|
+
badge: { fontSize: 10, fontWeight: "800", letterSpacing: 0.5, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 6, overflow: "hidden" },
|
|
5405
|
+
links: { flexDirection: "row", flexWrap: "wrap", gap: 16, marginTop: 4 },
|
|
5406
|
+
link: { fontSize: 14, fontWeight: "600" }
|
|
4420
5407
|
});
|
|
4421
5408
|
|
|
4422
5409
|
// src/ui/StepLayer.tsx
|
|
@@ -4444,25 +5431,28 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
4444
5431
|
const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
|
|
4445
5432
|
const space = { top: target.y - insets.top, bottom: viewport.height - insets.bottom - (target.y + target.height) };
|
|
4446
5433
|
const side = order.find(fits) ?? (space.bottom >= space.top ? "bottom" : "top");
|
|
4447
|
-
const
|
|
5434
|
+
const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
4448
5435
|
if (side === "top" || side === "bottom") {
|
|
4449
5436
|
const cx = target.x + target.width / 2;
|
|
4450
5437
|
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width - size.width : cx - size.width / 2;
|
|
4451
|
-
x2 =
|
|
5438
|
+
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size.width));
|
|
4452
5439
|
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset - size.height;
|
|
4453
|
-
y2 =
|
|
4454
|
-
return { x: x2, y: y2, side, arrow:
|
|
5440
|
+
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
|
|
5441
|
+
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET, size.width - ARROW_INSET) };
|
|
4455
5442
|
}
|
|
4456
5443
|
const cy = target.y + target.height / 2;
|
|
4457
5444
|
let y = align === "start" ? target.y : align === "end" ? target.y + target.height - size.height : cy - size.height / 2;
|
|
4458
|
-
y =
|
|
5445
|
+
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
|
|
4459
5446
|
const x = side === "right" ? target.x + target.width + offset : target.x - offset - size.width;
|
|
4460
|
-
return { x, y, side, arrow:
|
|
5447
|
+
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET, size.height - ARROW_INSET) };
|
|
4461
5448
|
}
|
|
4462
5449
|
|
|
4463
5450
|
// src/ui/StepCard.tsx
|
|
4464
5451
|
var import_react_native7 = require("react-native");
|
|
4465
|
-
var import_jsx_runtime7 =
|
|
5452
|
+
var import_jsx_runtime7 = (
|
|
5453
|
+
// An empty description marks the image as decorative: screen readers skip it.
|
|
5454
|
+
require("react/jsx-runtime")
|
|
5455
|
+
);
|
|
4466
5456
|
function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
4467
5457
|
const { step, guide } = view;
|
|
4468
5458
|
const buttons = visibleButtons(step, view.canGoBack);
|
|
@@ -4470,7 +5460,16 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
|
4470
5460
|
const showProgress = step.showProgress && view.total > 1;
|
|
4471
5461
|
const onLink = (url) => client.navigate(url);
|
|
4472
5462
|
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)(
|
|
5463
|
+
step.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
5464
|
+
import_react_native7.Image,
|
|
5465
|
+
{
|
|
5466
|
+
source: { uri: step.imageUrl },
|
|
5467
|
+
style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }],
|
|
5468
|
+
resizeMode: "cover",
|
|
5469
|
+
accessibilityIgnoresInvertColors: true,
|
|
5470
|
+
...step.imageAlt?.trim() ? { accessible: true, accessibilityRole: "image", accessibilityLabel: step.imageAlt.trim() } : { accessible: false, importantForAccessibility: "no" }
|
|
5471
|
+
}
|
|
5472
|
+
) : null,
|
|
4474
5473
|
step.title ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: styles6.header, children: [
|
|
4475
5474
|
/* @__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
5475
|
step.showClose ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
|
|
@@ -5058,21 +6057,36 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5058
6057
|
const basePalette = palette(ui.theme, scheme);
|
|
5059
6058
|
const messenger = ui.messenger ? client.getMessenger() : null;
|
|
5060
6059
|
const showRcLauncher = !messenger && !!client.options.showResourceCenterLauncher && !!client.getResourceCenter();
|
|
6060
|
+
const unread = showRcLauncher ? client.getUnreadAnnouncements() : 0;
|
|
5061
6061
|
const messengerLeft = messenger?.getSnapshot().config.launcher.position === "bottom-left";
|
|
5062
6062
|
const checklistOnSameSide = ui.checklists.some((c) => c.guide.config.checklist?.position === "bottom-left" === messengerLeft) && client.options.showChecklistLauncher !== false;
|
|
6063
|
+
const messengerBottom = insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0);
|
|
5063
6064
|
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
6065
|
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.
|
|
6066
|
+
showRcLauncher ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
5066
6067
|
import_react_native10.Pressable,
|
|
5067
6068
|
{
|
|
5068
6069
|
accessibilityRole: "button",
|
|
5069
|
-
accessibilityLabel: client
|
|
6070
|
+
accessibilityLabel: rcLauncherLabel(client),
|
|
5070
6071
|
onPress: () => client.openResourceCenter(),
|
|
5071
6072
|
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:
|
|
6073
|
+
children: [
|
|
6074
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: [styles9.rcText, { color: basePalette.onPrimary }], children: "?" }),
|
|
6075
|
+
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
|
|
6076
|
+
]
|
|
6077
|
+
}
|
|
6078
|
+
) : null,
|
|
6079
|
+
messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
6080
|
+
MessengerLauncher,
|
|
6081
|
+
{
|
|
6082
|
+
store: messenger,
|
|
6083
|
+
client,
|
|
6084
|
+
palette: basePalette,
|
|
6085
|
+
style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: messengerBottom }],
|
|
6086
|
+
frame: { width, height, insets, size: LAUNCHER_SIZE, defaultBottom: messengerBottom },
|
|
6087
|
+
defaultSide: messengerLeft ? "left" : "right"
|
|
5073
6088
|
}
|
|
5074
6089
|
) : null,
|
|
5075
|
-
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,
|
|
5076
6090
|
ui.survey ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
|
|
5077
6091
|
ui.steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
|
|
5078
6092
|
messenger ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, topInset: insets.top, bottomInset: insets.bottom })
|
|
@@ -5080,28 +6094,76 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5080
6094
|
}
|
|
5081
6095
|
var styles9 = import_react_native10.StyleSheet.create({
|
|
5082
6096
|
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" }
|
|
6097
|
+
rcText: { fontSize: 22, fontWeight: "800" },
|
|
6098
|
+
rcBadge: { position: "absolute", top: -4, right: -4, minWidth: 20, height: 20, paddingHorizontal: 5, borderRadius: 10, borderWidth: 2, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center" },
|
|
6099
|
+
rcBadgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" }
|
|
5084
6100
|
});
|
|
6101
|
+
function rcLauncherLabel(client) {
|
|
6102
|
+
const label = client.getResourceCenter()?.launcher.label || "Help";
|
|
6103
|
+
const unread = client.getUnreadAnnouncements();
|
|
6104
|
+
return unread ? `${label} (${unread} new update${unread === 1 ? "" : "s"})` : label;
|
|
6105
|
+
}
|
|
5085
6106
|
|
|
5086
|
-
// src/ui/
|
|
5087
|
-
var import_react8 = require("react");
|
|
5088
|
-
var import_react_native11 = require("react-native");
|
|
6107
|
+
// src/ui/Autocapture.tsx
|
|
5089
6108
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
5090
|
-
|
|
5091
|
-
|
|
6109
|
+
function GuidelyMask({ children, ...rest }) {
|
|
6110
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.View, { ...rest, children });
|
|
6111
|
+
}
|
|
6112
|
+
var RESOLVE = {
|
|
6113
|
+
isIgnored: (type) => type === GuidelyOverlay,
|
|
6114
|
+
isMask: (type) => type === GuidelyMask
|
|
6115
|
+
};
|
|
6116
|
+
function useTapObserver(client) {
|
|
6117
|
+
const clientRef = (0, import_react8.useRef)(client);
|
|
6118
|
+
clientRef.current = client;
|
|
6119
|
+
return (0, import_react8.useMemo)(() => {
|
|
6120
|
+
const tracker = new TapTracker();
|
|
6121
|
+
const guard = (fn) => (e) => {
|
|
6122
|
+
try {
|
|
6123
|
+
fn(e);
|
|
6124
|
+
} catch {
|
|
6125
|
+
tracker.cancel();
|
|
6126
|
+
}
|
|
6127
|
+
};
|
|
6128
|
+
return {
|
|
6129
|
+
onTouchStartCapture: guard((e) => {
|
|
6130
|
+
const n = e.nativeEvent;
|
|
6131
|
+
if (!clientRef.current?.isTapCaptureOn()) return tracker.cancel();
|
|
6132
|
+
tracker.begin(n.pageX, n.pageY, Date.now(), Array.isArray(n.touches) ? n.touches.length : 1, n.identifier);
|
|
6133
|
+
}),
|
|
6134
|
+
onTouchMoveCapture: guard((e) => tracker.move(e.nativeEvent.pageX, e.nativeEvent.pageY, e.nativeEvent.identifier)),
|
|
6135
|
+
onTouchEndCapture: guard((e) => {
|
|
6136
|
+
const n = e.nativeEvent;
|
|
6137
|
+
const tap = tracker.end(n.pageX, n.pageY, Date.now(), n.identifier);
|
|
6138
|
+
const c = clientRef.current;
|
|
6139
|
+
if (!tap || !c) return;
|
|
6140
|
+
const r = resolveTapTarget(e, RESOLVE);
|
|
6141
|
+
if (!r.ignored) c.recordTap({ x: tap.x, y: tap.y, t: Date.now(), element: r.element, hit: r.hit });
|
|
6142
|
+
}),
|
|
6143
|
+
onTouchCancelCapture: guard(() => tracker.cancel())
|
|
6144
|
+
};
|
|
6145
|
+
}, []);
|
|
6146
|
+
}
|
|
6147
|
+
|
|
6148
|
+
// src/ui/Target.tsx
|
|
6149
|
+
var import_react9 = require("react");
|
|
6150
|
+
var import_react_native12 = require("react-native");
|
|
6151
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
6152
|
+
var GuidelyContext = (0, import_react9.createContext)(null);
|
|
6153
|
+
var GuidelyScreenContext = (0, import_react9.createContext)(null);
|
|
5092
6154
|
function GuidelyScreenScope({ name, children }) {
|
|
5093
|
-
return /* @__PURE__ */ (0,
|
|
6155
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(GuidelyScreenContext.Provider, { value: name, children });
|
|
5094
6156
|
}
|
|
5095
6157
|
function guidelyScreenLayout({ route, children }) {
|
|
5096
|
-
return /* @__PURE__ */ (0,
|
|
6158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(GuidelyScreenScope, { name: route.name, children });
|
|
5097
6159
|
}
|
|
5098
|
-
var
|
|
6160
|
+
var TAP_SLOP2 = 10;
|
|
5099
6161
|
function useGuidelyTarget(id) {
|
|
5100
|
-
const client = (0,
|
|
5101
|
-
const screen = (0,
|
|
5102
|
-
const ref = (0,
|
|
5103
|
-
const start = (0,
|
|
5104
|
-
(0,
|
|
6162
|
+
const client = (0, import_react9.useContext)(GuidelyContext);
|
|
6163
|
+
const screen = (0, import_react9.useContext)(GuidelyScreenContext);
|
|
6164
|
+
const ref = (0, import_react9.useRef)(null);
|
|
6165
|
+
const start = (0, import_react9.useRef)(null);
|
|
6166
|
+
(0, import_react9.useEffect)(() => {
|
|
5105
6167
|
if (!client || !id) return;
|
|
5106
6168
|
return client.targets.register(id, {
|
|
5107
6169
|
screen,
|
|
@@ -5112,15 +6174,15 @@ function useGuidelyTarget(id) {
|
|
|
5112
6174
|
})
|
|
5113
6175
|
});
|
|
5114
6176
|
}, [client, id, screen]);
|
|
5115
|
-
const onLayout = (0,
|
|
5116
|
-
const onTouchStart = (0,
|
|
6177
|
+
const onLayout = (0, import_react9.useCallback)(() => client?.targets.changed(), [client]);
|
|
6178
|
+
const onTouchStart = (0, import_react9.useCallback)((e) => {
|
|
5117
6179
|
start.current = { x: e.nativeEvent.pageX, y: e.nativeEvent.pageY };
|
|
5118
6180
|
}, []);
|
|
5119
|
-
const onTouchEnd = (0,
|
|
6181
|
+
const onTouchEnd = (0, import_react9.useCallback)(
|
|
5120
6182
|
(e) => {
|
|
5121
6183
|
const s = start.current;
|
|
5122
6184
|
start.current = null;
|
|
5123
|
-
if (s && Math.abs(e.nativeEvent.pageX - s.x) <
|
|
6185
|
+
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
6186
|
},
|
|
5125
6187
|
[client, id]
|
|
5126
6188
|
);
|
|
@@ -5128,8 +6190,8 @@ function useGuidelyTarget(id) {
|
|
|
5128
6190
|
}
|
|
5129
6191
|
function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...rest }) {
|
|
5130
6192
|
const t = useGuidelyTarget(id);
|
|
5131
|
-
return /* @__PURE__ */ (0,
|
|
5132
|
-
|
|
6193
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6194
|
+
import_react_native12.View,
|
|
5133
6195
|
{
|
|
5134
6196
|
...rest,
|
|
5135
6197
|
ref: t.ref,
|
|
@@ -5153,7 +6215,7 @@ function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...re
|
|
|
5153
6215
|
|
|
5154
6216
|
// src/index.tsx
|
|
5155
6217
|
var import_flags6 = require("@guidelytech/flags");
|
|
5156
|
-
var
|
|
6218
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
5157
6219
|
function deviceLocale() {
|
|
5158
6220
|
try {
|
|
5159
6221
|
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
@@ -5162,33 +6224,34 @@ function deviceLocale() {
|
|
|
5162
6224
|
}
|
|
5163
6225
|
}
|
|
5164
6226
|
function defaultBridge(appVersion) {
|
|
5165
|
-
const { width, height } =
|
|
6227
|
+
const { width, height } = import_react_native13.Dimensions.get("window");
|
|
5166
6228
|
return {
|
|
5167
6229
|
device: {
|
|
5168
|
-
platform:
|
|
5169
|
-
deviceType:
|
|
6230
|
+
platform: import_react_native13.Platform.OS === "android" ? "android" : "ios",
|
|
6231
|
+
deviceType: import_react_native13.Platform.OS === "ios" && import_react_native13.Platform.isPad || Math.min(width, height) >= 600 ? "tablet" : "mobile",
|
|
5170
6232
|
locale: deviceLocale(),
|
|
5171
6233
|
appVersion
|
|
5172
6234
|
},
|
|
5173
|
-
openExternal: (url) => void
|
|
5174
|
-
})
|
|
6235
|
+
openExternal: (url) => void import_react_native13.Linking.openURL(url).catch(() => {
|
|
6236
|
+
}),
|
|
6237
|
+
windowSize: () => import_react_native13.Dimensions.get("window")
|
|
5175
6238
|
};
|
|
5176
6239
|
}
|
|
5177
6240
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
5178
|
-
const [client, setClient] = (0,
|
|
5179
|
-
const optionsRef = (0,
|
|
6241
|
+
const [client, setClient] = (0, import_react10.useState)(null);
|
|
6242
|
+
const optionsRef = (0, import_react10.useRef)(options);
|
|
5180
6243
|
optionsRef.current = options;
|
|
5181
|
-
const userRef = (0,
|
|
6244
|
+
const userRef = (0, import_react10.useRef)(user);
|
|
5182
6245
|
userRef.current = user;
|
|
5183
6246
|
const flagsOpts = options.flags === false ? null : options.flags ?? {};
|
|
5184
6247
|
const payload = flagsPayload ?? flagsOpts?.payload ?? null;
|
|
5185
|
-
const payloadRef = (0,
|
|
6248
|
+
const payloadRef = (0, import_react10.useRef)(payload);
|
|
5186
6249
|
payloadRef.current = payload;
|
|
5187
|
-
const appAttributes = (0,
|
|
5188
|
-
const appAttributesRef = (0,
|
|
6250
|
+
const appAttributes = (0, import_react10.useMemo)(() => ({ ...flagsOpts?.attributes ?? {}, ...flagAttributes ?? {} }), [(0, import_flags3.stableStringify)(flagsOpts?.attributes ?? {}), (0, import_flags3.stableStringify)(flagAttributes ?? {})]);
|
|
6251
|
+
const appAttributesRef = (0, import_react10.useRef)(appAttributes);
|
|
5189
6252
|
appAttributesRef.current = appAttributes;
|
|
5190
6253
|
const { projectId, environment, apiUrl, storage } = options;
|
|
5191
|
-
(0,
|
|
6254
|
+
(0, import_react10.useEffect)(() => {
|
|
5192
6255
|
if (!projectId) return;
|
|
5193
6256
|
let c;
|
|
5194
6257
|
try {
|
|
@@ -5214,7 +6277,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5214
6277
|
}
|
|
5215
6278
|
setClient(c);
|
|
5216
6279
|
void c.init();
|
|
5217
|
-
const sub =
|
|
6280
|
+
const sub = import_react_native13.AppState.addEventListener("change", (s) => c.setAppState(s));
|
|
5218
6281
|
return () => {
|
|
5219
6282
|
sub.remove();
|
|
5220
6283
|
c.destroy();
|
|
@@ -5222,8 +6285,8 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5222
6285
|
};
|
|
5223
6286
|
}, [projectId, environment, apiUrl, storage]);
|
|
5224
6287
|
const userKey = user ? JSON.stringify([user.id, user.traits ?? null, user.account ?? null]) : "";
|
|
5225
|
-
const hadUser = (0,
|
|
5226
|
-
(0,
|
|
6288
|
+
const hadUser = (0, import_react10.useRef)(false);
|
|
6289
|
+
(0, import_react10.useEffect)(() => {
|
|
5227
6290
|
if (!client) return;
|
|
5228
6291
|
if (user && user.id) {
|
|
5229
6292
|
hadUser.current = true;
|
|
@@ -5233,16 +6296,21 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5233
6296
|
client.reset();
|
|
5234
6297
|
}
|
|
5235
6298
|
}, [client, userKey]);
|
|
5236
|
-
(0,
|
|
6299
|
+
(0, import_react10.useEffect)(() => {
|
|
5237
6300
|
client?.flags.setAttributes(appAttributes);
|
|
5238
6301
|
}, [client, appAttributes]);
|
|
6302
|
+
(0, import_react10.useEffect)(() => {
|
|
6303
|
+
client?.setLocale(options.locale ?? null);
|
|
6304
|
+
}, [client, options.locale]);
|
|
5239
6305
|
const bootstrap = useBootstrap(payload, { ...user?.id ? { ...user.traits ?? {}, id: user.id } : {}, ...appAttributes });
|
|
5240
|
-
const flags = (0,
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
6306
|
+
const flags = (0, import_react10.useMemo)(() => ({ client: client?.flags ?? null, bootstrap }), [client, bootstrap]);
|
|
6307
|
+
const tapObserver = useTapObserver(client);
|
|
6308
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GuidelyContext.Provider, { value: client, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(FlagsContext.Provider, { value: flags, children: [
|
|
6309
|
+
options.autocapture === false ? children : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native13.View, { style: styles10.root, ...tapObserver, children }),
|
|
6310
|
+
client ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GuidelyOverlay, { client, insets, launcherOffset }) : null
|
|
5244
6311
|
] }) });
|
|
5245
6312
|
}
|
|
6313
|
+
var styles10 = import_react_native13.StyleSheet.create({ root: { flex: 1 } });
|
|
5246
6314
|
var NOOP_RESULTS = {
|
|
5247
6315
|
init: Promise.resolve(),
|
|
5248
6316
|
ready: Promise.resolve(),
|
|
@@ -5291,12 +6359,12 @@ var noopClient = new Proxy({}, {
|
|
|
5291
6359
|
}
|
|
5292
6360
|
});
|
|
5293
6361
|
function useGuidely() {
|
|
5294
|
-
return (0,
|
|
6362
|
+
return (0, import_react10.useContext)(GuidelyContext) ?? noopClient;
|
|
5295
6363
|
}
|
|
5296
6364
|
function useGuidelyReady() {
|
|
5297
|
-
const client = (0,
|
|
5298
|
-
const [ready, setReady] = (0,
|
|
5299
|
-
(0,
|
|
6365
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6366
|
+
const [ready, setReady] = (0, import_react10.useState)(() => !!client?.isClientReady());
|
|
6367
|
+
(0, import_react10.useEffect)(() => {
|
|
5300
6368
|
if (!client) return setReady(false);
|
|
5301
6369
|
setReady(client.isClientReady());
|
|
5302
6370
|
const off = client.on("ready", () => setReady(true));
|
|
@@ -5306,44 +6374,44 @@ function useGuidelyReady() {
|
|
|
5306
6374
|
return ready;
|
|
5307
6375
|
}
|
|
5308
6376
|
function useGuidelyIdentify(userId, traits, account) {
|
|
5309
|
-
const client = (0,
|
|
6377
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
5310
6378
|
const key = JSON.stringify([userId ?? null, traits ?? null, account ?? null]);
|
|
5311
|
-
(0,
|
|
6379
|
+
(0, import_react10.useEffect)(() => {
|
|
5312
6380
|
if (client && userId) void client.identify(userId, traits, account);
|
|
5313
6381
|
}, [client, key]);
|
|
5314
6382
|
}
|
|
5315
6383
|
function useGuidelyEvent(event, handler) {
|
|
5316
|
-
const client = (0,
|
|
5317
|
-
const ref = (0,
|
|
6384
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6385
|
+
const ref = (0, import_react10.useRef)(handler);
|
|
5318
6386
|
ref.current = handler;
|
|
5319
|
-
(0,
|
|
6387
|
+
(0, import_react10.useEffect)(() => {
|
|
5320
6388
|
if (!client) return;
|
|
5321
6389
|
return client.on(event, ((...args) => ref.current(...args)));
|
|
5322
6390
|
}, [client, event]);
|
|
5323
6391
|
}
|
|
5324
6392
|
function useGuidelyScreen(name, properties) {
|
|
5325
|
-
const client = (0,
|
|
5326
|
-
(0,
|
|
6393
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6394
|
+
(0, import_react10.useEffect)(() => {
|
|
5327
6395
|
if (client && name) client.screen(name, properties);
|
|
5328
6396
|
}, [client, name]);
|
|
5329
6397
|
}
|
|
5330
6398
|
function useGuidelyNavigation(navigationRef, options = {}) {
|
|
5331
|
-
const client = (0,
|
|
5332
|
-
const nameRef = (0,
|
|
6399
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6400
|
+
const nameRef = (0, import_react10.useRef)(options.screenName);
|
|
5333
6401
|
nameRef.current = options.screenName;
|
|
5334
6402
|
const report = () => {
|
|
5335
6403
|
const route = navigationRef.getCurrentRoute?.();
|
|
5336
6404
|
if (client && route?.name) client.screen(nameRef.current ? nameRef.current(route) : route.name);
|
|
5337
6405
|
};
|
|
5338
|
-
(0,
|
|
6406
|
+
(0, import_react10.useEffect)(report, [client]);
|
|
5339
6407
|
return { onReady: report, onStateChange: report };
|
|
5340
6408
|
}
|
|
5341
6409
|
function useUiSelector(select, fallback) {
|
|
5342
|
-
const client = (0,
|
|
5343
|
-
const selectRef = (0,
|
|
6410
|
+
const client = (0, import_react10.useContext)(GuidelyContext);
|
|
6411
|
+
const selectRef = (0, import_react10.useRef)(select);
|
|
5344
6412
|
selectRef.current = select;
|
|
5345
|
-
const [value, setValue] = (0,
|
|
5346
|
-
(0,
|
|
6413
|
+
const [value, setValue] = (0, import_react10.useState)(() => client ? select(client) : fallback);
|
|
6414
|
+
(0, import_react10.useEffect)(() => {
|
|
5347
6415
|
if (!client) return;
|
|
5348
6416
|
let last = JSON.stringify(selectRef.current(client));
|
|
5349
6417
|
setValue(selectRef.current(client));
|
|
@@ -5383,14 +6451,18 @@ function useActiveGuide() {
|
|
|
5383
6451
|
FeatureValue,
|
|
5384
6452
|
FlagsProvider,
|
|
5385
6453
|
GuidelyClient,
|
|
6454
|
+
GuidelyMask,
|
|
5386
6455
|
GuidelyOverlay,
|
|
5387
6456
|
GuidelyProvider,
|
|
5388
6457
|
GuidelyScreenScope,
|
|
5389
6458
|
GuidelyTarget,
|
|
6459
|
+
HeatmapBuffer,
|
|
5390
6460
|
InMemoryStickyBucketStore,
|
|
5391
6461
|
Messenger,
|
|
6462
|
+
RageClickDetector,
|
|
5392
6463
|
SDK_NAME,
|
|
5393
6464
|
SDK_VERSION,
|
|
6465
|
+
TapTracker,
|
|
5394
6466
|
TargetRegistry,
|
|
5395
6467
|
createFlagsClient,
|
|
5396
6468
|
createSseParser,
|