@guidelytech/react-native 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -2
- package/dist/index.cjs +1222 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +255 -32
- package/dist/index.d.ts +255 -32
- package/dist/index.js +1206 -138
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.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-XJXXVV3C.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,14 +399,14 @@ 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
|
}
|
|
402
407
|
return out;
|
|
403
408
|
}
|
|
404
|
-
var SOURCEMAP_MAX_FILE_BYTES =
|
|
409
|
+
var SOURCEMAP_MAX_FILE_BYTES = 100 * 1024 * 1024;
|
|
405
410
|
var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
406
411
|
var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
407
412
|
var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
|
|
@@ -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-XML74K2P.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",
|
|
@@ -861,10 +1010,12 @@ var KEYS = {
|
|
|
861
1010
|
/** Messenger conversations this browser can read: [{ id, token }]. */
|
|
862
1011
|
conversations: (key) => `guidely:conversations:${key}`,
|
|
863
1012
|
/** Announcement ids opened in the messenger's News tab. */
|
|
864
|
-
newsSeen: (key) => `guidely:news:${key}
|
|
1013
|
+
newsSeen: (key) => `guidely:news:${key}`,
|
|
1014
|
+
/** Where the visitor dragged the messenger launcher ({ side, y }). */
|
|
1015
|
+
launcher: (key) => `guidely:launcher:${key}`
|
|
865
1016
|
};
|
|
866
1017
|
|
|
867
|
-
// ../sdk-core/dist/chunk-
|
|
1018
|
+
// ../sdk-core/dist/chunk-2E2YUQAG.js
|
|
868
1019
|
var EventQueue = class {
|
|
869
1020
|
constructor(opts) {
|
|
870
1021
|
__publicField2(this, "opts", opts);
|
|
@@ -1140,12 +1291,32 @@ function areMutuallyExclusive(a, b) {
|
|
|
1140
1291
|
return la.includes(b.id) || la.includes(b.key) || lb.includes(a.id) || lb.includes(a.key);
|
|
1141
1292
|
}
|
|
1142
1293
|
function isWithinSchedule(guide, now = Date.now()) {
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1294
|
+
for (const w of [guide.config.schedule, guide.config.survey]) {
|
|
1295
|
+
if (!w) continue;
|
|
1296
|
+
if (w.startAt && now < Date.parse(w.startAt)) return false;
|
|
1297
|
+
if (w.endAt && now > Date.parse(w.endAt)) return false;
|
|
1298
|
+
}
|
|
1299
|
+
return true;
|
|
1300
|
+
}
|
|
1301
|
+
var MINUTE = 60 * 1e3;
|
|
1302
|
+
function countsTowardThrottle(guide) {
|
|
1303
|
+
return isExclusiveGuide(guide) && !guide.config.frequency.ignoreThrottling;
|
|
1304
|
+
}
|
|
1305
|
+
function isThrottleEligible(settings, launches, now = Date.now()) {
|
|
1306
|
+
if (!settings || !settings.enabled) return true;
|
|
1307
|
+
const times = launches.map((l) => Date.parse(l)).filter((t) => !Number.isNaN(t));
|
|
1308
|
+
const windowMs = Math.max(1, settings.windowHours) * HOUR;
|
|
1309
|
+
if (times.filter((t) => now - t < windowMs).length >= Math.max(1, settings.maxGuides)) return false;
|
|
1310
|
+
if (settings.minGapMinutes > 0 && times.some((t) => now - t < settings.minGapMinutes * MINUTE)) return false;
|
|
1147
1311
|
return true;
|
|
1148
1312
|
}
|
|
1313
|
+
function pruneLaunches(launches, settings, now = Date.now()) {
|
|
1314
|
+
const keepMs = Math.max(settings?.windowHours ?? 24, 1) * HOUR + (settings?.minGapMinutes ?? 0) * MINUTE;
|
|
1315
|
+
return launches.filter((l) => {
|
|
1316
|
+
const t = Date.parse(l);
|
|
1317
|
+
return !Number.isNaN(t) && now - t < keepMs;
|
|
1318
|
+
}).slice(-100);
|
|
1319
|
+
}
|
|
1149
1320
|
function dismissedAfterMerge(local, server) {
|
|
1150
1321
|
if (server.dismissResetAt && server.dismissResetAt !== local?.dismissResetAt) return server.dismissedAt ?? null;
|
|
1151
1322
|
return local?.dismissedAt ?? server.dismissedAt ?? null;
|
|
@@ -1154,6 +1325,158 @@ function dismissedAfterMerge(local, server) {
|
|
|
1154
1325
|
// src/core/client.ts
|
|
1155
1326
|
import { createFlagsClient as createFlagsClient2 } from "@guidelytech/flags";
|
|
1156
1327
|
|
|
1328
|
+
// src/core/launcherPlacement.ts
|
|
1329
|
+
var LAUNCHER_PLACEMENT_KEY = "guidely:messenger.launcher";
|
|
1330
|
+
var LAUNCHER_MARGIN = 16;
|
|
1331
|
+
var LAUNCHER_DRAG_THRESHOLD = 8;
|
|
1332
|
+
var clamp2 = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
1333
|
+
function sideX(f, side) {
|
|
1334
|
+
return side === "left" ? f.insets.left + LAUNCHER_MARGIN : f.width - f.insets.right - LAUNCHER_MARGIN - f.size;
|
|
1335
|
+
}
|
|
1336
|
+
function verticalRange(f) {
|
|
1337
|
+
const max = f.height - f.defaultBottom - f.size;
|
|
1338
|
+
return { min: Math.min(max, f.insets.top + LAUNCHER_MARGIN), max };
|
|
1339
|
+
}
|
|
1340
|
+
function clampToSafeArea(f, p) {
|
|
1341
|
+
const left = f.insets.left + LAUNCHER_MARGIN;
|
|
1342
|
+
const top = f.insets.top + LAUNCHER_MARGIN;
|
|
1343
|
+
return {
|
|
1344
|
+
x: clamp2(p.x, left, Math.max(left, f.width - f.insets.right - LAUNCHER_MARGIN - f.size)),
|
|
1345
|
+
y: clamp2(p.y, top, Math.max(top, f.height - f.insets.bottom - LAUNCHER_MARGIN - f.size))
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function snapPlacement(f, p) {
|
|
1349
|
+
const { min, max } = verticalRange(f);
|
|
1350
|
+
const side = p.x + f.size / 2 < f.width / 2 ? "left" : "right";
|
|
1351
|
+
return { side, fraction: max > min ? (max - clamp2(p.y, min, max)) / (max - min) : 0 };
|
|
1352
|
+
}
|
|
1353
|
+
function placementPoint(f, placement) {
|
|
1354
|
+
const { min, max } = verticalRange(f);
|
|
1355
|
+
return { x: sideX(f, placement.side), y: max - clamp2(placement.fraction, 0, 1) * (max - min) };
|
|
1356
|
+
}
|
|
1357
|
+
function parsePlacement(raw) {
|
|
1358
|
+
if (!raw) return null;
|
|
1359
|
+
try {
|
|
1360
|
+
const v = JSON.parse(raw);
|
|
1361
|
+
if (!v || typeof v !== "object" || v.side !== "left" && v.side !== "right" || typeof v.fraction !== "number" || !Number.isFinite(v.fraction)) return null;
|
|
1362
|
+
return { side: v.side, fraction: clamp2(v.fraction, 0, 1) };
|
|
1363
|
+
} catch {
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
function serializePlacement(p) {
|
|
1368
|
+
return JSON.stringify({ side: p.side, fraction: Math.round(clamp2(p.fraction, 0, 1) * 1e4) / 1e4 });
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/core/autocapture.ts
|
|
1372
|
+
var TAP_SLOP = 10;
|
|
1373
|
+
var TAP_MAX_MS = 1e3;
|
|
1374
|
+
var HEATMAP_MAX_POINTS = 40;
|
|
1375
|
+
function quoteValue(value) {
|
|
1376
|
+
return `"${value.replace(/["\\]/g, "\\$&")}"`;
|
|
1377
|
+
}
|
|
1378
|
+
function attrSelector(name, value) {
|
|
1379
|
+
return `[${name}=${quoteValue(value)}]`.slice(0, 300);
|
|
1380
|
+
}
|
|
1381
|
+
function cleanText(text) {
|
|
1382
|
+
const t = (text ?? "").replace(/\s+/g, " ").trim();
|
|
1383
|
+
return t ? t.slice(0, 100) : void 0;
|
|
1384
|
+
}
|
|
1385
|
+
function tapProperties(el, x, y, platform, maskAll) {
|
|
1386
|
+
const props = { selector: el.selector.slice(0, 300), tag: el.tag || "View", x: Math.round(x) || 0, y: Math.round(y) || 0, platform };
|
|
1387
|
+
const text = maskAll ? void 0 : cleanText(el.text);
|
|
1388
|
+
if (text) props.text = text;
|
|
1389
|
+
return props;
|
|
1390
|
+
}
|
|
1391
|
+
var r2 = (n) => Math.round(Math.min(1, Math.max(0, n)) * 100) / 100;
|
|
1392
|
+
function heatmapPoint(x, y, el, rect, rage) {
|
|
1393
|
+
const p = { k: rage ? "r" : "c", x: Math.round(x) || 0, y: Math.round(y) || 0 };
|
|
1394
|
+
const s = el?.selector.slice(0, 120);
|
|
1395
|
+
if (s) p.s = s;
|
|
1396
|
+
if (s && rect && rect.width > 0 && rect.height > 0) {
|
|
1397
|
+
p.rx = r2((x - rect.x) / rect.width);
|
|
1398
|
+
p.ry = r2((y - rect.y) / rect.height);
|
|
1399
|
+
}
|
|
1400
|
+
return p;
|
|
1401
|
+
}
|
|
1402
|
+
var RageClickDetector = class {
|
|
1403
|
+
constructor(threshold = 3, windowMs = 1e3, radius = 30) {
|
|
1404
|
+
__publicField(this, "threshold", threshold);
|
|
1405
|
+
__publicField(this, "windowMs", windowMs);
|
|
1406
|
+
__publicField(this, "radius", radius);
|
|
1407
|
+
__publicField(this, "clicks", []);
|
|
1408
|
+
__publicField(this, "fired", false);
|
|
1409
|
+
}
|
|
1410
|
+
/** Taps in the current burst once it has fired (rage), otherwise 0. */
|
|
1411
|
+
get burst() {
|
|
1412
|
+
return this.fired ? this.clicks.length : 0;
|
|
1413
|
+
}
|
|
1414
|
+
/** Records a tap; returns the tap count when it completes a burst, otherwise 0. */
|
|
1415
|
+
add(x, y, t) {
|
|
1416
|
+
const first = this.clicks[0];
|
|
1417
|
+
const last = this.clicks[this.clicks.length - 1];
|
|
1418
|
+
if (!first || !last || t - last.t > this.windowMs || Math.hypot(x - first.x, y - first.y) > this.radius) {
|
|
1419
|
+
this.clicks = [];
|
|
1420
|
+
this.fired = false;
|
|
1421
|
+
}
|
|
1422
|
+
this.clicks = this.clicks.filter((c) => t - c.t <= this.windowMs);
|
|
1423
|
+
this.clicks.push({ x, y, t });
|
|
1424
|
+
if (this.fired || this.clicks.length < this.threshold) return 0;
|
|
1425
|
+
this.fired = true;
|
|
1426
|
+
return this.clicks.length;
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
var HeatmapBuffer = class {
|
|
1430
|
+
constructor() {
|
|
1431
|
+
/** Screen path the buffered points belong to. */
|
|
1432
|
+
__publicField(this, "url", "");
|
|
1433
|
+
__publicField(this, "points", []);
|
|
1434
|
+
}
|
|
1435
|
+
/** Adds a tap (`burst` > 0 marks it and the burst's previous taps as rage); true when the batch is full. */
|
|
1436
|
+
add(url, point, burst) {
|
|
1437
|
+
this.url = url;
|
|
1438
|
+
for (let i = Math.max(0, this.points.length - burst + 1); i < this.points.length; i++) this.points[i].k = "r";
|
|
1439
|
+
this.points.push(point);
|
|
1440
|
+
return this.points.length >= HEATMAP_MAX_POINTS;
|
|
1441
|
+
}
|
|
1442
|
+
/** Empties the buffer, returning what was in it. */
|
|
1443
|
+
take() {
|
|
1444
|
+
const points = this.points;
|
|
1445
|
+
this.points = [];
|
|
1446
|
+
return points.length ? { url: this.url, points } : null;
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
var TapTracker = class {
|
|
1450
|
+
constructor() {
|
|
1451
|
+
__publicField(this, "start", null);
|
|
1452
|
+
__publicField(this, "moved", 0);
|
|
1453
|
+
}
|
|
1454
|
+
/** A finger went down. A second finger while one is tracked cancels the gesture (not a tap). */
|
|
1455
|
+
begin(x, y, t, touches, id) {
|
|
1456
|
+
if (this.start || touches > 1) {
|
|
1457
|
+
this.start = null;
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
this.start = { x, y, t, id };
|
|
1461
|
+
this.moved = 0;
|
|
1462
|
+
}
|
|
1463
|
+
move(x, y, id) {
|
|
1464
|
+
const s = this.start;
|
|
1465
|
+
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));
|
|
1466
|
+
}
|
|
1467
|
+
/** The finger lifted: the tap position when it moved ≤ 10pt and lasted < 1s, else null. */
|
|
1468
|
+
end(x, y, t, id) {
|
|
1469
|
+
const s = this.start;
|
|
1470
|
+
if (!s || id !== void 0 && s.id !== void 0 && id !== s.id) return null;
|
|
1471
|
+
this.start = null;
|
|
1472
|
+
const moved = Math.max(this.moved, Math.hypot(x - s.x, y - s.y));
|
|
1473
|
+
return moved <= TAP_SLOP && t - s.t < TAP_MAX_MS ? { x, y } : null;
|
|
1474
|
+
}
|
|
1475
|
+
cancel() {
|
|
1476
|
+
this.start = null;
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1157
1480
|
// src/core/targets.ts
|
|
1158
1481
|
var TargetTimeoutError = class extends Error {
|
|
1159
1482
|
constructor(ids) {
|
|
@@ -1256,6 +1579,35 @@ var TargetRegistry = class {
|
|
|
1256
1579
|
return null;
|
|
1257
1580
|
}
|
|
1258
1581
|
}
|
|
1582
|
+
/**
|
|
1583
|
+
* The registered target on the current screen whose visible frame contains the point (window
|
|
1584
|
+
* coordinates); the smallest one when targets are nested. Frames that do not answer within
|
|
1585
|
+
* `timeoutMs` are skipped.
|
|
1586
|
+
*/
|
|
1587
|
+
async hitTest(x, y, timeoutMs = 500) {
|
|
1588
|
+
const candidates = [];
|
|
1589
|
+
this.targets.forEach((list, id) => {
|
|
1590
|
+
for (const handle of list) if (!(handle.screen && this.screen && handle.screen !== this.screen)) candidates.push({ id, handle });
|
|
1591
|
+
});
|
|
1592
|
+
if (!candidates.length) return null;
|
|
1593
|
+
const rects = await Promise.all(
|
|
1594
|
+
candidates.map(
|
|
1595
|
+
({ handle }) => new Promise((resolve) => {
|
|
1596
|
+
const timer = setTimeout(() => resolve(null), timeoutMs);
|
|
1597
|
+
void this.measureHandle(handle).then((r) => {
|
|
1598
|
+
clearTimeout(timer);
|
|
1599
|
+
resolve(r);
|
|
1600
|
+
});
|
|
1601
|
+
})
|
|
1602
|
+
)
|
|
1603
|
+
);
|
|
1604
|
+
let best = null;
|
|
1605
|
+
rects.forEach((r, i) => {
|
|
1606
|
+
if (!r || x < r.x || y < r.y || x > r.x + r.width || y > r.y + r.height) return;
|
|
1607
|
+
if (!best || r.width * r.height < best.rect.width * best.rect.height) best = { id: candidates[i].id, rect: r };
|
|
1608
|
+
});
|
|
1609
|
+
return best;
|
|
1610
|
+
}
|
|
1259
1611
|
/** First of `ids` that is mounted and visible. */
|
|
1260
1612
|
async resolve(ids) {
|
|
1261
1613
|
for (const id of ids) {
|
|
@@ -2013,7 +2365,8 @@ var Messenger = class {
|
|
|
2013
2365
|
this.stopThreadPolling();
|
|
2014
2366
|
this.set({ open: true, view: { kind: "article", articleId: id, from }, article: null });
|
|
2015
2367
|
try {
|
|
2016
|
-
const
|
|
2368
|
+
const locale = this.deps.locale?.();
|
|
2369
|
+
const res = await this.request("GET", `/articles/${encodeURIComponent(id)}`, locale ? { params: { locale } } : {});
|
|
2017
2370
|
this.set({ article: res.article });
|
|
2018
2371
|
} catch (err) {
|
|
2019
2372
|
this.deps.onError(err);
|
|
@@ -2128,7 +2481,7 @@ var PersistentStore = class {
|
|
|
2128
2481
|
|
|
2129
2482
|
// src/core/client.ts
|
|
2130
2483
|
var SDK_NAME = "@guidelytech/react-native";
|
|
2131
|
-
var SDK_VERSION = "0.
|
|
2484
|
+
var SDK_VERSION = "0.12.0";
|
|
2132
2485
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
2133
2486
|
var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
|
|
2134
2487
|
var CACHED_START_MS = 1500;
|
|
@@ -2142,6 +2495,8 @@ function settlesWithin(promise, ms) {
|
|
|
2142
2495
|
promise.then(done, done);
|
|
2143
2496
|
});
|
|
2144
2497
|
}
|
|
2498
|
+
var appVersionKey = (key) => `guidely:app:${key}`;
|
|
2499
|
+
var UNKNOWN_VIEW = { selector: "View", tag: "View" };
|
|
2145
2500
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2146
2501
|
var emptyState = () => ({ guides: {}, checklists: {}, surveysSubmitted: [], audienceIds: [] });
|
|
2147
2502
|
var newSession = () => ({ id: `s_${randomId(16)}`, startedAt: nowIso(), lastActiveAt: nowIso(), shownGuides: [], events: [] });
|
|
@@ -2173,6 +2528,7 @@ var GuidelyClient = class {
|
|
|
2173
2528
|
__publicField(this, "activeSurvey", null);
|
|
2174
2529
|
__publicField(this, "queued", []);
|
|
2175
2530
|
__publicField(this, "flows", /* @__PURE__ */ new Map());
|
|
2531
|
+
/** Checklists on screen, with the guide as rendered (in the visitor's language). */
|
|
2176
2532
|
__publicField(this, "checklists", /* @__PURE__ */ new Map());
|
|
2177
2533
|
__publicField(this, "expanded", /* @__PURE__ */ new Set());
|
|
2178
2534
|
__publicField(this, "surveyView", null);
|
|
@@ -2192,6 +2548,27 @@ var GuidelyClient = class {
|
|
|
2192
2548
|
__publicField(this, "flags");
|
|
2193
2549
|
/** Experiments already reported this session (`experiment_viewed` is sent once per session). */
|
|
2194
2550
|
__publicField(this, "exposures", /* @__PURE__ */ new Set());
|
|
2551
|
+
// Product analytics (autocapture).
|
|
2552
|
+
__publicField(this, "rage", new RageClickDetector());
|
|
2553
|
+
__publicField(this, "heatmap", new HeatmapBuffer());
|
|
2554
|
+
/** Taps are processed in order (target frames are measured asynchronously). */
|
|
2555
|
+
__publicField(this, "tapChain", Promise.resolve());
|
|
2556
|
+
/** When the current screen's foreground measurement started; `screenLeft` once it has been reported. */
|
|
2557
|
+
__publicField(this, "screenStartedAt", 0);
|
|
2558
|
+
__publicField(this, "screenLeft", true);
|
|
2559
|
+
/** Last `app_open` (cold start or return to the foreground), for `app_background.foregroundMs`. */
|
|
2560
|
+
__publicField(this, "openedAt", Date.now());
|
|
2561
|
+
__publicField(this, "launchChecked", false);
|
|
2562
|
+
/** An identity was stored before this launch (an existing install adopting lifecycle events). */
|
|
2563
|
+
__publicField(this, "hadIdentity", false);
|
|
2564
|
+
/** Language set with `setLocale()` (or the `locale` option). */
|
|
2565
|
+
__publicField(this, "locale", null);
|
|
2566
|
+
/** Language each shown guide was rendered in; added to its events as `locale`. */
|
|
2567
|
+
__publicField(this, "guideLocales", /* @__PURE__ */ new Map());
|
|
2568
|
+
/** The resource center in the visitor's language, recomputed when the config or language changes. */
|
|
2569
|
+
__publicField(this, "localizedRc", null);
|
|
2570
|
+
/** "What's new" last-seen time when the resource center opened (entries after it read as new). */
|
|
2571
|
+
__publicField(this, "announcementsSeenAtOpen", null);
|
|
2195
2572
|
__publicField(this, "customListeners", /* @__PURE__ */ new Map());
|
|
2196
2573
|
__publicField(this, "eventBus", {
|
|
2197
2574
|
on: (name, cb) => {
|
|
@@ -2221,6 +2598,7 @@ var GuidelyClient = class {
|
|
|
2221
2598
|
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
2599
|
this.cleanups.push(this.targets.onTap((id) => this.handleTap(id)));
|
|
2223
2600
|
if (options.user) this.user = { ...options.user };
|
|
2601
|
+
this.locale = options.locale ? normalizeLocale(options.locale) || null : null;
|
|
2224
2602
|
this.flags = this.createFlags(options.flags);
|
|
2225
2603
|
}
|
|
2226
2604
|
createFlags(opts) {
|
|
@@ -2269,9 +2647,10 @@ var GuidelyClient = class {
|
|
|
2269
2647
|
async bootstrap() {
|
|
2270
2648
|
const key = this.options.projectId;
|
|
2271
2649
|
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)]);
|
|
2650
|
+
await this.store.hydrate([KEYS.anon, KEYS.seen, KEYS.session, KEYS.config(key), KEYS.etag(key), KEYS.state(key), KEYS.user(key), KEYS.queue(key), KEYS.outbox(key), KEYS.conversations(key), appVersionKey(key), LAUNCHER_PLACEMENT_KEY]);
|
|
2273
2651
|
if (this.destroyed) return;
|
|
2274
2652
|
this.anonymousId = this.store.get(KEYS.anon) || "";
|
|
2653
|
+
this.hadIdentity = !!this.anonymousId;
|
|
2275
2654
|
if (!this.anonymousId) {
|
|
2276
2655
|
this.anonymousId = `anon_${randomId(20)}`;
|
|
2277
2656
|
this.store.set(KEYS.anon, this.anonymousId);
|
|
@@ -2308,6 +2687,7 @@ var GuidelyClient = class {
|
|
|
2308
2687
|
if (!this.config) await fresh;
|
|
2309
2688
|
else if (!await settlesWithin(fresh, CACHED_START_MS)) void fresh.then(() => this.applyConfigChange(before));
|
|
2310
2689
|
if (this.destroyed) return;
|
|
2690
|
+
this.checkLaunch();
|
|
2311
2691
|
this.wireTimers();
|
|
2312
2692
|
if (this.user?.id) await settlesWithin(this.identify(this.user.id, this.user.traits, this.user.account), CACHED_START_MS);
|
|
2313
2693
|
if (this.destroyed) return;
|
|
@@ -2337,6 +2717,7 @@ var GuidelyClient = class {
|
|
|
2337
2717
|
if (res.etag) this.store.set(KEYS.etag(this.options.projectId), res.etag);
|
|
2338
2718
|
else this.store.remove(KEYS.etag(this.options.projectId));
|
|
2339
2719
|
this.syncMessenger();
|
|
2720
|
+
if (this.hydrated) this.checkLaunch();
|
|
2340
2721
|
this.emitter.emit("config", this.config);
|
|
2341
2722
|
this.log("config loaded", this.config.configVersion, `${this.config.guides.length} guides`);
|
|
2342
2723
|
return true;
|
|
@@ -2357,7 +2738,7 @@ var GuidelyClient = class {
|
|
|
2357
2738
|
if (!this.isReady || !this.config || this.backgroundedAt !== null) return;
|
|
2358
2739
|
const url = this.currentUrl();
|
|
2359
2740
|
const ctx = this.getContext();
|
|
2360
|
-
for (const guide of this.
|
|
2741
|
+
for (const guide of this.candidates()) if (guide.config.trigger.type === "element_visible" && guide.config.trigger.value) this.armVisibility(guide, ctx, url);
|
|
2361
2742
|
this.drainQueue(url, ctx);
|
|
2362
2743
|
}, 1e3);
|
|
2363
2744
|
this.cleanups.push(() => clearInterval(poll));
|
|
@@ -2381,7 +2762,9 @@ var GuidelyClient = class {
|
|
|
2381
2762
|
setAppState(state) {
|
|
2382
2763
|
if (this.destroyed || !this.hydrated) return;
|
|
2383
2764
|
if (state === "background") {
|
|
2765
|
+
const wasForeground = this.backgroundedAt === null;
|
|
2384
2766
|
this.backgroundedAt = Date.now();
|
|
2767
|
+
if (wasForeground) this.wentToBackground();
|
|
2385
2768
|
this.session.lastActiveAt = nowIso();
|
|
2386
2769
|
this.persistSession();
|
|
2387
2770
|
void this.queue?.flush();
|
|
@@ -2398,6 +2781,7 @@ var GuidelyClient = class {
|
|
|
2398
2781
|
this.persistSession();
|
|
2399
2782
|
this.firedDelays.clear();
|
|
2400
2783
|
}
|
|
2784
|
+
this.cameToForeground();
|
|
2401
2785
|
void this.flags.refresh();
|
|
2402
2786
|
if (Date.now() - this.configFetchedAt > this.options.configRefreshMs && this.api.health.canSend()) void this.refresh();
|
|
2403
2787
|
else if (this.isReady) this.evaluate("ready");
|
|
@@ -2410,6 +2794,7 @@ var GuidelyClient = class {
|
|
|
2410
2794
|
/** Tears everything down. Create a new client to start again. */
|
|
2411
2795
|
destroy() {
|
|
2412
2796
|
if (this.destroyed) return;
|
|
2797
|
+
this.flushHeatmap();
|
|
2413
2798
|
this.destroyed = true;
|
|
2414
2799
|
this.active?.controller.destroy();
|
|
2415
2800
|
this.active = null;
|
|
@@ -2461,6 +2846,7 @@ var GuidelyClient = class {
|
|
|
2461
2846
|
for (const [id, s] of Object.entries(data.guideState || {})) {
|
|
2462
2847
|
const local = guides[id];
|
|
2463
2848
|
guides[id] = {
|
|
2849
|
+
...local,
|
|
2464
2850
|
impressions: Math.max(local?.impressions ?? 0, s.impressions ?? 0),
|
|
2465
2851
|
completedAt: local?.completedAt ?? s.completedAt ?? null,
|
|
2466
2852
|
dismissedAt: local?.dismissedAt ?? s.dismissedAt ?? null,
|
|
@@ -2482,7 +2868,10 @@ var GuidelyClient = class {
|
|
|
2482
2868
|
checklists,
|
|
2483
2869
|
surveysSubmitted: Array.from(/* @__PURE__ */ new Set([...this.state.surveysSubmitted, ...data.surveysSubmitted || []])),
|
|
2484
2870
|
audienceIds: data.audienceIds || [],
|
|
2485
|
-
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt
|
|
2871
|
+
firstSeenAt: this.state.firstSeenAt ?? data.firstSeenAt,
|
|
2872
|
+
// Device-local only (the server doesn't hold them).
|
|
2873
|
+
...this.state.throttle ? { throttle: this.state.throttle } : {},
|
|
2874
|
+
...this.state.announcementsSeenAt ? { announcementsSeenAt: this.state.announcementsSeenAt } : {}
|
|
2486
2875
|
};
|
|
2487
2876
|
if (data.firstSeenAt && Date.now() - Date.parse(data.firstSeenAt) > 6e4) this.firstVisit = false;
|
|
2488
2877
|
this.persistState();
|
|
@@ -2574,6 +2963,8 @@ var GuidelyClient = class {
|
|
|
2574
2963
|
...extra
|
|
2575
2964
|
};
|
|
2576
2965
|
if (extra.properties) event.properties = sanitizeProperties(extra.properties);
|
|
2966
|
+
const locale = extra.guideId ? this.guideLocales.get(extra.guideId) : void 0;
|
|
2967
|
+
if (locale) event.properties = { ...event.properties ?? {}, locale };
|
|
2577
2968
|
this.queue.enqueue(event);
|
|
2578
2969
|
this.emitter.emit("track", event);
|
|
2579
2970
|
return event;
|
|
@@ -2606,11 +2997,19 @@ var GuidelyClient = class {
|
|
|
2606
2997
|
if (!name) return;
|
|
2607
2998
|
this.whenHydrated(() => {
|
|
2608
2999
|
const changed = name !== this.screenName;
|
|
3000
|
+
if (changed) {
|
|
3001
|
+
this.leaveScreen();
|
|
3002
|
+
this.flushHeatmap();
|
|
3003
|
+
}
|
|
2609
3004
|
this.screenName = name;
|
|
2610
3005
|
this.targets.setScreen(name);
|
|
2611
3006
|
if (changed) this.syncFlagAttributes();
|
|
2612
3007
|
const url = this.currentUrl();
|
|
2613
|
-
if (changed)
|
|
3008
|
+
if (changed) {
|
|
3009
|
+
this.screenStartedAt = Date.now();
|
|
3010
|
+
this.screenLeft = this.backgroundedAt !== null;
|
|
3011
|
+
if (this.pageviewsOn()) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name, ...this.deviceProperties() } });
|
|
3012
|
+
}
|
|
2614
3013
|
this.completeChecklistTasksBy("url_visited", url);
|
|
2615
3014
|
this.active?.controller.resume();
|
|
2616
3015
|
this.flows.forEach((f) => f.controller.resume());
|
|
@@ -2638,6 +3037,149 @@ var GuidelyClient = class {
|
|
|
2638
3037
|
if (this.isReady) this.evaluate("click", { targetId });
|
|
2639
3038
|
}
|
|
2640
3039
|
// ---------------------------------------------------------------------------
|
|
3040
|
+
// Product analytics (autocapture)
|
|
3041
|
+
// ---------------------------------------------------------------------------
|
|
3042
|
+
captureAllowed() {
|
|
3043
|
+
return !this.destroyed && this.analyticsEnabled();
|
|
3044
|
+
}
|
|
3045
|
+
/** A capability runs when the project enables it (absent on old servers → off), no option turns it off and analytics are allowed. */
|
|
3046
|
+
autocaptureOn(key) {
|
|
3047
|
+
const o = this.options.autocapture;
|
|
3048
|
+
return !!this.config?.settings?.autocapture?.[key] && o !== false && !(typeof o === "object" && o?.[key] === false) && this.captureAllowed();
|
|
3049
|
+
}
|
|
3050
|
+
/** `page_view` from `screen()` predates autocapture: only an explicit opt-out stops it. */
|
|
3051
|
+
pageviewsOn() {
|
|
3052
|
+
const o = this.options.autocapture;
|
|
3053
|
+
return this.config?.settings?.autocapture?.pageviews !== false && o !== false && !(typeof o === "object" && o?.pageviews === false);
|
|
3054
|
+
}
|
|
3055
|
+
/** True when taps are worth observing (clicks, rage taps or heatmaps are on); the provider skips resolving views otherwise. */
|
|
3056
|
+
isTapCaptureOn() {
|
|
3057
|
+
return this.hydrated && (this.autocaptureOn("clicks") || this.autocaptureOn("rageClicks") || this.autocaptureOn("heatmaps"));
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Records a tap observed by the provider: `click` for an interactive element, `rage_click` for
|
|
3061
|
+
* bursts and a heatmap point. A registered `<GuidelyTarget>` whose frame contains the point
|
|
3062
|
+
* (smallest wins) takes precedence over the resolved element and is reported as `#<id>`.
|
|
3063
|
+
*/
|
|
3064
|
+
recordTap(tap) {
|
|
3065
|
+
try {
|
|
3066
|
+
if (!this.hydrated || this.destroyed || !Number.isFinite(tap.x) || !Number.isFinite(tap.y)) return;
|
|
3067
|
+
const on = { clicks: this.autocaptureOn("clicks"), rage: this.autocaptureOn("rageClicks"), heat: this.autocaptureOn("heatmaps") };
|
|
3068
|
+
if (!on.clicks && !on.rage && !on.heat) return;
|
|
3069
|
+
const t = tap.t ?? Date.now();
|
|
3070
|
+
const url = this.screenName ? this.currentUrl() : "";
|
|
3071
|
+
this.tapChain = this.tapChain.then(() => this.processTap(tap, t, url, on)).catch((err) => this.reportError(err));
|
|
3072
|
+
} catch (err) {
|
|
3073
|
+
this.reportError(err);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
async processTap(tap, t, url, on) {
|
|
3077
|
+
let el = tap.element ?? null;
|
|
3078
|
+
let rect = null;
|
|
3079
|
+
const target = this.targets.ids().length ? await this.targets.hitTest(tap.x, tap.y) : null;
|
|
3080
|
+
if (target) {
|
|
3081
|
+
el = { selector: `#${target.id}`, tag: el?.tag ?? tap.hit?.tag ?? "View", text: el?.text };
|
|
3082
|
+
rect = target.rect;
|
|
3083
|
+
}
|
|
3084
|
+
if (this.destroyed) return;
|
|
3085
|
+
const platform = this.bridge.device.platform;
|
|
3086
|
+
const maskAll = !!this.config?.settings?.replay?.maskAllText;
|
|
3087
|
+
const eventUrl = this.collectUrls() && url ? url : void 0;
|
|
3088
|
+
if (on.clicks && el) this.enqueue("click", { url: eventUrl, properties: { ...tapProperties(el, tap.x, tap.y, platform, maskAll) } });
|
|
3089
|
+
const count = this.rage.add(tap.x, tap.y, t);
|
|
3090
|
+
if (on.rage && count) this.enqueue("rage_click", { url: eventUrl, properties: { ...tapProperties(el ?? tap.hit ?? UNKNOWN_VIEW, tap.x, tap.y, platform, maskAll), count } });
|
|
3091
|
+
if (!on.heat) return;
|
|
3092
|
+
const burst = this.rage.burst;
|
|
3093
|
+
if (el && !rect && el.measure) rect = await el.measure().catch(() => null);
|
|
3094
|
+
if (this.destroyed) return;
|
|
3095
|
+
if (this.heatmap.url !== url) this.flushHeatmap();
|
|
3096
|
+
if (this.heatmap.add(url, heatmapPoint(tap.x, tap.y, el, rect, burst > 0), burst)) this.flushHeatmap();
|
|
3097
|
+
}
|
|
3098
|
+
/** Sends the buffered heatmap taps as one `heatmap` event attributed to the screen they happened on. */
|
|
3099
|
+
flushHeatmap() {
|
|
3100
|
+
const batch = this.heatmap.take();
|
|
3101
|
+
if (!batch || !this.captureAllowed()) return;
|
|
3102
|
+
const collect = this.collectUrls() && !!batch.url;
|
|
3103
|
+
const { width, height } = this.windowSize();
|
|
3104
|
+
this.enqueue("heatmap", {
|
|
3105
|
+
url: collect ? batch.url : void 0,
|
|
3106
|
+
properties: { ...collect ? { path: batch.url } : {}, viewportWidth: width, viewportHeight: height, pageHeight: height, platform: this.bridge.device.platform, points: batch.points }
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
windowSize() {
|
|
3110
|
+
try {
|
|
3111
|
+
const s = this.bridge.windowSize?.();
|
|
3112
|
+
if (s && Number.isFinite(s.width) && Number.isFinite(s.height)) return { width: Math.round(s.width), height: Math.round(s.height) };
|
|
3113
|
+
} catch {
|
|
3114
|
+
}
|
|
3115
|
+
return { width: 0, height: 0 };
|
|
3116
|
+
}
|
|
3117
|
+
/** Ends the foreground measurement of the current screen with a `page_leave` (once per measurement). */
|
|
3118
|
+
leaveScreen() {
|
|
3119
|
+
if (!this.screenName || this.screenLeft) return;
|
|
3120
|
+
this.screenLeft = true;
|
|
3121
|
+
if (!this.autocaptureOn("pageLeave")) return;
|
|
3122
|
+
const url = this.currentUrl();
|
|
3123
|
+
const collect = this.collectUrls();
|
|
3124
|
+
this.enqueue("page_leave", {
|
|
3125
|
+
url: collect ? url : void 0,
|
|
3126
|
+
properties: { ...collect ? { path: url } : {}, screen: this.screenName, durationMs: Math.max(0, Date.now() - this.screenStartedAt), scrollDepth: 100, platform: this.bridge.device.platform }
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
/** `{ appVersion, build }` of this launch, without unknown keys. */
|
|
3130
|
+
appInfo() {
|
|
3131
|
+
const appVersion = this.options.appVersion ?? this.bridge.device.appVersion;
|
|
3132
|
+
const build = this.options.appBuild;
|
|
3133
|
+
return { ...appVersion ? { appVersion: String(appVersion) } : {}, ...build ? { build: String(build) } : {} };
|
|
3134
|
+
}
|
|
3135
|
+
/**
|
|
3136
|
+
* Once per launch, as soon as a config is known: `app_install` / `app_update` from the stored
|
|
3137
|
+
* version (when the app passes `appVersion` and `storage`), then `app_open` with `coldStart: true`.
|
|
3138
|
+
*/
|
|
3139
|
+
checkLaunch() {
|
|
3140
|
+
if (this.launchChecked || !this.config || this.destroyed || !this.hydrated) return;
|
|
3141
|
+
this.launchChecked = true;
|
|
3142
|
+
try {
|
|
3143
|
+
const on = this.autocaptureOn("appLifecycle");
|
|
3144
|
+
const info = this.appInfo();
|
|
3145
|
+
if (info.appVersion && this.options.storage) {
|
|
3146
|
+
const key = appVersionKey(this.options.projectId);
|
|
3147
|
+
const stored = readJson(this.store, key, null);
|
|
3148
|
+
if (!stored?.appVersion) {
|
|
3149
|
+
if (!this.hadIdentity && on) this.enqueue("app_install", { properties: { ...info } });
|
|
3150
|
+
} else if (stored.appVersion !== info.appVersion || !!stored.build && !!info.build && stored.build !== info.build) {
|
|
3151
|
+
if (on) this.enqueue("app_update", { properties: { ...info, previousVersion: stored.appVersion, ...stored.build ? { previousBuild: stored.build } : {} } });
|
|
3152
|
+
}
|
|
3153
|
+
writeJson(this.store, key, info);
|
|
3154
|
+
}
|
|
3155
|
+
if (on) this.enqueue("app_open", { properties: { ...info, coldStart: true } });
|
|
3156
|
+
} catch (err) {
|
|
3157
|
+
this.reportError(err);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
wentToBackground() {
|
|
3161
|
+
try {
|
|
3162
|
+
this.leaveScreen();
|
|
3163
|
+
this.flushHeatmap();
|
|
3164
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_background", { properties: { foregroundMs: Math.max(0, Date.now() - this.openedAt) } });
|
|
3165
|
+
} catch (err) {
|
|
3166
|
+
this.reportError(err);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
cameToForeground() {
|
|
3170
|
+
try {
|
|
3171
|
+
this.openedAt = Date.now();
|
|
3172
|
+
if (this.launchChecked && this.autocaptureOn("appLifecycle")) this.enqueue("app_open", { properties: { ...this.appInfo(), coldStart: false } });
|
|
3173
|
+
if (this.screenName && this.screenLeft) {
|
|
3174
|
+
this.screenStartedAt = Date.now();
|
|
3175
|
+
this.screenLeft = false;
|
|
3176
|
+
if (this.pageviewsOn() && this.autocaptureOn("pageLeave")) this.enqueue("page_view", { properties: { path: this.currentUrl(), screen: this.screenName, ...this.deviceProperties(), resumed: true } });
|
|
3177
|
+
}
|
|
3178
|
+
} catch (err) {
|
|
3179
|
+
this.reportError(err);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
// ---------------------------------------------------------------------------
|
|
2641
3183
|
// Targeting + triggers
|
|
2642
3184
|
// ---------------------------------------------------------------------------
|
|
2643
3185
|
/** Builds the targeting context for the current moment. */
|
|
@@ -2684,6 +3226,50 @@ var GuidelyClient = class {
|
|
|
2684
3226
|
if (guide.kind === "checklist") return !this.state.checklists[guide.id]?.dismissedAt;
|
|
2685
3227
|
return isFrequencyEligible(guide.config.frequency, this.state.guides[guide.id], this.session.shownGuides.includes(guide.id));
|
|
2686
3228
|
}
|
|
3229
|
+
/** Guide throttling: may this guide launch automatically now? */
|
|
3230
|
+
throttleAllows(guide) {
|
|
3231
|
+
if (!countsTowardThrottle(guide)) return true;
|
|
3232
|
+
return isThrottleEligible(this.config?.settings.throttling, this.state.throttle ?? []);
|
|
3233
|
+
}
|
|
3234
|
+
recordLaunch(guide, manual) {
|
|
3235
|
+
if (manual || !countsTowardThrottle(guide) || !this.config?.settings.throttling?.enabled) return;
|
|
3236
|
+
this.state.throttle = pruneLaunches([...this.state.throttle ?? [], nowIso()], this.config.settings.throttling);
|
|
3237
|
+
}
|
|
3238
|
+
/** Guides and A/B test control groups, which are evaluated the same way but never render. */
|
|
3239
|
+
candidates() {
|
|
3240
|
+
const guides = this.config?.guides ?? [];
|
|
3241
|
+
const holdouts = this.config?.guideHoldouts ?? [];
|
|
3242
|
+
return holdouts.length ? [...guides, ...holdouts.map((h) => h.guide)] : guides;
|
|
3243
|
+
}
|
|
3244
|
+
holdoutFor(guide) {
|
|
3245
|
+
return this.config?.guideHoldouts?.find((h) => h.guide.id === guide.id) ?? null;
|
|
3246
|
+
}
|
|
3247
|
+
// ---------------------------------------------------------------------------
|
|
3248
|
+
// Localization
|
|
3249
|
+
// ---------------------------------------------------------------------------
|
|
3250
|
+
/** The visitor's language candidates, most preferred first: `setLocale()` / `locale` option, traits, device. */
|
|
3251
|
+
locales() {
|
|
3252
|
+
return visitorLocales(this.locale, this.user?.traits, [this.bridge.device.locale]);
|
|
3253
|
+
}
|
|
3254
|
+
defaultLocale() {
|
|
3255
|
+
return this.config?.settings?.localization?.defaultLocale || "en";
|
|
3256
|
+
}
|
|
3257
|
+
/** The guide in the visitor's language (remembered so its events carry `locale`). */
|
|
3258
|
+
localize(guide) {
|
|
3259
|
+
const localized = localizeGuide(guide, this.locales(), this.defaultLocale());
|
|
3260
|
+
this.guideLocales.set(guide.id, localized.locale);
|
|
3261
|
+
return localized;
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Sets the language for guides and the resource center (`null` = back to the user's traits and
|
|
3265
|
+
* the device language). Guides already on screen keep their language; the resource center re-renders.
|
|
3266
|
+
*/
|
|
3267
|
+
setLocale(locale) {
|
|
3268
|
+
const next = locale ? normalizeLocale(locale) || null : null;
|
|
3269
|
+
if (next === this.locale) return;
|
|
3270
|
+
this.locale = next;
|
|
3271
|
+
this.notify();
|
|
3272
|
+
}
|
|
2687
3273
|
triggerFires(guide, reason, detail) {
|
|
2688
3274
|
const t = guide.config.trigger;
|
|
2689
3275
|
switch (t.type) {
|
|
@@ -2719,7 +3305,7 @@ var GuidelyClient = class {
|
|
|
2719
3305
|
this.lastAudiences = current;
|
|
2720
3306
|
}
|
|
2721
3307
|
const candidates = [];
|
|
2722
|
-
for (const guide of this.
|
|
3308
|
+
for (const guide of this.candidates()) {
|
|
2723
3309
|
const t = guide.config.trigger;
|
|
2724
3310
|
if (detail.endedGuide && detail.endedGuide.id === guide.id) continue;
|
|
2725
3311
|
if (reason === "ready" || reason === "navigation") {
|
|
@@ -2735,6 +3321,10 @@ var GuidelyClient = class {
|
|
|
2735
3321
|
if (t.type === "page_load" && t.delayMs > 0 && reason === "timer" && detail.guideId !== guide.id) continue;
|
|
2736
3322
|
if (!this.triggerFires(guide, reason, detail)) continue;
|
|
2737
3323
|
if (!this.isEligible(guide, ctx, url)) continue;
|
|
3324
|
+
if (!this.throttleAllows(guide)) {
|
|
3325
|
+
this.log("throttled", guide.key);
|
|
3326
|
+
continue;
|
|
3327
|
+
}
|
|
2738
3328
|
candidates.push(guide);
|
|
2739
3329
|
}
|
|
2740
3330
|
if (reason === "after_guide" || reason === "navigation") this.drainQueue(url, ctx);
|
|
@@ -2780,6 +3370,12 @@ var GuidelyClient = class {
|
|
|
2780
3370
|
}
|
|
2781
3371
|
tryShow(guide, url, manual) {
|
|
2782
3372
|
if (this.destroyed) return false;
|
|
3373
|
+
if (!manual && !this.throttleAllows(guide)) return false;
|
|
3374
|
+
if (this.holdoutFor(guide)) {
|
|
3375
|
+
if (this.hasExclusive() && !manual) return false;
|
|
3376
|
+
this.recordImpression(guide, manual);
|
|
3377
|
+
return true;
|
|
3378
|
+
}
|
|
2783
3379
|
if (!isExclusiveGuide(guide)) {
|
|
2784
3380
|
if (this.flows.has(guide.id) || this.checklists.has(guide.id)) return false;
|
|
2785
3381
|
this.show(guide, manual);
|
|
@@ -2821,14 +3417,18 @@ var GuidelyClient = class {
|
|
|
2821
3417
|
guideState(id) {
|
|
2822
3418
|
return this.state.guides[id] ?? (this.state.guides[id] = { impressions: 0 });
|
|
2823
3419
|
}
|
|
2824
|
-
recordImpression(guide) {
|
|
3420
|
+
recordImpression(guide, manual = false) {
|
|
2825
3421
|
const s = this.guideState(guide.id);
|
|
2826
3422
|
s.impressions += 1;
|
|
2827
3423
|
s.lastShownAt = nowIso();
|
|
2828
3424
|
if (!this.session.shownGuides.includes(guide.id)) this.session.shownGuides.push(guide.id);
|
|
3425
|
+
this.recordLaunch(guide, manual);
|
|
2829
3426
|
this.persistState();
|
|
2830
3427
|
this.persistSession();
|
|
2831
|
-
this.
|
|
3428
|
+
const holdout = this.holdoutFor(guide);
|
|
3429
|
+
const experiment = holdout ? { id: holdout.experimentId, variantId: holdout.variantId } : guide.experiment;
|
|
3430
|
+
if (experiment) this.enqueue("experiment_viewed", { properties: { guideExperimentId: experiment.id, variantId: experiment.variantId, ...holdout ? {} : { guideId: guide.id } } });
|
|
3431
|
+
if (!holdout) this.syncGuideState(guide.id, { impression: true });
|
|
2832
3432
|
}
|
|
2833
3433
|
recordEnd(guide, reason) {
|
|
2834
3434
|
const s = this.guideState(guide.id);
|
|
@@ -2856,7 +3456,8 @@ var GuidelyClient = class {
|
|
|
2856
3456
|
this.reportError(err);
|
|
2857
3457
|
}
|
|
2858
3458
|
}
|
|
2859
|
-
showFlow(
|
|
3459
|
+
showFlow(raw, manual, stepId) {
|
|
3460
|
+
const guide = this.localize(raw);
|
|
2860
3461
|
const exclusive = isExclusiveGuide(guide);
|
|
2861
3462
|
const flow = { guide, manual, view: null, controller: null };
|
|
2862
3463
|
let renders = 0;
|
|
@@ -2871,7 +3472,7 @@ var GuidelyClient = class {
|
|
|
2871
3472
|
const step = e.stepId ? guide.config.steps.find((s) => s.id === e.stepId) : void 0;
|
|
2872
3473
|
this.enqueue(e.name, { guideId: guide.id, guideVersion: guide.version, stepId: e.stepId, stepIndex: e.stepIndex, properties: e.properties });
|
|
2873
3474
|
if (e.name === "guide_started") {
|
|
2874
|
-
this.recordImpression(guide);
|
|
3475
|
+
this.recordImpression(guide, manual);
|
|
2875
3476
|
this.emitter.emit("guide:start", guide);
|
|
2876
3477
|
} else if (e.name === "step_viewed" && step) {
|
|
2877
3478
|
this.emitter.emit("guide:step", { guide, stepId: step.id, stepIndex: e.stepIndex ?? 0 });
|
|
@@ -2918,21 +3519,24 @@ var GuidelyClient = class {
|
|
|
2918
3519
|
if (this.flows.get(guide.id) === flow) this.flows.delete(guide.id);
|
|
2919
3520
|
}
|
|
2920
3521
|
}
|
|
2921
|
-
showChecklist(
|
|
3522
|
+
showChecklist(raw, open) {
|
|
3523
|
+
if (!raw.config.checklist || this.checklists.has(raw.id)) return;
|
|
3524
|
+
const guide = this.localize(raw);
|
|
2922
3525
|
const config = guide.config.checklist;
|
|
2923
|
-
if (!config
|
|
3526
|
+
if (!config) return;
|
|
2924
3527
|
const progress = this.getChecklistProgress(guide.id);
|
|
2925
3528
|
const first = !this.state.guides[guide.id]?.impressions;
|
|
2926
|
-
this.checklists.set(guide.id, { open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
3529
|
+
this.checklists.set(guide.id, { guide, open: open ?? (config.autoOpen && first && !progress.completedAt) });
|
|
2927
3530
|
if (first) this.recordImpression(guide);
|
|
2928
3531
|
if (this.checklists.get(guide.id)?.open) this.enqueue("guide_viewed", { guideId: guide.id, guideVersion: guide.version });
|
|
2929
3532
|
this.notify();
|
|
2930
3533
|
}
|
|
2931
|
-
showSurvey(
|
|
2932
|
-
if (!
|
|
3534
|
+
showSurvey(raw, manual) {
|
|
3535
|
+
if (!raw.config.survey) return;
|
|
3536
|
+
const guide = this.localize(raw);
|
|
2933
3537
|
this.activeSurvey = { guide, manual };
|
|
2934
3538
|
this.surveyView = { key: `${guide.id}:${randomId(6)}`, guide, submitted: false };
|
|
2935
|
-
this.recordImpression(guide);
|
|
3539
|
+
this.recordImpression(guide, manual);
|
|
2936
3540
|
this.enqueue("survey_displayed", { guideId: guide.id, guideVersion: guide.version });
|
|
2937
3541
|
this.emitter.emit("guide:start", guide);
|
|
2938
3542
|
this.notify();
|
|
@@ -2992,10 +3596,7 @@ var GuidelyClient = class {
|
|
|
2992
3596
|
const steps = [];
|
|
2993
3597
|
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
3598
|
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
|
-
});
|
|
3599
|
+
this.checklists.forEach((w, id) => checklists.push({ guide: w.guide, progress: this.getChecklistProgress(id), open: w.open }));
|
|
2999
3600
|
this.ui = { steps, checklists, survey: this.surveyView, resourceCenterOpen: this.resourceCenterOpen, theme: this.themeFor(), messenger: !!this.messenger };
|
|
3000
3601
|
for (const cb of Array.from(this.uiListeners)) {
|
|
3001
3602
|
try {
|
|
@@ -3080,10 +3681,58 @@ var GuidelyClient = class {
|
|
|
3080
3681
|
getAudienceIds() {
|
|
3081
3682
|
return this.state.audienceIds;
|
|
3082
3683
|
}
|
|
3684
|
+
/** The published resource center in the visitor's language, or null when it is off. */
|
|
3083
3685
|
getResourceCenter() {
|
|
3084
|
-
const rc = this.
|
|
3686
|
+
const rc = this.localizedResourceCenter();
|
|
3085
3687
|
return rc && rc.enabled ? rc : null;
|
|
3086
3688
|
}
|
|
3689
|
+
/** The resource center in the visitor's language (cached until the config or language changes). */
|
|
3690
|
+
localizedResourceCenter() {
|
|
3691
|
+
const rc = this.config?.resourceCenter;
|
|
3692
|
+
if (!rc) return null;
|
|
3693
|
+
const locales = this.locales();
|
|
3694
|
+
const key = `${locales.join(",")}|${this.defaultLocale()}`;
|
|
3695
|
+
if (this.localizedRc?.source !== rc || this.localizedRc.locales !== key) this.localizedRc = { source: rc, locales: key, value: localizeResourceCenter(rc, locales, this.defaultLocale()) };
|
|
3696
|
+
return this.localizedRc.value;
|
|
3697
|
+
}
|
|
3698
|
+
/** Evaluates targeting rules (e.g. a resource-center section's) for the current visitor and screen. */
|
|
3699
|
+
matchesTargeting(rules) {
|
|
3700
|
+
if (!rules) return true;
|
|
3701
|
+
try {
|
|
3702
|
+
return evaluateRules(rules, this.getContext());
|
|
3703
|
+
} catch (err) {
|
|
3704
|
+
this.reportError(err);
|
|
3705
|
+
return false;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
/** Resource-center sections this visitor sees, in order (disabled and non-matching ones left out). */
|
|
3709
|
+
getResourceCenterModules() {
|
|
3710
|
+
const rc = this.getResourceCenter();
|
|
3711
|
+
if (!rc) return [];
|
|
3712
|
+
return orderedModules(rc.modules).filter((m) => m.enabled && (!("targeting" in m) || this.matchesTargeting(m.targeting))).map((m) => m.id);
|
|
3713
|
+
}
|
|
3714
|
+
/**
|
|
3715
|
+
* Sections of the messenger's Help tab, in order: the resource center's modules without "What's
|
|
3716
|
+
* new" (the messenger has its own News tab); every section when no resource center is published.
|
|
3717
|
+
*/
|
|
3718
|
+
getHelpModules() {
|
|
3719
|
+
const modules = this.getResourceCenter() ? this.getResourceCenterModules() : [...RC_MODULE_IDS];
|
|
3720
|
+
return modules.filter((id) => id !== "announcements");
|
|
3721
|
+
}
|
|
3722
|
+
/** "What's new" entries this visitor sees (newest first); empty when the section is off. */
|
|
3723
|
+
getAnnouncements() {
|
|
3724
|
+
const rc = this.getResourceCenter();
|
|
3725
|
+
if (!rc || !this.getResourceCenterModules().includes("announcements")) return [];
|
|
3726
|
+
return visibleAnnouncements(rc.announcements, this.state.audienceIds);
|
|
3727
|
+
}
|
|
3728
|
+
/** "What's new" entries published since the visitor last opened the resource center. */
|
|
3729
|
+
getUnreadAnnouncements() {
|
|
3730
|
+
return unreadAnnouncements(this.getAnnouncements(), this.state.announcementsSeenAt);
|
|
3731
|
+
}
|
|
3732
|
+
/** Whether an entry is new to the visitor in the resource center that is open now. */
|
|
3733
|
+
isAnnouncementNew(a) {
|
|
3734
|
+
return unreadAnnouncements([a], this.announcementsSeenAtOpen) > 0;
|
|
3735
|
+
}
|
|
3087
3736
|
async searchResourceCenter(q) {
|
|
3088
3737
|
try {
|
|
3089
3738
|
const res = await this.api.get("/resource-center/search", { params: { q } });
|
|
@@ -3092,6 +3741,11 @@ var GuidelyClient = class {
|
|
|
3092
3741
|
return [];
|
|
3093
3742
|
}
|
|
3094
3743
|
}
|
|
3744
|
+
/** The language of the resource center as shown, when it isn't the default (sent with article fetches). */
|
|
3745
|
+
articleLocale() {
|
|
3746
|
+
const locale = this.localizedResourceCenter()?.locale;
|
|
3747
|
+
return locale && locale.toLowerCase() !== this.defaultLocale().toLowerCase() ? locale : void 0;
|
|
3748
|
+
}
|
|
3095
3749
|
articleViewed(article) {
|
|
3096
3750
|
this.enqueue("article_viewed", { properties: { articleId: article.id, slug: article.slug } });
|
|
3097
3751
|
}
|
|
@@ -3226,6 +3880,11 @@ var GuidelyClient = class {
|
|
|
3226
3880
|
}
|
|
3227
3881
|
if (this.resourceCenterOpen) return;
|
|
3228
3882
|
this.resourceCenterOpen = true;
|
|
3883
|
+
this.announcementsSeenAtOpen = this.state.announcementsSeenAt ?? null;
|
|
3884
|
+
if (this.getAnnouncements().length) {
|
|
3885
|
+
this.state.announcementsSeenAt = nowIso();
|
|
3886
|
+
this.persistState();
|
|
3887
|
+
}
|
|
3229
3888
|
this.enqueue("resource_center_opened");
|
|
3230
3889
|
this.emitter.emit("resource-center:open");
|
|
3231
3890
|
this.notify();
|
|
@@ -3261,7 +3920,8 @@ var GuidelyClient = class {
|
|
|
3261
3920
|
onMessage: (message, conversation) => this.emitter.emit("conversation:message", { message, conversation }),
|
|
3262
3921
|
onStartGuide: (key) => this.setTimer(() => this.startGuide(key), 0),
|
|
3263
3922
|
onError: (err) => this.log("messenger", err instanceof Error ? err.message : err),
|
|
3264
|
-
health: this.api.health
|
|
3923
|
+
health: this.api.health,
|
|
3924
|
+
locale: () => this.articleLocale()
|
|
3265
3925
|
});
|
|
3266
3926
|
this.resourceCenterOpen = false;
|
|
3267
3927
|
}
|
|
@@ -3275,6 +3935,14 @@ var GuidelyClient = class {
|
|
|
3275
3935
|
getMessenger() {
|
|
3276
3936
|
return this.messenger;
|
|
3277
3937
|
}
|
|
3938
|
+
/** Where the visitor dragged the messenger bubble on this device, or null to use the configured spot. */
|
|
3939
|
+
getMessengerLauncherPlacement() {
|
|
3940
|
+
return parsePlacement(this.store.get(LAUNCHER_PLACEMENT_KEY));
|
|
3941
|
+
}
|
|
3942
|
+
/** Remembers the bubble's spot on this device (in memory only when no `storage` was passed). */
|
|
3943
|
+
setMessengerLauncherPlacement(placement) {
|
|
3944
|
+
this.store.set(LAUNCHER_PLACEMENT_KEY, serializePlacement(placement));
|
|
3945
|
+
}
|
|
3278
3946
|
/** Opens the Scout messenger, optionally on a tab. No-op when Scout is off for this platform. */
|
|
3279
3947
|
openMessenger(tab) {
|
|
3280
3948
|
this.whenHydrated(() => this.messenger?.open(tab));
|
|
@@ -3305,6 +3973,11 @@ var GuidelyClient = class {
|
|
|
3305
3973
|
onFlagsChange(cb) {
|
|
3306
3974
|
return this.flags.subscribe(cb);
|
|
3307
3975
|
}
|
|
3976
|
+
/** Device facts on every screen view, read by the Pages devices / browsers breakdowns. */
|
|
3977
|
+
deviceProperties() {
|
|
3978
|
+
const d = this.bridge.device;
|
|
3979
|
+
return { platform: d.platform, deviceType: d.deviceType, os: d.platform };
|
|
3980
|
+
}
|
|
3308
3981
|
/** Attributes flags are evaluated with: identity, traits, account, audiences, screen and device. */
|
|
3309
3982
|
flagContext() {
|
|
3310
3983
|
const d = this.bridge.device;
|
|
@@ -3395,7 +4068,191 @@ var GuidelyClient = class {
|
|
|
3395
4068
|
}
|
|
3396
4069
|
};
|
|
3397
4070
|
function forPlatform(config, platform) {
|
|
3398
|
-
|
|
4071
|
+
const keep = (g) => supportsPlatform(g.config, platform);
|
|
4072
|
+
const holdouts = config.guideHoldouts?.filter((h) => keep(h.guide)).map((h) => ({ ...h, guide: withoutCode(h.guide) }));
|
|
4073
|
+
return { ...config, guides: config.guides.filter(keep).map(withoutCode), ...holdouts ? { guideHoldouts: holdouts } : {} };
|
|
4074
|
+
}
|
|
4075
|
+
function withoutCode(guide) {
|
|
4076
|
+
if (!guide.config.steps.some((s) => s.code)) return guide;
|
|
4077
|
+
return { ...guide, config: { ...guide.config, steps: guide.config.steps.map(({ code: _code, ...step }) => step) } };
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
// src/ui/Autocapture.tsx
|
|
4081
|
+
import { useMemo as useMemo5, useRef as useRef5 } from "react";
|
|
4082
|
+
import { View as View10 } from "react-native";
|
|
4083
|
+
|
|
4084
|
+
// src/core/viewTree.ts
|
|
4085
|
+
var HOST_TEXT = 6;
|
|
4086
|
+
var MAX_ANCESTORS = 500;
|
|
4087
|
+
var MAX_TEXT_NODES = 300;
|
|
4088
|
+
var INTERACTIVE_ROLES = /* @__PURE__ */ new Set(["button", "link", "tab", "menuitem", "checkbox", "switch", "radio", "togglebutton", "imagebutton", "option", "combobox", "adjustable"]);
|
|
4089
|
+
var TEXT_INPUT_HOST = /TextInput/i;
|
|
4090
|
+
var NOISE = /^(Anonymous|Unknown|ForwardRef|Memo|Context|Provider|Consumer|Fragment|StrictMode|Suspense|Profiler)$|Context$|Provider$|Consumer$/;
|
|
4091
|
+
var isFiber = (v) => !!v && typeof v === "object" && "return" in v && "memoizedProps" in v;
|
|
4092
|
+
function fiberFromEvent(event) {
|
|
4093
|
+
try {
|
|
4094
|
+
const e = event;
|
|
4095
|
+
if (isFiber(e?._targetInst)) return e._targetInst;
|
|
4096
|
+
const target = e?.target;
|
|
4097
|
+
if (!target || typeof target !== "object") return null;
|
|
4098
|
+
for (const key of ["__internalInstanceHandle", "_internalInstanceHandle", "_internalFiberInstanceHandleDEV"]) if (isFiber(target[key])) return target[key];
|
|
4099
|
+
for (const sym of Object.getOwnPropertySymbols(target)) if (sym.description === "internalInstanceHandle" && isFiber(target[sym])) return target[sym];
|
|
4100
|
+
} catch {
|
|
4101
|
+
}
|
|
4102
|
+
return null;
|
|
4103
|
+
}
|
|
4104
|
+
function componentName(type, depth = 0) {
|
|
4105
|
+
if (!type || depth > 3) return null;
|
|
4106
|
+
if (typeof type === "function") {
|
|
4107
|
+
const f = type;
|
|
4108
|
+
const n = typeof f.displayName === "string" && f.displayName ? f.displayName : typeof f.name === "string" ? f.name : "";
|
|
4109
|
+
return n || null;
|
|
4110
|
+
}
|
|
4111
|
+
if (typeof type === "object") {
|
|
4112
|
+
const o = type;
|
|
4113
|
+
if (typeof o.displayName === "string" && o.displayName) return o.displayName;
|
|
4114
|
+
return componentName(o.render, depth + 1) ?? componentName(o.type, depth + 1);
|
|
4115
|
+
}
|
|
4116
|
+
return null;
|
|
4117
|
+
}
|
|
4118
|
+
var isHost = (f) => typeof f.type === "string";
|
|
4119
|
+
var isTextInput = (f) => typeof f.type === "string" ? TEXT_INPUT_HOST.test(f.type) : componentName(f.type) === "TextInput";
|
|
4120
|
+
function isInteractive(props) {
|
|
4121
|
+
if (!props || typeof props !== "object") return null;
|
|
4122
|
+
if (typeof props.onPress === "function" || typeof props.onLongPress === "function") return "press";
|
|
4123
|
+
const role = props.accessibilityRole ?? props.role;
|
|
4124
|
+
return typeof role === "string" && INTERACTIVE_ROLES.has(role) ? "role" : null;
|
|
4125
|
+
}
|
|
4126
|
+
function firstHost(f) {
|
|
4127
|
+
let cur = f;
|
|
4128
|
+
for (let i = 0; cur && i < 30; i++, cur = cur.child) if (isHost(cur)) return cur;
|
|
4129
|
+
return null;
|
|
4130
|
+
}
|
|
4131
|
+
function idSelector(f) {
|
|
4132
|
+
const host = firstHost(f);
|
|
4133
|
+
const sources = [f.memoizedProps, host?.memoizedProps];
|
|
4134
|
+
for (const key of ["nativeID", "testID"]) {
|
|
4135
|
+
for (const p of sources) {
|
|
4136
|
+
const v = p && typeof p === "object" ? p[key] : void 0;
|
|
4137
|
+
if (typeof v === "string" && v) return attrSelector(key, v);
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
return null;
|
|
4141
|
+
}
|
|
4142
|
+
function pathSelector(f) {
|
|
4143
|
+
const parts = [];
|
|
4144
|
+
let cur = f;
|
|
4145
|
+
for (let i = 0; cur && parts.length < 4 && i < MAX_ANCESTORS; i++, cur = cur.return) {
|
|
4146
|
+
if (isHost(cur)) continue;
|
|
4147
|
+
const name = componentName(cur.type);
|
|
4148
|
+
if (!name || NOISE.test(name)) continue;
|
|
4149
|
+
let same = 0;
|
|
4150
|
+
let index = 0;
|
|
4151
|
+
for (let s = cur.return?.child, n = 0; s && n < 500; s = s.sibling, n++) {
|
|
4152
|
+
if (s.type !== cur.type) continue;
|
|
4153
|
+
same += 1;
|
|
4154
|
+
if (s === cur) index = same;
|
|
4155
|
+
}
|
|
4156
|
+
parts.unshift(same > 1 && index ? `${name}:nth-of-type(${index})` : name);
|
|
4157
|
+
}
|
|
4158
|
+
return parts.join(" > ").slice(0, 300) || "View";
|
|
4159
|
+
}
|
|
4160
|
+
function subtreeText(f, opts) {
|
|
4161
|
+
const out = [];
|
|
4162
|
+
let visited = 0;
|
|
4163
|
+
let masked = false;
|
|
4164
|
+
const walk = (node) => {
|
|
4165
|
+
for (let n = node; n && visited < MAX_TEXT_NODES && !masked; n = n.sibling) {
|
|
4166
|
+
visited += 1;
|
|
4167
|
+
if (opts.isMask?.(n.type)) {
|
|
4168
|
+
masked = true;
|
|
4169
|
+
return;
|
|
4170
|
+
}
|
|
4171
|
+
if (isTextInput(n)) continue;
|
|
4172
|
+
if (n.tag === HOST_TEXT && (typeof n.memoizedProps === "string" || typeof n.memoizedProps === "number")) out.push(String(n.memoizedProps));
|
|
4173
|
+
else walk(n.child);
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
4176
|
+
if (opts.isMask?.(f.type)) return { text: "", masked: true };
|
|
4177
|
+
walk(f.child);
|
|
4178
|
+
return { text: out.join(" "), masked };
|
|
4179
|
+
}
|
|
4180
|
+
function measureHost(host) {
|
|
4181
|
+
return new Promise((resolve) => {
|
|
4182
|
+
let settled = false;
|
|
4183
|
+
const done = (r) => {
|
|
4184
|
+
if (settled) return;
|
|
4185
|
+
settled = true;
|
|
4186
|
+
clearTimeout(timer);
|
|
4187
|
+
resolve(r);
|
|
4188
|
+
};
|
|
4189
|
+
const timer = setTimeout(() => done(null), 500);
|
|
4190
|
+
const cb = (x, y, width, height) => done(Number.isFinite(x) && Number.isFinite(y) && width > 0 && height > 0 ? { x, y, width, height } : null);
|
|
4191
|
+
try {
|
|
4192
|
+
const sn = host.stateNode;
|
|
4193
|
+
const pub = sn?.canonical?.publicInstance ?? sn;
|
|
4194
|
+
if (pub && typeof pub.measureInWindow === "function") return pub.measureInWindow(cb);
|
|
4195
|
+
const ui = globalThis.nativeFabricUIManager;
|
|
4196
|
+
if (sn?.node && typeof ui?.measureInWindow === "function") return ui.measureInWindow(sn.node, cb);
|
|
4197
|
+
} catch {
|
|
4198
|
+
}
|
|
4199
|
+
done(null);
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
function describe(f, masked, opts, withText) {
|
|
4203
|
+
const props = f.memoizedProps && typeof f.memoizedProps === "object" ? f.memoizedProps : {};
|
|
4204
|
+
const host = firstHost(f);
|
|
4205
|
+
const el = { selector: idSelector(f) ?? pathSelector(f), tag: componentName(f.type) ?? "View" };
|
|
4206
|
+
if (withText && !masked) {
|
|
4207
|
+
const sub = subtreeText(f, opts);
|
|
4208
|
+
if (!sub.masked) {
|
|
4209
|
+
const hostProps = host?.memoizedProps && typeof host.memoizedProps === "object" ? host.memoizedProps : {};
|
|
4210
|
+
const label = props.accessibilityLabel ?? props["aria-label"] ?? hostProps.accessibilityLabel ?? hostProps["aria-label"];
|
|
4211
|
+
const text = cleanText(sub.text) ?? (typeof label === "string" ? cleanText(label) : void 0);
|
|
4212
|
+
if (text) el.text = text;
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
if (host) el.measure = () => measureHost(host);
|
|
4216
|
+
return el;
|
|
4217
|
+
}
|
|
4218
|
+
function resolveFiber(target, opts = {}) {
|
|
4219
|
+
const none = { ignored: false, element: null, hit: null };
|
|
4220
|
+
if (!target) return none;
|
|
4221
|
+
try {
|
|
4222
|
+
let press = null;
|
|
4223
|
+
let role = null;
|
|
4224
|
+
let hitFiber = null;
|
|
4225
|
+
let stopped = false;
|
|
4226
|
+
let masked = false;
|
|
4227
|
+
for (let f = target, i = 0; f && i < MAX_ANCESTORS; f = f.return, i++) {
|
|
4228
|
+
if (opts.isIgnored?.(f.type)) return { ignored: true, element: null, hit: null };
|
|
4229
|
+
if (opts.isMask?.(f.type)) masked = true;
|
|
4230
|
+
if (stopped) continue;
|
|
4231
|
+
if (isTextInput(f)) {
|
|
4232
|
+
stopped = true;
|
|
4233
|
+
continue;
|
|
4234
|
+
}
|
|
4235
|
+
if (isHost(f)) continue;
|
|
4236
|
+
const name = componentName(f.type);
|
|
4237
|
+
if (!hitFiber && name && !NOISE.test(name)) hitFiber = f;
|
|
4238
|
+
const kind = isInteractive(f.memoizedProps);
|
|
4239
|
+
if (kind === "press") {
|
|
4240
|
+
press = f;
|
|
4241
|
+
stopped = true;
|
|
4242
|
+
} else if (kind === "role" && !role) role = f;
|
|
4243
|
+
}
|
|
4244
|
+
const element = press ?? role;
|
|
4245
|
+
return {
|
|
4246
|
+
ignored: false,
|
|
4247
|
+
element: element ? describe(element, masked, opts, true) : null,
|
|
4248
|
+
hit: hitFiber ? describe(hitFiber, masked, opts, componentName(hitFiber.type) === "Text") : null
|
|
4249
|
+
};
|
|
4250
|
+
} catch {
|
|
4251
|
+
return none;
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
function resolveTapTarget(event, opts = {}) {
|
|
4255
|
+
return resolveFiber(fiberFromEvent(event), opts);
|
|
3399
4256
|
}
|
|
3400
4257
|
|
|
3401
4258
|
// src/ui/Overlay.tsx
|
|
@@ -3919,9 +4776,9 @@ var styles3 = StyleSheet3.create({
|
|
|
3919
4776
|
});
|
|
3920
4777
|
|
|
3921
4778
|
// src/ui/Messenger.tsx
|
|
3922
|
-
import { useCallback, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
3923
|
-
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
|
|
4779
|
+
import { Fragment as Fragment2, useCallback, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef2, useState as useState3, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
4780
|
+
import { AccessibilityInfo, ActivityIndicator, Animated, Easing, Keyboard, Modal as Modal2, PanResponder, Platform as Platform2, Pressable as Pressable4, ScrollView as ScrollView3, StyleSheet as StyleSheet4, Text as Text4, TextInput, View as View4 } from "react-native";
|
|
4781
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
3925
4782
|
function useMessenger(store) {
|
|
3926
4783
|
return useSyncExternalStore2(
|
|
3927
4784
|
useCallback((cb) => store.subscribe(cb), [store]),
|
|
@@ -3949,14 +4806,89 @@ function ago(iso) {
|
|
|
3949
4806
|
if (s < 86400) return `${Math.floor(s / 3600)}h`;
|
|
3950
4807
|
return `${Math.floor(s / 86400)}d`;
|
|
3951
4808
|
}
|
|
3952
|
-
|
|
4809
|
+
var LAUNCHER_SIZE = 54;
|
|
4810
|
+
function useReduceMotion() {
|
|
4811
|
+
const [on, setOn] = useState3(false);
|
|
4812
|
+
useEffect3(() => {
|
|
4813
|
+
let live = true;
|
|
4814
|
+
AccessibilityInfo.isReduceMotionEnabled?.().then((v) => live && setOn(v)).catch(() => {
|
|
4815
|
+
});
|
|
4816
|
+
const sub = AccessibilityInfo.addEventListener?.("reduceMotionChanged", setOn);
|
|
4817
|
+
return () => {
|
|
4818
|
+
live = false;
|
|
4819
|
+
sub?.remove();
|
|
4820
|
+
};
|
|
4821
|
+
}, []);
|
|
4822
|
+
return on;
|
|
4823
|
+
}
|
|
4824
|
+
function MessengerLauncher({ store, client, palette: p, style, frame, defaultSide }) {
|
|
3953
4825
|
const snap = useMessenger(store);
|
|
4826
|
+
const reduceMotion = useReduceMotion();
|
|
4827
|
+
const [placement, setPlacement] = useState3(() => client.getMessengerLauncherPlacement());
|
|
4828
|
+
const pan = useRef2(new Animated.ValueXY()).current;
|
|
4829
|
+
const offset = useRef2({ x: 0, y: 0 });
|
|
4830
|
+
const dragging = useRef2(false);
|
|
4831
|
+
const live = useRef2({ frame, defaultSide, reduceMotion });
|
|
4832
|
+
live.current = { frame, defaultSide, reduceMotion };
|
|
4833
|
+
const { width, height, size, defaultBottom } = frame;
|
|
4834
|
+
const { top, bottom, left, right } = frame.insets;
|
|
4835
|
+
useLayoutEffect(() => {
|
|
4836
|
+
if (dragging.current) return;
|
|
4837
|
+
const f = live.current.frame;
|
|
4838
|
+
const anchor = placementPoint(f, { side: defaultSide, fraction: 0 });
|
|
4839
|
+
const at = placement ? placementPoint(f, placement) : anchor;
|
|
4840
|
+
offset.current = { x: at.x - anchor.x, y: at.y - anchor.y };
|
|
4841
|
+
pan.setValue(offset.current);
|
|
4842
|
+
}, [pan, placement, defaultSide, width, height, size, defaultBottom, top, bottom, left, right]);
|
|
4843
|
+
const responder = useMemo3(() => {
|
|
4844
|
+
const moved = (g) => Math.hypot(g.dx, g.dy) > LAUNCHER_DRAG_THRESHOLD;
|
|
4845
|
+
const anchor = () => placementPoint(live.current.frame, { side: live.current.defaultSide, fraction: 0 });
|
|
4846
|
+
let start = { x: 0, y: 0 };
|
|
4847
|
+
const follow = (g) => {
|
|
4848
|
+
const a = anchor();
|
|
4849
|
+
const at = clampToSafeArea(live.current.frame, { x: a.x + start.x + g.dx, y: a.y + start.y + g.dy });
|
|
4850
|
+
return { x: at.x - a.x, y: at.y - a.y };
|
|
4851
|
+
};
|
|
4852
|
+
const drop = (g) => {
|
|
4853
|
+
const a = anchor();
|
|
4854
|
+
const d = follow(g);
|
|
4855
|
+
const next = snapPlacement(live.current.frame, { x: a.x + d.x, y: a.y + d.y });
|
|
4856
|
+
const at = placementPoint(live.current.frame, next);
|
|
4857
|
+
const to = { x: at.x - a.x, y: at.y - a.y };
|
|
4858
|
+
offset.current = to;
|
|
4859
|
+
client.setMessengerLauncherPlacement(next);
|
|
4860
|
+
const done = ({ finished }) => {
|
|
4861
|
+
if (finished) dragging.current = false;
|
|
4862
|
+
setPlacement(next);
|
|
4863
|
+
};
|
|
4864
|
+
if (live.current.reduceMotion) {
|
|
4865
|
+
pan.setValue(to);
|
|
4866
|
+
done({ finished: true });
|
|
4867
|
+
} else Animated.timing(pan, { toValue: to, duration: 300, easing: Easing.out(Easing.cubic), useNativeDriver: true }).start(done);
|
|
4868
|
+
};
|
|
4869
|
+
return PanResponder.create({
|
|
4870
|
+
// Taps stay with the Pressable; only a real drag is taken (which cancels its press).
|
|
4871
|
+
onMoveShouldSetPanResponderCapture: (_, g) => moved(g),
|
|
4872
|
+
onMoveShouldSetPanResponder: (_, g) => moved(g),
|
|
4873
|
+
onPanResponderGrant: () => {
|
|
4874
|
+
dragging.current = true;
|
|
4875
|
+
start = offset.current;
|
|
4876
|
+
pan.stopAnimation((v) => {
|
|
4877
|
+
if (Number.isFinite(v?.x) && Number.isFinite(v?.y)) start = v;
|
|
4878
|
+
});
|
|
4879
|
+
},
|
|
4880
|
+
onPanResponderMove: (_, g) => pan.setValue(follow(g)),
|
|
4881
|
+
onPanResponderRelease: (_, g) => drop(g),
|
|
4882
|
+
onPanResponderTerminate: (_, g) => drop(g),
|
|
4883
|
+
onPanResponderTerminationRequest: () => false
|
|
4884
|
+
});
|
|
4885
|
+
}, [client, pan]);
|
|
3954
4886
|
if (snap.open) return null;
|
|
3955
4887
|
const label = snap.unread ? `Open ${snap.config.name}, ${snap.unread} unread` : `Open ${snap.config.name}`;
|
|
3956
|
-
return /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher,
|
|
4888
|
+
return /* @__PURE__ */ jsx5(Animated.View, { ...responder.panHandlers, style: [styles4.launcherWrap, style, { transform: pan.getTranslateTransform() }], children: /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => !dragging.current && store.open(), style: ({ pressed }) => [styles4.launcher, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
|
|
3957
4889
|
/* @__PURE__ */ jsx5(Text4, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
|
|
3958
4890
|
snap.unread ? /* @__PURE__ */ jsx5(View4, { style: styles4.badge, children: /* @__PURE__ */ jsx5(Text4, { style: styles4.badgeText, children: snap.unread }) }) : null
|
|
3959
|
-
] });
|
|
4891
|
+
] }) });
|
|
3960
4892
|
}
|
|
3961
4893
|
function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
|
|
3962
4894
|
const snap = useMessenger(store);
|
|
@@ -4155,25 +5087,41 @@ function Help({ store, client, palette: p }) {
|
|
|
4155
5087
|
] }, title);
|
|
4156
5088
|
const audiences = client.getAudienceIds();
|
|
4157
5089
|
const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
|
|
4158
|
-
const
|
|
5090
|
+
const modules = client.getHelpModules();
|
|
5091
|
+
const searchOn = modules.includes("search");
|
|
5092
|
+
const listed = searchOn ? results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences)) : void 0;
|
|
5093
|
+
const checklists = (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist");
|
|
5094
|
+
const renderModule = (id) => {
|
|
5095
|
+
switch (id) {
|
|
5096
|
+
case "guides":
|
|
5097
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null;
|
|
5098
|
+
case "checklists":
|
|
5099
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u2611"))) : null;
|
|
5100
|
+
case "articles":
|
|
5101
|
+
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
5102
|
+
!articles ? /* @__PURE__ */ jsx5(ActivityIndicator, { color: p.muted }) : null,
|
|
5103
|
+
sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a)))))
|
|
5104
|
+
] });
|
|
5105
|
+
case "faqs":
|
|
5106
|
+
return rc?.faqs.length ? section(
|
|
5107
|
+
"FAQ",
|
|
5108
|
+
rc.faqs.map((f) => /* @__PURE__ */ jsxs3(View4, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
|
|
5109
|
+
/* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
|
|
5110
|
+
/* @__PURE__ */ jsx5(Text4, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
|
|
5111
|
+
/* @__PURE__ */ jsx5(Text4, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
5112
|
+
] }),
|
|
5113
|
+
faq === f.id ? /* @__PURE__ */ jsx5(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
5114
|
+
] }, f.id))
|
|
5115
|
+
) : null;
|
|
5116
|
+
case "links":
|
|
5117
|
+
return rc?.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
5118
|
+
default:
|
|
5119
|
+
return null;
|
|
5120
|
+
}
|
|
5121
|
+
};
|
|
4159
5122
|
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
|
-
] })
|
|
5123
|
+
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,
|
|
5124
|
+
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
5125
|
] });
|
|
4178
5126
|
}
|
|
4179
5127
|
function Article({ article, palette: p, client }) {
|
|
@@ -4185,7 +5133,8 @@ function Article({ article, palette: p, client }) {
|
|
|
4185
5133
|
}
|
|
4186
5134
|
var styles4 = StyleSheet4.create({
|
|
4187
5135
|
flex: { flex: 1 },
|
|
4188
|
-
|
|
5136
|
+
launcherWrap: { position: "absolute", width: LAUNCHER_SIZE, height: LAUNCHER_SIZE, elevation: 8 },
|
|
5137
|
+
launcher: { width: LAUNCHER_SIZE, height: LAUNCHER_SIZE, borderRadius: 27, alignItems: "center", justifyContent: "center", elevation: 8, shadowColor: "#000", shadowOpacity: 0.22, shadowRadius: 12, shadowOffset: { width: 0, height: 4 } },
|
|
4189
5138
|
launcherIcon: { fontSize: 24 },
|
|
4190
5139
|
badge: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center", paddingHorizontal: 5, borderWidth: 2, borderColor: "#ffffff" },
|
|
4191
5140
|
badgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" },
|
|
@@ -4240,9 +5189,9 @@ var styles4 = StyleSheet4.create({
|
|
|
4240
5189
|
});
|
|
4241
5190
|
|
|
4242
5191
|
// src/ui/ResourceCenter.tsx
|
|
4243
|
-
import { useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
5192
|
+
import { Fragment as Fragment4, useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
4244
5193
|
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
|
|
5194
|
+
import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
4246
5195
|
function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
4247
5196
|
const rc = client.getResourceCenter();
|
|
4248
5197
|
const [query, setQuery] = useState4("");
|
|
@@ -4276,6 +5225,8 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4276
5225
|
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
5226
|
if (!rc) return null;
|
|
4278
5227
|
const close = () => client.closeResourceCenter();
|
|
5228
|
+
const modules = client.getResourceCenterModules();
|
|
5229
|
+
const announcements = client.getAnnouncements();
|
|
4279
5230
|
const openArticle = (a) => {
|
|
4280
5231
|
if (a.externalUrl) return client.navigate(a.externalUrl);
|
|
4281
5232
|
client.articleViewed(a);
|
|
@@ -4294,7 +5245,51 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4294
5245
|
] }, title);
|
|
4295
5246
|
const audiences = client.getAudienceIds();
|
|
4296
5247
|
const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
|
|
4297
|
-
const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
|
|
5248
|
+
const listed = modules.includes("search") ? results?.filter((a) => isArticleListed(rc.collections, a, audiences)) : void 0;
|
|
5249
|
+
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);
|
|
5250
|
+
const announcement2 = (a) => {
|
|
5251
|
+
const linked = a.articleId ? rc.articles.find((x) => x.id === a.articleId) : void 0;
|
|
5252
|
+
const isNew = client.isAnnouncementNew(a);
|
|
5253
|
+
return /* @__PURE__ */ jsxs4(View5, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5254
|
+
/* @__PURE__ */ jsxs4(View5, { style: styles5.faqHead, children: [
|
|
5255
|
+
/* @__PURE__ */ jsx6(Text5, { accessibilityRole: "header", style: [styles5.rowTitle, styles5.flex, { color: p.text, fontFamily: p.fontFamily }], children: a.title }),
|
|
5256
|
+
isNew ? /* @__PURE__ */ jsx6(Text5, { style: [styles5.badge, { color: p.onPrimary, backgroundColor: p.primary }], children: "NEW" }) : null
|
|
5257
|
+
] }),
|
|
5258
|
+
a.body ? /* @__PURE__ */ jsx6(RichText, { html: a.body, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null,
|
|
5259
|
+
a.guideId || linked || a.url ? /* @__PURE__ */ jsxs4(View5, { style: styles5.links, children: [
|
|
5260
|
+
a.guideId ? link("Show me", () => (close(), client.startGuide(a.guideId))) : null,
|
|
5261
|
+
linked ? link("Read more", () => openArticle(linked)) : null,
|
|
5262
|
+
a.url ? link(a.linkLabel || "Learn more", () => client.navigate(a.url)) : null
|
|
5263
|
+
] }) : null
|
|
5264
|
+
] }, a.id);
|
|
5265
|
+
};
|
|
5266
|
+
const renderModule = (id) => {
|
|
5267
|
+
switch (id) {
|
|
5268
|
+
case "announcements":
|
|
5269
|
+
return announcements.length ? section("What's new", announcements.map(announcement2)) : null;
|
|
5270
|
+
case "guides":
|
|
5271
|
+
return recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null;
|
|
5272
|
+
case "checklists":
|
|
5273
|
+
return checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null;
|
|
5274
|
+
case "articles":
|
|
5275
|
+
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)))));
|
|
5276
|
+
case "faqs":
|
|
5277
|
+
return rc.faqs.length ? section(
|
|
5278
|
+
"FAQ",
|
|
5279
|
+
rc.faqs.map((f) => /* @__PURE__ */ jsxs4(View5, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
|
|
5280
|
+
/* @__PURE__ */ jsxs4(Pressable5, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
|
|
5281
|
+
/* @__PURE__ */ jsx6(Text5, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
|
|
5282
|
+
/* @__PURE__ */ jsx6(Text5, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
|
|
5283
|
+
] }),
|
|
5284
|
+
faq === f.id ? /* @__PURE__ */ jsx6(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
|
|
5285
|
+
] }, f.id))
|
|
5286
|
+
) : null;
|
|
5287
|
+
case "links":
|
|
5288
|
+
return rc.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null;
|
|
5289
|
+
default:
|
|
5290
|
+
return null;
|
|
5291
|
+
}
|
|
5292
|
+
};
|
|
4298
5293
|
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
5294
|
/* @__PURE__ */ jsxs4(View5, { style: [styles5.header, { borderColor: p.border }], children: [
|
|
4300
5295
|
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 +5300,7 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4305
5300
|
/* @__PURE__ */ jsx6(Text5, { accessibilityRole: "header", style: [styles5.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
|
|
4306
5301
|
/* @__PURE__ */ jsx6(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
|
|
4307
5302
|
] }) : /* @__PURE__ */ jsxs4(ScrollView4, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], keyboardShouldPersistTaps: "handled", children: [
|
|
4308
|
-
/* @__PURE__ */ jsx6(
|
|
5303
|
+
modules.includes("search") ? /* @__PURE__ */ jsx6(
|
|
4309
5304
|
TextInput2,
|
|
4310
5305
|
{
|
|
4311
5306
|
value: query,
|
|
@@ -4317,26 +5312,11 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
|
|
|
4317
5312
|
returnKeyType: "search",
|
|
4318
5313
|
style: [styles5.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }]
|
|
4319
5314
|
}
|
|
4320
|
-
),
|
|
5315
|
+
) : null,
|
|
4321
5316
|
listed ? section(
|
|
4322
5317
|
`Results for \u201C${query.trim()}\u201D`,
|
|
4323
5318
|
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
|
-
] })
|
|
5319
|
+
) : /* @__PURE__ */ jsx6(Fragment5, { children: modules.filter((id) => id !== "search").map((id) => /* @__PURE__ */ jsx6(Fragment4, { children: renderModule(id) }, id)) })
|
|
4340
5320
|
] })
|
|
4341
5321
|
] }) });
|
|
4342
5322
|
}
|
|
@@ -4356,12 +5336,15 @@ var styles5 = StyleSheet5.create({
|
|
|
4356
5336
|
chev: { fontSize: 18 },
|
|
4357
5337
|
faq: { flexDirection: "column", alignItems: "stretch" },
|
|
4358
5338
|
faqHead: { flexDirection: "row", alignItems: "center", gap: 10 },
|
|
4359
|
-
articleTitle: { fontSize: 22, fontWeight: "700" }
|
|
5339
|
+
articleTitle: { fontSize: 22, fontWeight: "700" },
|
|
5340
|
+
badge: { fontSize: 10, fontWeight: "800", letterSpacing: 0.5, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 6, overflow: "hidden" },
|
|
5341
|
+
links: { flexDirection: "row", flexWrap: "wrap", gap: 16, marginTop: 4 },
|
|
5342
|
+
link: { fontSize: 14, fontWeight: "600" }
|
|
4360
5343
|
});
|
|
4361
5344
|
|
|
4362
5345
|
// src/ui/StepLayer.tsx
|
|
4363
5346
|
import { useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
|
|
4364
|
-
import { AccessibilityInfo, Animated, Easing, Pressable as Pressable6, StyleSheet as StyleSheet7, View as View7 } from "react-native";
|
|
5347
|
+
import { AccessibilityInfo as AccessibilityInfo2, Animated as Animated2, Easing as Easing2, Pressable as Pressable6, StyleSheet as StyleSheet7, View as View7 } from "react-native";
|
|
4365
5348
|
|
|
4366
5349
|
// src/core/position.ts
|
|
4367
5350
|
var MARGIN = 12;
|
|
@@ -4384,20 +5367,20 @@ function positionCard(target, size, viewport, insets, placement, offset) {
|
|
|
4384
5367
|
const order = rawSide === "auto" ? ["bottom", "top"] : [rawSide, opposite[rawSide], "bottom", "top"];
|
|
4385
5368
|
const space = { top: target.y - insets.top, bottom: viewport.height - insets.bottom - (target.y + target.height) };
|
|
4386
5369
|
const side = order.find(fits) ?? (space.bottom >= space.top ? "bottom" : "top");
|
|
4387
|
-
const
|
|
5370
|
+
const clamp3 = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
4388
5371
|
if (side === "top" || side === "bottom") {
|
|
4389
5372
|
const cx = target.x + target.width / 2;
|
|
4390
5373
|
let x2 = align === "start" ? target.x : align === "end" ? target.x + target.width - size.width : cx - size.width / 2;
|
|
4391
|
-
x2 =
|
|
5374
|
+
x2 = clamp3(x2, insets.left + MARGIN, Math.max(insets.left + MARGIN, viewport.width - insets.right - MARGIN - size.width));
|
|
4392
5375
|
let y2 = side === "bottom" ? target.y + target.height + offset : target.y - offset - size.height;
|
|
4393
|
-
y2 =
|
|
4394
|
-
return { x: x2, y: y2, side, arrow:
|
|
5376
|
+
y2 = clamp3(y2, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
|
|
5377
|
+
return { x: x2, y: y2, side, arrow: clamp3(cx - x2, ARROW_INSET, size.width - ARROW_INSET) };
|
|
4395
5378
|
}
|
|
4396
5379
|
const cy = target.y + target.height / 2;
|
|
4397
5380
|
let y = align === "start" ? target.y : align === "end" ? target.y + target.height - size.height : cy - size.height / 2;
|
|
4398
|
-
y =
|
|
5381
|
+
y = clamp3(y, insets.top + MARGIN, Math.max(insets.top + MARGIN, viewport.height - insets.bottom - MARGIN - size.height));
|
|
4399
5382
|
const x = side === "right" ? target.x + target.width + offset : target.x - offset - size.width;
|
|
4400
|
-
return { x, y, side, arrow:
|
|
5383
|
+
return { x, y, side, arrow: clamp3(cy - y, ARROW_INSET, size.height - ARROW_INSET) };
|
|
4401
5384
|
}
|
|
4402
5385
|
|
|
4403
5386
|
// src/ui/StepCard.tsx
|
|
@@ -4410,7 +5393,19 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
|
|
|
4410
5393
|
const showProgress = step.showProgress && view.total > 1;
|
|
4411
5394
|
const onLink = (url) => client.navigate(url);
|
|
4412
5395
|
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 ?
|
|
5396
|
+
step.imageUrl ? (
|
|
5397
|
+
// An empty description marks the image as decorative: screen readers skip it.
|
|
5398
|
+
/* @__PURE__ */ jsx7(
|
|
5399
|
+
Image2,
|
|
5400
|
+
{
|
|
5401
|
+
source: { uri: step.imageUrl },
|
|
5402
|
+
style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }],
|
|
5403
|
+
resizeMode: "cover",
|
|
5404
|
+
accessibilityIgnoresInvertColors: true,
|
|
5405
|
+
...step.imageAlt?.trim() ? { accessible: true, accessibilityRole: "image", accessibilityLabel: step.imageAlt.trim() } : { accessible: false, importantForAccessibility: "no" }
|
|
5406
|
+
}
|
|
5407
|
+
)
|
|
5408
|
+
) : null,
|
|
4414
5409
|
step.title ? /* @__PURE__ */ jsxs5(View6, { style: styles6.header, children: [
|
|
4415
5410
|
/* @__PURE__ */ jsx7(Text6, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
|
|
4416
5411
|
step.showClose ? /* @__PURE__ */ jsx7(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
|
|
@@ -4481,10 +5476,10 @@ function useTargetRect(client, targetId, origin) {
|
|
|
4481
5476
|
return rect;
|
|
4482
5477
|
}
|
|
4483
5478
|
function useFadeIn(key) {
|
|
4484
|
-
const opacity = useRef3(new
|
|
5479
|
+
const opacity = useRef3(new Animated2.Value(0)).current;
|
|
4485
5480
|
useEffect5(() => {
|
|
4486
5481
|
opacity.setValue(0);
|
|
4487
|
-
|
|
5482
|
+
Animated2.timing(opacity, { toValue: 1, duration: 160, easing: Easing2.out(Easing2.quad), useNativeDriver: true }).start();
|
|
4488
5483
|
}, [key, opacity]);
|
|
4489
5484
|
return opacity;
|
|
4490
5485
|
}
|
|
@@ -4496,22 +5491,22 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
4496
5491
|
const cardWidth = Math.min(viewport.width - 32, step.width ?? 340);
|
|
4497
5492
|
useEffect5(() => {
|
|
4498
5493
|
if (step.type === "hotspot" && !view.expanded) return;
|
|
4499
|
-
|
|
5494
|
+
AccessibilityInfo2.announceForAccessibility?.(announcement(view));
|
|
4500
5495
|
}, [view.key, view.expanded]);
|
|
4501
5496
|
const backdrop = (children) => /* @__PURE__ */ jsx8(Pressable6, { accessible: false, style: [StyleSheet7.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
|
|
4502
5497
|
const anchored = !view.asModal && ["tooltip", "popover", "spotlight", "hotspot"].includes(step.type);
|
|
4503
5498
|
if (!anchored && step.type === "banner") {
|
|
4504
5499
|
const top = step.position.startsWith("top");
|
|
4505
|
-
return /* @__PURE__ */ jsx8(
|
|
5500
|
+
return /* @__PURE__ */ jsx8(Animated2.View, { pointerEvents: "box-none", style: [styles7.banner, top ? { top: insets.top + 8 } : { bottom: insets.bottom + 8 }, { left: insets.left + 8, right: insets.right + 8, opacity }], children: /* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width: viewport.width - insets.left - insets.right - 16, compact: true }) });
|
|
4506
5501
|
}
|
|
4507
5502
|
if (!anchored && step.type === "slideout") {
|
|
4508
5503
|
const wide = viewport.width >= 600;
|
|
4509
5504
|
const right = !step.position.endsWith("left");
|
|
4510
5505
|
const width = wide ? Math.min(380, cardWidth) : viewport.width - insets.left - insets.right - 24;
|
|
4511
|
-
return /* @__PURE__ */ jsx8(
|
|
5506
|
+
return /* @__PURE__ */ jsx8(Animated2.View, { pointerEvents: "box-none", style: [styles7.abs, { bottom: insets.bottom + 12, opacity }, wide ? right ? { right: insets.right + 16 } : { left: insets.left + 16 } : { left: insets.left + 12 }], children: /* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width }) });
|
|
4512
5507
|
}
|
|
4513
5508
|
if (!anchored) {
|
|
4514
|
-
return /* @__PURE__ */ jsxs6(
|
|
5509
|
+
return /* @__PURE__ */ jsxs6(Animated2.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, styles7.center, { opacity }], children: [
|
|
4515
5510
|
step.showOverlay ? backdrop() : null,
|
|
4516
5511
|
/* @__PURE__ */ jsx8(StepCard, { view, client, palette: p, width: Math.min(viewport.width - 40, step.width ?? 360) })
|
|
4517
5512
|
] });
|
|
@@ -4536,7 +5531,7 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
4536
5531
|
[hole.x, hole.y + hole.height - r, -r, -2 * r],
|
|
4537
5532
|
[hole.x + hole.width - r, hole.y + hole.height - r, -2 * r, -2 * r]
|
|
4538
5533
|
];
|
|
4539
|
-
return /* @__PURE__ */ jsxs6(
|
|
5534
|
+
return /* @__PURE__ */ jsxs6(Animated2.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
|
|
4540
5535
|
/* @__PURE__ */ jsx8(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
|
|
4541
5536
|
/* @__PURE__ */ jsx8(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
|
|
4542
5537
|
/* @__PURE__ */ jsx8(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, width: Math.max(0, hole.x), top: hole.y, height: hole.height }] }),
|
|
@@ -4546,7 +5541,7 @@ function StepLayer({ view, client, palette: p, geometry }) {
|
|
|
4546
5541
|
/* @__PURE__ */ jsx8(Anchored, { view, client, palette: p, geometry, rect: hole, width: cardWidth })
|
|
4547
5542
|
] });
|
|
4548
5543
|
}
|
|
4549
|
-
return /* @__PURE__ */ jsxs6(
|
|
5544
|
+
return /* @__PURE__ */ jsxs6(Animated2.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
|
|
4550
5545
|
step.showOverlay ? backdrop() : null,
|
|
4551
5546
|
/* @__PURE__ */ jsx8(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth })
|
|
4552
5547
|
] });
|
|
@@ -4568,11 +5563,11 @@ function Arrow({ side, at, color, border }) {
|
|
|
4568
5563
|
return /* @__PURE__ */ jsx8(View7, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
|
|
4569
5564
|
}
|
|
4570
5565
|
function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
4571
|
-
const scale = useRef3(new
|
|
5566
|
+
const scale = useRef3(new Animated2.Value(0)).current;
|
|
4572
5567
|
const [reduceMotion, setReduceMotion] = useState5(false);
|
|
4573
5568
|
useEffect5(() => {
|
|
4574
5569
|
let alive = true;
|
|
4575
|
-
|
|
5570
|
+
AccessibilityInfo2.isReduceMotionEnabled?.().then((v) => alive && setReduceMotion(v), () => {
|
|
4576
5571
|
});
|
|
4577
5572
|
return () => {
|
|
4578
5573
|
alive = false;
|
|
@@ -4580,13 +5575,13 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
|
|
|
4580
5575
|
}, []);
|
|
4581
5576
|
useEffect5(() => {
|
|
4582
5577
|
if (!pulse || reduceMotion) return;
|
|
4583
|
-
const loop =
|
|
5578
|
+
const loop = Animated2.loop(Animated2.timing(scale, { toValue: 1, duration: 1400, easing: Easing2.out(Easing2.ease), useNativeDriver: true }));
|
|
4584
5579
|
loop.start();
|
|
4585
5580
|
return () => loop.stop();
|
|
4586
5581
|
}, [pulse, reduceMotion, scale]);
|
|
4587
5582
|
return /* @__PURE__ */ jsxs6(Pressable6, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size, height: size }], children: [
|
|
4588
5583
|
pulse && !reduceMotion ? /* @__PURE__ */ jsx8(
|
|
4589
|
-
|
|
5584
|
+
Animated2.View,
|
|
4590
5585
|
{
|
|
4591
5586
|
pointerEvents: "none",
|
|
4592
5587
|
style: [styles7.abs, { width: size, height: size, borderRadius: size / 2, backgroundColor: color, opacity: scale.interpolate({ inputRange: [0, 1], outputRange: [0.55, 0] }), transform: [{ scale: scale.interpolate({ inputRange: [0, 1], outputRange: [1, 2.6] }) }] }]
|
|
@@ -4605,7 +5600,7 @@ var styles7 = StyleSheet7.create({
|
|
|
4605
5600
|
// src/ui/Survey.tsx
|
|
4606
5601
|
import { useEffect as useEffect6, useState as useState6 } from "react";
|
|
4607
5602
|
import {
|
|
4608
|
-
AccessibilityInfo as
|
|
5603
|
+
AccessibilityInfo as AccessibilityInfo3,
|
|
4609
5604
|
KeyboardAvoidingView,
|
|
4610
5605
|
Platform as Platform4,
|
|
4611
5606
|
Pressable as Pressable7,
|
|
@@ -4628,7 +5623,7 @@ function Survey({
|
|
|
4628
5623
|
useEffect6(() => {
|
|
4629
5624
|
setAnswers({});
|
|
4630
5625
|
setError("");
|
|
4631
|
-
|
|
5626
|
+
AccessibilityInfo3.announceForAccessibility?.(
|
|
4632
5627
|
`Survey: ${config?.questions[0]?.title ?? view.guide.name}`
|
|
4633
5628
|
);
|
|
4634
5629
|
}, [view.key]);
|
|
@@ -5008,21 +6003,36 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5008
6003
|
const basePalette = palette(ui.theme, scheme);
|
|
5009
6004
|
const messenger = ui.messenger ? client.getMessenger() : null;
|
|
5010
6005
|
const showRcLauncher = !messenger && !!client.options.showResourceCenterLauncher && !!client.getResourceCenter();
|
|
6006
|
+
const unread = showRcLauncher ? client.getUnreadAnnouncements() : 0;
|
|
5011
6007
|
const messengerLeft = messenger?.getSnapshot().config.launcher.position === "bottom-left";
|
|
5012
6008
|
const checklistOnSameSide = ui.checklists.some((c) => c.guide.config.checklist?.position === "bottom-left" === messengerLeft) && client.options.showChecklistLauncher !== false;
|
|
6009
|
+
const messengerBottom = insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0);
|
|
5013
6010
|
return /* @__PURE__ */ jsxs8(View9, { ref, collapsable: false, pointerEvents: "box-none", style: StyleSheet9.absoluteFill, onLayout: measureOrigin, children: [
|
|
5014
6011
|
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__ */
|
|
6012
|
+
showRcLauncher ? /* @__PURE__ */ jsxs8(
|
|
5016
6013
|
Pressable8,
|
|
5017
6014
|
{
|
|
5018
6015
|
accessibilityRole: "button",
|
|
5019
|
-
accessibilityLabel: client
|
|
6016
|
+
accessibilityLabel: rcLauncherLabel(client),
|
|
5020
6017
|
onPress: () => client.openResourceCenter(),
|
|
5021
6018
|
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:
|
|
6019
|
+
children: [
|
|
6020
|
+
/* @__PURE__ */ jsx10(Text8, { style: [styles9.rcText, { color: basePalette.onPrimary }], children: "?" }),
|
|
6021
|
+
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
|
|
6022
|
+
]
|
|
6023
|
+
}
|
|
6024
|
+
) : null,
|
|
6025
|
+
messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ jsx10(
|
|
6026
|
+
MessengerLauncher,
|
|
6027
|
+
{
|
|
6028
|
+
store: messenger,
|
|
6029
|
+
client,
|
|
6030
|
+
palette: basePalette,
|
|
6031
|
+
style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: messengerBottom }],
|
|
6032
|
+
frame: { width, height, insets, size: LAUNCHER_SIZE, defaultBottom: messengerBottom },
|
|
6033
|
+
defaultSide: messengerLeft ? "left" : "right"
|
|
5023
6034
|
}
|
|
5024
6035
|
) : null,
|
|
5025
|
-
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,
|
|
5026
6036
|
ui.survey ? /* @__PURE__ */ jsx10(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
|
|
5027
6037
|
ui.steps.map((s) => /* @__PURE__ */ jsx10(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
|
|
5028
6038
|
messenger ? /* @__PURE__ */ jsx10(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ jsx10(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, topInset: insets.top, bottomInset: insets.bottom })
|
|
@@ -5030,27 +6040,75 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
|
|
|
5030
6040
|
}
|
|
5031
6041
|
var styles9 = StyleSheet9.create({
|
|
5032
6042
|
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" }
|
|
6043
|
+
rcText: { fontSize: 22, fontWeight: "800" },
|
|
6044
|
+
rcBadge: { position: "absolute", top: -4, right: -4, minWidth: 20, height: 20, paddingHorizontal: 5, borderRadius: 10, borderWidth: 2, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center" },
|
|
6045
|
+
rcBadgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" }
|
|
5034
6046
|
});
|
|
6047
|
+
function rcLauncherLabel(client) {
|
|
6048
|
+
const label = client.getResourceCenter()?.launcher.label || "Help";
|
|
6049
|
+
const unread = client.getUnreadAnnouncements();
|
|
6050
|
+
return unread ? `${label} (${unread} new update${unread === 1 ? "" : "s"})` : label;
|
|
6051
|
+
}
|
|
5035
6052
|
|
|
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";
|
|
6053
|
+
// src/ui/Autocapture.tsx
|
|
5039
6054
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
6055
|
+
function GuidelyMask({ children, ...rest }) {
|
|
6056
|
+
return /* @__PURE__ */ jsx11(View10, { ...rest, children });
|
|
6057
|
+
}
|
|
6058
|
+
var RESOLVE = {
|
|
6059
|
+
isIgnored: (type) => type === GuidelyOverlay,
|
|
6060
|
+
isMask: (type) => type === GuidelyMask
|
|
6061
|
+
};
|
|
6062
|
+
function useTapObserver(client) {
|
|
6063
|
+
const clientRef = useRef5(client);
|
|
6064
|
+
clientRef.current = client;
|
|
6065
|
+
return useMemo5(() => {
|
|
6066
|
+
const tracker = new TapTracker();
|
|
6067
|
+
const guard = (fn) => (e) => {
|
|
6068
|
+
try {
|
|
6069
|
+
fn(e);
|
|
6070
|
+
} catch {
|
|
6071
|
+
tracker.cancel();
|
|
6072
|
+
}
|
|
6073
|
+
};
|
|
6074
|
+
return {
|
|
6075
|
+
onTouchStartCapture: guard((e) => {
|
|
6076
|
+
const n = e.nativeEvent;
|
|
6077
|
+
if (!clientRef.current?.isTapCaptureOn()) return tracker.cancel();
|
|
6078
|
+
tracker.begin(n.pageX, n.pageY, Date.now(), Array.isArray(n.touches) ? n.touches.length : 1, n.identifier);
|
|
6079
|
+
}),
|
|
6080
|
+
onTouchMoveCapture: guard((e) => tracker.move(e.nativeEvent.pageX, e.nativeEvent.pageY, e.nativeEvent.identifier)),
|
|
6081
|
+
onTouchEndCapture: guard((e) => {
|
|
6082
|
+
const n = e.nativeEvent;
|
|
6083
|
+
const tap = tracker.end(n.pageX, n.pageY, Date.now(), n.identifier);
|
|
6084
|
+
const c = clientRef.current;
|
|
6085
|
+
if (!tap || !c) return;
|
|
6086
|
+
const r = resolveTapTarget(e, RESOLVE);
|
|
6087
|
+
if (!r.ignored) c.recordTap({ x: tap.x, y: tap.y, t: Date.now(), element: r.element, hit: r.hit });
|
|
6088
|
+
}),
|
|
6089
|
+
onTouchCancelCapture: guard(() => tracker.cancel())
|
|
6090
|
+
};
|
|
6091
|
+
}, []);
|
|
6092
|
+
}
|
|
6093
|
+
|
|
6094
|
+
// src/ui/Target.tsx
|
|
6095
|
+
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect8, useRef as useRef6 } from "react";
|
|
6096
|
+
import { View as View11 } from "react-native";
|
|
6097
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
5040
6098
|
var GuidelyContext = createContext2(null);
|
|
5041
6099
|
var GuidelyScreenContext = createContext2(null);
|
|
5042
6100
|
function GuidelyScreenScope({ name, children }) {
|
|
5043
|
-
return /* @__PURE__ */
|
|
6101
|
+
return /* @__PURE__ */ jsx12(GuidelyScreenContext.Provider, { value: name, children });
|
|
5044
6102
|
}
|
|
5045
6103
|
function guidelyScreenLayout({ route, children }) {
|
|
5046
|
-
return /* @__PURE__ */
|
|
6104
|
+
return /* @__PURE__ */ jsx12(GuidelyScreenScope, { name: route.name, children });
|
|
5047
6105
|
}
|
|
5048
|
-
var
|
|
6106
|
+
var TAP_SLOP2 = 10;
|
|
5049
6107
|
function useGuidelyTarget(id) {
|
|
5050
6108
|
const client = useContext2(GuidelyContext);
|
|
5051
6109
|
const screen = useContext2(GuidelyScreenContext);
|
|
5052
|
-
const ref =
|
|
5053
|
-
const start =
|
|
6110
|
+
const ref = useRef6(null);
|
|
6111
|
+
const start = useRef6(null);
|
|
5054
6112
|
useEffect8(() => {
|
|
5055
6113
|
if (!client || !id) return;
|
|
5056
6114
|
return client.targets.register(id, {
|
|
@@ -5070,7 +6128,7 @@ function useGuidelyTarget(id) {
|
|
|
5070
6128
|
(e) => {
|
|
5071
6129
|
const s = start.current;
|
|
5072
6130
|
start.current = null;
|
|
5073
|
-
if (s && Math.abs(e.nativeEvent.pageX - s.x) <
|
|
6131
|
+
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
6132
|
},
|
|
5075
6133
|
[client, id]
|
|
5076
6134
|
);
|
|
@@ -5078,8 +6136,8 @@ function useGuidelyTarget(id) {
|
|
|
5078
6136
|
}
|
|
5079
6137
|
function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...rest }) {
|
|
5080
6138
|
const t = useGuidelyTarget(id);
|
|
5081
|
-
return /* @__PURE__ */
|
|
5082
|
-
|
|
6139
|
+
return /* @__PURE__ */ jsx12(
|
|
6140
|
+
View11,
|
|
5083
6141
|
{
|
|
5084
6142
|
...rest,
|
|
5085
6143
|
ref: t.ref,
|
|
@@ -5103,7 +6161,7 @@ function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...re
|
|
|
5103
6161
|
|
|
5104
6162
|
// src/index.tsx
|
|
5105
6163
|
import { createFlagsClient as createFlagsClient3, InMemoryStickyBucketStore } from "@guidelytech/flags";
|
|
5106
|
-
import { jsx as
|
|
6164
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
5107
6165
|
function deviceLocale() {
|
|
5108
6166
|
try {
|
|
5109
6167
|
return Intl.DateTimeFormat().resolvedOptions().locale;
|
|
@@ -5121,21 +6179,22 @@ function defaultBridge(appVersion) {
|
|
|
5121
6179
|
appVersion
|
|
5122
6180
|
},
|
|
5123
6181
|
openExternal: (url) => void Linking.openURL(url).catch(() => {
|
|
5124
|
-
})
|
|
6182
|
+
}),
|
|
6183
|
+
windowSize: () => Dimensions.get("window")
|
|
5125
6184
|
};
|
|
5126
6185
|
}
|
|
5127
6186
|
function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
|
|
5128
6187
|
const [client, setClient] = useState8(null);
|
|
5129
|
-
const optionsRef =
|
|
6188
|
+
const optionsRef = useRef7(options);
|
|
5130
6189
|
optionsRef.current = options;
|
|
5131
|
-
const userRef =
|
|
6190
|
+
const userRef = useRef7(user);
|
|
5132
6191
|
userRef.current = user;
|
|
5133
6192
|
const flagsOpts = options.flags === false ? null : options.flags ?? {};
|
|
5134
6193
|
const payload = flagsPayload ?? flagsOpts?.payload ?? null;
|
|
5135
|
-
const payloadRef =
|
|
6194
|
+
const payloadRef = useRef7(payload);
|
|
5136
6195
|
payloadRef.current = payload;
|
|
5137
|
-
const appAttributes =
|
|
5138
|
-
const appAttributesRef =
|
|
6196
|
+
const appAttributes = useMemo6(() => ({ ...flagsOpts?.attributes ?? {}, ...flagAttributes ?? {} }), [stableStringify3(flagsOpts?.attributes ?? {}), stableStringify3(flagAttributes ?? {})]);
|
|
6197
|
+
const appAttributesRef = useRef7(appAttributes);
|
|
5139
6198
|
appAttributesRef.current = appAttributes;
|
|
5140
6199
|
const { projectId, environment, apiUrl, storage } = options;
|
|
5141
6200
|
useEffect9(() => {
|
|
@@ -5172,7 +6231,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5172
6231
|
};
|
|
5173
6232
|
}, [projectId, environment, apiUrl, storage]);
|
|
5174
6233
|
const userKey = user ? JSON.stringify([user.id, user.traits ?? null, user.account ?? null]) : "";
|
|
5175
|
-
const hadUser =
|
|
6234
|
+
const hadUser = useRef7(false);
|
|
5176
6235
|
useEffect9(() => {
|
|
5177
6236
|
if (!client) return;
|
|
5178
6237
|
if (user && user.id) {
|
|
@@ -5186,13 +6245,18 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPay
|
|
|
5186
6245
|
useEffect9(() => {
|
|
5187
6246
|
client?.flags.setAttributes(appAttributes);
|
|
5188
6247
|
}, [client, appAttributes]);
|
|
6248
|
+
useEffect9(() => {
|
|
6249
|
+
client?.setLocale(options.locale ?? null);
|
|
6250
|
+
}, [client, options.locale]);
|
|
5189
6251
|
const bootstrap = useBootstrap(payload, { ...user?.id ? { ...user.traits ?? {}, id: user.id } : {}, ...appAttributes });
|
|
5190
|
-
const flags =
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
6252
|
+
const flags = useMemo6(() => ({ client: client?.flags ?? null, bootstrap }), [client, bootstrap]);
|
|
6253
|
+
const tapObserver = useTapObserver(client);
|
|
6254
|
+
return /* @__PURE__ */ jsx13(GuidelyContext.Provider, { value: client, children: /* @__PURE__ */ jsxs9(FlagsContext.Provider, { value: flags, children: [
|
|
6255
|
+
options.autocapture === false ? children : /* @__PURE__ */ jsx13(View12, { style: styles10.root, ...tapObserver, children }),
|
|
6256
|
+
client ? /* @__PURE__ */ jsx13(GuidelyOverlay, { client, insets, launcherOffset }) : null
|
|
5194
6257
|
] }) });
|
|
5195
6258
|
}
|
|
6259
|
+
var styles10 = StyleSheet10.create({ root: { flex: 1 } });
|
|
5196
6260
|
var NOOP_RESULTS = {
|
|
5197
6261
|
init: Promise.resolve(),
|
|
5198
6262
|
ready: Promise.resolve(),
|
|
@@ -5264,7 +6328,7 @@ function useGuidelyIdentify(userId, traits, account) {
|
|
|
5264
6328
|
}
|
|
5265
6329
|
function useGuidelyEvent(event, handler) {
|
|
5266
6330
|
const client = useContext3(GuidelyContext);
|
|
5267
|
-
const ref =
|
|
6331
|
+
const ref = useRef7(handler);
|
|
5268
6332
|
ref.current = handler;
|
|
5269
6333
|
useEffect9(() => {
|
|
5270
6334
|
if (!client) return;
|
|
@@ -5279,7 +6343,7 @@ function useGuidelyScreen(name, properties) {
|
|
|
5279
6343
|
}
|
|
5280
6344
|
function useGuidelyNavigation(navigationRef, options = {}) {
|
|
5281
6345
|
const client = useContext3(GuidelyContext);
|
|
5282
|
-
const nameRef =
|
|
6346
|
+
const nameRef = useRef7(options.screenName);
|
|
5283
6347
|
nameRef.current = options.screenName;
|
|
5284
6348
|
const report = () => {
|
|
5285
6349
|
const route = navigationRef.getCurrentRoute?.();
|
|
@@ -5290,7 +6354,7 @@ function useGuidelyNavigation(navigationRef, options = {}) {
|
|
|
5290
6354
|
}
|
|
5291
6355
|
function useUiSelector(select, fallback) {
|
|
5292
6356
|
const client = useContext3(GuidelyContext);
|
|
5293
|
-
const selectRef =
|
|
6357
|
+
const selectRef = useRef7(select);
|
|
5294
6358
|
selectRef.current = select;
|
|
5295
6359
|
const [value, setValue] = useState8(() => client ? select(client) : fallback);
|
|
5296
6360
|
useEffect9(() => {
|
|
@@ -5332,14 +6396,18 @@ export {
|
|
|
5332
6396
|
FeatureValue,
|
|
5333
6397
|
FlagsProvider,
|
|
5334
6398
|
GuidelyClient,
|
|
6399
|
+
GuidelyMask,
|
|
5335
6400
|
GuidelyOverlay,
|
|
5336
6401
|
GuidelyProvider,
|
|
5337
6402
|
GuidelyScreenScope,
|
|
5338
6403
|
GuidelyTarget,
|
|
6404
|
+
HeatmapBuffer,
|
|
5339
6405
|
InMemoryStickyBucketStore,
|
|
5340
6406
|
Messenger,
|
|
6407
|
+
RageClickDetector,
|
|
5341
6408
|
SDK_NAME,
|
|
5342
6409
|
SDK_VERSION,
|
|
6410
|
+
TapTracker,
|
|
5343
6411
|
TargetRegistry,
|
|
5344
6412
|
createFlagsClient3 as createFlagsClient,
|
|
5345
6413
|
createSseParser,
|