@guidelytech/react-native 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -1
- package/dist/index.cjs +1064 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -6
- package/dist/index.d.ts +214 -6
- package/dist/index.js +1032 -109
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3,9 +3,9 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
5
|
// src/index.tsx
|
|
6
|
-
import { useContext as useContext3, useEffect as useEffect9, useMemo as
|
|
7
|
-
import { AppState as AppState2, Dimensions, Linking, Platform as Platform6 } from "react-native";
|
|
8
|
-
import { createStaticFlags as createStaticFlags2, stableStringify as
|
|
6
|
+
import { useContext as useContext3, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef7, useState as useState8 } from "react";
|
|
7
|
+
import { AppState as AppState2, Dimensions, Linking, Platform as Platform6, StyleSheet as StyleSheet10, View as View12 } from "react-native";
|
|
8
|
+
import { createStaticFlags as createStaticFlags2, stableStringify as stableStringify3 } from "@guidelytech/flags";
|
|
9
9
|
|
|
10
10
|
// src/flags.tsx
|
|
11
11
|
import { createFlagsClient, createStaticFlags, jsonEquals, stableStringify } from "@guidelytech/flags";
|
|
@@ -96,7 +96,7 @@ function FlagsProvider({ children, client: external, payload, ...options }) {
|
|
|
96
96
|
return /* @__PURE__ */ jsx(FlagsContext.Provider, { value, children });
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// ../shared/dist/chunk-
|
|
99
|
+
// ../shared/dist/chunk-TGIUEYXM.js
|
|
100
100
|
function fnv1a(input) {
|
|
101
101
|
let h = 2166136261;
|
|
102
102
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -276,6 +276,8 @@ function resolveField(rule, ctx) {
|
|
|
276
276
|
return ctx.now.getHours();
|
|
277
277
|
case "rollout.percentage":
|
|
278
278
|
return rolloutBucket(ctx.userId || ctx.anonymousId, ctx.rolloutSalt || "guidely");
|
|
279
|
+
case "experiment.bucket":
|
|
280
|
+
return rule.key ? rolloutBucket(ctx.userId || ctx.anonymousId, rule.key) : void 0;
|
|
279
281
|
case "environment":
|
|
280
282
|
return ctx.environment;
|
|
281
283
|
case "locale":
|
|
@@ -286,6 +288,9 @@ function resolveField(rule, ctx) {
|
|
|
286
288
|
return ctx.screen;
|
|
287
289
|
case "app.version":
|
|
288
290
|
return ctx.appVersion;
|
|
291
|
+
case "page.views":
|
|
292
|
+
case "feature.clicks":
|
|
293
|
+
return rule.key ? ctx.tagUsage?.[rule.key] ?? 0 : 0;
|
|
289
294
|
default:
|
|
290
295
|
return void 0;
|
|
291
296
|
}
|
|
@@ -394,8 +399,8 @@ function sanitizeProperties(props) {
|
|
|
394
399
|
for (const [k, v] of Object.entries(props)) {
|
|
395
400
|
if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
|
|
396
401
|
if (typeof v === "function" || typeof v === "symbol") continue;
|
|
397
|
-
const
|
|
398
|
-
size += k.length + (
|
|
402
|
+
const str2 = typeof v === "string" ? v : JSON.stringify(v ?? null);
|
|
403
|
+
size += k.length + (str2?.length ?? 0);
|
|
399
404
|
if (size > MAX_PROPERTY_BYTES) break;
|
|
400
405
|
out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
|
|
401
406
|
}
|
|
@@ -552,9 +557,151 @@ function isArticleListed(collections, article, member) {
|
|
|
552
557
|
}
|
|
553
558
|
return true;
|
|
554
559
|
}
|
|
560
|
+
var RC_MODULE_IDS = ["announcements", "search", "checklists", "guides", "articles", "links", "faqs"];
|
|
561
|
+
function orderedModules(modules) {
|
|
562
|
+
const known = (modules ?? []).filter((m, i, all) => RC_MODULE_IDS.includes(m.id) && all.findIndex((x) => x.id === m.id) === i);
|
|
563
|
+
const missing2 = RC_MODULE_IDS.filter((id) => !known.some((m) => m.id === id)).map((id) => ({ id, enabled: true }));
|
|
564
|
+
return [...known, ...missing2];
|
|
565
|
+
}
|
|
566
|
+
function visibleAnnouncements(items, member, now = Date.now()) {
|
|
567
|
+
return (items ?? []).filter((a) => {
|
|
568
|
+
const t = Date.parse(a.publishedAt);
|
|
569
|
+
return !Number.isNaN(t) && t <= now && matchesAudiences(a.audienceIds, member);
|
|
570
|
+
}).sort((a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt));
|
|
571
|
+
}
|
|
572
|
+
function unreadAnnouncements(items, lastSeenAt) {
|
|
573
|
+
const seen = lastSeenAt ? Date.parse(lastSeenAt) : NaN;
|
|
574
|
+
if (Number.isNaN(seen)) return items.length;
|
|
575
|
+
return items.filter((a) => Date.parse(a.publishedAt) > seen).length;
|
|
576
|
+
}
|
|
555
577
|
var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
578
|
+
var DEFAULT_LOCALE = "en";
|
|
579
|
+
function normalizeLocale(input) {
|
|
580
|
+
if (typeof input !== "string") return "";
|
|
581
|
+
const parts = input.trim().replace(/_/g, "-").split("-").filter(Boolean);
|
|
582
|
+
if (!parts.length || !/^[a-zA-Z]{2,3}$/.test(parts[0])) return "";
|
|
583
|
+
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("-");
|
|
584
|
+
}
|
|
585
|
+
function baseLanguage(locale) {
|
|
586
|
+
return normalizeLocale(locale).split("-")[0] ?? "";
|
|
587
|
+
}
|
|
588
|
+
function resolveLocale(requested, available, defaultLocale = DEFAULT_LOCALE) {
|
|
589
|
+
const def = normalizeLocale(defaultLocale) || DEFAULT_LOCALE;
|
|
590
|
+
const avail = available.map(normalizeLocale).filter(Boolean);
|
|
591
|
+
const all = [def, ...avail.filter((a) => a.toLowerCase() !== def.toLowerCase())];
|
|
592
|
+
for (const raw of requested) {
|
|
593
|
+
const want = normalizeLocale(raw);
|
|
594
|
+
if (!want) continue;
|
|
595
|
+
const exact = all.find((a) => a.toLowerCase() === want.toLowerCase());
|
|
596
|
+
if (exact) return exact;
|
|
597
|
+
const base = baseLanguage(want);
|
|
598
|
+
const sameBase = all.find((a) => a.toLowerCase() === base) ?? all.find((a) => baseLanguage(a) === base);
|
|
599
|
+
if (sameBase) return sameBase;
|
|
600
|
+
}
|
|
601
|
+
return def;
|
|
602
|
+
}
|
|
603
|
+
function visitorLocales(explicit, traits, platform = []) {
|
|
604
|
+
const out = [];
|
|
605
|
+
const push = (v) => {
|
|
606
|
+
const n = normalizeLocale(v);
|
|
607
|
+
if (n && !out.includes(n)) out.push(n);
|
|
608
|
+
};
|
|
609
|
+
push(explicit);
|
|
610
|
+
push(traits?.language);
|
|
611
|
+
push(traits?.locale);
|
|
612
|
+
for (const p of platform) push(p);
|
|
613
|
+
return out;
|
|
614
|
+
}
|
|
615
|
+
function guideLocales(config) {
|
|
616
|
+
const t = config.localization?.translations ?? {};
|
|
617
|
+
return Object.keys(t).filter((l) => Object.values(t[l] ?? {}).some((v) => typeof v === "string" && v.trim() !== ""));
|
|
618
|
+
}
|
|
619
|
+
function pick(map, key, fallback) {
|
|
620
|
+
const v = map?.[key];
|
|
621
|
+
return typeof v === "string" && v.trim() !== "" ? v : fallback;
|
|
622
|
+
}
|
|
623
|
+
function applyGuideTranslations(config, map) {
|
|
624
|
+
if (!map || !Object.keys(map).length) return config;
|
|
625
|
+
return {
|
|
626
|
+
...config,
|
|
627
|
+
steps: config.steps.map((s) => ({
|
|
628
|
+
...s,
|
|
629
|
+
title: pick(map, `steps.${s.id}.title`, s.title),
|
|
630
|
+
body: pick(map, `steps.${s.id}.body`, s.body),
|
|
631
|
+
customHtml: pick(map, `steps.${s.id}.customHtml`, s.customHtml),
|
|
632
|
+
imageAlt: pick(map, `steps.${s.id}.imageAlt`, s.imageAlt),
|
|
633
|
+
buttons: s.buttons.map((b) => ({ ...b, label: pick(map, `steps.${s.id}.buttons.${b.id}.label`, b.label) }))
|
|
634
|
+
})),
|
|
635
|
+
checklist: config.checklist ? {
|
|
636
|
+
...config.checklist,
|
|
637
|
+
launcherLabel: pick(map, "checklist.launcherLabel", config.checklist.launcherLabel),
|
|
638
|
+
completionTitle: pick(map, "checklist.completionTitle", config.checklist.completionTitle),
|
|
639
|
+
completionBody: pick(map, "checklist.completionBody", config.checklist.completionBody),
|
|
640
|
+
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) }))
|
|
641
|
+
} : config.checklist,
|
|
642
|
+
survey: config.survey ? {
|
|
643
|
+
...config.survey,
|
|
644
|
+
thankYouTitle: pick(map, "survey.thankYouTitle", config.survey.thankYouTitle),
|
|
645
|
+
thankYouBody: pick(map, "survey.thankYouBody", config.survey.thankYouBody),
|
|
646
|
+
questions: config.survey.questions.map((q) => ({
|
|
647
|
+
...q,
|
|
648
|
+
title: pick(map, `survey.questions.${q.id}.title`, q.title),
|
|
649
|
+
description: pick(map, `survey.questions.${q.id}.description`, q.description),
|
|
650
|
+
placeholder: pick(map, `survey.questions.${q.id}.placeholder`, q.placeholder),
|
|
651
|
+
lowLabel: pick(map, `survey.questions.${q.id}.lowLabel`, q.lowLabel),
|
|
652
|
+
highLabel: pick(map, `survey.questions.${q.id}.highLabel`, q.highLabel),
|
|
653
|
+
options: q.options.map((o) => ({ ...o, label: pick(map, `survey.questions.${q.id}.options.${o.id}.label`, o.label) }))
|
|
654
|
+
}))
|
|
655
|
+
} : config.survey
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function localizeGuide(guide, requested, defaultLocale = DEFAULT_LOCALE) {
|
|
659
|
+
const translations = guide.config.localization?.translations ?? {};
|
|
660
|
+
const locale = resolveLocale(requested, guideLocales(guide.config), defaultLocale);
|
|
661
|
+
const key = Object.keys(translations).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
|
|
662
|
+
if (!key) return { ...guide, locale };
|
|
663
|
+
return { ...guide, locale, config: applyGuideTranslations(guide.config, translations[key]) };
|
|
664
|
+
}
|
|
665
|
+
function resourceCenterLocales(rc) {
|
|
666
|
+
const set = new Set(Object.keys(rc.translations ?? {}));
|
|
667
|
+
for (const a of rc.articles) for (const l of Object.keys(a.translations ?? {})) set.add(l);
|
|
668
|
+
for (const a of rc.announcements ?? []) for (const l of Object.keys(a.translations ?? {})) set.add(l);
|
|
669
|
+
return [...set];
|
|
670
|
+
}
|
|
671
|
+
function translationFor(map, locale) {
|
|
672
|
+
if (!map) return void 0;
|
|
673
|
+
const key = Object.keys(map).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
|
|
674
|
+
return key ? map[key] : void 0;
|
|
675
|
+
}
|
|
676
|
+
function localizeArticle(a, locale) {
|
|
677
|
+
const t = translationFor(a.translations, locale);
|
|
678
|
+
if (!t) return a;
|
|
679
|
+
const body = t.body?.trim() ? t.body : a.body;
|
|
680
|
+
return { ...a, title: t.title?.trim() ? t.title : a.title, summary: t.summary?.trim() ? t.summary : a.summary, body, ...t.bodyOmitted ? { bodyOmitted: true } : {} };
|
|
681
|
+
}
|
|
682
|
+
function localizeAnnouncement(a, locale) {
|
|
683
|
+
const t = translationFor(a.translations, locale);
|
|
684
|
+
if (!t) return a;
|
|
685
|
+
return { ...a, title: t.title?.trim() ? t.title : a.title, body: t.body?.trim() ? t.body : a.body };
|
|
686
|
+
}
|
|
687
|
+
function localizeResourceCenter(rc, requested, defaultLocale = DEFAULT_LOCALE) {
|
|
688
|
+
const locale = resolveLocale(requested, resourceCenterLocales(rc), defaultLocale);
|
|
689
|
+
if (locale.toLowerCase() === normalizeLocale(defaultLocale).toLowerCase()) return { ...rc, locale };
|
|
690
|
+
const map = translationFor(rc.translations, locale);
|
|
691
|
+
return {
|
|
692
|
+
...rc,
|
|
693
|
+
locale,
|
|
694
|
+
title: pick(map, "title", rc.title),
|
|
695
|
+
launcher: { ...rc.launcher, label: pick(map, "launcher.label", rc.launcher.label) },
|
|
696
|
+
collections: rc.collections.map((c) => ({ ...c, name: pick(map, `collections.${c.id}.name`, c.name), description: pick(map, `collections.${c.id}.description`, c.description) })),
|
|
697
|
+
links: rc.links.map((l) => ({ ...l, label: pick(map, `links.${l.id}.label`, l.label) })),
|
|
698
|
+
faqs: rc.faqs.map((f) => ({ ...f, question: pick(map, `faqs.${f.id}.question`, f.question), answer: pick(map, `faqs.${f.id}.answer`, f.answer) })),
|
|
699
|
+
articles: rc.articles.map((a) => localizeArticle(a, locale)),
|
|
700
|
+
announcements: rc.announcements?.map((a) => localizeAnnouncement(a, locale))
|
|
701
|
+
};
|
|
702
|
+
}
|
|
556
703
|
|
|
557
|
-
// ../sdk-core/dist/chunk-
|
|
704
|
+
// ../sdk-core/dist/chunk-JUSM7J5N.js
|
|
558
705
|
var __defProp2 = Object.defineProperty;
|
|
559
706
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
560
707
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -852,6 +999,8 @@ var KEYS = {
|
|
|
852
999
|
queue: (key) => `guidely:queue:${key}`,
|
|
853
1000
|
/** Survey answers and guide/checklist progress not yet accepted by the server. */
|
|
854
1001
|
outbox: (key) => `guidely:outbox:${key}`,
|
|
1002
|
+
/** Agent Analytics turns not yet accepted by the server. */
|
|
1003
|
+
agentOutbox: (key) => `guidely:agents:${key}`,
|
|
855
1004
|
session: "guidely:session",
|
|
856
1005
|
active: "guidely:active",
|
|
857
1006
|
authoring: "guidely:authoring",
|
|
@@ -864,7 +1013,7 @@ var KEYS = {
|
|
|
864
1013
|
newsSeen: (key) => `guidely:news:${key}`
|
|
865
1014
|
};
|
|
866
1015
|
|
|
867
|
-
// ../sdk-core/dist/chunk-
|
|
1016
|
+
// ../sdk-core/dist/chunk-ZAF2KRJA.js
|
|
868
1017
|
var EventQueue = class {
|
|
869
1018
|
constructor(opts) {
|
|
870
1019
|
__publicField2(this, "opts", opts);
|
|
@@ -1140,12 +1289,32 @@ function areMutuallyExclusive(a, b) {
|
|
|
1140
1289
|
return la.includes(b.id) || la.includes(b.key) || lb.includes(a.id) || lb.includes(a.key);
|
|
1141
1290
|
}
|
|
1142
1291
|
function isWithinSchedule(guide, now = Date.now()) {
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1292
|
+
for (const w of [guide.config.schedule, guide.config.survey]) {
|
|
1293
|
+
if (!w) continue;
|
|
1294
|
+
if (w.startAt && now < Date.parse(w.startAt)) return false;
|
|
1295
|
+
if (w.endAt && now > Date.parse(w.endAt)) return false;
|
|
1296
|
+
}
|
|
1147
1297
|
return true;
|
|
1148
1298
|
}
|
|
1299
|
+
var MINUTE = 60 * 1e3;
|
|
1300
|
+
function countsTowardThrottle(guide) {
|
|
1301
|
+
return isExclusiveGuide(guide) && !guide.config.frequency.ignoreThrottling;
|
|
1302
|
+
}
|
|
1303
|
+
function isThrottleEligible(settings, launches, now = Date.now()) {
|
|
1304
|
+
if (!settings || !settings.enabled) return true;
|
|
1305
|
+
const times = launches.map((l) => Date.parse(l)).filter((t) => !Number.isNaN(t));
|
|
1306
|
+
const windowMs = Math.max(1, settings.windowHours) * HOUR;
|
|
1307
|
+
if (times.filter((t) => now - t < windowMs).length >= Math.max(1, settings.maxGuides)) return false;
|
|
1308
|
+
if (settings.minGapMinutes > 0 && times.some((t) => now - t < settings.minGapMinutes * MINUTE)) return false;
|
|
1309
|
+
return true;
|
|
1310
|
+
}
|
|
1311
|
+
function pruneLaunches(launches, settings, now = Date.now()) {
|
|
1312
|
+
const keepMs = Math.max(settings?.windowHours ?? 24, 1) * HOUR + (settings?.minGapMinutes ?? 0) * MINUTE;
|
|
1313
|
+
return launches.filter((l) => {
|
|
1314
|
+
const t = Date.parse(l);
|
|
1315
|
+
return !Number.isNaN(t) && now - t < keepMs;
|
|
1316
|
+
}).slice(-100);
|
|
1317
|
+
}
|
|
1149
1318
|
function dismissedAfterMerge(local, server) {
|
|
1150
1319
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1151
1320
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
@@ -1154,6 +1323,115 @@ function dismissedAfterMerge(local, server) {
|
|
|
1154
1323
|
// src/core/client.ts
|
|
1155
1324
|
import { createFlagsClient as createFlagsClient2 } from "@guidelytech/flags";
|
|
1156
1325
|
|
|
1326
|
+
// src/core/autocapture.ts
|
|
1327
|
+
var TAP_SLOP = 10;
|
|
1328
|
+
var TAP_MAX_MS = 1e3;
|
|
1329
|
+
var HEATMAP_MAX_POINTS = 40;
|
|
1330
|
+
function quoteValue(value) {
|
|
1331
|
+
return `"${value.replace(/["\\]/g, "\\$&")}"`;
|
|
1332
|
+
}
|
|
1333
|
+
function attrSelector(name, value) {
|
|
1334
|
+
return `[${name}=${quoteValue(value)}]`.slice(0, 300);
|
|
1335
|
+
}
|
|
1336
|
+
function cleanText(text) {
|
|
1337
|
+
const t = (text ?? "").replace(/\s+/g, " ").trim();
|
|
1338
|
+
return t ? t.slice(0, 100) : void 0;
|
|
1339
|
+
}
|
|
1340
|
+
function tapProperties(el, x, y, platform, maskAll) {
|
|
1341
|
+
const props = { selector: el.selector.slice(0, 300), tag: el.tag || "View", x: Math.round(x) || 0, y: Math.round(y) || 0, platform };
|
|
1342
|
+
const text = maskAll ? void 0 : cleanText(el.text);
|
|
1343
|
+
if (text) props.text = text;
|
|
1344
|
+
return props;
|
|
1345
|
+
}
|
|
1346
|
+
var r2 = (n) => Math.round(Math.min(1, Math.max(0, n)) * 100) / 100;
|
|
1347
|
+
function heatmapPoint(x, y, el, rect, rage) {
|
|
1348
|
+
const p = { k: rage ? "r" : "c", x: Math.round(x) || 0, y: Math.round(y) || 0 };
|
|
1349
|
+
const s = el?.selector.slice(0, 120);
|
|
1350
|
+
if (s) p.s = s;
|
|
1351
|
+
if (s && rect && rect.width > 0 && rect.height > 0) {
|
|
1352
|
+
p.rx = r2((x - rect.x) / rect.width);
|
|
1353
|
+
p.ry = r2((y - rect.y) / rect.height);
|
|
1354
|
+
}
|
|
1355
|
+
return p;
|
|
1356
|
+
}
|
|
1357
|
+
var RageClickDetector = class {
|
|
1358
|
+
constructor(threshold = 3, windowMs = 1e3, radius = 30) {
|
|
1359
|
+
__publicField(this, "threshold", threshold);
|
|
1360
|
+
__publicField(this, "windowMs", windowMs);
|
|
1361
|
+
__publicField(this, "radius", radius);
|
|
1362
|
+
__publicField(this, "clicks", []);
|
|
1363
|
+
__publicField(this, "fired", false);
|
|
1364
|
+
}
|
|
1365
|
+
/** Taps in the current burst once it has fired (rage), otherwise 0. */
|
|
1366
|
+
get burst() {
|
|
1367
|
+
return this.fired ? this.clicks.length : 0;
|
|
1368
|
+
}
|
|
1369
|
+
/** Records a tap; returns the tap count when it completes a burst, otherwise 0. */
|
|
1370
|
+
add(x, y, t) {
|
|
1371
|
+
const first = this.clicks[0];
|
|
1372
|
+
const last = this.clicks[this.clicks.length - 1];
|
|
1373
|
+
if (!first || !last || t - last.t > this.windowMs || Math.hypot(x - first.x, y - first.y) > this.radius) {
|
|
1374
|
+
this.clicks = [];
|
|
1375
|
+
this.fired = false;
|
|
1376
|
+
}
|
|
1377
|
+
this.clicks = this.clicks.filter((c) => t - c.t <= this.windowMs);
|
|
1378
|
+
this.clicks.push({ x, y, t });
|
|
1379
|
+
if (this.fired || this.clicks.length < this.threshold) return 0;
|
|
1380
|
+
this.fired = true;
|
|
1381
|
+
return this.clicks.length;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
var HeatmapBuffer = class {
|
|
1385
|
+
constructor() {
|
|
1386
|
+
/** Screen path the buffered points belong to. */
|
|
1387
|
+
__publicField(this, "url", "");
|
|
1388
|
+
__publicField(this, "points", []);
|
|
1389
|
+
}
|
|
1390
|
+
/** Adds a tap (`burst` > 0 marks it and the burst's previous taps as rage); true when the batch is full. */
|
|
1391
|
+
add(url, point, burst) {
|
|
1392
|
+
this.url = url;
|
|
1393
|
+
for (let i = Math.max(0, this.points.length - burst + 1); i < this.points.length; i++) this.points[i].k = "r";
|
|
1394
|
+
this.points.push(point);
|
|
1395
|
+
return this.points.length >= HEATMAP_MAX_POINTS;
|
|
1396
|
+
}
|
|
1397
|
+
/** Empties the buffer, returning what was in it. */
|
|
1398
|
+
take() {
|
|
1399
|
+
const points = this.points;
|
|
1400
|
+
this.points = [];
|
|
1401
|
+
return points.length ? { url: this.url, points } : null;
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
var TapTracker = class {
|
|
1405
|
+
constructor() {
|
|
1406
|
+
__publicField(this, "start", null);
|
|
1407
|
+
__publicField(this, "moved", 0);
|
|
1408
|
+
}
|
|
1409
|
+
/** A finger went down. A second finger while one is tracked cancels the gesture (not a tap). */
|
|
1410
|
+
begin(x, y, t, touches, id) {
|
|
1411
|
+
if (this.start || touches > 1) {
|
|
1412
|
+
this.start = null;
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
this.start = { x, y, t, id };
|
|
1416
|
+
this.moved = 0;
|
|
1417
|
+
}
|
|
1418
|
+
move(x, y, id) {
|
|
1419
|
+
const s = this.start;
|
|
1420
|
+
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));
|
|
1421
|
+
}
|
|
1422
|
+
/** The finger lifted: the tap position when it moved ≤ 10pt and lasted < 1s, else null. */
|
|
1423
|
+
end(x, y, t, id) {
|
|
1424
|
+
const s = this.start;
|
|
1425
|
+
if (!s || id !== void 0 && s.id !== void 0 && id !== s.id) return null;
|
|
1426
|
+
this.start = null;
|
|
1427
|
+
const moved = Math.max(this.moved, Math.hypot(x - s.x, y - s.y));
|
|
1428
|
+
return moved <= TAP_SLOP && t - s.t < TAP_MAX_MS ? { x, y } : null;
|
|
1429
|
+
}
|
|
1430
|
+
cancel() {
|
|
1431
|
+
this.start = null;
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1157
1435
|
// src/core/targets.ts
|
|
1158
1436
|
var TargetTimeoutError = class extends Error {
|
|
1159
1437
|
constructor(ids) {
|
|
@@ -1256,6 +1534,35 @@ var TargetRegistry = class {
|
|
|
1256
1534
|
return null;
|
|
1257
1535
|
}
|
|
1258
1536
|
}
|
|
1537
|
+
/**
|
|
1538
|
+
* The registered target on the current screen whose visible frame contains the point (window
|
|
1539
|
+
* coordinates); the smallest one when targets are nested. Frames that do not answer within
|
|
1540
|
+
* `timeoutMs` are skipped.
|
|
1541
|
+
*/
|
|
1542
|
+
async hitTest(x, y, timeoutMs = 500) {
|
|
1543
|
+
const candidates = [];
|
|
1544
|
+
this.targets.forEach((list, id) => {
|
|
1545
|
+
for (const handle of list) if (!(handle.screen && this.screen && handle.screen !== this.screen)) candidates.push({ id, handle });
|
|
1546
|
+
});
|
|
1547
|
+
if (!candidates.length) return null;
|
|
1548
|
+
const rects = await Promise.all(
|
|
1549
|
+
candidates.map(
|
|
1550
|
+
({ handle }) => new Promise((resolve) => {
|
|
1551
|
+
const timer = setTimeout(() => resolve(null), timeoutMs);
|
|
1552
|
+
void this.measureHandle(handle).then((r) => {
|
|
1553
|
+
clearTimeout(timer);
|
|
1554
|
+
resolve(r);
|
|
1555
|
+
});
|
|
1556
|
+
})
|
|
1557
|
+
)
|
|
1558
|
+
);
|
|
1559
|
+
let best = null;
|
|
1560
|
+
rects.forEach((r, i) => {
|
|
1561
|
+
if (!r || x < r.x || y < r.y || x > r.x + r.width || y > r.y + r.height) return;
|
|
1562
|
+
if (!best || r.width * r.height < best.rect.width * best.rect.height) best = { id: candidates[i].id, rect: r };
|
|
1563
|
+
});
|
|
1564
|
+
return best;
|
|
1565
|
+
}
|
|
1259
1566
|
/** First of `ids` that is mounted and visible. */
|
|
1260
1567
|
async resolve(ids) {
|
|
1261
1568
|
for (const id of ids) {
|
|
@@ -2013,7 +2320,8 @@ var Messenger = class {
|
|
|
2013
2320
|
this.stopThreadPolling();
|
|
2014
2321
|
this.set({ open: true, view: { kind: "article", articleId: id, from }, article: null });
|
|
2015
2322
|
try {
|
|
2016
|
-
const
|
|
2323
|
+
const locale = this.deps.locale?.();
|
|
2324
|
+
const res = await this.request("GET", `/articles/${encodeURIComponent(id)}`, locale ? { params: { locale } } : {});
|
|
2017
2325
|
this.set({ article: res.article });
|
|
2018
2326
|
} catch (err) {
|
|
2019
2327
|
this.deps.onError(err);
|
|
@@ -2128,7 +2436,7 @@ var PersistentStore = class {
|
|
|
2128
2436
|
|
|
2129
2437
|
// src/core/client.ts
|
|
2130
2438
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2131
|
-
var SDK_VERSION = "0.
|
|
2439
|
+
var SDK_VERSION = "0.11.0";
|
|
2132
2440
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2133
2441
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2134
2442
|
var CACHED_START_MS = 1500;
|
|
@@ -2142,6 +2450,8 @@ function settlesWithin(promise, ms) {
|
|
|
2142
2450
|
promise.then(done, done);
|
|
2143
2451
|
});
|
|
2144
2452
|
}
|
|
2453
|
+
var appVersionKey = (key) => `guidely:app:${key}`;
|
|
2454
|
+
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
2145
2455
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2146
2456
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
2147
2457
|
var newSession = () => ({ id: `s_${randomId(16)}`, startedAt: nowIso(), lastActiveAt: nowIso(), shownGuides: [], events: [] });
|
|
@@ -2173,6 +2483,7 @@ var GuidelyClient = class {
|
|
|
2173
2483
|
__publicField(this, "activeSurvey", null);
|
|
2174
2484
|
__publicField(this, "queued", []);
|
|
2175
2485
|
__publicField(this, "flows", /* @__PURE__ */ new Map());
|
|
2486
|
+
/** Checklists on screen, with the guide as rendered (in the visitor's language). */
|
|
2176
2487
|
__publicField(this, "checklists", /* @__PURE__ */ new Map());
|
|
2177
2488
|
__publicField(this, "expanded", /* @__PURE__ */ new Set());
|
|
2178
2489
|
__publicField(this, "surveyView", null);
|
|
@@ -2192,6 +2503,27 @@ var GuidelyClient = class {
|
|
|
2192
2503
|
__publicField(this, "flags");
|
|
2193
2504
|
/** Experiments already reported this session (`experiment_viewed` is sent once per session). */
|
|
2194
2505
|
__publicField(this, "exposures", /* @__PURE__ */ new Set());
|
|
2506
|
+
// Product analytics (autocapture).
|
|
2507
|
+
__publicField(this, "rage", new RageClickDetector());
|
|
2508
|
+
__publicField(this, "heatmap", new HeatmapBuffer());
|
|
2509
|
+
/** Taps are processed in order (target frames are measured asynchronously). */
|
|
2510
|
+
__publicField(this, "tapChain", Promise.resolve());
|
|
2511
|
+
/** When the current screen's foreground measurement started; `screenLeft` once it has been reported. */
|
|
2512
|
+
__publicField(this, "screenStartedAt", 0);
|
|
2513
|
+
__publicField(this, "screenLeft", true);
|
|
2514
|
+
/** Last `app_open` (cold start or return to the foreground), for `app_background.foregroundMs`. */
|
|
2515
|
+
__publicField(this, "openedAt", Date.now());
|
|
2516
|
+
__publicField(this, "launchChecked", false);
|
|
2517
|
+
/** An identity was stored before this launch (an existing install adopting lifecycle events). */
|
|
2518
|
+
__publicField(this, "hadIdentity", false);
|
|
2519
|
+
/** Language set with `setLocale()` (or the `locale` option). */
|
|
2520
|
+
__publicField(this, "locale", null);
|
|
2521
|
+
/** Language each shown guide was rendered in; added to its events as `locale`. */
|
|
2522
|
+
__publicField(this, "guideLocales", /* @__PURE__ */ new Map());
|
|
2523
|
+
/** The resource center in the visitor's language, recomputed when the config or language changes. */
|
|
2524
|
+
__publicField(this, "localizedRc", null);
|
|
2525
|
+
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
2526
|
+
__publicField(this, "announcementsSeenAtOpen", null);
|
|
2195
2527
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
2196
2528
|
__publicField(this, "eventBus", {
|
|
2197
2529
|
on: (name, cb) => {
|
|
@@ -2221,6 +2553,7 @@ var GuidelyClient = class {
|
|
|
2221
2553
|
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.");
|
|
2222
2554
|
this.cleanups.push(this.targets.onTap((id) => this.handleTap(id)));
|
|
2223
2555
|
if (options.user) this.user = { ...options.user };
|
|
2556
|
+
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
2224
2557
|
this.flags = this.createFlags(options.flags);
|
|
2225
2558
|
}
|
|
2226
2559
|
createFlags(opts) {
|
|
@@ -2269,9 +2602,10 @@ var GuidelyClient = class {
|
|
|
2269
2602
|
async bootstrap() {
|
|
2270
2603
|
const key = this.options.projectId;
|
|
2271
2604
|
try {
|
|
2272
|
-
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)]);
|
|
2605
|
+
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)]);
|
|
2273
2606
|
if (this.destroyed) return;
|
|
2274
2607
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
2608
|
+
this.hadIdentity = !!this.anonymousId;
|
|
2275
2609
|
if (!this.anonymousId) {
|
|
2276
2610
|
this.anonymousId = `anon_${randomId(20)}`;
|
|
2277
2611
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
@@ -2308,6 +2642,7 @@ var GuidelyClient = class {
|
|
|
2308
2642
|
if (!this.config) await fresh;
|
|
2309
2643
|
else if (!await settlesWithin(fresh, CACHED_START_MS)) void fresh.then(() => this.applyConfigChange(before));
|
|
2310
2644
|
if (this.destroyed) return;
|
|
2645
|
+
this.checkLaunch();
|
|
2311
2646
|
this.wireTimers();
|
|
2312
2647
|
if (this.user?.id) await settlesWithin(this.identify(this.user.id, this.user.traits, this.user.account), CACHED_START_MS);
|
|
2313
2648
|
if (this.destroyed) return;
|
|
@@ -2337,6 +2672,7 @@ var GuidelyClient = class {
|
|
|
2337
2672
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
2338
2673
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
2339
2674
|
this.syncMessenger();
|
|
2675
|
+
if (this.hydrated) this.checkLaunch();
|
|
2340
2676
|
this.emitter.emit("config", this.config);
|
|
2341
2677
|
this.log("config loaded", this.config.configVersion, `${this.config.guides.length} guides`);
|
|
2342
2678
|
return true;
|
|
@@ -2357,7 +2693,7 @@ var GuidelyClient = class {
|
|
|
2357
2693
|
if (!this.isReady || !this.config || this.backgroundedAt !== null) return;
|
|
2358
2694
|
const url = this.currentUrl();
|
|
2359
2695
|
const ctx = this.getContext();
|
|
2360
|
-
for (const guide of this.
|
|
2696
|
+
for (const guide of this.candidates()) if (guide.config.trigger.type === "element_visible" && guide.config.trigger.value) this.armVisibility(guide, ctx, url);
|
|
2361
2697
|
this.drainQueue(url, ctx);
|
|
2362
2698
|
}, 1e3);
|
|
2363
2699
|
this.cleanups.push(() => clearInterval(poll));
|
|
@@ -2381,7 +2717,9 @@ var GuidelyClient = class {
|
|
|
2381
2717
|
setAppState(state) {
|
|
2382
2718
|
if (this.destroyed || !this.hydrated) return;
|
|
2383
2719
|
if (state === "background") {
|
|
2720
|
+
const wasForeground = this.backgroundedAt === null;
|
|
2384
2721
|
this.backgroundedAt = Date.now();
|
|
2722
|
+
if (wasForeground) this.wentToBackground();
|
|
2385
2723
|
this.session.lastActiveAt = nowIso();
|
|
2386
2724
|
this.persistSession();
|
|
2387
2725
|
void this.queue?.flush();
|
|
@@ -2398,6 +2736,7 @@ var GuidelyClient = class {
|
|
|
2398
2736
|
this.persistSession();
|
|
2399
2737
|
this.firedDelays.clear();
|
|
2400
2738
|
}
|
|
2739
|
+
this.cameToForeground();
|
|
2401
2740
|
void this.flags.refresh();
|
|
2402
2741
|
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
|
|
2403
2742
|
else if (this.isReady) this.evaluate("ready");
|
|
@@ -2410,6 +2749,7 @@ var GuidelyClient = class {
|
|
|
2410
2749
|
/** Tears everything down. Create a new client to start again. */
|
|
2411
2750
|
destroy() {
|
|
2412
2751
|
if (this.destroyed) return;
|
|
2752
|
+
this.flushHeatmap();
|
|
2413
2753
|
this.destroyed = true;
|
|
2414
2754
|
this.active?.controller.destroy();
|
|
2415
2755
|
this.active = null;
|
|
@@ -2461,6 +2801,7 @@ var GuidelyClient = class {
|
|
|
2461
2801
|
for (const [id, s] of Object.entries(data.guideState || {})) {
|
|
2462
2802
|
const local = guides[id];
|
|
2463
2803
|
guides[id] = {
|
|
2804
|
+
...local,
|
|
2464
2805
|
impressions: Math.max(local?.impressions ?? 0, s.impressions ?? 0),
|
|
2465
2806
|
completedAt: local?.completedAt ?? s.completedAt ?? null,
|
|
2466
2807
|
dismissedAt: local?.dismissedAt ?? s.dismissedAt ?? null,
|
|
@@ -2482,7 +2823,10 @@ var GuidelyClient = class {
|
|
|
2482
2823
|
checklists,
|
|
2483
2824
|
surveysSubmitted: Array.from(/* @__PURE__ */ new Set([...this.state.surveysSubmitted, ...data.surveysSubmitted || []])),
|
|
2484
2825
|
audienceIds: data.audienceIds || [],
|
|
2485
|
-
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt
|
|
2826
|
+
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt,
|
|
2827
|
+
// Device-local only (the server doesn't hold them).
|
|
2828
|
+
...this.state.throttle ? { throttle: this.state.throttle } : {},
|
|
2829
|
+
...this.state.announcementsSeenAt ? { announcementsSeenAt: this.state.announcementsSeenAt } : {}
|
|
2486
2830
|
};
|
|
2487
2831
|
if (data.firstSeenAt && Date.now() - Date.parse(data.firstSeenAt) > 6e4) this.firstVisit = false;
|
|
2488
2832
|
this.persistState();
|
|
@@ -2574,6 +2918,8 @@ var GuidelyClient = class {
|
|
|
2574
2918
|
...extra
|
|
2575
2919
|
};
|
|
2576
2920
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
2921
|
+
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
2922
|
+
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
2577
2923
|
this.queue.enqueue(event);
|
|
2578
2924
|
this.emitter.emit("track", event);
|
|
2579
2925
|
return event;
|
|
@@ -2606,11 +2952,19 @@ var GuidelyClient = class {
|
|
|
2606
2952
|
if (!name) return;
|
|
2607
2953
|
this.whenHydrated(() => {
|
|
2608
2954
|
const changed = name !== this.screenName;
|
|
2955
|
+
if (changed) {
|
|
2956
|
+
this.leaveScreen();
|
|
2957
|
+
this.flushHeatmap();
|
|
2958
|
+
}
|
|
2609
2959
|
this.screenName = name;
|
|
2610
2960
|
this.targets.setScreen(name);
|
|
2611
2961
|
if (changed) this.syncFlagAttributes();
|
|
2612
2962
|
const url = this.currentUrl();
|
|
2613
|
-
if (changed)
|
|
2963
|
+
if (changed) {
|
|
2964
|
+
this.screenStartedAt = Date.now();
|
|
2965
|
+
this.screenLeft = this.backgroundedAt !== null;
|
|
2966
|
+
if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name, platform: this.bridge.device.platform } });
|
|
2967
|
+
}
|
|
2614
2968
|
this.completeChecklistTasksBy("url_visited", url);
|
|
2615
2969
|
this.active?.controller.resume();
|
|
2616
2970
|
this.flows.forEach((f) => f.controller.resume());
|
|
@@ -2638,6 +2992,149 @@ var GuidelyClient = class {
|
|
|
2638
2992
|
if (this.isReady) this.evaluate("click", { targetId });
|
|
2639
2993
|
}
|
|
2640
2994
|
// ---------------------------------------------------------------------------
|
|
2995
|
+
// Product analytics (autocapture)
|
|
2996
|
+
// ---------------------------------------------------------------------------
|
|
2997
|
+
captureAllowed() {
|
|
2998
|
+
return !this.destroyed && this.analyticsEnabled();
|
|
2999
|
+
}
|
|
3000
|
+
/** A capability runs when the project enables it (absent on old servers → off), no option turns it off and analytics are allowed. */
|
|
3001
|
+
autocaptureOn(key) {
|
|
3002
|
+
const o = this.options.autocapture;
|
|
3003
|
+
return !!this.config?.settings?.autocapture?.[key] && o !== false && !(typeof o === "object" && o?.[key] === false) && this.captureAllowed();
|
|
3004
|
+
}
|
|
3005
|
+
/** `page_view` from `screen()` predates autocapture: only an explicit opt-out stops it. */
|
|
3006
|
+
pageviewsOn() {
|
|
3007
|
+
const o = this.options.autocapture;
|
|
3008
|
+
return this.config?.settings?.autocapture?.pageviews !== false && o !== false && !(typeof o === "object" && o?.pageviews === false);
|
|
3009
|
+
}
|
|
3010
|
+
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3011
|
+
isTapCaptureOn() {
|
|
3012
|
+
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps"));
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
3016
|
+
* bursts and a heatmap point. A registered `<GuidelyTarget>` whose frame contains the point
|
|
3017
|
+
* (smallest wins) takes precedence over the resolved element and is reported as `#<id>`.
|
|
3018
|
+
*/
|
|
3019
|
+
recordTap(tap) {
|
|
3020
|
+
try {
|
|
3021
|
+
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3022
|
+
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3023
|
+
if (!on.clicks && !on.rage && !on.heat) return;
|
|
3024
|
+
const t = tap.t ?? Date.now();
|
|
3025
|
+
const url = this.screenName ? this.currentUrl() : "";
|
|
3026
|
+
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
3027
|
+
} catch (err) {
|
|
3028
|
+
this.reportError(err);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
async processTap(tap, t, url, on) {
|
|
3032
|
+
let el = tap.element ?? null;
|
|
3033
|
+
let rect = null;
|
|
3034
|
+
const target = this.targets.ids().length ? await this.targets.hitTest(tap.x, tap.y) : null;
|
|
3035
|
+
if (target) {
|
|
3036
|
+
el = { selector: `#${target.id}`, tag: el?.tag ?? tap.hit?.tag ?? "View", text: el?.text };
|
|
3037
|
+
rect = target.rect;
|
|
3038
|
+
}
|
|
3039
|
+
if (this.destroyed) return;
|
|
3040
|
+
const platform = this.bridge.device.platform;
|
|
3041
|
+
const maskAll = !!this.config?.settings?.replay?.maskAllText;
|
|
3042
|
+
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
3043
|
+
if (on.clicks && el) this.enqueue("click", { url: eventUrl, properties: { ...tapProperties(el, tap.x, tap.y, platform, maskAll) } });
|
|
3044
|
+
const count = this.rage.add(tap.x, tap.y, t);
|
|
3045
|
+
if (on.rage && count) this.enqueue("rage_click", { url: eventUrl, properties: { ...tapProperties(el ?? tap.hit ?? UNKNOWN_VIEW, tap.x, tap.y, platform, maskAll), count } });
|
|
3046
|
+
if (!on.heat) return;
|
|
3047
|
+
const burst = this.rage.burst;
|
|
3048
|
+
if (el && !rect && el.measure) rect = await el.measure().catch(() => null);
|
|
3049
|
+
if (this.destroyed) return;
|
|
3050
|
+
if (this.heatmap.url !== url) this.flushHeatmap();
|
|
3051
|
+
if (this.heatmap.add(url, heatmapPoint(tap.x, tap.y, el, rect, burst > 0), burst)) this.flushHeatmap();
|
|
3052
|
+
}
|
|
3053
|
+
/** Sends the buffered heatmap taps as one `heatmap` event attributed to the screen they happened on. */
|
|
3054
|
+
flushHeatmap() {
|
|
3055
|
+
const batch = this.heatmap.take();
|
|
3056
|
+
if (!batch || !this.captureAllowed()) return;
|
|
3057
|
+
const collect = this.collectUrls() && !!batch.url;
|
|
3058
|
+
const { width, height } = this.windowSize();
|
|
3059
|
+
this.enqueue("heatmap", {
|
|
3060
|
+
url: collect ? batch.url : void 0,
|
|
3061
|
+
properties: { ...collect ? { path: batch.url } : {}, viewportWidth: width, viewportHeight: height, pageHeight: height, platform: this.bridge.device.platform, points: batch.points }
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
windowSize() {
|
|
3065
|
+
try {
|
|
3066
|
+
const s = this.bridge.windowSize?.();
|
|
3067
|
+
if (s && Number.isFinite(s.width) && Number.isFinite(s.height)) return { width: Math.round(s.width), height: Math.round(s.height) };
|
|
3068
|
+
} catch {
|
|
3069
|
+
}
|
|
3070
|
+
return { width: 0, height: 0 };
|
|
3071
|
+
}
|
|
3072
|
+
/** Ends the foreground measurement of the current screen with a `page_leave` (once per measurement). */
|
|
3073
|
+
leaveScreen() {
|
|
3074
|
+
if (!this.screenName || this.screenLeft) return;
|
|
3075
|
+
this.screenLeft = true;
|
|
3076
|
+
if (!this.autocaptureOn("pageLeave")) return;
|
|
3077
|
+
const url = this.currentUrl();
|
|
3078
|
+
const collect = this.collectUrls();
|
|
3079
|
+
this.enqueue("page_leave", {
|
|
3080
|
+
url: collect ? url : void 0,
|
|
3081
|
+
properties: { ...collect ? { path: url } : {}, screen: this.screenName, durationMs: Math.max(0, Date.now() - this.screenStartedAt), scrollDepth: 100, platform: this.bridge.device.platform }
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
/** `{ appVersion, build }` of this launch, without unknown keys. */
|
|
3085
|
+
appInfo() {
|
|
3086
|
+
const appVersion = this.options.appVersion ?? this.bridge.device.appVersion;
|
|
3087
|
+
const build = this.options.appBuild;
|
|
3088
|
+
return { ...appVersion ? { appVersion: String(appVersion) } : {}, ...build ? { build: String(build) } : {} };
|
|
3089
|
+
}
|
|
3090
|
+
/**
|
|
3091
|
+
* Once per launch, as soon as a config is known: `app_install` / `app_update` from the stored
|
|
3092
|
+
* version (when the app passes `appVersion` and `storage`), then `app_open` with `coldStart: true`.
|
|
3093
|
+
*/
|
|
3094
|
+
checkLaunch() {
|
|
3095
|
+
if (this.launchChecked || !this.config || this.destroyed || !this.hydrated) return;
|
|
3096
|
+
this.launchChecked = true;
|
|
3097
|
+
try {
|
|
3098
|
+
const on = this.autocaptureOn("appLifecycle");
|
|
3099
|
+
const info = this.appInfo();
|
|
3100
|
+
if (info.appVersion && this.options.storage) {
|
|
3101
|
+
const key = appVersionKey(this.options.projectId);
|
|
3102
|
+
const stored = readJson(this.store, key, null);
|
|
3103
|
+
if (!stored?.appVersion) {
|
|
3104
|
+
if (!this.hadIdentity && on) this.enqueue("app_install", { properties: { ...info } });
|
|
3105
|
+
} else if (stored.appVersion !== info.appVersion || !!stored.build && !!info.build && stored.build !== info.build) {
|
|
3106
|
+
if (on) this.enqueue("app_update", { properties: { ...info, previousVersion: stored.appVersion, ...stored.build ? { previousBuild: stored.build } : {} } });
|
|
3107
|
+
}
|
|
3108
|
+
writeJson(this.store, key, info);
|
|
3109
|
+
}
|
|
3110
|
+
if (on) this.enqueue("app_open", { properties: { ...info, coldStart: true } });
|
|
3111
|
+
} catch (err) {
|
|
3112
|
+
this.reportError(err);
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
wentToBackground() {
|
|
3116
|
+
try {
|
|
3117
|
+
this.leaveScreen();
|
|
3118
|
+
this.flushHeatmap();
|
|
3119
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_background", { properties: { foregroundMs: Math.max(0, Date.now() - this.openedAt) } });
|
|
3120
|
+
} catch (err) {
|
|
3121
|
+
this.reportError(err);
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
cameToForeground() {
|
|
3125
|
+
try {
|
|
3126
|
+
this.openedAt = Date.now();
|
|
3127
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_open", { properties: { ...this.appInfo(), coldStart: false } });
|
|
3128
|
+
if (this.screenName && this.screenLeft) {
|
|
3129
|
+
this.screenStartedAt = Date.now();
|
|
3130
|
+
this.screenLeft = false;
|
|
3131
|
+
if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName, platform: this.bridge.device.platform, resumed: true } });
|
|
3132
|
+
}
|
|
3133
|
+
} catch (err) {
|
|
3134
|
+
this.reportError(err);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
// ---------------------------------------------------------------------------
|
|
2641
3138
|
// Targeting + triggers
|
|
2642
3139
|
// ---------------------------------------------------------------------------
|
|
2643
3140
|
/** Builds the targeting context for the current moment. */
|
|
@@ -2684,6 +3181,50 @@ var GuidelyClient = class {
|
|
|
2684
3181
|
if (guide.kind === "checklist") return !this.state.checklists[guide.id]?.dismissedAt;
|
|
2685
3182
|
return isFrequencyEligible(guide.config.frequency, this.state.guides[guide.id], this.session.shownGuides.includes(guide.id));
|
|
2686
3183
|
}
|
|
3184
|
+
/** Guide throttling: may this guide launch automatically now? */
|
|
3185
|
+
throttleAllows(guide) {
|
|
3186
|
+
if (!countsTowardThrottle(guide)) return true;
|
|
3187
|
+
return isThrottleEligible(this.config?.settings.throttling, this.state.throttle ?? []);
|
|
3188
|
+
}
|
|
3189
|
+
recordLaunch(guide, manual) {
|
|
3190
|
+
if (manual || !countsTowardThrottle(guide) || !this.config?.settings.throttling?.enabled) return;
|
|
3191
|
+
this.state.throttle = pruneLaunches([...this.state.throttle ?? [], nowIso()], this.config.settings.throttling);
|
|
3192
|
+
}
|
|
3193
|
+
/** Guides and A/B test control groups, which are evaluated the same way but never render. */
|
|
3194
|
+
candidates() {
|
|
3195
|
+
const guides = this.config?.guides ?? [];
|
|
3196
|
+
const holdouts = this.config?.guideHoldouts ?? [];
|
|
3197
|
+
return holdouts.length ? [...guides, ...holdouts.map((h) => h.guide)] : guides;
|
|
3198
|
+
}
|
|
3199
|
+
holdoutFor(guide) {
|
|
3200
|
+
return this.config?.guideHoldouts?.find((h) => h.guide.id === guide.id) ?? null;
|
|
3201
|
+
}
|
|
3202
|
+
// ---------------------------------------------------------------------------
|
|
3203
|
+
// Localization
|
|
3204
|
+
// ---------------------------------------------------------------------------
|
|
3205
|
+
/** The visitor's language candidates, most preferred first: `setLocale()` / `locale` option, traits, device. */
|
|
3206
|
+
locales() {
|
|
3207
|
+
return visitorLocales(this.locale, this.user?.traits, [this.bridge.device.locale]);
|
|
3208
|
+
}
|
|
3209
|
+
defaultLocale() {
|
|
3210
|
+
return this.config?.settings?.localization?.defaultLocale || "en";
|
|
3211
|
+
}
|
|
3212
|
+
/** The guide in the visitor's language (remembered so its events carry `locale`). */
|
|
3213
|
+
localize(guide) {
|
|
3214
|
+
const localized = localizeGuide(guide, this.locales(), this.defaultLocale());
|
|
3215
|
+
this.guideLocales.set(guide.id, localized.locale);
|
|
3216
|
+
return localized;
|
|
3217
|
+
}
|
|
3218
|
+
/**
|
|
3219
|
+
* Sets the language for guides and the resource center (`null` = back to the user's traits and
|
|
3220
|
+
* the device language). Guides already on screen keep their language; the resource center re-renders.
|
|
3221
|
+
*/
|
|
3222
|
+
setLocale(locale) {
|
|
3223
|
+
const next = locale ? normalizeLocale(locale) || null : null;
|
|
3224
|
+
if (next === this.locale) return;
|
|
3225
|
+
this.locale = next;
|
|
3226
|
+
this.notify();
|
|
3227
|
+
}
|
|
2687
3228
|
triggerFires(guide, reason, detail) {
|
|
2688
3229
|
const t = guide.config.trigger;
|
|
2689
3230
|
switch (t.type) {
|
|
@@ -2719,7 +3260,7 @@ var GuidelyClient = class {
|
|
|
2719
3260
|
this.lastAudiences = current;
|
|
2720
3261
|
}
|
|
2721
3262
|
const candidates = [];
|
|
2722
|
-
for (const guide of this.
|
|
3263
|
+
for (const guide of this.candidates()) {
|
|
2723
3264
|
const t = guide.config.trigger;
|
|
2724
3265
|
if (detail.endedGuide && detail.endedGuide.id === guide.id) continue;
|
|
2725
3266
|
if (reason === "ready" || reason === "navigation") {
|
|
@@ -2735,6 +3276,10 @@ var GuidelyClient = class {
|
|
|
2735
3276
|
if (t.type === "page_load" && t.delayMs > 0 && reason === "timer" && detail.guideId !== guide.id) continue;
|
|
2736
3277
|
if (!this.triggerFires(guide, reason, detail)) continue;
|
|
2737
3278
|
if (!this.isEligible(guide, ctx, url)) continue;
|
|
3279
|
+
if (!this.throttleAllows(guide)) {
|
|
3280
|
+
this.log("throttled", guide.key);
|
|
3281
|
+
continue;
|
|
3282
|
+
}
|
|
2738
3283
|
candidates.push(guide);
|
|
2739
3284
|
}
|
|
2740
3285
|
if (reason === "after_guide" || reason === "navigation") this.drainQueue(url, ctx);
|
|
@@ -2780,6 +3325,12 @@ var GuidelyClient = class {
|
|
|
2780
3325
|
}
|
|
2781
3326
|
tryShow(guide, url, manual) {
|
|
2782
3327
|
if (this.destroyed) return false;
|
|
3328
|
+
if (!manual && !this.throttleAllows(guide)) return false;
|
|
3329
|
+
if (this.holdoutFor(guide)) {
|
|
3330
|
+
if (this.hasExclusive() && !manual) return false;
|
|
3331
|
+
this.recordImpression(guide, manual);
|
|
3332
|
+
return true;
|
|
3333
|
+
}
|
|
2783
3334
|
if (!isExclusiveGuide(guide)) {
|
|
2784
3335
|
if (this.flows.has(guide.id) || this.checklists.has(guide.id)) return false;
|
|
2785
3336
|
this.show(guide, manual);
|
|
@@ -2821,14 +3372,18 @@ var GuidelyClient = class {
|
|
|
2821
3372
|
guideState(id) {
|
|
2822
3373
|
return this.state.guides[id] ?? (this.state.guides[id] = { impressions: 0 });
|
|
2823
3374
|
}
|
|
2824
|
-
recordImpression(guide) {
|
|
3375
|
+
recordImpression(guide, manual = false) {
|
|
2825
3376
|
const s = this.guideState(guide.id);
|
|
2826
3377
|
s.impressions += 1;
|
|
2827
3378
|
s.lastShownAt = nowIso();
|
|
2828
3379
|
if (!this.session.shownGuides.includes(guide.id)) this.session.shownGuides.push(guide.id);
|
|
3380
|
+
this.recordLaunch(guide, manual);
|
|
2829
3381
|
this.persistState();
|
|
2830
3382
|
this.persistSession();
|
|
2831
|
-
this.
|
|
3383
|
+
const holdout = this.holdoutFor(guide);
|
|
3384
|
+
const experiment = holdout ? { id: holdout.experimentId, variantId: holdout.variantId } : guide.experiment;
|
|
3385
|
+
if (experiment) this.enqueue("experiment_viewed", { properties: { guideExperimentId: experiment.id, variantId: experiment.variantId, ...holdout ? {} : { guideId: guide.id } } });
|
|
3386
|
+
if (!holdout) this.syncGuideState(guide.id, { impression: true });
|
|
2832
3387
|
}
|
|
2833
3388
|
recordEnd(guide, reason) {
|
|
2834
3389
|
const s = this.guideState(guide.id);
|
|
@@ -2856,7 +3411,8 @@ var GuidelyClient = class {
|
|
|
2856
3411
|
this.reportError(err);
|
|
2857
3412
|
}
|
|
2858
3413
|
}
|
|
2859
|
-
showFlow(
|
|
3414
|
+
showFlow(raw, manual, stepId) {
|
|
3415
|
+
const guide = this.localize(raw);
|
|
2860
3416
|
const exclusive = isExclusiveGuide(guide);
|
|
2861
3417
|
const flow = { guide, manual, view: null, controller: null };
|
|
2862
3418
|
let renders = 0;
|
|
@@ -2871,7 +3427,7 @@ var GuidelyClient = class {
|
|
|
2871
3427
|
const step = e.stepId ? guide.config.steps.find((s) => s.id === e.stepId) : void 0;
|
|
2872
3428
|
this.enqueue(e.name, { guideId: guide.id, guideVersion: guide.version, stepId: e.stepId, stepIndex: e.stepIndex, properties: e.properties });
|
|
2873
3429
|
if (e.name === "guide_started") {
|
|
2874
|
-
this.recordImpression(guide);
|
|
3430
|
+
this.recordImpression(guide, manual);
|
|
2875
3431
|
this.emitter.emit("guide:start", guide);
|
|
2876
3432
|
} else if (e.name === "step_viewed" && step) {
|
|
2877
3433
|
this.emitter.emit("guide:step", { guide, stepId: step.id, stepIndex: e.stepIndex ?? 0 });
|
|
@@ -2918,21 +3474,24 @@ var GuidelyClient = class {
|
|
|
2918
3474
|
if (this.flows.get(guide.id) === flow) this.flows.delete(guide.id);
|
|
2919
3475
|
}
|
|
2920
3476
|
}
|
|
2921
|
-
showChecklist(
|
|
3477
|
+
showChecklist(raw, open) {
|
|
3478
|
+
if (!raw.config.checklist || this.checklists.has(raw.id)) return;
|
|
3479
|
+
const guide = this.localize(raw);
|
|
2922
3480
|
const config = guide.config.checklist;
|
|
2923
|
-
if (!config
|
|
3481
|
+
if (!config) return;
|
|
2924
3482
|
const progress = this.getChecklistProgress(guide.id);
|
|
2925
3483
|
const first = !this.state.guides[guide.id]?.impressions;
|
|
2926
|
-
this.checklists.set(guide.id, { open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
3484
|
+
this.checklists.set(guide.id, { guide, open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
2927
3485
|
if (first) this.recordImpression(guide);
|
|
2928
3486
|
if (this.checklists.get(guide.id)?.open) this.enqueue("guide_viewed", { guideId: guide.id, guideVersion: guide.version });
|
|
2929
3487
|
this.notify();
|
|
2930
3488
|
}
|
|
2931
|
-
showSurvey(
|
|
2932
|
-
if (!
|
|
3489
|
+
showSurvey(raw, manual) {
|
|
3490
|
+
if (!raw.config.survey) return;
|
|
3491
|
+
const guide = this.localize(raw);
|
|
2933
3492
|
this.activeSurvey = { guide, manual };
|
|
2934
3493
|
this.surveyView = { key: `${guide.id}:${randomId(6)}`, guide, submitted: false };
|
|
2935
|
-
this.recordImpression(guide);
|
|
3494
|
+
this.recordImpression(guide, manual);
|
|
2936
3495
|
this.enqueue("survey_displayed", { guideId: guide.id, guideVersion: guide.version });
|
|
2937
3496
|
this.emitter.emit("guide:start", guide);
|
|
2938
3497
|
this.notify();
|
|
@@ -2992,10 +3551,7 @@ var GuidelyClient = class {
|
|
|
2992
3551
|
const steps = [];
|
|
2993
3552
|
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) });
|
|
2994
3553
|
const checklists = [];
|
|
2995
|
-
this.checklists.forEach((w, id) => {
|
|
2996
|
-
const guide = this.findGuide(id);
|
|
2997
|
-
if (guide) checklists.push({ guide, progress: this.getChecklistProgress(id), open: w.open });
|
|
2998
|
-
});
|
|
3554
|
+
this.checklists.forEach((w, id) => checklists.push({ guide: w.guide, progress: this.getChecklistProgress(id), open: w.open }));
|
|
2999
3555
|
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, theme: this.themeFor(), messenger: !!this.messenger };
|
|
3000
3556
|
for (const cb of Array.from(this.uiListeners)) {
|
|
3001
3557
|
try {
|
|
@@ -3080,10 +3636,58 @@ var GuidelyClient = class {
|
|
|
3080
3636
|
getAudienceIds() {
|
|
3081
3637
|
return this.state.audienceIds;
|
|
3082
3638
|
}
|
|
3639
|
+
/** The published resource center in the visitor's language, or null when it is off. */
|
|
3083
3640
|
getResourceCenter() {
|
|
3084
|
-
const rc = this.
|
|
3641
|
+
const rc = this.localizedResourceCenter();
|
|
3085
3642
|
return rc && rc.enabled ? rc : null;
|
|
3086
3643
|
}
|
|
3644
|
+
/** The resource center in the visitor's language (cached until the config or language changes). */
|
|
3645
|
+
localizedResourceCenter() {
|
|
3646
|
+
const rc = this.config?.resourceCenter;
|
|
3647
|
+
if (!rc) return null;
|
|
3648
|
+
const locales = this.locales();
|
|
3649
|
+
const key = `${locales.join(",")}|${this.defaultLocale()}`;
|
|
3650
|
+
if (this.localizedRc?.source !== rc || this.localizedRc.locales !== key) this.localizedRc = { source: rc, locales: key, value: localizeResourceCenter(rc, locales, this.defaultLocale()) };
|
|
3651
|
+
return this.localizedRc.value;
|
|
3652
|
+
}
|
|
3653
|
+
/** Evaluates targeting rules (e.g. a resource-center section's) for the current visitor and screen. */
|
|
3654
|
+
matchesTargeting(rules) {
|
|
3655
|
+
if (!rules) return true;
|
|
3656
|
+
try {
|
|
3657
|
+
return evaluateRules(rules, this.getContext());
|
|
3658
|
+
} catch (err) {
|
|
3659
|
+
this.reportError(err);
|
|
3660
|
+
return false;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
/** Resource-center sections this visitor sees, in order (disabled and non-matching ones left out). */
|
|
3664
|
+
getResourceCenterModules() {
|
|
3665
|
+
const rc = this.getResourceCenter();
|
|
3666
|
+
if (!rc) return [];
|
|
3667
|
+
return orderedModules(rc.modules).filter((m) => m.enabled && (!("targeting" in m) || this.matchesTargeting(m.targeting))).map((m) => m.id);
|
|
3668
|
+
}
|
|
3669
|
+
/**
|
|
3670
|
+
* Sections of the messenger's Help tab, in order: the resource center's modules without "What's
|
|
3671
|
+
* new" (the messenger has its own News tab); every section when no resource center is published.
|
|
3672
|
+
*/
|
|
3673
|
+
getHelpModules() {
|
|
3674
|
+
const modules = this.getResourceCenter() ? this.getResourceCenterModules() : [...RC_MODULE_IDS];
|
|
3675
|
+
return modules.filter((id) => id !== "announcements");
|
|
3676
|
+
}
|
|
3677
|
+
/** "What's new" entries this visitor sees (newest first); empty when the section is off. */
|
|
3678
|
+
getAnnouncements() {
|
|
3679
|
+
const rc = this.getResourceCenter();
|
|
3680
|
+
if (!rc || !this.getResourceCenterModules().includes("announcements")) return [];
|
|
3681
|
+
return visibleAnnouncements(rc.announcements, this.state.audienceIds);
|
|
3682
|
+
}
|
|
3683
|
+
/** "What's new" entries published since the visitor last opened the resource center. */
|
|
3684
|
+
getUnreadAnnouncements() {
|
|
3685
|
+
return unreadAnnouncements(this.getAnnouncements(), this.state.announcementsSeenAt);
|
|
3686
|
+
}
|
|
3687
|
+
/** Whether an entry is new to the visitor in the resource center that is open now. */
|
|
3688
|
+
isAnnouncementNew(a) {
|
|
3689
|
+
return unreadAnnouncements([a], this.announcementsSeenAtOpen) > 0;
|
|
3690
|
+
}
|
|
3087
3691
|
async searchResourceCenter(q) {
|
|
3088
3692
|
try {
|
|
3089
3693
|
const res = await this.api.get("/resource-center/search", { params: { q } });
|
|
@@ -3092,6 +3696,11 @@ var GuidelyClient = class {
|
|
|
3092
3696
|
return [];
|
|
3093
3697
|
}
|
|
3094
3698
|
}
|
|
3699
|
+
/** The language of the resource center as shown, when it isn't the default (sent with article fetches). */
|
|
3700
|
+
articleLocale() {
|
|
3701
|
+
const locale = this.localizedResourceCenter()?.locale;
|
|
3702
|
+
return locale && locale.toLowerCase() !== this.defaultLocale().toLowerCase() ? locale : void 0;
|
|
3703
|
+
}
|
|
3095
3704
|
articleViewed(article) {
|
|
3096
3705
|
this.enqueue("article_viewed", { properties: { articleId: article.id, slug: article.slug } });
|
|
3097
3706
|
}
|
|
@@ -3226,6 +3835,11 @@ var GuidelyClient = class {
|
|
|
3226
3835
|
}
|
|
3227
3836
|
if (this.resourceCenterOpen) return;
|
|
3228
3837
|
this.resourceCenterOpen = true;
|
|
3838
|
+
this.announcementsSeenAtOpen = this.state.announcementsSeenAt ?? null;
|
|
3839
|
+
if (this.getAnnouncements().length) {
|
|
3840
|
+
this.state.announcementsSeenAt = nowIso();
|
|
3841
|
+
this.persistState();
|
|
3842
|
+
}
|
|
3229
3843
|
this.enqueue("resource_center_opened");
|
|
3230
3844
|
this.emitter.emit("resource-center:open");
|
|
3231
3845
|
this.notify();
|
|
@@ -3261,7 +3875,8 @@ var GuidelyClient = class {
|
|
|
3261
3875
|
onMessage: (message, conversation) => this.emitter.emit("conversation:message", { message, conversation }),
|
|
3262
3876
|
onStartGuide: (key) => this.setTimer(() => this.startGuide(key), 0),
|
|
3263
3877
|
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err),
|
|
3264
|
-
health: this.api.health
|
|
3878
|
+
health: this.api.health,
|
|
3879
|
+
locale: () => this.articleLocale()
|
|
3265
3880
|
});
|
|
3266
3881
|
this.resourceCenterOpen = false;
|
|
3267
3882
|
}
|
|
@@ -3395,7 +4010,191 @@ var GuidelyClient = class {
|
|
|
3395
4010
|
}
|
|
3396
4011
|
};
|
|
3397
4012
|
function forPlatform(config, platform) {
|
|
3398
|
-
|
|
4013
|
+
const keep = (g) => supportsPlatform(g.config, platform);
|
|
4014
|
+
const holdouts = config.guideHoldouts?.filter((h) => keep(h.guide)).map((h) => ({ ...h, guide: withoutCode(h.guide) }));
|
|
4015
|
+
return { ...config, guides: config.guides.filter(keep).map(withoutCode), ...holdouts ? { guideHoldouts: holdouts } : {} };
|
|
4016
|
+
}
|
|
4017
|
+
function withoutCode(guide) {
|
|
4018
|
+
if (!guide.config.steps.some((s) => s.code)) return guide;
|
|
4019
|
+
return { ...guide, config: { ...guide.config, steps: guide.config.steps.map(({ code: _code, ...step }) => step) } };
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
// src/ui/Autocapture.tsx
|
|
4023
|
+
import { useMemo as useMemo5, useRef as useRef5 } from "react";
|
|
4024
|
+
import { View as View10 } from "react-native";
|
|
4025
|
+
|
|
4026
|
+
// src/core/viewTree.ts
|
|
4027
|
+
var HOST_TEXT = 6;
|
|
4028
|
+
var MAX_ANCESTORS = 500;
|
|
4029
|
+
var MAX_TEXT_NODES = 300;
|
|
4030
|
+
var INTERACTIVE_ROLES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem", "checkbox", "switch", "radio", "togglebutton", "imagebutton", "option", "combobox", "adjustable"]);
|
|
4031
|
+
var TEXT_INPUT_HOST = /TextInput/i;
|
|
4032
|
+
var NOISE = /^(Anonymous|Unknown|ForwardRef|Memo|Context|Provider|Consumer|Fragment|StrictMode|Suspense|Profiler)$|Context$|Provider$|Consumer$/;
|
|
4033
|
+
var isFiber = (v) => !!v && typeof v === "object" && "return" in v && "memoizedProps" in v;
|
|
4034
|
+
function fiberFromEvent(event) {
|
|
4035
|
+
try {
|
|
4036
|
+
const e = event;
|
|
4037
|
+
if (isFiber(e?._targetInst)) return e._targetInst;
|
|
4038
|
+
const target = e?.target;
|
|
4039
|
+
if (!target || typeof target !== "object") return null;
|
|
4040
|
+
for (const key of ["__internalInstanceHandle", "_internalInstanceHandle", "_internalFiberInstanceHandleDEV"]) if (isFiber(target[key])) return target[key];
|
|
4041
|
+
for (const sym of Object.getOwnPropertySymbols(target)) if (sym.description === "internalInstanceHandle" && isFiber(target[sym])) return target[sym];
|
|
4042
|
+
} catch {
|
|
4043
|
+
}
|
|
4044
|
+
return null;
|
|
4045
|
+
}
|
|
4046
|
+
function componentName(type, depth = 0) {
|
|
4047
|
+
if (!type || depth > 3) return null;
|
|
4048
|
+
if (typeof type === "function") {
|
|
4049
|
+
const f = type;
|
|
4050
|
+
const n = typeof f.displayName === "string" && f.displayName ? f.displayName : typeof f.name === "string" ? f.name : "";
|
|
4051
|
+
return n || null;
|
|
4052
|
+
}
|
|
4053
|
+
if (typeof type === "object") {
|
|
4054
|
+
const o = type;
|
|
4055
|
+
if (typeof o.displayName === "string" && o.displayName) return o.displayName;
|
|
4056
|
+
return componentName(o.render, depth + 1) ?? componentName(o.type, depth + 1);
|
|
4057
|
+
}
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
var isHost = (f) => typeof f.type === "string";
|
|
4061
|
+
var isTextInput = (f) => typeof f.type === "string" ? TEXT_INPUT_HOST.test(f.type) : componentName(f.type) === "TextInput";
|
|
4062
|
+
function isInteractive(props) {
|
|
4063
|
+
if (!props || typeof props !== "object") return null;
|
|
4064
|
+
if (typeof props.onPress === "function" || typeof props.onLongPress === "function") return "press";
|
|
4065
|
+
const role = props.accessibilityRole ?? props.role;
|
|
4066
|
+
return typeof role === "string" && INTERACTIVE_ROLES.has(role) ? "role" : null;
|
|
4067
|
+
}
|
|
4068
|
+
function firstHost(f) {
|
|
4069
|
+
let cur = f;
|
|
4070
|
+
for (let i = 0; cur && i < 30; i++, cur = cur.child) if (isHost(cur)) return cur;
|
|
4071
|
+
return null;
|
|
4072
|
+
}
|
|
4073
|
+
function idSelector(f) {
|
|
4074
|
+
const host = firstHost(f);
|
|
4075
|
+
const sources = [f.memoizedProps, host?.memoizedProps];
|
|
4076
|
+
for (const key of ["nativeID", "testID"]) {
|
|
4077
|
+
for (const p of sources) {
|
|
4078
|
+
const v = p && typeof p === "object" ? p[key] : void 0;
|
|
4079
|
+
if (typeof v === "string" && v) return attrSelector(key, v);
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
return null;
|
|
4083
|
+
}
|
|
4084
|
+
function pathSelector(f) {
|
|
4085
|
+
const parts = [];
|
|
4086
|
+
let cur = f;
|
|
4087
|
+
for (let i = 0; cur && parts.length < 4 && i < MAX_ANCESTORS; i++, cur = cur.return) {
|
|
4088
|
+
if (isHost(cur)) continue;
|
|
4089
|
+
const name = componentName(cur.type);
|
|
4090
|
+
if (!name || NOISE.test(name)) continue;
|
|
4091
|
+
let same = 0;
|
|
4092
|
+
let index = 0;
|
|
4093
|
+
for (let s = cur.return?.child, n = 0; s && n < 500; s = s.sibling, n++) {
|
|
4094
|
+
if (s.type !== cur.type) continue;
|
|
4095
|
+
same += 1;
|
|
4096
|
+
if (s === cur) index = same;
|
|
4097
|
+
}
|
|
4098
|
+
parts.unshift(same > 1 && index ? `${name}:nth-of-type(${index})` : name);
|
|
4099
|
+
}
|
|
4100
|
+
return parts.join(" > ").slice(0, 300) || "View";
|
|
4101
|
+
}
|
|
4102
|
+
function subtreeText(f, opts) {
|
|
4103
|
+
const out = [];
|
|
4104
|
+
let visited = 0;
|
|
4105
|
+
let masked = false;
|
|
4106
|
+
const walk = (node) => {
|
|
4107
|
+
for (let n = node; n && visited < MAX_TEXT_NODES && !masked; n = n.sibling) {
|
|
4108
|
+
visited += 1;
|
|
4109
|
+
if (opts.isMask?.(n.type)) {
|
|
4110
|
+
masked = true;
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
if (isTextInput(n)) continue;
|
|
4114
|
+
if (n.tag === HOST_TEXT && (typeof n.memoizedProps === "string" || typeof n.memoizedProps === "number")) out.push(String(n.memoizedProps));
|
|
4115
|
+
else walk(n.child);
|
|
4116
|
+
}
|
|
4117
|
+
};
|
|
4118
|
+
if (opts.isMask?.(f.type)) return { text: "", masked: true };
|
|
4119
|
+
walk(f.child);
|
|
4120
|
+
return { text: out.join(" "), masked };
|
|
4121
|
+
}
|
|
4122
|
+
function measureHost(host) {
|
|
4123
|
+
return new Promise((resolve) => {
|
|
4124
|
+
let settled = false;
|
|
4125
|
+
const done = (r) => {
|
|
4126
|
+
if (settled) return;
|
|
4127
|
+
settled = true;
|
|
4128
|
+
clearTimeout(timer);
|
|
4129
|
+
resolve(r);
|
|
4130
|
+
};
|
|
4131
|
+
const timer = setTimeout(() => done(null), 500);
|
|
4132
|
+
const cb = (x, y, width, height) => done(Number.isFinite(x) && Number.isFinite(y) && width > 0 && height > 0 ? { x, y, width, height } : null);
|
|
4133
|
+
try {
|
|
4134
|
+
const sn = host.stateNode;
|
|
4135
|
+
const pub = sn?.canonical?.publicInstance ?? sn;
|
|
4136
|
+
if (pub && typeof pub.measureInWindow === "function") return pub.measureInWindow(cb);
|
|
4137
|
+
const ui = globalThis.nativeFabricUIManager;
|
|
4138
|
+
if (sn?.node && typeof ui?.measureInWindow === "function") return ui.measureInWindow(sn.node, cb);
|
|
4139
|
+
} catch {
|
|
4140
|
+
}
|
|
4141
|
+
done(null);
|
|
4142
|
+
});
|
|
4143
|
+
}
|
|
4144
|
+
function describe(f, masked, opts, withText) {
|
|
4145
|
+
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4146
|
+
const host = firstHost(f);
|
|
4147
|
+
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
4148
|
+
if (withText && !masked) {
|
|
4149
|
+
const sub = subtreeText(f, opts);
|
|
4150
|
+
if (!sub.masked) {
|
|
4151
|
+
const hostProps = host?.memoizedProps && typeof host.memoizedProps === "object" ? host.memoizedProps : {};
|
|
4152
|
+
const label = props.accessibilityLabel ?? props["aria-label"] ?? hostProps.accessibilityLabel ?? hostProps["aria-label"];
|
|
4153
|
+
const text = cleanText(sub.text) ?? (typeof label === "string" ? cleanText(label) : void 0);
|
|
4154
|
+
if (text) el.text = text;
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
if (host) el.measure = () => measureHost(host);
|
|
4158
|
+
return el;
|
|
4159
|
+
}
|
|
4160
|
+
function resolveFiber(target, opts = {}) {
|
|
4161
|
+
const none = { ignored: false, element: null, hit: null };
|
|
4162
|
+
if (!target) return none;
|
|
4163
|
+
try {
|
|
4164
|
+
let press = null;
|
|
4165
|
+
let role = null;
|
|
4166
|
+
let hitFiber = null;
|
|
4167
|
+
let stopped = false;
|
|
4168
|
+
let masked = false;
|
|
4169
|
+
for (let f = target, i = 0; f && i < MAX_ANCESTORS; f = f.return, i++) {
|
|
4170
|
+
if (opts.isIgnored?.(f.type)) return { ignored: true, element: null, hit: null };
|
|
4171
|
+
if (opts.isMask?.(f.type)) masked = true;
|
|
4172
|
+
if (stopped) continue;
|
|
4173
|
+
if (isTextInput(f)) {
|
|
4174
|
+
stopped = true;
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
if (isHost(f)) continue;
|
|
4178
|
+
const name = componentName(f.type);
|
|
4179
|
+
if (!hitFiber && name && !NOISE.test(name)) hitFiber = f;
|
|
4180
|
+
const kind = isInteractive(f.memoizedProps);
|
|
4181
|
+
if (kind === "press") {
|
|
4182
|
+
press = f;
|
|
4183
|
+
stopped = true;
|
|
4184
|
+
} else if (kind === "role" && !role) role = f;
|
|
4185
|
+
}
|
|
4186
|
+
const element = press ?? role;
|
|
4187
|
+
return {
|
|
4188
|
+
ignored: false,
|
|
4189
|
+
element: element ? describe(element, masked, opts, true) : null,
|
|
4190
|
+
hit: hitFiber ? describe(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4191
|
+
};
|
|
4192
|
+
} catch {
|
|
4193
|
+
return none;
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
function resolveTapTarget(event, opts = {}) {
|
|
4197
|
+
return resolveFiber(fiberFromEvent(event), opts);
|
|
3399
4198
|
}
|
|
3400
4199
|
|
|
3401
4200
|
// src/ui/Overlay.tsx
|
|
@@ -3919,9 +4718,9 @@ var styles3 = StyleSheet3.create({
|
|
|
3919
4718
|
});
|
|
3920
4719
|
|
|
3921
4720
|
// src/ui/Messenger.tsx
|
|
3922
|
-
import { useCallback, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
4721
|
+
import { Fragment as Fragment2, useCallback, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
3923
4722
|
import { ActivityIndicator, Keyboard, Modal as Modal2, Platform as Platform2, Pressable as Pressable4, ScrollView as ScrollView3, StyleSheet as StyleSheet4, Text as Text4, TextInput, View as View4 } from "react-native";
|
|
3924
|
-
import { Fragment as
|
|
4723
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3925
4724
|
function useMessenger(store) {
|
|
3926
4725
|
return useSyncExternalStore2(
|
|
3927
4726
|
useCallback((cb) => store.subscribe(cb), [store]),
|
|
@@ -4155,25 +4954,41 @@ function Help({ store, client, palette: p }) {
|
|
|
4155
4954
|
] }, title);
|
|
4156
4955
|
const audiences = client.getAudienceIds();
|
|
4157
4956
|
const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
|
|
4158
|
-
const
|
|
4957
|
+
const modules = client.getHelpModules();
|
|
4958
|
+
const searchOn = modules.includes("search");
|
|
4959
|
+
const listed = searchOn ? results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences)) : void 0;
|
|
4960
|
+
const checklists = (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist");
|
|
4961
|
+
const renderModule = (id) => {
|
|
4962
|
+
switch (id) {
|
|
4963
|
+
case "guides":
|
|
4964
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null;
|
|
4965
|
+
case "checklists":
|
|
4966
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u2611"))) : null;
|
|
4967
|
+
case "articles":
|
|
4968
|
+
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
4969
|
+
!articles ? /* @__PURE__ */ jsx5(ActivityIndicator, { color: p.muted }) : null,
|
|
4970
|
+
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a)))))
|
|
4971
|
+
] });
|
|
4972
|
+
case "faqs":
|
|
4973
|
+
return rc?.faqs.length ? section(
|
|
4974
|
+
"FAQ",
|
|
4975
|
+
rc.faqs.map((f) => /* @__PURE__ */ jsxs3(View4, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
|
|
4976
|
+
/* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
|
|
4977
|
+
/* @__PURE__ */ jsx5(Text4, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
|
|
4978
|
+
/* @__PURE__ */ jsx5(Text4, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
4979
|
+
] }),
|
|
4980
|
+
faq === f.id ? /* @__PURE__ */ jsx5(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
4981
|
+
] }, f.id))
|
|
4982
|
+
) : null;
|
|
4983
|
+
case "links":
|
|
4984
|
+
return rc?.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
4985
|
+
default:
|
|
4986
|
+
return null;
|
|
4987
|
+
}
|
|
4988
|
+
};
|
|
4159
4989
|
return /* @__PURE__ */ jsxs3(ScrollView3, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
|
|
4160
|
-
/* @__PURE__ */ jsx5(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) }] }),
|
|
4161
|
-
listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ jsx5(Text4, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */
|
|
4162
|
-
recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null,
|
|
4163
|
-
!articles ? /* @__PURE__ */ jsx5(ActivityIndicator, { color: p.muted }) : null,
|
|
4164
|
-
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a))))),
|
|
4165
|
-
rc?.faqs.length ? section(
|
|
4166
|
-
"FAQ",
|
|
4167
|
-
rc.faqs.map((f) => /* @__PURE__ */ jsxs3(View4, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
|
|
4168
|
-
/* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
|
|
4169
|
-
/* @__PURE__ */ jsx5(Text4, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
|
|
4170
|
-
/* @__PURE__ */ jsx5(Text4, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
4171
|
-
] }),
|
|
4172
|
-
faq === f.id ? /* @__PURE__ */ jsx5(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
4173
|
-
] }, f.id))
|
|
4174
|
-
) : null,
|
|
4175
|
-
rc?.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null
|
|
4176
|
-
] })
|
|
4990
|
+
searchOn ? /* @__PURE__ */ jsx5(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,
|
|
4991
|
+
listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ jsx5(Text4, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ jsx5(Fragment3, { children: modules.filter((id) => id !== "search").map((id) => /* @__PURE__ */ jsx5(Fragment2, { children: renderModule(id) }, id)) })
|
|
4177
4992
|
] });
|
|
4178
4993
|
}
|
|
4179
4994
|
function Article({ article, palette: p, client }) {
|
|
@@ -4240,9 +5055,9 @@ var styles4 = StyleSheet4.create({
|
|
|
4240
5055
|
});
|
|
4241
5056
|
|
|
4242
5057
|
// src/ui/ResourceCenter.tsx
|
|
4243
|
-
import { useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
5058
|
+
import { Fragment as Fragment4, useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
4244
5059
|
import { Modal as Modal3, Platform as Platform3, Pressable as Pressable5, ScrollView as ScrollView4, StyleSheet as StyleSheet5, Text as Text5, TextInput as TextInput2, View as View5 } from "react-native";
|
|
4245
|
-
import { Fragment as
|
|
5060
|
+
import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
4246
5061
|
function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
4247
5062
|
const rc = client.getResourceCenter();
|
|
4248
5063
|
const [query, setQuery] = useState4("");
|
|
@@ -4276,6 +5091,8 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4276
5091
|
const checklists = useMemo4(() => (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist"), [rc, guides]);
|
|
4277
5092
|
if (!rc) return null;
|
|
4278
5093
|
const close = () => client.closeResourceCenter();
|
|
5094
|
+
const modules = client.getResourceCenterModules();
|
|
5095
|
+
const announcements = client.getAnnouncements();
|
|
4279
5096
|
const openArticle = (a) => {
|
|
4280
5097
|
if (a.externalUrl) return client.navigate(a.externalUrl);
|
|
4281
5098
|
client.articleViewed(a);
|
|
@@ -4294,7 +5111,51 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4294
5111
|
] }, title);
|
|
4295
5112
|
const audiences = client.getAudienceIds();
|
|
4296
5113
|
const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
|
|
4297
|
-
const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
|
|
5114
|
+
const listed = modules.includes("search") ? results?.filter((a) => isArticleListed(rc.collections, a, audiences)) : void 0;
|
|
5115
|
+
const link = (label, onPress) => /* @__PURE__ */ jsx6(Pressable5, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 8, children: /* @__PURE__ */ jsx6(Text5, { style: [styles5.link, { color: p.primary }], children: label }) }, label);
|
|
5116
|
+
const announcement2 = (a) => {
|
|
5117
|
+
const linked = a.articleId ? rc.articles.find((x) => x.id === a.articleId) : void 0;
|
|
5118
|
+
const isNew = client.isAnnouncementNew(a);
|
|
5119
|
+
return /* @__PURE__ */ jsxs4(View5, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5120
|
+
/* @__PURE__ */ jsxs4(View5, { style: styles5.faqHead, children: [
|
|
5121
|
+
/* @__PURE__ */ jsx6(Text5, { accessibilityRole: "header", style: [styles5.rowTitle, styles5.flex, { color: p.text, fontFamily: p.fontFamily }], children: a.title }),
|
|
5122
|
+
isNew ? /* @__PURE__ */ jsx6(Text5, { style: [styles5.badge, { color: p.onPrimary, backgroundColor: p.primary }], children: "NEW" }) : null
|
|
5123
|
+
] }),
|
|
5124
|
+
a.body ? /* @__PURE__ */ jsx6(RichText, { html: a.body, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null,
|
|
5125
|
+
a.guideId || linked || a.url ? /* @__PURE__ */ jsxs4(View5, { style: styles5.links, children: [
|
|
5126
|
+
a.guideId ? link("Show me", () => (close(), client.startGuide(a.guideId))) : null,
|
|
5127
|
+
linked ? link("Read more", () => openArticle(linked)) : null,
|
|
5128
|
+
a.url ? link(a.linkLabel || "Learn more", () => client.navigate(a.url)) : null
|
|
5129
|
+
] }) : null
|
|
5130
|
+
] }, a.id);
|
|
5131
|
+
};
|
|
5132
|
+
const renderModule = (id) => {
|
|
5133
|
+
switch (id) {
|
|
5134
|
+
case "announcements":
|
|
5135
|
+
return announcements.length ? section("What's new", announcements.map(announcement2)) : null;
|
|
5136
|
+
case "guides":
|
|
5137
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null;
|
|
5138
|
+
case "checklists":
|
|
5139
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null;
|
|
5140
|
+
case "articles":
|
|
5141
|
+
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)))));
|
|
5142
|
+
case "faqs":
|
|
5143
|
+
return rc.faqs.length ? section(
|
|
5144
|
+
"FAQ",
|
|
5145
|
+
rc.faqs.map((f) => /* @__PURE__ */ jsxs4(View5, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5146
|
+
/* @__PURE__ */ jsxs4(Pressable5, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
|
|
5147
|
+
/* @__PURE__ */ jsx6(Text5, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
|
|
5148
|
+
/* @__PURE__ */ jsx6(Text5, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
5149
|
+
] }),
|
|
5150
|
+
faq === f.id ? /* @__PURE__ */ jsx6(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
5151
|
+
] }, f.id))
|
|
5152
|
+
) : null;
|
|
5153
|
+
case "links":
|
|
5154
|
+
return rc.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
5155
|
+
default:
|
|
5156
|
+
return null;
|
|
5157
|
+
}
|
|
5158
|
+
};
|
|
4298
5159
|
return /* @__PURE__ */ jsx6(Modal3, { visible: open, animationType: "slide", presentationStyle: Platform3.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => article ? setArticle(null) : close(), children: /* @__PURE__ */ jsxs4(View5, { style: [styles5.sheet, { backgroundColor: p.background, paddingTop: Platform3.OS === "ios" ? 8 : topInset }], children: [
|
|
4299
5160
|
/* @__PURE__ */ jsxs4(View5, { style: [styles5.header, { borderColor: p.border }], children: [
|
|
4300
5161
|
article ? /* @__PURE__ */ jsx6(Pressable5, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => setArticle(null), hitSlop: 10, children: /* @__PURE__ */ jsx6(Text5, { style: [styles5.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
|
|
@@ -4305,7 +5166,7 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4305
5166
|
/* @__PURE__ */ jsx6(Text5, { accessibilityRole: "header", style: [styles5.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
|
|
4306
5167
|
/* @__PURE__ */ jsx6(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
|
|
4307
5168
|
] }) : /* @__PURE__ */ jsxs4(ScrollView4, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], keyboardShouldPersistTaps: "handled", children: [
|
|
4308
|
-
/* @__PURE__ */ jsx6(
|
|
5169
|
+
modules.includes("search") ? /* @__PURE__ */ jsx6(
|
|
4309
5170
|
TextInput2,
|
|
4310
5171
|
{
|
|
4311
5172
|
value: query,
|
|
@@ -4317,26 +5178,11 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4317
5178
|
returnKeyType: "search",
|
|
4318
5179
|
style: [styles5.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }]
|
|
4319
5180
|
}
|
|
4320
|
-
),
|
|
5181
|
+
) : null,
|
|
4321
5182
|
listed ? section(
|
|
4322
5183
|
`Results for \u201C${query.trim()}\u201D`,
|
|
4323
5184
|
listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ jsx6(Text5, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
|
|
4324
|
-
) : /* @__PURE__ */
|
|
4325
|
-
recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null,
|
|
4326
|
-
checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null,
|
|
4327
|
-
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))))),
|
|
4328
|
-
rc.faqs.length ? section(
|
|
4329
|
-
"FAQ",
|
|
4330
|
-
rc.faqs.map((f) => /* @__PURE__ */ jsxs4(View5, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
4331
|
-
/* @__PURE__ */ jsxs4(Pressable5, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
|
|
4332
|
-
/* @__PURE__ */ jsx6(Text5, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
|
|
4333
|
-
/* @__PURE__ */ jsx6(Text5, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
4334
|
-
] }),
|
|
4335
|
-
faq === f.id ? /* @__PURE__ */ jsx6(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
4336
|
-
] }, f.id))
|
|
4337
|
-
) : null,
|
|
4338
|
-
rc.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null
|
|
4339
|
-
] })
|
|
5185
|
+
) : /* @__PURE__ */ jsx6(Fragment5, { children: modules.filter((id) => id !== "search").map((id) => /* @__PURE__ */ jsx6(Fragment4, { children: renderModule(id) }, id)) })
|
|
4340
5186
|
] })
|
|
4341
5187
|
] }) });
|
|
4342
5188
|
}
|
|
@@ -4356,7 +5202,10 @@ var styles5 = StyleSheet5.create({
|
|
|
4356
5202
|
chev: { fontSize: 18 },
|
|
4357
5203
|
faq: { flexDirection: "column", alignItems: "stretch" },
|
|
4358
5204
|
faqHead: { flexDirection: "row", alignItems: "center", gap: 10 },
|
|
4359
|
-
articleTitle: { fontSize: 22, fontWeight: "700" }
|
|
5205
|
+
articleTitle: { fontSize: 22, fontWeight: "700" },
|
|
5206
|
+
badge: { fontSize: 10, fontWeight: "800", letterSpacing: 0.5, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 6, overflow: "hidden" },
|
|
5207
|
+
links: { flexDirection: "row", flexWrap: "wrap", gap: 16, marginTop: 4 },
|
|
5208
|
+
link: { fontSize: 14, fontWeight: "600" }
|
|
4360
5209
|
});
|
|
4361
5210
|
|
|
4362
5211
|
// src/ui/StepLayer.tsx
|
|
@@ -4410,7 +5259,19 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
|
4410
5259
|
const showProgress = step.showProgress && view.total > 1;
|
|
4411
5260
|
const onLink = (url) => client.navigate(url);
|
|
4412
5261
|
return /* @__PURE__ */ jsx7(Surface, { palette: p, style: [{ width }, style], children: /* @__PURE__ */ jsxs5(View6, { accessible: false, accessibilityViewIsModal: view.asModal || step.type === "modal", style: compact ? styles6.compact : styles6.inner, children: [
|
|
4413
|
-
step.imageUrl ?
|
|
5262
|
+
step.imageUrl ? (
|
|
5263
|
+
// An empty description marks the image as decorative: screen readers skip it.
|
|
5264
|
+
/* @__PURE__ */ jsx7(
|
|
5265
|
+
Image2,
|
|
5266
|
+
{
|
|
5267
|
+
source: { uri: step.imageUrl },
|
|
5268
|
+
style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }],
|
|
5269
|
+
resizeMode: "cover",
|
|
5270
|
+
accessibilityIgnoresInvertColors: true,
|
|
5271
|
+
...step.imageAlt?.trim() ? { accessible: true, accessibilityRole: "image", accessibilityLabel: step.imageAlt.trim() } : { accessible: false, importantForAccessibility: "no" }
|
|
5272
|
+
}
|
|
5273
|
+
)
|
|
5274
|
+
) : null,
|
|
4414
5275
|
step.title ? /* @__PURE__ */ jsxs5(View6, { style: styles6.header, children: [
|
|
4415
5276
|
/* @__PURE__ */ jsx7(Text6, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
|
|
4416
5277
|
step.showClose ? /* @__PURE__ */ jsx7(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
|
|
@@ -5008,18 +5869,22 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5008
5869
|
const basePalette = palette(ui.theme, scheme);
|
|
5009
5870
|
const messenger = ui.messenger ? client.getMessenger() : null;
|
|
5010
5871
|
const showRcLauncher = !messenger && !!client.options.showResourceCenterLauncher && !!client.getResourceCenter();
|
|
5872
|
+
const unread = showRcLauncher ? client.getUnreadAnnouncements() : 0;
|
|
5011
5873
|
const messengerLeft = messenger?.getSnapshot().config.launcher.position === "bottom-left";
|
|
5012
5874
|
const checklistOnSameSide = ui.checklists.some((c) => c.guide.config.checklist?.position === "bottom-left" === messengerLeft) && client.options.showChecklistLauncher !== false;
|
|
5013
5875
|
return /* @__PURE__ */ jsxs8(View9, { ref, collapsable: false, pointerEvents: "box-none", style: StyleSheet9.absoluteFill, onLayout: measureOrigin, children: [
|
|
5014
5876
|
ui.checklists.map((c) => /* @__PURE__ */ jsx10(Checklist, { view: c, client, palette: palette(client.themeFor(c.guide), scheme), geometry, showLauncher: client.options.showChecklistLauncher !== false, launcherBottom: launcherOffset }, c.guide.id)),
|
|
5015
|
-
showRcLauncher ? /* @__PURE__ */
|
|
5877
|
+
showRcLauncher ? /* @__PURE__ */ jsxs8(
|
|
5016
5878
|
Pressable8,
|
|
5017
5879
|
{
|
|
5018
5880
|
accessibilityRole: "button",
|
|
5019
|
-
accessibilityLabel: client
|
|
5881
|
+
accessibilityLabel: rcLauncherLabel(client),
|
|
5020
5882
|
onPress: () => client.openResourceCenter(),
|
|
5021
5883
|
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 }],
|
|
5022
|
-
children:
|
|
5884
|
+
children: [
|
|
5885
|
+
/* @__PURE__ */ jsx10(Text8, { style: [styles9.rcText, { color: basePalette.onPrimary }], children: "?" }),
|
|
5886
|
+
unread ? /* @__PURE__ */ jsx10(View9, { pointerEvents: "none", style: [styles9.rcBadge, { borderColor: basePalette.primary }], children: /* @__PURE__ */ jsx10(Text8, { style: styles9.rcBadgeText, children: unread > 9 ? "9+" : unread }) }) : null
|
|
5887
|
+
]
|
|
5023
5888
|
}
|
|
5024
5889
|
) : null,
|
|
5025
5890
|
messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ jsx10(MessengerLauncher, { store: messenger, palette: basePalette, style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0) }] }) : null,
|
|
@@ -5030,27 +5895,75 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5030
5895
|
}
|
|
5031
5896
|
var styles9 = StyleSheet9.create({
|
|
5032
5897
|
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 } },
|
|
5033
|
-
rcText: { fontSize: 22, fontWeight: "800" }
|
|
5898
|
+
rcText: { fontSize: 22, fontWeight: "800" },
|
|
5899
|
+
rcBadge: { position: "absolute", top: -4, right: -4, minWidth: 20, height: 20, paddingHorizontal: 5, borderRadius: 10, borderWidth: 2, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center" },
|
|
5900
|
+
rcBadgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" }
|
|
5034
5901
|
});
|
|
5902
|
+
function rcLauncherLabel(client) {
|
|
5903
|
+
const label = client.getResourceCenter()?.launcher.label || "Help";
|
|
5904
|
+
const unread = client.getUnreadAnnouncements();
|
|
5905
|
+
return unread ? `${label} (${unread} new update${unread === 1 ? "" : "s"})` : label;
|
|
5906
|
+
}
|
|
5035
5907
|
|
|
5036
|
-
// src/ui/
|
|
5037
|
-
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect8, useRef as useRef5 } from "react";
|
|
5038
|
-
import { View as View10 } from "react-native";
|
|
5908
|
+
// src/ui/Autocapture.tsx
|
|
5039
5909
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
5910
|
+
function GuidelyMask({ children, ...rest }) {
|
|
5911
|
+
return /* @__PURE__ */ jsx11(View10, { ...rest, children });
|
|
5912
|
+
}
|
|
5913
|
+
var RESOLVE = {
|
|
5914
|
+
isIgnored: (type) => type === GuidelyOverlay,
|
|
5915
|
+
isMask: (type) => type === GuidelyMask
|
|
5916
|
+
};
|
|
5917
|
+
function useTapObserver(client) {
|
|
5918
|
+
const clientRef = useRef5(client);
|
|
5919
|
+
clientRef.current = client;
|
|
5920
|
+
return useMemo5(() => {
|
|
5921
|
+
const tracker = new TapTracker();
|
|
5922
|
+
const guard = (fn) => (e) => {
|
|
5923
|
+
try {
|
|
5924
|
+
fn(e);
|
|
5925
|
+
} catch {
|
|
5926
|
+
tracker.cancel();
|
|
5927
|
+
}
|
|
5928
|
+
};
|
|
5929
|
+
return {
|
|
5930
|
+
onTouchStartCapture: guard((e) => {
|
|
5931
|
+
const n = e.nativeEvent;
|
|
5932
|
+
if (!clientRef.current?.isTapCaptureOn()) return tracker.cancel();
|
|
5933
|
+
tracker.begin(n.pageX, n.pageY, Date.now(), Array.isArray(n.touches) ? n.touches.length : 1, n.identifier);
|
|
5934
|
+
}),
|
|
5935
|
+
onTouchMoveCapture: guard((e) => tracker.move(e.nativeEvent.pageX, e.nativeEvent.pageY, e.nativeEvent.identifier)),
|
|
5936
|
+
onTouchEndCapture: guard((e) => {
|
|
5937
|
+
const n = e.nativeEvent;
|
|
5938
|
+
const tap = tracker.end(n.pageX, n.pageY, Date.now(), n.identifier);
|
|
5939
|
+
const c = clientRef.current;
|
|
5940
|
+
if (!tap || !c) return;
|
|
5941
|
+
const r = resolveTapTarget(e, RESOLVE);
|
|
5942
|
+
if (!r.ignored) c.recordTap({ x: tap.x, y: tap.y, t: Date.now(), element: r.element, hit: r.hit });
|
|
5943
|
+
}),
|
|
5944
|
+
onTouchCancelCapture: guard(() => tracker.cancel())
|
|
5945
|
+
};
|
|
5946
|
+
}, []);
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5949
|
+
// src/ui/Target.tsx
|
|
5950
|
+
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect8, useRef as useRef6 } from "react";
|
|
5951
|
+
import { View as View11 } from "react-native";
|
|
5952
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
5040
5953
|
var GuidelyContext = createContext2(null);
|
|
5041
5954
|
var GuidelyScreenContext = createContext2(null);
|
|
5042
5955
|
function GuidelyScreenScope({ name, children }) {
|
|
5043
|
-
return /* @__PURE__ */
|
|
5956
|
+
return /* @__PURE__ */ jsx12(GuidelyScreenContext.Provider, { value: name, children });
|
|
5044
5957
|
}
|
|
5045
5958
|
function guidelyScreenLayout({ route, children }) {
|
|
5046
|
-
return /* @__PURE__ */
|
|
5959
|
+
return /* @__PURE__ */ jsx12(GuidelyScreenScope, { name: route.name, children });
|
|
5047
5960
|
}
|
|
5048
|
-
var
|
|
5961
|
+
var TAP_SLOP2 = 10;
|
|
5049
5962
|
function useGuidelyTarget(id) {
|
|
5050
5963
|
const client = useContext2(GuidelyContext);
|
|
5051
5964
|
const screen = useContext2(GuidelyScreenContext);
|
|
5052
|
-
const ref =
|
|
5053
|
-
const start =
|
|
5965
|
+
const ref = useRef6(null);
|
|
5966
|
+
const start = useRef6(null);
|
|
5054
5967
|
useEffect8(() => {
|
|
5055
5968
|
if (!client || !id) return;
|
|
5056
5969
|
return client.targets.register(id, {
|
|
@@ -5070,7 +5983,7 @@ function useGuidelyTarget(id) {
|
|
|
5070
5983
|
(e) => {
|
|
5071
5984
|
const s = start.current;
|
|
5072
5985
|
start.current = null;
|
|
5073
|
-
if (s && Math.abs(e.nativeEvent.pageX - s.x) <
|
|
5986
|
+
if (s && Math.abs(e.nativeEvent.pageX - s.x) < TAP_SLOP2 && Math.abs(e.nativeEvent.pageY - s.y) < TAP_SLOP2) client?.targets.tapped(id);
|
|
5074
5987
|
},
|
|
5075
5988
|
[client, id]
|
|
5076
5989
|
);
|
|
@@ -5078,8 +5991,8 @@ function useGuidelyTarget(id) {
|
|
|
5078
5991
|
}
|
|
5079
5992
|
function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...rest }) {
|
|
5080
5993
|
const t = useGuidelyTarget(id);
|
|
5081
|
-
return /* @__PURE__ */
|
|
5082
|
-
|
|
5994
|
+
return /* @__PURE__ */ jsx12(
|
|
5995
|
+
View11,
|
|
5083
5996
|
{
|
|
5084
5997
|
...rest,
|
|
5085
5998
|
ref: t.ref,
|
|
@@ -5103,7 +6016,7 @@ function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...re
|
|
|
5103
6016
|
|
|
5104
6017
|
// src/index.tsx
|
|
5105
6018
|
import { createFlagsClient as createFlagsClient3, InMemoryStickyBucketStore } from "@guidelytech/flags";
|
|
5106
|
-
import { jsx as
|
|
6019
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
5107
6020
|
function deviceLocale() {
|
|
5108
6021
|
try {
|
|
5109
6022
|
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
@@ -5121,21 +6034,22 @@ function defaultBridge(appVersion) {
|
|
|
5121
6034
|
appVersion
|
|
5122
6035
|
},
|
|
5123
6036
|
openExternal: (url) => void Linking.openURL(url).catch(() => {
|
|
5124
|
-
})
|
|
6037
|
+
}),
|
|
6038
|
+
windowSize: () => Dimensions.get("window")
|
|
5125
6039
|
};
|
|
5126
6040
|
}
|
|
5127
6041
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
5128
6042
|
const [client, setClient] = useState8(null);
|
|
5129
|
-
const optionsRef =
|
|
6043
|
+
const optionsRef = useRef7(options);
|
|
5130
6044
|
optionsRef.current = options;
|
|
5131
|
-
const userRef =
|
|
6045
|
+
const userRef = useRef7(user);
|
|
5132
6046
|
userRef.current = user;
|
|
5133
6047
|
const flagsOpts = options.flags === false ? null : options.flags ?? {};
|
|
5134
6048
|
const payload = flagsPayload ?? flagsOpts?.payload ?? null;
|
|
5135
|
-
const payloadRef =
|
|
6049
|
+
const payloadRef = useRef7(payload);
|
|
5136
6050
|
payloadRef.current = payload;
|
|
5137
|
-
const appAttributes =
|
|
5138
|
-
const appAttributesRef =
|
|
6051
|
+
const appAttributes = useMemo6(() => ({ ...flagsOpts?.attributes ?? {}, ...flagAttributes ?? {} }), [stableStringify3(flagsOpts?.attributes ?? {}), stableStringify3(flagAttributes ?? {})]);
|
|
6052
|
+
const appAttributesRef = useRef7(appAttributes);
|
|
5139
6053
|
appAttributesRef.current = appAttributes;
|
|
5140
6054
|
const { projectId, environment, apiUrl, storage } = options;
|
|
5141
6055
|
useEffect9(() => {
|
|
@@ -5172,7 +6086,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5172
6086
|
};
|
|
5173
6087
|
}, [projectId, environment, apiUrl, storage]);
|
|
5174
6088
|
const userKey = user ? JSON.stringify([user.id, user.traits ?? null, user.account ?? null]) : "";
|
|
5175
|
-
const hadUser =
|
|
6089
|
+
const hadUser = useRef7(false);
|
|
5176
6090
|
useEffect9(() => {
|
|
5177
6091
|
if (!client) return;
|
|
5178
6092
|
if (user && user.id) {
|
|
@@ -5186,13 +6100,18 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5186
6100
|
useEffect9(() => {
|
|
5187
6101
|
client?.flags.setAttributes(appAttributes);
|
|
5188
6102
|
}, [client, appAttributes]);
|
|
6103
|
+
useEffect9(() => {
|
|
6104
|
+
client?.setLocale(options.locale ?? null);
|
|
6105
|
+
}, [client, options.locale]);
|
|
5189
6106
|
const bootstrap = useBootstrap(payload, { ...user?.id ? { ...user.traits ?? {}, id: user.id } : {}, ...appAttributes });
|
|
5190
|
-
const flags =
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
6107
|
+
const flags = useMemo6(() => ({ client: client?.flags ?? null, bootstrap }), [client, bootstrap]);
|
|
6108
|
+
const tapObserver = useTapObserver(client);
|
|
6109
|
+
return /* @__PURE__ */ jsx13(GuidelyContext.Provider, { value: client, children: /* @__PURE__ */ jsxs9(FlagsContext.Provider, { value: flags, children: [
|
|
6110
|
+
options.autocapture === false ? children : /* @__PURE__ */ jsx13(View12, { style: styles10.root, ...tapObserver, children }),
|
|
6111
|
+
client ? /* @__PURE__ */ jsx13(GuidelyOverlay, { client, insets, launcherOffset }) : null
|
|
5194
6112
|
] }) });
|
|
5195
6113
|
}
|
|
6114
|
+
var styles10 = StyleSheet10.create({ root: { flex: 1 } });
|
|
5196
6115
|
var NOOP_RESULTS = {
|
|
5197
6116
|
init: Promise.resolve(),
|
|
5198
6117
|
ready: Promise.resolve(),
|
|
@@ -5264,7 +6183,7 @@ function useGuidelyIdentify(userId, traits, account) {
|
|
|
5264
6183
|
}
|
|
5265
6184
|
function useGuidelyEvent(event, handler) {
|
|
5266
6185
|
const client = useContext3(GuidelyContext);
|
|
5267
|
-
const ref =
|
|
6186
|
+
const ref = useRef7(handler);
|
|
5268
6187
|
ref.current = handler;
|
|
5269
6188
|
useEffect9(() => {
|
|
5270
6189
|
if (!client) return;
|
|
@@ -5279,7 +6198,7 @@ function useGuidelyScreen(name, properties) {
|
|
|
5279
6198
|
}
|
|
5280
6199
|
function useGuidelyNavigation(navigationRef, options = {}) {
|
|
5281
6200
|
const client = useContext3(GuidelyContext);
|
|
5282
|
-
const nameRef =
|
|
6201
|
+
const nameRef = useRef7(options.screenName);
|
|
5283
6202
|
nameRef.current = options.screenName;
|
|
5284
6203
|
const report = () => {
|
|
5285
6204
|
const route = navigationRef.getCurrentRoute?.();
|
|
@@ -5290,7 +6209,7 @@ function useGuidelyNavigation(navigationRef, options = {}) {
|
|
|
5290
6209
|
}
|
|
5291
6210
|
function useUiSelector(select, fallback) {
|
|
5292
6211
|
const client = useContext3(GuidelyContext);
|
|
5293
|
-
const selectRef =
|
|
6212
|
+
const selectRef = useRef7(select);
|
|
5294
6213
|
selectRef.current = select;
|
|
5295
6214
|
const [value, setValue] = useState8(() => client ? select(client) : fallback);
|
|
5296
6215
|
useEffect9(() => {
|
|
@@ -5332,14 +6251,18 @@ export {
|
|
|
5332
6251
|
FeatureValue,
|
|
5333
6252
|
FlagsProvider,
|
|
5334
6253
|
GuidelyClient,
|
|
6254
|
+
GuidelyMask,
|
|
5335
6255
|
GuidelyOverlay,
|
|
5336
6256
|
GuidelyProvider,
|
|
5337
6257
|
GuidelyScreenScope,
|
|
5338
6258
|
GuidelyTarget,
|
|
6259
|
+
HeatmapBuffer,
|
|
5339
6260
|
InMemoryStickyBucketStore,
|
|
5340
6261
|
Messenger,
|
|
6262
|
+
RageClickDetector,
|
|
5341
6263
|
SDK_NAME,
|
|
5342
6264
|
SDK_VERSION,
|
|
6265
|
+
TapTracker,
|
|
5343
6266
|
TargetRegistry,
|
|
5344
6267
|
createFlagsClient3 as createFlagsClient,
|
|
5345
6268
|
createSseParser,
|