@mrrocket/sdk 0.17.0 → 0.18.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/dist/browser/mrrocket-flows.js +9 -2
  3. package/dist/browser/mrrocket-messenger.js +3 -3
  4. package/dist/browser/mrrocket.js +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/browser.js +2 -1
  7. package/dist/browser.js.map +1 -1
  8. package/dist/client.d.ts +23 -3
  9. package/dist/client.js +15 -1
  10. package/dist/client.js.map +1 -1
  11. package/dist/context.d.ts +3 -1
  12. package/dist/context.js.map +1 -1
  13. package/dist/core.d.ts +3 -1
  14. package/dist/core.js +1 -1
  15. package/dist/core.js.map +1 -1
  16. package/dist/finish-web.d.ts +18 -0
  17. package/dist/finish-web.js +139 -0
  18. package/dist/finish-web.js.map +1 -0
  19. package/dist/flows-ui.d.ts +1 -10
  20. package/dist/flows-ui.js +105 -6
  21. package/dist/flows-ui.js.map +1 -1
  22. package/dist/flows-web.d.ts +8 -1
  23. package/dist/flows-web.js +8 -0
  24. package/dist/flows-web.js.map +1 -1
  25. package/dist/flows.d.ts +159 -7
  26. package/dist/flows.js +240 -28
  27. package/dist/flows.js.map +1 -1
  28. package/dist/icons.d.ts +24 -0
  29. package/dist/icons.js +61 -0
  30. package/dist/icons.js.map +1 -0
  31. package/dist/index.d.ts +4 -2
  32. package/dist/index.js +5 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/messenger-ui.js +78 -53
  35. package/dist/messenger-ui.js.map +1 -1
  36. package/dist/onboarding-state.d.ts +23 -0
  37. package/dist/onboarding-state.js +43 -0
  38. package/dist/onboarding-state.js.map +1 -0
  39. package/dist/react-native/index.d.cts +177 -15
  40. package/dist/react-native/index.d.ts +177 -15
  41. package/dist/react-native/index.js +776 -270
  42. package/dist/react-native/index.js.map +1 -1
  43. package/package.json +1 -1
@@ -48,10 +48,10 @@ __export(native_exports, {
48
48
  useVariant: () => useVariant
49
49
  });
50
50
  module.exports = __toCommonJS(native_exports);
51
- var import_react3 = require("react");
51
+ var import_react4 = require("react");
52
52
 
53
53
  // src/core.ts
54
- var SDK_VERSION = "0.17.0";
54
+ var SDK_VERSION = "0.18.0";
55
55
  var propOf = (person, name) => {
56
56
  const want = name.trim().toLowerCase();
57
57
  for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
@@ -173,6 +173,46 @@ var textChanges = (config, distinctId, preview) => {
173
173
  };
174
174
  var newDistinctId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ck_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
175
175
 
176
+ // src/onboarding-state.ts
177
+ var import_react = require("react");
178
+ var none = [];
179
+ var useOnboardingItem = (flows, id, start) => {
180
+ const [state, setState] = (0, import_react.useState)(() => flows && id ? flows.onboarding.item(id) : null);
181
+ (0, import_react.useEffect)(() => {
182
+ if (!flows || !id) {
183
+ setState(null);
184
+ return;
185
+ }
186
+ const up = () => {
187
+ const s = flows.onboarding.item(id);
188
+ setState(s);
189
+ if (s && !s.done && !s.dismissed) flows.onboarding.seen(id);
190
+ };
191
+ up();
192
+ return flows.subscribe(up);
193
+ }, [flows, id]);
194
+ return (0, import_react.useMemo)(
195
+ () => ({
196
+ item: state?.item ?? null,
197
+ steps: state?.steps ?? none,
198
+ done: !!state?.done,
199
+ dismissed: !!state?.dismissed,
200
+ eligible: !!state?.eligible,
201
+ complete: (stepId) => {
202
+ if (flows && id) flows.onboarding.complete(id, stepId);
203
+ },
204
+ dismiss: () => {
205
+ if (flows && id) flows.onboarding.dismissItem(id);
206
+ },
207
+ answer: (question, value) => {
208
+ if (flows && id) flows.onboarding.answerItem(id, question, value);
209
+ },
210
+ start
211
+ }),
212
+ [state, flows, id, start]
213
+ );
214
+ };
215
+
176
216
  // src/identity.ts
177
217
  var UNSIGNED_HELP = "401: sign the user id on your server with userHash(id) from @mrrocket/sdk/next/server and pass identify(id, props, { hash }).";
178
218
  var warnUnsigned = (status, log = (m) => console.warn(m)) => {
@@ -359,7 +399,21 @@ var matchesTrigger = (trigger, event) => {
359
399
  };
360
400
  var startsOnLoad = (trigger) => /^(first (launch|visit|open|session|login|sign ?in|app open)|app (opened|launched)|on load|every visit|always)?$/i.test(trigger.trim());
361
401
  var isPageTrigger = (trigger) => trigger.trim().startsWith("/");
362
- var manual = (trigger) => eventSlug(trigger) === "from_a_checklist";
402
+ var NEEDS_NEWER = "Needs tracking code 0.18.0";
403
+ var momentOf = (f) => f.when ?? f.trigger;
404
+ var manual = (trigger) => eventSlug(trigger) === "from_a_checklist" || eventSlug(trigger) === "from_your_code" || eventSlug(trigger) === eventSlug(NEEDS_NEWER);
405
+ var AFTER_PREFIX = "after:";
406
+ var afterOf = (trigger) => {
407
+ const t = (trigger ?? "").trim();
408
+ return t.toLowerCase().startsWith(AFTER_PREFIX) ? t.slice(AFTER_PREFIX.length).trim() || null : null;
409
+ };
410
+ var opensOf = (step) => step.opens ?? (step.startsTour ? { kind: "tour", id: step.startsTour } : void 0);
411
+ var answerOk = (q, value) => {
412
+ if (!q || value == null) return false;
413
+ if (q.type === "choice") return (q.options ?? []).some((o) => o.id === value);
414
+ return value.trim().length > 0 && value.length <= 500;
415
+ };
416
+ var answerLabel = (q, value) => q?.type === "choice" ? q.options?.find((o) => o.id === value)?.label ?? value : value.trim();
363
417
  var videoEmbed = (url) => {
364
418
  const u = (url ?? "").trim();
365
419
  if (!/^https?:\/\//i.test(u)) return null;
@@ -425,7 +479,12 @@ var createFlows = (env) => {
425
479
  const now = () => env.now?.() ?? Date.now();
426
480
  let timer = null;
427
481
  let returnTo = null;
482
+ const answers = /* @__PURE__ */ new Map();
483
+ const drafts = /* @__PURE__ */ new Map();
484
+ const celebrated = /* @__PURE__ */ new Set();
485
+ let finaleNow = null;
428
486
  const flows = () => config?.onboarding ?? [];
487
+ const drawn = () => flows().filter((f) => f.shownBy !== "code");
429
488
  const flowById = (id) => id ? flows().find((f) => f.id === id) : void 0;
430
489
  const prog = (id) => {
431
490
  let p = progress.get(id);
@@ -499,6 +558,14 @@ var createFlows = (env) => {
499
558
  };
500
559
  const keyOf = (pid) => `${PROGRESS_KEY}:${pid ?? "visitor"}`;
501
560
  const eventsKeyOf = (pid) => `${PROGRESS_KEY}:events:${pid ?? "visitor"}`;
561
+ const answersKeyOf = (pid) => `${PROGRESS_KEY}:answers:${pid ?? "visitor"}`;
562
+ const celebrate = (f) => {
563
+ if (!f.finish || f.finish === "none" || f.shownBy === "code" || celebrated.has(f.id)) return;
564
+ const p = progress.get(f.id);
565
+ if (!p || p.dismissed || walkLine(f, nodesOf(f), p, false).at !== "end") return;
566
+ celebrated.add(f.id);
567
+ finaleNow = { kind: f.finish, key: `${f.id}:${now()}` };
568
+ };
502
569
  const save = () => {
503
570
  dirty = false;
504
571
  const store = env.storage?.();
@@ -514,6 +581,7 @@ var createFlows = (env) => {
514
581
  };
515
582
  try {
516
583
  store.set(keyOf(env.personId()), JSON.stringify(out));
584
+ if (answers.size) store.set(answersKeyOf(env.personId()), JSON.stringify(Object.fromEntries(answers)));
517
585
  if (seenEvents.size) store.set(eventsKeyOf(env.personId()), JSON.stringify([...seenEvents].slice(-200)));
518
586
  } catch {
519
587
  }
@@ -566,8 +634,17 @@ var createFlows = (env) => {
566
634
  type: "checklist",
567
635
  flow,
568
636
  items: items.map((s) => {
569
- const tour = s.startsTour && s.startsTour !== flow.id ? flowById(s.startsTour) : void 0;
570
- return { step: s, done: p.done.has(s.id), ...tour && nodesOf(tour).length ? { tour } : {} };
637
+ const o = opensOf(s);
638
+ const t = o && (o.kind === "tour" || o.kind === "modal") && o.id !== flow.id ? flowById(o.id) : void 0;
639
+ const target = t && t.shownBy !== "code" && nodesOf(t).length ? t : void 0;
640
+ const opens = o && (o.kind === "page" ? !!o.target.trim() : o.kind === "code" || !!target) ? o : void 0;
641
+ return {
642
+ step: s,
643
+ done: p.done.has(s.id),
644
+ ...target && o?.kind === "tour" ? { tour: target } : {},
645
+ ...opens ? { opens } : {},
646
+ ...target ? { target } : {}
647
+ };
571
648
  })
572
649
  };
573
650
  }
@@ -578,7 +655,16 @@ var createFlows = (env) => {
578
655
  const rest = line.slice(index + 1);
579
656
  const fork = rest.findIndex((n) => n.kind === "condition");
580
657
  const ahead = (fork < 0 ? rest : rest.slice(0, fork)).filter(isStep).length;
581
- return { type: "step", flow, step, index: w.before, total: w.before + 1 + ahead, last: !ahead && fork < 0 };
658
+ return {
659
+ type: "step",
660
+ flow,
661
+ step,
662
+ index: w.before,
663
+ total: w.before + 1 + ahead,
664
+ last: !ahead && fork < 0,
665
+ // 0.18.0: a question's answer so far, and whether there is one before it to go back to.
666
+ ...step.kind === "question" ? { answer: drafts.get(step.id) ?? (step.question ? answers.get(step.question.key) : void 0), back: w.before > 0 } : {}
667
+ };
582
668
  };
583
669
  const view = () => {
584
670
  if (cancel) {
@@ -605,23 +691,25 @@ var createFlows = (env) => {
605
691
  if (ob) return ob;
606
692
  if (active && !holding) {
607
693
  const back = returnTo;
608
- const ended = active;
694
+ const ended2 = active;
609
695
  returnTo = null;
610
696
  active = null;
611
697
  const list = back ? flowById(back.flow) : void 0;
612
698
  if (back && list) {
613
699
  const item = stepsIn(nodesOf(list)).find((s) => s.id === back.step);
614
700
  const p = prog(list.id);
615
- if (item && !item.event && !progress.get(ended)?.dismissed && !p.done.has(item.id)) {
701
+ if (item && !item.event && !progress.get(ended2)?.dismissed && !p.done.has(item.id)) {
616
702
  markDone(p, item.id);
617
703
  record({ flow: list.id, step: item.id, kind: "checklist", action: "completed" });
704
+ celebrate(list);
618
705
  }
619
706
  active = list.id;
620
707
  const again = onboardingView();
621
708
  if (again) return again;
622
709
  if (!holding) active = null;
623
710
  } else if (known) {
624
- const after = flowById(ended)?.trigger;
711
+ const was = flowById(ended2);
712
+ const after = was ? momentOf(was) : void 0;
625
713
  Promise.resolve().then(() => autoStart(after));
626
714
  }
627
715
  }
@@ -649,22 +737,64 @@ var createFlows = (env) => {
649
737
  emit();
650
738
  return true;
651
739
  };
652
- const allowed = (f) => f.audience !== "signed_in" || !!env.personId();
740
+ const allowed = (f) => f.audience === "answered" ? !!f.answered && answers.get(f.answered.question) === f.answered.option : f.audience !== "signed_in" || !!env.personId();
741
+ const predecessorEnded = (id) => {
742
+ const g = flowById(id);
743
+ return !g || finished(g);
744
+ };
745
+ const due = (f, trigger = momentOf(f)) => {
746
+ const path = env.path?.();
747
+ const pred = afterOf(trigger);
748
+ if (pred) return predecessorEnded(pred);
749
+ if (manual(trigger)) return false;
750
+ if (isPageTrigger(trigger)) return path !== void 0 && pageMatches(trigger, path);
751
+ if (startsOnLoad(trigger)) return true;
752
+ for (const e of seenEvents) if (matchesTrigger(trigger, e)) return true;
753
+ return false;
754
+ };
653
755
  const autoStart = (after) => {
654
756
  if (!auto || active || !known) return;
655
757
  if (!env.personId() && !env.storage?.()) return;
656
758
  const path = env.path?.();
657
759
  const same2 = after && !manual(after) ? eventSlug(after) : "";
658
- const next = flows().find(
659
- (f) => (isPageTrigger(f.trigger) ? path !== void 0 && pageMatches(f.trigger, path) : startsOnLoad(f.trigger) || !!same2 && eventSlug(f.trigger) === same2) && allowed(f) && nodesOf(f).length && !finished(f)
760
+ const next = drawn().find(
761
+ (f) => (afterOf(momentOf(f)) ? predecessorEnded(afterOf(momentOf(f))) : isPageTrigger(momentOf(f)) ? path !== void 0 && pageMatches(momentOf(f), path) : startsOnLoad(momentOf(f)) || !!same2 && eventSlug(momentOf(f)) === same2) && allowed(f) && nodesOf(f).length && !finished(f)
660
762
  );
661
763
  if (next) start(next);
662
764
  };
765
+ const openItem = (list, stepId, target) => {
766
+ const p = prog(target.id);
767
+ p.done.clear();
768
+ p.at.clear();
769
+ p.dismissed = false;
770
+ returnTo = { flow: list.id, step: stepId };
771
+ active = target.id;
772
+ env.track(ONBOARDING_EVENT, { flow: target.id, action: "started", from: list.id });
773
+ save();
774
+ emit();
775
+ };
776
+ const answerStep = (flow, step, raw) => {
777
+ const q = step.question;
778
+ const value = q.type === "text" ? raw.trim().slice(0, 500) : raw;
779
+ answers.set(q.key, value);
780
+ drafts.delete(step.id);
781
+ markDone(prog(flow.id), step.id);
782
+ env.track(ONBOARDING_EVENT, { flow: flow.id, step: step.id, kind: "question", action: "completed", question: q.key, answer: value, label: answerLabel(q, value) });
783
+ celebrate(flow);
784
+ save();
785
+ };
786
+ const ended = (f) => {
787
+ save();
788
+ if (finished(f)) Promise.resolve().then(() => autoStart());
789
+ emit();
790
+ };
663
791
  const refresh = () => {
664
792
  const pid = env.personId();
665
793
  if (progressFor !== null && progressFor !== pid) {
666
794
  progress.clear();
667
795
  seenEvents.clear();
796
+ answers.clear();
797
+ drafts.clear();
668
798
  handled.clear();
669
799
  active = null;
670
800
  returnTo = null;
@@ -681,6 +811,10 @@ var createFlows = (env) => {
681
811
  if (seen) {
682
812
  for (const e of JSON.parse(seen)) if (typeof e === "string") seenEvents.add(e);
683
813
  }
814
+ const said = await store.get(answersKeyOf(pid));
815
+ if (said) {
816
+ for (const [k, v] of Object.entries(JSON.parse(said))) if (typeof v === "string") answers.set(k, v);
817
+ }
684
818
  } catch {
685
819
  }
686
820
  }
@@ -691,6 +825,7 @@ var createFlows = (env) => {
691
825
  if (state && env.personId() === pid) {
692
826
  merge(state.onboarding);
693
827
  for (const e of state.events ?? []) if (typeof e === "string") seenEvents.add(eventSlug(e));
828
+ for (const [k, v] of Object.entries(state.answers ?? {})) if (typeof v === "string" && !answers.has(k)) answers.set(k, v);
694
829
  messages = (state.messages ?? []).filter((m) => m && m.id && m.title);
695
830
  }
696
831
  }
@@ -784,27 +919,33 @@ var createFlows = (env) => {
784
919
  if (fresh) seenEvents.add(slug);
785
920
  const flow = flowById(active);
786
921
  let ticked = false;
787
- if (flow) {
788
- const p = prog(flow.id);
789
- for (const s of stepsIn(nodesOf(flow)))
922
+ const lists = [...flow ? [flow] : [], ...flows().filter((f) => f.shownBy === "code" && f.id !== flow?.id)];
923
+ for (const l of lists) {
924
+ const p = prog(l.id);
925
+ let here = false;
926
+ for (const s of stepsIn(nodesOf(l)))
790
927
  if (s.kind === "checklist" && s.event && !p.done.has(s.id) && matchesTrigger(s.event, name)) {
791
928
  markDone(p, s.id);
792
- ticked = true;
793
- env.track(ONBOARDING_EVENT, { flow: flow.id, step: s.id, kind: s.kind, action: "completed" });
929
+ here = true;
930
+ env.track(ONBOARDING_EVENT, { flow: l.id, step: s.id, kind: s.kind, action: "completed" });
794
931
  }
932
+ if (here) {
933
+ ticked = true;
934
+ celebrate(l);
935
+ }
795
936
  }
796
- if (ticked || fresh && flow) {
937
+ if (ticked || fresh && (flow || lists.length)) {
797
938
  save();
798
939
  emit();
799
940
  } else if (fresh) save();
800
941
  if (!auto || !env.personId() && !env.storage?.()) return;
801
- const next = flows().find((f) => nodesOf(f).length && !manual(f.trigger) && matchesTrigger(f.trigger, name) && allowed(f) && !finished(f));
942
+ const next = drawn().find((f) => nodesOf(f).length && !manual(momentOf(f)) && matchesTrigger(momentOf(f), name) && allowed(f) && !finished(f));
802
943
  if (next) start(next);
803
944
  },
804
945
  openCancelFlow,
805
946
  startOnboarding: (id) => {
806
- const f = id ? flowById(id) : flows().find((x) => nodesOf(x).length && !manual(x.trigger) && !finished(x));
807
- if (!f || !nodesOf(f).length) return false;
947
+ const f = id ? flowById(id) : drawn().find((x) => nodesOf(x).length && !manual(momentOf(x)) && !afterOf(momentOf(x)) && !finished(x));
948
+ if (!f || !nodesOf(f).length || f.shownBy === "code") return false;
808
949
  const p = prog(f.id);
809
950
  p.dismissed = false;
810
951
  return start(f);
@@ -895,8 +1036,17 @@ var createFlows = (env) => {
895
1036
  next: () => {
896
1037
  const v = onboardingView();
897
1038
  if (v?.type !== "step") return;
1039
+ if (v.step.kind === "question") {
1040
+ const q = v.step.question;
1041
+ const a = drafts.get(v.step.id) ?? (q ? answers.get(q.key) : void 0);
1042
+ if (!q || !answerOk(q, a)) return;
1043
+ answerStep(v.flow, v.step, a);
1044
+ emit();
1045
+ return;
1046
+ }
898
1047
  markDone(prog(v.flow.id), v.step.id);
899
1048
  env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: v.step.kind, action: "completed" });
1049
+ celebrate(v.flow);
900
1050
  save();
901
1051
  emit();
902
1052
  const url = v.step.kind === "modal" ? safeUrl(v.step.url) : void 0;
@@ -909,6 +1059,7 @@ var createFlows = (env) => {
909
1059
  if (!item || item.done) return;
910
1060
  markDone(prog(v.flow.id), stepId);
911
1061
  env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
1062
+ celebrate(v.flow);
912
1063
  save();
913
1064
  emit();
914
1065
  },
@@ -925,20 +1076,112 @@ var createFlows = (env) => {
925
1076
  startTour: (stepId) => {
926
1077
  const v = onboardingView();
927
1078
  if (v?.type !== "checklist") return false;
928
- const tour = v.items.find((i) => i.step.id === stepId)?.tour;
929
- if (!tour) return false;
930
- const p = prog(tour.id);
931
- p.done.clear();
932
- p.at.clear();
933
- p.dismissed = false;
934
- returnTo = { flow: v.flow.id, step: stepId };
935
- active = tour.id;
936
- env.track(ONBOARDING_EVENT, { flow: tour.id, action: "started", from: v.flow.id });
1079
+ const target = v.items.find((i) => i.step.id === stepId)?.target;
1080
+ if (!target) return false;
1081
+ openItem(v.flow, stepId, target);
1082
+ return true;
1083
+ },
1084
+ open: (stepId) => {
1085
+ const v = onboardingView();
1086
+ if (v?.type !== "checklist") return false;
1087
+ const it = v.items.find((i) => i.step.id === stepId);
1088
+ const o = it?.opens;
1089
+ if (!it || !o) return false;
1090
+ env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "opened", opens: o.kind });
1091
+ if ((o.kind === "tour" || o.kind === "modal") && it.target) {
1092
+ openItem(v.flow, stepId, it.target);
1093
+ return true;
1094
+ }
1095
+ if (!it.done && !it.step.event) {
1096
+ markDone(prog(v.flow.id), stepId);
1097
+ env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
1098
+ celebrate(v.flow);
1099
+ }
937
1100
  save();
938
1101
  emit();
1102
+ if (o.kind === "page") env.navigate?.(o.target.trim());
1103
+ else env.task?.(stepId, v.flow);
939
1104
  return true;
1105
+ },
1106
+ answer: (value) => {
1107
+ const v = onboardingView();
1108
+ if (v?.type !== "step" || v.step.kind !== "question") return;
1109
+ drafts.set(v.step.id, String(value).slice(0, 500));
1110
+ emit();
1111
+ },
1112
+ back: () => {
1113
+ const v = onboardingView();
1114
+ if (v?.type !== "step" || v.step.kind !== "question" || !v.back) return;
1115
+ const line = stepsIn(nodesOf(v.flow));
1116
+ const at = line.findIndex((s) => s.id === v.step.id);
1117
+ const p = prog(v.flow.id);
1118
+ for (let i = at - 1; i >= 0; i--)
1119
+ if (p.done.has(line[i].id)) {
1120
+ p.done.delete(line[i].id);
1121
+ p.at.delete(line[i].id);
1122
+ break;
1123
+ }
1124
+ save();
1125
+ emit();
1126
+ },
1127
+ item: (id) => {
1128
+ const f = flowById(id);
1129
+ if (!f) return null;
1130
+ const p = progress.get(f.id);
1131
+ const steps = stepsIn(nodesOf(f)).map((s) => {
1132
+ const a = s.question ? answers.get(s.question.key) : void 0;
1133
+ return { ...s, done: !!p?.done.has(s.id), ...a !== void 0 ? { answer: a } : {} };
1134
+ });
1135
+ const done = steps.length > 0 && steps.every((s) => s.done);
1136
+ const dismissed = !!p?.dismissed;
1137
+ return { item: f, steps, done, dismissed, eligible: !done && !dismissed && allowed(f) && due(f) };
1138
+ },
1139
+ seen: (id) => {
1140
+ const f = flowById(id);
1141
+ if (!f || f.shownBy !== "code" || !known) return;
1142
+ if (!shown.has(`f:${f.id}`)) {
1143
+ shown.add(`f:${f.id}`);
1144
+ env.track(ONBOARDING_EVENT, { flow: f.id, action: "started" });
1145
+ }
1146
+ const p = progress.get(f.id);
1147
+ for (const s of stepsIn(nodesOf(f)))
1148
+ if (!p?.done.has(s.id) && !shown.has(`s:${s.id}`)) {
1149
+ shown.add(`s:${s.id}`);
1150
+ env.track(ONBOARDING_EVENT, { flow: f.id, step: s.id, kind: s.kind, action: "shown" });
1151
+ if (s.kind !== "checklist") break;
1152
+ }
1153
+ },
1154
+ complete: (id, stepId) => {
1155
+ const f = flowById(id);
1156
+ const s = f && stepsIn(nodesOf(f)).find((x) => x.id === stepId);
1157
+ if (!f || !s || s.kind === "question") return;
1158
+ const p = prog(f.id);
1159
+ if (p.done.has(s.id)) return;
1160
+ markDone(p, s.id);
1161
+ env.track(ONBOARDING_EVENT, { flow: f.id, step: s.id, kind: s.kind, action: "completed" });
1162
+ ended(f);
1163
+ },
1164
+ dismissItem: (id) => {
1165
+ const f = flowById(id);
1166
+ if (!f) return;
1167
+ const p = prog(f.id);
1168
+ if (p.dismissed) return;
1169
+ p.dismissed = true;
1170
+ env.track(ONBOARDING_EVENT, { flow: f.id, action: "dismissed" });
1171
+ if (active === f.id) active = null;
1172
+ ended(f);
1173
+ },
1174
+ answerItem: (id, question, value) => {
1175
+ const f = flowById(id);
1176
+ const s = f && stepsIn(nodesOf(f)).find((x) => x.kind === "question" && (x.id === question || x.question?.key === question));
1177
+ if (!f || !s?.question || !answerOk(s.question, value)) return;
1178
+ answerStep(f, s, value);
1179
+ ended(f);
940
1180
  }
941
1181
  },
1182
+ finale: () => finaleNow,
1183
+ accent: () => config?.onboardingAccent,
1184
+ answers: () => Object.fromEntries(answers),
942
1185
  message: {
943
1186
  click: () => {
944
1187
  const v = view();
@@ -1372,7 +1615,14 @@ var createClient = (options, deps) => {
1372
1615
  set: (k, v) => void set(k, v)
1373
1616
  } : null,
1374
1617
  openUrl: deps.openUrl,
1375
- fetchInApp: (id) => fetchInApp(id)
1618
+ fetchInApp: (id) => fetchInApp(id),
1619
+ // 0.18.0: a task's page or screen goes to the app's navigator; without one only a link with a scheme opens.
1620
+ navigate: (target) => {
1621
+ if (options.onNavigate) return options.onNavigate(target);
1622
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target) && !/^(javascript|data|vbscript):/i.test(target)) deps.openUrl?.(target);
1623
+ else warn(`A checklist task opens "${target}": pass onNavigate to MRRocketProvider to open screens and paths.`);
1624
+ },
1625
+ task: (taskId, item) => options.onTask?.(taskId, item)
1376
1626
  });
1377
1627
  const refreshFlows = () => {
1378
1628
  if (!isReady || closed) return;
@@ -2010,30 +2260,216 @@ var activeRouteName = (state) => {
2010
2260
  };
2011
2261
 
2012
2262
  // src/native/flows-view.ts
2013
- var import_react = require("react");
2263
+ var import_react2 = require("react");
2264
+
2265
+ // src/icons.ts
2266
+ var FLOW_ICONS = {
2267
+ check: { label: "Check", paths: ["M5 12.5l4.5 4.5L19 7.5"], emoji: "\u2705" },
2268
+ star: { label: "Star", paths: ["M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9l-5.2 2.7 1-5.8-4.3-4.1 5.9-.9z"], emoji: "\u2B50" },
2269
+ rocket: {
2270
+ label: "Rocket",
2271
+ paths: ["M12 3c3 2 4.5 5.5 4 10l-2 3h-4l-2-3c-.5-4.5 1-8 4-10z", "M8 13.5l-3 3 3.5.5", "M16 13.5l3 3-3.5.5", "M13.5 9.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z", "M11 19.5v1.5M13 19.5v1.5"],
2272
+ emoji: "\u{1F680}"
2273
+ },
2274
+ user: { label: "Person", paths: ["M15.5 8a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0z", "M5 20c.8-3.5 3.6-5.5 7-5.5s6.2 2 7 5.5"], emoji: "\u{1F464}" },
2275
+ users: {
2276
+ label: "Team",
2277
+ paths: ["M13 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z", "M3.5 19c.6-3 3-4.8 6.5-4.8s5.9 1.8 6.5 4.8", "M16 5.2a3 3 0 0 1 0 5.6", "M18 14.6c1.5.6 2.4 2 2.7 4.4"],
2278
+ emoji: "\u{1F465}"
2279
+ },
2280
+ settings: {
2281
+ label: "Settings",
2282
+ paths: ["M12 2.8v2.4M12 18.8v2.4M2.8 12h2.4M18.8 12h2.4M5.5 5.5l1.7 1.7M16.8 16.8l1.7 1.7M5.5 18.5l1.7-1.7M16.8 7.2l1.7-1.7", "M17 12a5 5 0 1 1-10 0 5 5 0 0 1 10 0z", "M14 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"],
2283
+ emoji: "\u2699\uFE0F"
2284
+ },
2285
+ bell: { label: "Bell", paths: ["M6 16v-5a6 6 0 0 1 12 0v5l1.5 2h-15z", "M10 20.5a2 2 0 0 0 4 0"], emoji: "\u{1F514}" },
2286
+ mail: { label: "Mail", paths: ["M4 6h16v12H4z", "M4 7l8 6 8-6"], emoji: "\u2709\uFE0F" },
2287
+ chat: { label: "Chat", paths: ["M4 5h16v11H9l-5 4z"], emoji: "\u{1F4AC}" },
2288
+ heart: { label: "Heart", paths: ["M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"], emoji: "\u2764\uFE0F" },
2289
+ flag: { label: "Flag", paths: ["M5 21V4", "M5 4h11l-2 4 2 4H5"], emoji: "\u{1F6A9}" },
2290
+ bolt: { label: "Bolt", paths: ["M13 3L5 13.5h6L10 21l8-10.5h-6z"], emoji: "\u26A1" },
2291
+ book: { label: "Book", paths: ["M4 5.5C6.5 4 9.5 4 12 6c2.5-2 5.5-2 8-.5V19c-2.5-1.5-5.5-1.5-8 .5-2.5-2-5.5-2-8-.5z", "M12 6v13.5"], emoji: "\u{1F4D6}" },
2292
+ calendar: { label: "Calendar", paths: ["M4 6h16v14H4z", "M4 10h16", "M8 3.5v4M16 3.5v4"], emoji: "\u{1F4C5}" },
2293
+ camera: { label: "Camera", paths: ["M4 8h3.5L9 5.5h6L16.5 8H20v11H4z", "M15.5 13a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0z"], emoji: "\u{1F4F7}" },
2294
+ cart: { label: "Cart", paths: ["M3.5 4.5H6l2 10h10l2-7H7", "M9.5 19.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z", "M18.5 19.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"], emoji: "\u{1F6D2}" },
2295
+ chart: { label: "Chart", paths: ["M4 20h16", "M7 16v-4M12 16V8M17 16V9"], emoji: "\u{1F4CA}" },
2296
+ gift: { label: "Gift", paths: ["M4 10h16v10H4z", "M3 7h18v3H3z", "M12 7v13", "M12 7c-1.5-3-5-3-5-1s3.5 1 5 1z", "M12 7c1.5-3 5-3 5-1s-3.5 1-5 1z"], emoji: "\u{1F381}" },
2297
+ home: { label: "Home", paths: ["M4 11l8-7 8 7", "M6 9.5V20h12V9.5", "M10 20v-6h4v6"], emoji: "\u{1F3E0}" },
2298
+ link: { label: "Link", paths: ["M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1", "M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"], emoji: "\u{1F517}" },
2299
+ lock: { label: "Lock", paths: ["M6 11h12v9H6z", "M8.5 11V8a3.5 3.5 0 0 1 7 0v3"], emoji: "\u{1F512}" },
2300
+ pencil: { label: "Pencil", paths: ["M4 20l1-4L16 5l3 3L8 19z", "M14 7l3 3"], emoji: "\u270F\uFE0F" },
2301
+ plus: { label: "Plus", paths: ["M12 5v14M5 12h14"], emoji: "\u2795" },
2302
+ search: { label: "Search", paths: ["M16 10.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z", "M14.5 14.5L20 20"], emoji: "\u{1F50D}" },
2303
+ upload: { label: "Upload", paths: ["M12 15V4", "M7.5 8.5L12 4l4.5 4.5", "M4 15v5h16v-5"], emoji: "\u2B06\uFE0F" },
2304
+ play: { label: "Play", paths: ["M8 5l11 7-11 7z"], emoji: "\u25B6\uFE0F" },
2305
+ sparkle: { label: "Sparkle", paths: ["M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z", "M19 16l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7z"], emoji: "\u2728" },
2306
+ globe: {
2307
+ label: "Globe",
2308
+ paths: ["M20 12a8 8 0 1 1-16 0 8 8 0 0 1 16 0z", "M4 12h16", "M12 4c2.2 2.2 3.3 4.9 3.3 8s-1.1 5.8-3.3 8c-2.2-2.2-3.3-4.9-3.3-8S9.8 6.2 12 4z"],
2309
+ emoji: "\u{1F310}"
2310
+ }
2311
+ };
2312
+ var FLOW_ICON_NAMES = Object.keys(FLOW_ICONS);
2313
+ var iconOf = (icon) => {
2314
+ if (!icon) return void 0;
2315
+ if (icon.kind === "builtin") return icon.name && FLOW_ICONS[icon.name] ? icon : void 0;
2316
+ if (icon.kind === "emoji") return icon.value && icon.value.trim() && [...icon.value.trim()].length <= 8 ? icon : void 0;
2317
+ if (icon.kind === "image") return icon.url && /^https?:\/\//i.test(icon.url) ? icon : void 0;
2318
+ return void 0;
2319
+ };
2320
+
2321
+ // src/native/flows-view.ts
2014
2322
  var RN = require("react-native");
2323
+ var svg = (() => {
2324
+ try {
2325
+ return require("react-native-svg");
2326
+ } catch {
2327
+ return null;
2328
+ }
2329
+ })();
2330
+ var iconNode = (icon, size, color) => {
2331
+ const i = iconOf(icon);
2332
+ if (!i) return null;
2333
+ if (i.kind === "image")
2334
+ return RN.Image ? (0, import_react2.createElement)(RN.Image, { source: { uri: i.url }, accessibilityElementsHidden: true, style: { width: size, height: size, borderRadius: size / 4 } }) : null;
2335
+ const Svg = svg?.Svg ?? svg?.default;
2336
+ if (i.kind === "builtin" && Svg && svg?.Path)
2337
+ return (0, import_react2.createElement)(
2338
+ Svg,
2339
+ { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
2340
+ ...FLOW_ICONS[i.name].paths.map((d, k) => (0, import_react2.createElement)(svg.Path, { key: k, d }))
2341
+ );
2342
+ return (0, import_react2.createElement)(RN.Text, { style: { fontSize: size * 0.8, lineHeight: size, textAlign: "center", width: size } }, i.kind === "emoji" ? i.value : FLOW_ICONS[i.name].emoji);
2343
+ };
2344
+ var FINALE_COLORS = ["#f59e0b", "#10b981", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899"];
2345
+ var finaleBits = (kind, accent) => {
2346
+ const rnd = (a, b) => a + Math.random() * (b - a);
2347
+ const colors = [accent, accent, ...FINALE_COLORS];
2348
+ const pick = () => colors[Math.floor(Math.random() * colors.length)];
2349
+ if (kind === "confetti")
2350
+ return Array.from({ length: 48 }, (_, i) => ({
2351
+ x: i % 2 ? 12 : 82,
2352
+ y: 92,
2353
+ dx: (i % 2 ? 1 : -1) * rnd(40, 200),
2354
+ mid: -rnd(380, 620),
2355
+ dy: -rnd(120, 300),
2356
+ size: rnd(7, 11),
2357
+ rot: rnd(-540, 540),
2358
+ color: pick(),
2359
+ delay: rnd(0, 160),
2360
+ life: rnd(1500, 2e3),
2361
+ shape: "rect"
2362
+ }));
2363
+ if (kind === "fireworks")
2364
+ return Array.from({ length: 3 }, (_, b) => {
2365
+ const x = rnd(20, 75);
2366
+ const y = rnd(18, 45);
2367
+ const color = pick();
2368
+ return Array.from({ length: 18 }, (_2, i) => {
2369
+ const a = Math.PI * 2 * i / 18;
2370
+ const r = rnd(70, 130);
2371
+ return { x, y, dx: Math.cos(a) * r, mid: Math.sin(a) * r * 0.8, dy: Math.sin(a) * r + 40, size: 5, rot: 0, color, delay: b * 320, life: rnd(900, 1200), shape: "dot" };
2372
+ });
2373
+ }).flat();
2374
+ return Array.from({ length: 22 }, () => ({
2375
+ x: rnd(6, 88),
2376
+ y: rnd(20, 80),
2377
+ dx: 0,
2378
+ mid: -rnd(10, 25),
2379
+ dy: -rnd(30, 60),
2380
+ size: rnd(16, 28),
2381
+ rot: rnd(-40, 40),
2382
+ color: pick(),
2383
+ delay: rnd(0, 600),
2384
+ life: rnd(900, 1300),
2385
+ shape: "star"
2386
+ }));
2387
+ };
2388
+ var Finale = ({ kind, accent }) => {
2389
+ const A = RN.Animated;
2390
+ const [bits] = (0, import_react2.useState)(() => finaleBits(kind, accent));
2391
+ const [values] = (0, import_react2.useState)(() => A ? bits.map(() => new A.Value(0)) : []);
2392
+ const [on, setOn] = (0, import_react2.useState)(!!A);
2393
+ (0, import_react2.useEffect)(() => {
2394
+ if (!A) return;
2395
+ let stopped = false;
2396
+ let run = null;
2397
+ const go = () => {
2398
+ if (stopped) return;
2399
+ run = A.parallel(values.map((v, i) => A.timing(v, { toValue: 1, duration: bits[i].life, delay: bits[i].delay, useNativeDriver: true })));
2400
+ run.start(() => !stopped && setOn(false));
2401
+ };
2402
+ Promise.resolve(RN.AccessibilityInfo?.isReduceMotionEnabled?.()).then((reduce) => reduce ? !stopped && setOn(false) : go()).catch(go);
2403
+ return () => {
2404
+ stopped = true;
2405
+ run?.stop();
2406
+ };
2407
+ }, [A, bits, values]);
2408
+ if (!on || !A) return null;
2409
+ return (0, import_react2.createElement)(
2410
+ RN.View,
2411
+ { pointerEvents: "none", accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 } },
2412
+ ...bits.map((b, i) => {
2413
+ const t = values[i];
2414
+ const fade = t.interpolate({ inputRange: [0, 0.7, 1], outputRange: [1, 1, 0] });
2415
+ const scale = b.shape === "star" ? t.interpolate({ inputRange: [0, 0.25, 1], outputRange: [0.2, 1.15, 0.6] }) : 1;
2416
+ return (0, import_react2.createElement)(
2417
+ A.View,
2418
+ {
2419
+ key: i,
2420
+ style: {
2421
+ position: "absolute",
2422
+ left: `${b.x}%`,
2423
+ top: `${b.y}%`,
2424
+ width: b.size,
2425
+ height: b.shape === "rect" ? b.size * 0.45 : b.size,
2426
+ borderRadius: b.shape === "dot" ? b.size / 2 : 2,
2427
+ backgroundColor: b.shape === "star" ? "transparent" : b.color,
2428
+ alignItems: "center",
2429
+ justifyContent: "center",
2430
+ opacity: fade,
2431
+ transform: [
2432
+ { translateX: t.interpolate({ inputRange: [0, 1], outputRange: [0, b.dx] }) },
2433
+ { translateY: t.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, b.mid, b.dy] }) },
2434
+ { rotate: t.interpolate({ inputRange: [0, 1], outputRange: ["0deg", `${b.rot}deg`] }) },
2435
+ { scale }
2436
+ ]
2437
+ }
2438
+ },
2439
+ b.shape === "star" ? (0, import_react2.createElement)(RN.Text, { style: { color: b.color, fontSize: b.size, lineHeight: b.size } }, "\u2605") : null
2440
+ );
2441
+ })
2442
+ );
2443
+ };
2015
2444
  var LIGHT = { bg: "#ffffff", fg: "#1c1d20", muted: "#5d6068", soft: "#f4f5f7", line: "#e4e5e8" };
2016
2445
  var DARK = { bg: "#17181b", fg: "#f1f2f4", muted: "#a9acb3", soft: "#232428", line: "#2e2f35" };
2017
2446
  var months = (n) => `${n} ${n === 1 ? "month" : "months"}`;
2018
- var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2447
+ var MRRocketFlows = (props) => {
2448
+ const [, setTick] = (0, import_react2.useState)(0);
2449
+ (0, import_react2.useEffect)(() => props.flows.subscribe(() => setTick((t) => t + 1)), [props.flows]);
2450
+ const fin = props.flows.finale?.() ?? null;
2451
+ const accent = props.appearance?.accent ?? props.flows.accent?.() ?? "#3f76ff";
2452
+ return (0, import_react2.createElement)(import_react2.Fragment, null, (0, import_react2.createElement)(FlowsBody, props), fin ? (0, import_react2.createElement)(Finale, { key: fin.key, kind: fin.kind, accent }) : null);
2453
+ };
2454
+ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
2019
2455
  const scheme = RN.useColorScheme?.();
2020
2456
  const dark = appearance.theme === "dark" || (appearance.theme ?? "auto") === "auto" && scheme === "dark";
2021
- const p = { ...dark ? DARK : LIGHT, accent: appearance.accent ?? "#3f76ff", danger: "#d92d20" };
2022
- const [, setTick] = (0, import_react.useState)(0);
2023
- (0, import_react.useEffect)(() => flows.subscribe(() => setTick((t) => t + 1)), [flows]);
2024
- (0, import_react.useEffect)(() => paywall?.subscribe(() => setTick((t) => t + 1)), [paywall]);
2457
+ const p = { ...dark ? DARK : LIGHT, accent: appearance.accent ?? flows.accent?.() ?? "#3f76ff", danger: "#d92d20" };
2458
+ const [, setTick] = (0, import_react2.useState)(0);
2459
+ (0, import_react2.useEffect)(() => flows.subscribe(() => setTick((t) => t + 1)), [flows]);
2460
+ (0, import_react2.useEffect)(() => paywall?.subscribe(() => setTick((t) => t + 1)), [paywall]);
2025
2461
  const pw = paywall?.view() ?? null;
2026
2462
  const v = flows.view();
2027
- const [comment, setComment] = (0, import_react.useState)("");
2463
+ const [comment, setComment] = (0, import_react2.useState)("");
2028
2464
  const screen = v?.type === "cancel" ? v.screen : null;
2029
- (0, import_react.useEffect)(() => {
2465
+ (0, import_react2.useEffect)(() => {
2030
2466
  if (!screen) setComment("");
2031
2467
  }, [screen]);
2032
2468
  if (!v && !pw) return null;
2033
- const text2 = (children, style = {}) => (0, import_react.createElement)(RN.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
2469
+ const text2 = (children, style = {}) => (0, import_react2.createElement)(RN.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
2034
2470
  const muted = (children, style = {}) => text2(children, { color: p.muted, fontSize: 13, ...style });
2035
- const title = (children) => (0, import_react.createElement)(RN.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 18, fontWeight: "700" } }, children);
2036
- const btn = (label, kind, onPress, disabled = false) => (0, import_react.createElement)(
2471
+ const title = (children) => (0, import_react2.createElement)(RN.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 18, fontWeight: "700" } }, children);
2472
+ const btn = (label, kind, onPress, disabled = false) => (0, import_react2.createElement)(
2037
2473
  RN.Pressable,
2038
2474
  {
2039
2475
  key: label,
@@ -2057,12 +2493,12 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2057
2493
  fontWeight: kind === "quiet" ? "500" : "600"
2058
2494
  })
2059
2495
  );
2060
- const close = (label, onPress) => (0, import_react.createElement)(
2496
+ const close = (label, onPress) => (0, import_react2.createElement)(
2061
2497
  RN.Pressable,
2062
2498
  { onPress, accessibilityRole: "button", accessibilityLabel: label, hitSlop: 12, style: { marginLeft: "auto", padding: 4 } },
2063
2499
  text2("\xD7", { fontSize: 22, color: p.muted })
2064
2500
  );
2065
- const card = (children, style = {}) => (0, import_react.createElement)(
2501
+ const card = (children, style = {}) => (0, import_react2.createElement)(
2066
2502
  RN.View,
2067
2503
  {
2068
2504
  style: {
@@ -2079,16 +2515,16 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2079
2515
  },
2080
2516
  ...children
2081
2517
  );
2082
- const sheet = (children, onClose) => (0, import_react.createElement)(
2518
+ const sheet = (children, onClose) => (0, import_react2.createElement)(
2083
2519
  RN.Modal,
2084
2520
  { visible: true, transparent: true, animationType: "fade", onRequestClose: onClose },
2085
- (0, import_react.createElement)(
2521
+ (0, import_react2.createElement)(
2086
2522
  RN.View,
2087
2523
  { style: { flex: 1, backgroundColor: "rgba(0,0,0,0.4)", justifyContent: "flex-end" } },
2088
2524
  card(children, { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, paddingBottom: 34 })
2089
2525
  )
2090
2526
  );
2091
- const input = (placeholder, value = comment, onChange = setComment) => (0, import_react.createElement)(RN.TextInput, {
2527
+ const input = (placeholder, value = comment, onChange = setComment) => (0, import_react2.createElement)(RN.TextInput, {
2092
2528
  value,
2093
2529
  onChangeText: onChange,
2094
2530
  placeholder,
@@ -2103,19 +2539,19 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2103
2539
  const host = paywall;
2104
2540
  return sheet(
2105
2541
  [
2106
- (0, import_react.createElement)(
2542
+ (0, import_react2.createElement)(
2107
2543
  RN.View,
2108
2544
  { key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
2109
2545
  ...w.total > 1 ? Array.from(
2110
2546
  { length: w.total },
2111
- (_, i) => (0, import_react.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= w.index ? p.accent : p.line } })
2547
+ (_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= w.index ? p.accent : p.line } })
2112
2548
  ) : [],
2113
2549
  close("Close", host.close)
2114
2550
  ),
2115
2551
  title(pg.title),
2116
2552
  pg.body ? muted(pg.body, { fontSize: 15 }) : null,
2117
2553
  ...pg.kind === "benefits" ? (pg.items ?? []).map(
2118
- (item, i) => (0, import_react.createElement)(
2554
+ (item, i) => (0, import_react2.createElement)(
2119
2555
  RN.View,
2120
2556
  { key: `b${i}`, style: { flexDirection: "row", gap: 10, alignItems: "flex-start" } },
2121
2557
  text2("\u2713", { color: p.accent, fontWeight: "700" }),
@@ -2123,13 +2559,13 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2123
2559
  )
2124
2560
  ) : [],
2125
2561
  ...pg.kind === "proof" ? (pg.quotes ?? []).map(
2126
- (q, i) => (0, import_react.createElement)(RN.View, { key: `q${i}`, style: { backgroundColor: p.soft, borderRadius: 12, padding: 12, gap: 6 } }, text2(q.text), q.by ? muted(q.by) : null)
2562
+ (q, i) => (0, import_react2.createElement)(RN.View, { key: `q${i}`, style: { backgroundColor: p.soft, borderRadius: 12, padding: 12, gap: 6 } }, text2(q.text), q.by ? muted(q.by) : null)
2127
2563
  ) : [],
2128
- pg.kind === "plans" ? (0, import_react.createElement)(
2564
+ pg.kind === "plans" ? (0, import_react2.createElement)(
2129
2565
  RN.View,
2130
2566
  { key: "plans", accessibilityRole: "radiogroup", style: { gap: 6 } },
2131
2567
  ...w.plans.map(
2132
- (plan) => (0, import_react.createElement)(
2568
+ (plan) => (0, import_react2.createElement)(
2133
2569
  RN.Pressable,
2134
2570
  {
2135
2571
  key: plan.id,
@@ -2149,7 +2585,7 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2149
2585
  gap: 10
2150
2586
  }
2151
2587
  },
2152
- (0, import_react.createElement)(RN.View, { style: { flex: 1 } }, text2(plan.name, { fontWeight: "600" }), plan.note ? muted(plan.note) : null),
2588
+ (0, import_react2.createElement)(RN.View, { style: { flex: 1 } }, text2(plan.name, { fontWeight: "600" }), plan.note ? muted(plan.note) : null),
2153
2589
  text2(plan.price, { fontWeight: "700" })
2154
2590
  )
2155
2591
  )
@@ -2164,22 +2600,22 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2164
2600
  if (pw) return paywallSheet(pw);
2165
2601
  if (!v) return null;
2166
2602
  if (v.type === "cancel") {
2167
- const top = (0, import_react.createElement)(
2603
+ const top = (0, import_react2.createElement)(
2168
2604
  RN.View,
2169
2605
  { style: { flexDirection: "row", alignItems: "center", gap: 4 } },
2170
2606
  ...v.steps > 1 ? Array.from(
2171
2607
  { length: v.steps },
2172
- (_, i) => (0, import_react.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i < v.step ? p.accent : p.line } })
2608
+ (_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i < v.step ? p.accent : p.line } })
2173
2609
  ) : [],
2174
2610
  close("Close", flows.cancel.close)
2175
2611
  );
2176
2612
  const error = v.error ? text2(v.error, { color: p.danger, fontSize: 13 }) : null;
2177
2613
  let body = [];
2178
- const choices = (items) => (0, import_react.createElement)(
2614
+ const choices = (items) => (0, import_react2.createElement)(
2179
2615
  RN.ScrollView,
2180
2616
  { style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
2181
2617
  ...items.map(
2182
- (c) => (0, import_react.createElement)(
2618
+ (c) => (0, import_react2.createElement)(
2183
2619
  RN.Pressable,
2184
2620
  {
2185
2621
  key: c.key,
@@ -2224,7 +2660,7 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2224
2660
  body = [
2225
2661
  label ? muted(`You said: ${label}`) : null,
2226
2662
  title(o.headline),
2227
- detail ? (0, import_react.createElement)(RN.View, { style: { backgroundColor: p.soft, borderRadius: 10, padding: 12 } }, text2(detail, { fontWeight: "600" })) : null,
2663
+ detail ? (0, import_react2.createElement)(RN.View, { style: { backgroundColor: p.soft, borderRadius: 10, padding: 12 } }, text2(detail, { fontWeight: "600" })) : null,
2228
2664
  o.kind === "contact" ? input("What would make you stay?") : null,
2229
2665
  btn(v.busy ? "One moment\u2026" : offerButton(o), "primary", () => flows.cancel.accept(comment), v.busy),
2230
2666
  btn("No thanks, continue cancelling", "quiet", flows.cancel.decline, v.busy)
@@ -2243,7 +2679,7 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2243
2679
  const m2 = mediaOf(s);
2244
2680
  if (!m2) return null;
2245
2681
  if (m2.kind === "image")
2246
- return RN.Image ? (0, import_react.createElement)(RN.Image, {
2682
+ return RN.Image ? (0, import_react2.createElement)(RN.Image, {
2247
2683
  key: "media",
2248
2684
  source: { uri: m2.url },
2249
2685
  accessibilityLabel: m2.alt,
@@ -2255,15 +2691,75 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2255
2691
  return RN.Linking ? btn("\u25B6 Watch the video", "secondary", () => void RN.Linking?.openURL(open).catch(() => {
2256
2692
  })) : null;
2257
2693
  };
2694
+ if (v.type === "step" && v.step.kind === "question") {
2695
+ const s = v.step;
2696
+ const q = s.question;
2697
+ const ok = answerOk(q, v.answer);
2698
+ return sheet(
2699
+ [
2700
+ (0, import_react2.createElement)(
2701
+ RN.View,
2702
+ { key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
2703
+ ...v.total > 1 ? Array.from(
2704
+ { length: v.total },
2705
+ (_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= v.index ? p.accent : p.line } })
2706
+ ) : [],
2707
+ close("Close", flows.onboarding.dismiss)
2708
+ ),
2709
+ v.index === 0 && v.flow.icon ? (0, import_react2.createElement)(RN.View, { key: "icon" }, iconNode(v.flow.icon, 36, p.accent)) : null,
2710
+ title(s.title),
2711
+ s.body ? muted(s.body, { fontSize: 15 }) : null,
2712
+ q?.type === "choice" ? (0, import_react2.createElement)(
2713
+ RN.ScrollView,
2714
+ { key: "opts", style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
2715
+ ...(q.options ?? []).map(
2716
+ (o) => (0, import_react2.createElement)(
2717
+ RN.Pressable,
2718
+ {
2719
+ key: o.id,
2720
+ onPress: () => flows.onboarding.answer(o.id),
2721
+ accessibilityRole: "radio",
2722
+ accessibilityState: { checked: v.answer === o.id },
2723
+ style: {
2724
+ minHeight: 44,
2725
+ paddingHorizontal: 12,
2726
+ borderRadius: 10,
2727
+ borderWidth: 1,
2728
+ borderColor: v.answer === o.id ? p.accent : p.line,
2729
+ backgroundColor: v.answer === o.id ? p.soft : p.bg,
2730
+ justifyContent: "center"
2731
+ }
2732
+ },
2733
+ text2(o.label, { fontWeight: "500" })
2734
+ )
2735
+ )
2736
+ ) : q ? (0, import_react2.createElement)(RN.TextInput, {
2737
+ key: "answer",
2738
+ value: v.answer ?? "",
2739
+ onChangeText: (t) => flows.onboarding.answer(t),
2740
+ onSubmitEditing: flows.onboarding.next,
2741
+ placeholder: q.placeholder || "Your answer",
2742
+ placeholderTextColor: p.muted,
2743
+ maxLength: 500,
2744
+ accessibilityLabel: s.title,
2745
+ style: { height: 44, borderWidth: 1, borderColor: p.line, borderRadius: 10, paddingHorizontal: 12, color: p.fg }
2746
+ }) : null,
2747
+ btn(s.button || (v.last ? "Done" : "Continue"), "primary", flows.onboarding.next, !ok),
2748
+ v.back ? btn("Back", "quiet", flows.onboarding.back) : null
2749
+ ],
2750
+ flows.onboarding.dismiss
2751
+ );
2752
+ }
2258
2753
  if (v.type === "step")
2259
2754
  return sheet(
2260
2755
  [
2261
- (0, import_react.createElement)(
2756
+ (0, import_react2.createElement)(
2262
2757
  RN.View,
2263
2758
  { style: { flexDirection: "row", alignItems: "center" } },
2264
2759
  v.total > 1 ? muted(`Step ${v.index + 1} of ${v.total}`) : null,
2265
2760
  close("Close the tour", flows.onboarding.dismiss)
2266
2761
  ),
2762
+ v.step.kind === "modal" && v.flow.icon ? (0, import_react2.createElement)(RN.View, { key: "icon" }, iconNode(v.flow.icon, 36, p.accent)) : null,
2267
2763
  media(v.step),
2268
2764
  title(v.step.title),
2269
2765
  v.step.body ? muted(v.step.body, { fontSize: 15 }) : null,
@@ -2273,11 +2769,11 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2273
2769
  );
2274
2770
  if (v.type === "checklist") {
2275
2771
  const done = v.items.filter((i) => i.done).length;
2276
- return (0, import_react.createElement)(
2772
+ return (0, import_react2.createElement)(
2277
2773
  RN.View,
2278
2774
  { pointerEvents: "box-none", style: { position: "absolute", left: 12, right: 12, bottom: 28 } },
2279
2775
  card([
2280
- (0, import_react.createElement)(
2776
+ (0, import_react2.createElement)(
2281
2777
  RN.View,
2282
2778
  { key: "t", style: { flexDirection: "row", alignItems: "center" } },
2283
2779
  title(v.flow.name),
@@ -2285,18 +2781,19 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2285
2781
  ),
2286
2782
  muted(`${done} of ${v.items.length} done`),
2287
2783
  ...v.items.map(
2288
- ({ step, done: d, tour }) => (0, import_react.createElement)(
2784
+ ({ step, done: d, opens }) => (0, import_react2.createElement)(
2289
2785
  RN.Pressable,
2290
2786
  {
2291
2787
  key: step.id,
2292
- // 0.17.0: an item that starts a tour opens it; finishing the tour ticks it.
2293
- disabled: d || !!step.event && !tour,
2294
- onPress: () => tour ? flows.onboarding.startTour(step.id) : flows.onboarding.check(step.id),
2295
- accessibilityRole: tour && !d ? "button" : "checkbox",
2788
+ // 0.17.0/0.18.0: a task that opens something (a tour, a modal, a screen, the app's code) does it;
2789
+ // a finished tour or modal ticks it, a screen or the app's code once opened (unless an event does).
2790
+ disabled: d || !!step.event && !opens,
2791
+ onPress: () => opens ? flows.onboarding.open(step.id) : flows.onboarding.check(step.id),
2792
+ accessibilityRole: opens && !d ? "button" : "checkbox",
2296
2793
  accessibilityState: { checked: d },
2297
2794
  style: { flexDirection: "row", gap: 10, alignItems: "flex-start", paddingVertical: 6 }
2298
2795
  },
2299
- (0, import_react.createElement)(
2796
+ (0, import_react2.createElement)(
2300
2797
  RN.View,
2301
2798
  {
2302
2799
  style: {
@@ -2312,7 +2809,8 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2312
2809
  },
2313
2810
  d ? text2("\u2713", { color: "#fff", fontSize: 12 }) : null
2314
2811
  ),
2315
- (0, import_react.createElement)(
2812
+ step.icon ? iconNode(step.icon, 20, p.accent) : null,
2813
+ (0, import_react2.createElement)(
2316
2814
  RN.View,
2317
2815
  { style: { flex: 1 } },
2318
2816
  text2(step.title, { fontWeight: "600", color: d ? p.muted : p.fg, textDecorationLine: d ? "line-through" : "none" }),
@@ -2325,14 +2823,14 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2325
2823
  }
2326
2824
  const m = v.message;
2327
2825
  if (m.style === "banner")
2328
- return (0, import_react.createElement)(
2826
+ return (0, import_react2.createElement)(
2329
2827
  RN.View,
2330
2828
  { pointerEvents: "box-none", style: { position: "absolute", left: 12, right: 12, top: 56 } },
2331
2829
  card([
2332
- (0, import_react.createElement)(
2830
+ (0, import_react2.createElement)(
2333
2831
  RN.View,
2334
2832
  { key: "t", style: { flexDirection: "row", alignItems: "center", gap: 8 } },
2335
- (0, import_react.createElement)(RN.View, { style: { flex: 1 } }, text2(m.title, { fontWeight: "700" }), m.body ? muted(m.body) : null),
2833
+ (0, import_react2.createElement)(RN.View, { style: { flex: 1 } }, text2(m.title, { fontWeight: "700" }), m.body ? muted(m.body) : null),
2336
2834
  close("Dismiss", flows.message.dismiss)
2337
2835
  ),
2338
2836
  m.button ? btn(m.button, "primary", flows.message.click) : null
@@ -2340,7 +2838,7 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
2340
2838
  );
2341
2839
  return sheet(
2342
2840
  [
2343
- (0, import_react.createElement)(RN.View, { key: "t", style: { flexDirection: "row" } }, close("Dismiss", flows.message.dismiss)),
2841
+ (0, import_react2.createElement)(RN.View, { key: "t", style: { flexDirection: "row" } }, close("Dismiss", flows.message.dismiss)),
2344
2842
  title(m.title),
2345
2843
  m.body ? muted(m.body, { fontSize: 15 }) : null,
2346
2844
  m.button ? btn(m.button, "primary", flows.message.click) : null
@@ -2523,7 +3021,7 @@ var optionalModules = () => ({
2523
3021
  });
2524
3022
 
2525
3023
  // src/native/widgets-view.ts
2526
- var import_react2 = require("react");
3024
+ var import_react3 = require("react");
2527
3025
  var RN3 = require("react-native");
2528
3026
  var usePalette = (appearance = {}) => {
2529
3027
  const scheme = RN3.useColorScheme?.();
@@ -2531,10 +3029,10 @@ var usePalette = (appearance = {}) => {
2531
3029
  return { ...dark ? DARK : LIGHT, accent: appearance.accent ?? "#3f76ff", danger: "#d92d20" };
2532
3030
  };
2533
3031
  var kit = (p) => {
2534
- const text2 = (children, style = {}) => (0, import_react2.createElement)(RN3.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
3032
+ const text2 = (children, style = {}) => (0, import_react3.createElement)(RN3.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
2535
3033
  const muted = (children, style = {}) => text2(children, { color: p.muted, fontSize: 13, ...style });
2536
- const title = (children) => (0, import_react2.createElement)(RN3.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 17, fontWeight: "700", flex: 1 } }, children);
2537
- const btn = (label, kind, onPress, disabled = false, style = {}) => (0, import_react2.createElement)(
3034
+ const title = (children) => (0, import_react3.createElement)(RN3.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 17, fontWeight: "700", flex: 1 } }, children);
3035
+ const btn = (label, kind, onPress, disabled = false, style = {}) => (0, import_react3.createElement)(
2538
3036
  RN3.Pressable,
2539
3037
  {
2540
3038
  key: label,
@@ -2557,8 +3055,8 @@ var kit = (p) => {
2557
3055
  },
2558
3056
  text2(label, { color: kind === "primary" ? "#ffffff" : p.fg, fontWeight: "600" })
2559
3057
  );
2560
- const closeX = (label, onPress) => (0, import_react2.createElement)(RN3.Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, hitSlop: 12, style: { padding: 4 } }, text2("\xD7", { fontSize: 24, color: p.muted }));
2561
- const field = (o) => (0, import_react2.createElement)(RN3.TextInput, {
3058
+ const closeX = (label, onPress) => (0, import_react3.createElement)(RN3.Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, hitSlop: 12, style: { padding: 4 } }, text2("\xD7", { fontSize: 24, color: p.muted }));
3059
+ const field = (o) => (0, import_react3.createElement)(RN3.TextInput, {
2562
3060
  value: o.value,
2563
3061
  onChangeText: o.onChange,
2564
3062
  placeholder: o.placeholder,
@@ -2581,7 +3079,7 @@ var kit = (p) => {
2581
3079
  ...o.style
2582
3080
  }
2583
3081
  });
2584
- const chip = (label, on, onPress, key = label) => (0, import_react2.createElement)(
3082
+ const chip = (label, on, onPress, key = label) => (0, import_react3.createElement)(
2585
3083
  RN3.Pressable,
2586
3084
  {
2587
3085
  key,
@@ -2592,13 +3090,13 @@ var kit = (p) => {
2592
3090
  },
2593
3091
  text2(label, { fontSize: 13, fontWeight: "600", color: on ? p.accent : p.fg })
2594
3092
  );
2595
- const page = (heading, onClose, children) => (0, import_react2.createElement)(
3093
+ const page = (heading, onClose, children) => (0, import_react3.createElement)(
2596
3094
  RN3.Modal,
2597
3095
  { visible: true, animationType: "slide", presentationStyle: "pageSheet", onRequestClose: onClose },
2598
- (0, import_react2.createElement)(
3096
+ (0, import_react3.createElement)(
2599
3097
  RN3.KeyboardAvoidingView,
2600
3098
  { behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: p.bg } },
2601
- (0, import_react2.createElement)(
3099
+ (0, import_react3.createElement)(
2602
3100
  RN3.View,
2603
3101
  { style: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingTop: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: p.line } },
2604
3102
  title(heading),
@@ -2626,8 +3124,8 @@ var opener = () => {
2626
3124
  listeners.forEach((l) => l(v));
2627
3125
  };
2628
3126
  const useOpen = () => {
2629
- const [v, setV] = (0, import_react2.useState)(open);
2630
- (0, import_react2.useEffect)(() => {
3127
+ const [v, setV] = (0, import_react3.useState)(open);
3128
+ (0, import_react3.useEffect)(() => {
2631
3129
  listeners.add(setV);
2632
3130
  setV(open);
2633
3131
  return () => {
@@ -2641,19 +3139,19 @@ var opener = () => {
2641
3139
  var isAnswered = (a) => Array.isArray(a) ? a.length > 0 : typeof a === "number" ? true : !!a?.trim();
2642
3140
  var SurveySheet = ({ survey, api, p, onDone }) => {
2643
3141
  const k = kit(p);
2644
- const [step, setStep] = (0, import_react2.useState)(0);
2645
- const [answers, setAnswers] = (0, import_react2.useState)({});
2646
- const [done, setDone] = (0, import_react2.useState)(false);
2647
- const [error, setError] = (0, import_react2.useState)("");
2648
- const started = (0, import_react2.useRef)(Date.now());
2649
- const finished = (0, import_react2.useRef)(false);
3142
+ const [step, setStep] = (0, import_react3.useState)(0);
3143
+ const [answers, setAnswers] = (0, import_react3.useState)({});
3144
+ const [done, setDone] = (0, import_react3.useState)(false);
3145
+ const [error, setError] = (0, import_react3.useState)("");
3146
+ const started = (0, import_react3.useRef)(Date.now());
3147
+ const finished = (0, import_react3.useRef)(false);
2650
3148
  const q = survey.questions[step];
2651
3149
  const last = step === survey.questions.length - 1;
2652
3150
  const value = answers[q.key];
2653
- (0, import_react2.useEffect)(() => {
3151
+ (0, import_react3.useEffect)(() => {
2654
3152
  api.seen(survey.id).catch(() => void 0);
2655
3153
  }, [api, survey.id]);
2656
- (0, import_react2.useEffect)(() => {
3154
+ (0, import_react3.useEffect)(() => {
2657
3155
  if (!done) return;
2658
3156
  const t = setTimeout(onDone, 2500);
2659
3157
  return () => clearTimeout(t);
@@ -2682,16 +3180,16 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
2682
3180
  if (q.kind === "nps" || q.kind === "rating") {
2683
3181
  const scale = q.kind === "nps" ? 11 : Math.min(Math.max(q.scale ?? 5, 2), 10);
2684
3182
  const start = q.kind === "nps" ? 0 : 1;
2685
- input = (0, import_react2.createElement)(
3183
+ input = (0, import_react3.createElement)(
2686
3184
  RN3.View,
2687
3185
  { style: { gap: 6 } },
2688
- (0, import_react2.createElement)(
3186
+ (0, import_react3.createElement)(
2689
3187
  RN3.View,
2690
3188
  { style: { flexDirection: "row", flexWrap: "wrap", gap: 6 }, accessibilityRole: "radiogroup", accessibilityLabel: q.title },
2691
3189
  ...Array.from({ length: scale }, (_, i) => {
2692
3190
  const n = i + start;
2693
3191
  const on = value === n;
2694
- return (0, import_react2.createElement)(
3192
+ return (0, import_react3.createElement)(
2695
3193
  RN3.Pressable,
2696
3194
  {
2697
3195
  key: n,
@@ -2705,17 +3203,17 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
2705
3203
  );
2706
3204
  })
2707
3205
  ),
2708
- q.low || q.high || q.kind === "nps" ? (0, import_react2.createElement)(RN3.View, { style: { flexDirection: "row", justifyContent: "space-between" } }, k.muted(q.low ?? (q.kind === "nps" ? "Not likely" : "")), k.muted(q.high ?? (q.kind === "nps" ? "Very likely" : ""))) : null
3206
+ q.low || q.high || q.kind === "nps" ? (0, import_react3.createElement)(RN3.View, { style: { flexDirection: "row", justifyContent: "space-between" } }, k.muted(q.low ?? (q.kind === "nps" ? "Not likely" : "")), k.muted(q.high ?? (q.kind === "nps" ? "Very likely" : ""))) : null
2709
3207
  );
2710
3208
  } else if (q.kind === "single" || q.kind === "multi") {
2711
3209
  const multi = q.kind === "multi";
2712
3210
  const picked = multi ? value ?? [] : [];
2713
- input = (0, import_react2.createElement)(
3211
+ input = (0, import_react3.createElement)(
2714
3212
  RN3.View,
2715
3213
  { style: { gap: 6 } },
2716
3214
  ...(q.options ?? []).map((o) => {
2717
3215
  const on = multi ? picked.includes(o) : value === o;
2718
- return (0, import_react2.createElement)(
3216
+ return (0, import_react3.createElement)(
2719
3217
  RN3.Pressable,
2720
3218
  {
2721
3219
  key: o,
@@ -2730,26 +3228,26 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
2730
3228
  );
2731
3229
  } else input = k.field({ value: value ?? "", onChange: pick, placeholder: q.placeholder ?? "Type your answer", multiline: true });
2732
3230
  const body = done ? [k.text(survey.thanks || "Thanks for your feedback.", { fontWeight: "600", textAlign: "center", paddingVertical: 16 })] : [
2733
- (0, import_react2.createElement)(
3231
+ (0, import_react3.createElement)(
2734
3232
  RN3.View,
2735
3233
  { key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
2736
- ...survey.questions.map((x, i) => (0, import_react2.createElement)(RN3.View, { key: x.key, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= step ? p.accent : p.line } })),
2737
- (0, import_react2.createElement)(RN3.View, { style: { flex: 1 } }),
3234
+ ...survey.questions.map((x, i) => (0, import_react3.createElement)(RN3.View, { key: x.key, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= step ? p.accent : p.line } })),
3235
+ (0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
2738
3236
  k.closeX("Close survey", dismiss)
2739
3237
  ),
2740
- (0, import_react2.createElement)(RN3.Text, { key: "q", accessibilityRole: "header", style: { color: p.fg, fontSize: 17, fontWeight: "700" } }, q.title, q.required ? "" : (0, import_react2.createElement)(RN3.Text, { style: { color: p.muted, fontSize: 13, fontWeight: "400" } }, " optional")),
3238
+ (0, import_react3.createElement)(RN3.Text, { key: "q", accessibilityRole: "header", style: { color: p.fg, fontSize: 17, fontWeight: "700" } }, q.title, q.required ? "" : (0, import_react3.createElement)(RN3.Text, { style: { color: p.muted, fontSize: 13, fontWeight: "400" } }, " optional")),
2741
3239
  q.description ? k.muted(q.description) : null,
2742
3240
  input,
2743
3241
  error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
2744
3242
  k.btn(last ? "Send" : "Next", "primary", next)
2745
3243
  ];
2746
- return (0, import_react2.createElement)(
3244
+ return (0, import_react3.createElement)(
2747
3245
  RN3.Modal,
2748
3246
  { visible: true, transparent: true, animationType: "slide", onRequestClose: dismiss },
2749
- (0, import_react2.createElement)(
3247
+ (0, import_react3.createElement)(
2750
3248
  RN3.KeyboardAvoidingView,
2751
3249
  { behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: "rgba(0,0,0,0.35)", justifyContent: "flex-end" } },
2752
- (0, import_react2.createElement)(
3250
+ (0, import_react3.createElement)(
2753
3251
  RN3.View,
2754
3252
  {
2755
3253
  accessibilityViewIsModal: true,
@@ -2763,10 +3261,10 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
2763
3261
  };
2764
3262
  var MRRocketSurveys = ({ client, appearance }) => {
2765
3263
  const p = usePalette(appearance);
2766
- const [survey, setSurvey] = (0, import_react2.useState)(null);
2767
- const [who2, setWho] = (0, import_react2.useState)(() => client.getDistinctId());
2768
- (0, import_react2.useEffect)(() => client.subscribe(() => setWho(client.getDistinctId())), [client]);
2769
- (0, import_react2.useEffect)(() => {
3264
+ const [survey, setSurvey] = (0, import_react3.useState)(null);
3265
+ const [who2, setWho] = (0, import_react3.useState)(() => client.getDistinctId());
3266
+ (0, import_react3.useEffect)(() => client.subscribe(() => setWho(client.getDistinctId())), [client]);
3267
+ (0, import_react3.useEffect)(() => {
2770
3268
  let stop = false;
2771
3269
  const ctrl = new AbortController();
2772
3270
  const timer = setTimeout(() => {
@@ -2786,33 +3284,33 @@ var MRRocketSurveys = ({ client, appearance }) => {
2786
3284
  ctrl.abort();
2787
3285
  };
2788
3286
  }, [client, who2]);
2789
- const done = (0, import_react2.useCallback)(() => {
3287
+ const done = (0, import_react3.useCallback)(() => {
2790
3288
  setSurvey((cur) => {
2791
3289
  if (cur) client.markSurveyDone(cur.s.id);
2792
3290
  return null;
2793
3291
  });
2794
3292
  }, [client]);
2795
3293
  if (!survey) return null;
2796
- return (0, import_react2.createElement)(SurveySheet, { key: survey.s.id, survey: survey.s, api: survey.api, p, onDone: done });
3294
+ return (0, import_react3.createElement)(SurveySheet, { key: survey.s.id, survey: survey.s, api: survey.api, p, onDone: done });
2797
3295
  };
2798
3296
  var feedbackOpen = opener();
2799
3297
  var useFeedback = () => ({ open: () => feedbackOpen.set(true), close: () => feedbackOpen.set(false) });
2800
3298
  var FeedbackSheet = ({ client, p, title }) => {
2801
3299
  const k = kit(p);
2802
- const [tab, setTab] = (0, import_react2.useState)("top");
2803
- const [posts, setPosts] = (0, import_react2.useState)(null);
2804
- const [boards, setBoards] = (0, import_react2.useState)([]);
2805
- const [error, setError] = (0, import_react2.useState)("");
2806
- const [board, setBoard] = (0, import_react2.useState)("feature");
2807
- const [subject, setSubject] = (0, import_react2.useState)("");
2808
- const [details, setDetails] = (0, import_react2.useState)("");
2809
- const [name, setName] = (0, import_react2.useState)("");
2810
- const [email, setEmail] = (0, import_react2.useState)("");
2811
- const [sending, setSending] = (0, import_react2.useState)(false);
2812
- const [sent, setSent] = (0, import_react2.useState)(false);
3300
+ const [tab, setTab] = (0, import_react3.useState)("top");
3301
+ const [posts, setPosts] = (0, import_react3.useState)(null);
3302
+ const [boards, setBoards] = (0, import_react3.useState)([]);
3303
+ const [error, setError] = (0, import_react3.useState)("");
3304
+ const [board, setBoard] = (0, import_react3.useState)("feature");
3305
+ const [subject, setSubject] = (0, import_react3.useState)("");
3306
+ const [details, setDetails] = (0, import_react3.useState)("");
3307
+ const [name, setName] = (0, import_react3.useState)("");
3308
+ const [email, setEmail] = (0, import_react3.useState)("");
3309
+ const [sending, setSending] = (0, import_react3.useState)(false);
3310
+ const [sent, setSent] = (0, import_react3.useState)(false);
2813
3311
  const close = () => feedbackOpen.set(false);
2814
3312
  const user = client.getUser();
2815
- (0, import_react2.useEffect)(() => {
3313
+ (0, import_react3.useEffect)(() => {
2816
3314
  const api = client.api();
2817
3315
  if (!api) return setError("Feedback is off while tracking is off.");
2818
3316
  const ctrl = new AbortController();
@@ -2844,7 +3342,7 @@ var FeedbackSheet = ({ client, p, title }) => {
2844
3342
  setSent(true);
2845
3343
  }).catch((e) => setError(e.message)).finally(() => setSending(false));
2846
3344
  };
2847
- const tabs = (0, import_react2.createElement)(
3345
+ const tabs = (0, import_react3.createElement)(
2848
3346
  RN3.View,
2849
3347
  { style: { flexDirection: "row", gap: 8, paddingHorizontal: 16, paddingTop: 12 } },
2850
3348
  k.chip("Top requests", tab === "top", () => setTab("top")),
@@ -2853,17 +3351,17 @@ var FeedbackSheet = ({ client, p, title }) => {
2853
3351
  setSent(false);
2854
3352
  })
2855
3353
  );
2856
- const list = (0, import_react2.createElement)(
3354
+ const list = (0, import_react3.createElement)(
2857
3355
  RN3.ScrollView,
2858
3356
  { style: { flex: 1 }, contentContainerStyle: { padding: 16, gap: 10 } },
2859
3357
  error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
2860
3358
  posts === null && !error ? k.muted("Loading\u2026") : null,
2861
3359
  posts?.length === 0 ? k.muted("No requests yet. Be the first to suggest one.") : null,
2862
3360
  ...(posts ?? []).map(
2863
- (post) => (0, import_react2.createElement)(
3361
+ (post) => (0, import_react3.createElement)(
2864
3362
  RN3.View,
2865
3363
  { key: post.id, style: { flexDirection: "row", gap: 12, alignItems: "flex-start", paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: p.line } },
2866
- (0, import_react2.createElement)(
3364
+ (0, import_react3.createElement)(
2867
3365
  RN3.Pressable,
2868
3366
  {
2869
3367
  onPress: () => vote(post),
@@ -2875,15 +3373,15 @@ var FeedbackSheet = ({ client, p, title }) => {
2875
3373
  k.text("\u25B2", { fontSize: 11, color: post.voted ? p.accent : p.muted }),
2876
3374
  k.text(String(post.votes), { fontWeight: "700", fontSize: 14, color: post.voted ? p.accent : p.fg })
2877
3375
  ),
2878
- (0, import_react2.createElement)(RN3.View, { style: { flex: 1, gap: 2 } }, k.text(post.title, { fontWeight: "600" }), post.body ? k.muted(post.body.slice(0, 160)) : null)
3376
+ (0, import_react3.createElement)(RN3.View, { style: { flex: 1, gap: 2 } }, k.text(post.title, { fontWeight: "600" }), post.body ? k.muted(post.body.slice(0, 160)) : null)
2879
3377
  )
2880
3378
  )
2881
3379
  );
2882
- const form = (0, import_react2.createElement)(
3380
+ const form = (0, import_react3.createElement)(
2883
3381
  RN3.ScrollView,
2884
3382
  { style: { flex: 1 }, contentContainerStyle: { padding: 16, gap: 10 }, keyboardShouldPersistTaps: "handled" },
2885
3383
  sent ? k.text("Thanks! Your request is on the board.", { fontWeight: "600" }) : null,
2886
- boards.length > 1 ? (0, import_react2.createElement)(RN3.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: 6 } }, ...boards.map((b) => k.chip(b.label, board === b.id, () => setBoard(b.id), b.id))) : null,
3384
+ boards.length > 1 ? (0, import_react3.createElement)(RN3.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: 6 } }, ...boards.map((b) => k.chip(b.label, board === b.id, () => setBoard(b.id), b.id))) : null,
2887
3385
  k.field({ value: subject, onChange: setSubject, placeholder: "Short title", max: 120 }),
2888
3386
  k.field({ value: details, onChange: setDetails, placeholder: "Details (optional)", multiline: true }),
2889
3387
  ...user?.email ? [] : [
@@ -2899,7 +3397,7 @@ var FeedbackSheet = ({ client, p, title }) => {
2899
3397
  var MRRocketFeedback = ({ client, appearance, options = {} }) => {
2900
3398
  const p = usePalette(appearance);
2901
3399
  const open = feedbackOpen.useOpen();
2902
- return open ? (0, import_react2.createElement)(FeedbackSheet, { client, p, title: options.title ?? "Feedback" }) : null;
3400
+ return open ? (0, import_react3.createElement)(FeedbackSheet, { client, p, title: options.title ?? "Feedback" }) : null;
2903
3401
  };
2904
3402
  var POLL_OPEN_MS = 4e3;
2905
3403
  var POLL_WAITING_MS = 8e3;
@@ -2939,11 +3437,11 @@ try {
2939
3437
  }
2940
3438
  var PHOTO_TYPES = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif" };
2941
3439
  var teamOf = (t) => (t?.messages ?? []).filter((m) => m.from !== "visitor");
2942
- var svg = null;
3440
+ var svg2 = null;
2943
3441
  try {
2944
- svg = require("react-native-svg");
3442
+ svg2 = require("react-native-svg");
2945
3443
  } catch {
2946
- svg = null;
3444
+ svg2 = null;
2947
3445
  }
2948
3446
  var GLYPH = {
2949
3447
  chat: "M2.5 4.5a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v4.5a2 2 0 0 1-2 2H7l-3 2.5V11a2 2 0 0 1-1.5-2z",
@@ -2958,15 +3456,15 @@ var GLYPH = {
2958
3456
  };
2959
3457
  var FALLBACK = { chat: "\u{1F4AC}", home: "\u2302", help: "?", send: "\u2192", up: "\u2191", next: "\u203A", search: "\u2315", cross: "\xD7", clip: "\u{1F4CE}" };
2960
3458
  var glyph = (name, color, size = 20) => {
2961
- const Svg = svg?.Svg ?? svg?.default;
2962
- if (!svg || !Svg) return (0, import_react2.createElement)(RN3.Text, { style: { color, fontSize: Math.round(size * 0.85), lineHeight: size + 2, textAlign: "center", minWidth: size } }, FALLBACK[name]);
2963
- return (0, import_react2.createElement)(Svg, { width: size, height: size, viewBox: "0 0 16 16", fill: "none" }, (0, import_react2.createElement)(svg.Path, { d: GLYPH[name], stroke: color, strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }));
3459
+ const Svg = svg2?.Svg ?? svg2?.default;
3460
+ if (!svg2 || !Svg) return (0, import_react3.createElement)(RN3.Text, { style: { color, fontSize: Math.round(size * 0.85), lineHeight: size + 2, textAlign: "center", minWidth: size } }, FALLBACK[name]);
3461
+ return (0, import_react3.createElement)(Svg, { width: size, height: size, viewBox: "0 0 16 16", fill: "none" }, (0, import_react3.createElement)(svg2.Path, { d: GLYPH[name], stroke: color, strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }));
2964
3462
  };
2965
3463
  var alpha = (hex, a) => /^#[0-9a-f]{6}$/i.test(hex) ? `${hex}${Math.round(a * 255).toString(16).padStart(2, "0")}` : hex;
2966
- var teamMark = (title, bg, fg, size = 36) => (0, import_react2.createElement)(
3464
+ var teamMark = (title, bg, fg, size = 36) => (0, import_react3.createElement)(
2967
3465
  RN3.View,
2968
3466
  { style: { width: size, height: size, borderRadius: size / 2, backgroundColor: bg, alignItems: "center", justifyContent: "center" } },
2969
- (0, import_react2.createElement)(RN3.Text, { style: { color: fg, fontSize: Math.round(size * 0.42), fontWeight: "700" } }, (title.trim()[0] ?? "?").toUpperCase())
3467
+ (0, import_react3.createElement)(RN3.Text, { style: { color: fg, fontSize: Math.round(size * 0.42), fontWeight: "700" } }, (title.trim()[0] ?? "?").toUpperCase())
2970
3468
  );
2971
3469
  var HU = {
2972
3470
  light: { bg: "#ffffff", surface: "#f4f4f5", def: "#ececee", segment: "#ffffff", fg: "#18181b", muted: "#71717a" },
@@ -2974,20 +3472,20 @@ var HU = {
2974
3472
  };
2975
3473
  var hairline = { shadowColor: "#000", shadowOpacity: 0.07, shadowRadius: 3, shadowOffset: { width: 0, height: 1 }, elevation: 1 };
2976
3474
  var useChat = (client, o, open, view) => {
2977
- const [thread, setThread] = (0, import_react2.useState)(null);
2978
- const [loaded, setLoaded] = (0, import_react2.useState)(false);
2979
- const [error, setError] = (0, import_react2.useState)("");
2980
- const [unseen, setUnseen] = (0, import_react2.useState)([]);
2981
- const known = (0, import_react2.useRef)(null);
3475
+ const [thread, setThread] = (0, import_react3.useState)(null);
3476
+ const [loaded, setLoaded] = (0, import_react3.useState)(false);
3477
+ const [error, setError] = (0, import_react3.useState)("");
3478
+ const [unseen, setUnseen] = (0, import_react3.useState)([]);
3479
+ const known = (0, import_react3.useRef)(null);
2982
3480
  const watching = open && view === "messages";
2983
- const watch = (0, import_react2.useRef)(watching);
3481
+ const watch = (0, import_react3.useRef)(watching);
2984
3482
  watch.current = watching;
2985
- const [active, setActive] = (0, import_react2.useState)(RN3.AppState.currentState === "active");
2986
- (0, import_react2.useEffect)(() => {
3483
+ const [active, setActive] = (0, import_react3.useState)(RN3.AppState.currentState === "active");
3484
+ (0, import_react3.useEffect)(() => {
2987
3485
  const sub = RN3.AppState.addEventListener("change", (st) => setActive(st === "active"));
2988
3486
  return () => sub.remove();
2989
3487
  }, []);
2990
- const take = (0, import_react2.useCallback)(
3488
+ const take = (0, import_react3.useCallback)(
2991
3489
  (t) => {
2992
3490
  const team = teamOf(t);
2993
3491
  if (known.current !== null) {
@@ -3003,10 +3501,10 @@ var useChat = (client, o, open, view) => {
3003
3501
  },
3004
3502
  [o.haptics]
3005
3503
  );
3006
- (0, import_react2.useEffect)(() => {
3504
+ (0, import_react3.useEffect)(() => {
3007
3505
  if (watching) setUnseen([]);
3008
3506
  }, [watching]);
3009
- (0, import_react2.useEffect)(() => {
3507
+ (0, import_react3.useEffect)(() => {
3010
3508
  if (!active) return;
3011
3509
  let stop = false;
3012
3510
  let timer;
@@ -3046,9 +3544,9 @@ var useChat = (client, o, open, view) => {
3046
3544
  return { thread, loaded, error, setError, unseen, take };
3047
3545
  };
3048
3546
  var useArticles = (client, on, query) => {
3049
- const [list, setList] = (0, import_react2.useState)(null);
3050
- const [any, setAny] = (0, import_react2.useState)(null);
3051
- (0, import_react2.useEffect)(() => {
3547
+ const [list, setList] = (0, import_react3.useState)(null);
3548
+ const [any, setAny] = (0, import_react3.useState)(null);
3549
+ (0, import_react3.useEffect)(() => {
3052
3550
  if (!on) return;
3053
3551
  let stop = false;
3054
3552
  const t = setTimeout(
@@ -3069,7 +3567,7 @@ var useArticles = (client, on, query) => {
3069
3567
  return { list, any };
3070
3568
  };
3071
3569
  var spansOf = (p, spans, base) => spans.map(
3072
- (s, i) => (0, import_react2.createElement)(
3570
+ (s, i) => (0, import_react3.createElement)(
3073
3571
  RN3.Text,
3074
3572
  {
3075
3573
  key: i,
@@ -3088,10 +3586,10 @@ var spansOf = (p, spans, base) => spans.map(
3088
3586
  );
3089
3587
  var ArticleView = ({ client, p, id, onAsk }) => {
3090
3588
  const k = kit(p);
3091
- const [a, setA] = (0, import_react2.useState)(null);
3092
- const [error, setError] = (0, import_react2.useState)("");
3093
- const [rated, setRated] = (0, import_react2.useState)(false);
3094
- (0, import_react2.useEffect)(() => {
3589
+ const [a, setA] = (0, import_react3.useState)(null);
3590
+ const [error, setError] = (0, import_react3.useState)("");
3591
+ const [rated, setRated] = (0, import_react3.useState)(false);
3592
+ (0, import_react3.useEffect)(() => {
3095
3593
  const ctrl = new AbortController();
3096
3594
  client.api()?.article(id, ctrl.signal).then(setA).catch((e) => e.name !== "AbortError" && setError(e.message));
3097
3595
  return () => ctrl.abort();
@@ -3100,30 +3598,30 @@ var ArticleView = ({ client, p, id, onAsk }) => {
3100
3598
  if (!a) return k.muted("Loading\u2026");
3101
3599
  const base = { color: p.fg, fontSize: 15, lineHeight: 22 };
3102
3600
  const blocks = a.blocks.map((b, i) => {
3103
- if (b.type === "h2" || b.type === "h3") return (0, import_react2.createElement)(RN3.Text, { key: i, accessibilityRole: "header", style: { ...base, fontSize: b.type === "h2" ? 17 : 16, fontWeight: "700", marginTop: 8 } }, ...spansOf(p, b.spans, {}));
3104
- if (b.type === "p") return (0, import_react2.createElement)(RN3.Text, { key: i, style: base }, ...spansOf(p, b.spans, {}));
3105
- if (b.type === "quote") return (0, import_react2.createElement)(RN3.View, { key: i, style: { borderLeftWidth: 3, borderLeftColor: p.line, paddingLeft: 10 } }, (0, import_react2.createElement)(RN3.Text, { style: { ...base, color: p.muted } }, ...spansOf(p, b.spans, {})));
3601
+ if (b.type === "h2" || b.type === "h3") return (0, import_react3.createElement)(RN3.Text, { key: i, accessibilityRole: "header", style: { ...base, fontSize: b.type === "h2" ? 17 : 16, fontWeight: "700", marginTop: 8 } }, ...spansOf(p, b.spans, {}));
3602
+ if (b.type === "p") return (0, import_react3.createElement)(RN3.Text, { key: i, style: base }, ...spansOf(p, b.spans, {}));
3603
+ if (b.type === "quote") return (0, import_react3.createElement)(RN3.View, { key: i, style: { borderLeftWidth: 3, borderLeftColor: p.line, paddingLeft: 10 } }, (0, import_react3.createElement)(RN3.Text, { style: { ...base, color: p.muted } }, ...spansOf(p, b.spans, {})));
3106
3604
  if (b.type === "ul" || b.type === "ol")
3107
- return (0, import_react2.createElement)(
3605
+ return (0, import_react3.createElement)(
3108
3606
  RN3.View,
3109
3607
  { key: i, style: { gap: 4 } },
3110
- ...b.items.map((it, j) => (0, import_react2.createElement)(RN3.View, { key: j, style: { flexDirection: "row", gap: 8 } }, k.text(b.type === "ol" ? `${j + 1}.` : "\u2022", base), (0, import_react2.createElement)(RN3.Text, { style: { ...base, flex: 1 } }, ...spansOf(p, it, {}))))
3608
+ ...b.items.map((it, j) => (0, import_react3.createElement)(RN3.View, { key: j, style: { flexDirection: "row", gap: 8 } }, k.text(b.type === "ol" ? `${j + 1}.` : "\u2022", base), (0, import_react3.createElement)(RN3.Text, { style: { ...base, flex: 1 } }, ...spansOf(p, it, {}))))
3111
3609
  );
3112
- if (b.type === "code") return (0, import_react2.createElement)(RN3.View, { key: i, style: { backgroundColor: p.soft, borderRadius: 8, padding: 10 } }, k.text(b.text, { fontFamily: RN3.Platform.OS === "ios" ? "Menlo" : "monospace", fontSize: 13 }));
3113
- return (0, import_react2.createElement)(RN3.View, { key: i, style: { height: 1, backgroundColor: p.line } });
3610
+ if (b.type === "code") return (0, import_react3.createElement)(RN3.View, { key: i, style: { backgroundColor: p.soft, borderRadius: 8, padding: 10 } }, k.text(b.text, { fontFamily: RN3.Platform.OS === "ios" ? "Menlo" : "monospace", fontSize: 13 }));
3611
+ return (0, import_react3.createElement)(RN3.View, { key: i, style: { height: 1, backgroundColor: p.line } });
3114
3612
  });
3115
3613
  const rate = (helpful) => {
3116
3614
  setRated(true);
3117
3615
  client.api()?.rateArticle(id, helpful).catch(() => void 0);
3118
3616
  if (!helpful) onAsk();
3119
3617
  };
3120
- return (0, import_react2.createElement)(
3618
+ return (0, import_react3.createElement)(
3121
3619
  RN3.View,
3122
3620
  { style: { gap: 10 } },
3123
3621
  k.muted(a.collection),
3124
- (0, import_react2.createElement)(RN3.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 20, fontWeight: "700" } }, a.title),
3622
+ (0, import_react3.createElement)(RN3.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 20, fontWeight: "700" } }, a.title),
3125
3623
  ...blocks,
3126
- (0, import_react2.createElement)(
3624
+ (0, import_react3.createElement)(
3127
3625
  RN3.View,
3128
3626
  { style: { flexDirection: "row", alignItems: "center", gap: 8, padding: 12, borderRadius: 10, backgroundColor: p.soft, marginTop: 8 } },
3129
3627
  rated ? k.muted("Thanks for letting us know.") : k.muted("Did this answer your question?", { flex: 1 }),
@@ -3143,19 +3641,26 @@ var MessengerSheet = ({
3143
3641
  brand
3144
3642
  }) => {
3145
3643
  const k = kit(p);
3146
- const [body, setBody] = (0, import_react2.useState)("");
3147
- const [email, setEmail] = (0, import_react2.useState)("");
3148
- const [pending, setPending] = (0, import_react2.useState)(null);
3149
- const [photos, setPhotos] = (0, import_react2.useState)([]);
3150
- const [query, setQuery] = (0, import_react2.useState)("");
3644
+ const [body, setBody] = (0, import_react3.useState)("");
3645
+ const [email, setEmail] = (0, import_react3.useState)("");
3646
+ const [pending, setPending] = (0, import_react3.useState)(null);
3647
+ const [slow, setSlow] = (0, import_react3.useState)(false);
3648
+ (0, import_react3.useEffect)(() => {
3649
+ setSlow(false);
3650
+ if (!pending) return;
3651
+ const t = setTimeout(() => setSlow(true), 1500);
3652
+ return () => clearTimeout(t);
3653
+ }, [pending]);
3654
+ const [photos, setPhotos] = (0, import_react3.useState)([]);
3655
+ const [query, setQuery] = (0, import_react3.useState)("");
3151
3656
  const helpOn = o.help !== false;
3152
3657
  const arts = useArticles(client, helpOn, query);
3153
- const scroll = (0, import_react2.useRef)(null);
3658
+ const scroll = (0, import_react3.useRef)(null);
3154
3659
  const close = () => messengerOpen.set(false);
3155
3660
  const greeting = o.greeting ?? "Hi! How can we help?";
3156
3661
  const { thread } = chat;
3157
3662
  const count = (thread?.messages.length ?? 0) + (pending ? 1 : 0);
3158
- (0, import_react2.useEffect)(() => {
3663
+ (0, import_react3.useEffect)(() => {
3159
3664
  if (view === "messages") setTimeout(() => scroll.current?.scrollToEnd?.({ animated: true }), 50);
3160
3665
  }, [count, view]);
3161
3666
  const needEmail = (o.askEmail ?? true) && !thread?.hasEmail && !client.getUser()?.email;
@@ -3238,7 +3743,7 @@ var MessengerSheet = ({
3238
3743
  setPending(null);
3239
3744
  }
3240
3745
  };
3241
- const localUris = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
3746
+ const localUris = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
3242
3747
  for (const f of photos) localUris.current.set(f.key, f.uri);
3243
3748
  const fileUrl = (id) => localUris.current.get(id) ?? client.api()?.attachmentUrl(id) ?? "";
3244
3749
  const ink = inkOn(p.accent);
@@ -3253,11 +3758,11 @@ var MessengerSheet = ({
3253
3758
  const title = o.title?.trim() ?? "";
3254
3759
  const bubble = (m, isPending = false) => {
3255
3760
  const mine = m.from === "visitor";
3256
- return (0, import_react2.createElement)(
3761
+ return (0, import_react3.createElement)(
3257
3762
  RN3.View,
3258
3763
  { key: m.id, style: { alignSelf: mine ? "flex-end" : "flex-start", maxWidth: "82%", gap: 4 } },
3259
3764
  ...(m.attachments ?? []).map(
3260
- (f) => (0, import_react2.createElement)(
3765
+ (f) => (0, import_react3.createElement)(
3261
3766
  RN3.Pressable,
3262
3767
  {
3263
3768
  key: f.id,
@@ -3266,10 +3771,10 @@ var MessengerSheet = ({
3266
3771
  accessibilityLabel: f.name,
3267
3772
  style: { alignSelf: mine ? "flex-end" : "flex-start", opacity: isPending ? 0.7 : 1 }
3268
3773
  },
3269
- f.type.startsWith("image/") ? (0, import_react2.createElement)(RN3.Image, { source: { uri: fileUrl(f.id) }, resizeMode: "cover", style: { width: 200, height: 150, borderRadius: r.bubble, backgroundColor: p.soft } }) : (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } }, k.text(`\u{1F4C4} ${f.name}`, { fontSize: 13 }))
3774
+ f.type.startsWith("image/") ? (0, import_react3.createElement)(RN3.Image, { source: { uri: fileUrl(f.id) }, resizeMode: "cover", style: { width: 200, height: 150, borderRadius: r.bubble, backgroundColor: p.soft } }) : (0, import_react3.createElement)(RN3.View, { style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } }, k.text(`\u{1F4C4} ${f.name}`, { fontSize: 13 }))
3270
3775
  )
3271
3776
  ),
3272
- !m.body && m.attachments?.length ? null : (0, import_react2.createElement)(
3777
+ !m.body && m.attachments?.length ? null : (0, import_react3.createElement)(
3273
3778
  RN3.View,
3274
3779
  {
3275
3780
  style: {
@@ -3286,23 +3791,23 @@ var MessengerSheet = ({
3286
3791
  k.text(m.body, { color: mine ? ink : hu.fg })
3287
3792
  ),
3288
3793
  ...(m.articles ?? []).map(
3289
- (a) => (0, import_react2.createElement)(
3794
+ (a) => (0, import_react3.createElement)(
3290
3795
  RN3.Pressable,
3291
3796
  { key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "link", style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } },
3292
3797
  k.text(`\u{1F4C4} ${a.title}`, { fontSize: 13 })
3293
3798
  )
3294
3799
  ),
3295
- m.at ? k.muted(`${mine ? "" : `${who(m.from, o.agentName)} \xB7 `}${isPending ? "Sending\u2026" : timeAgo(m.at)}`, { fontSize: 11, textAlign: mine ? "right" : "left", paddingHorizontal: 6 }) : null
3800
+ m.at ? k.muted(`${mine ? "" : `${who(m.from, o.agentName)} \xB7 `}${isPending && slow ? "Sending\u2026" : timeAgo(m.at)}`, { fontSize: 11, textAlign: mine ? "right" : "left", paddingHorizontal: 6 }) : null
3296
3801
  );
3297
3802
  };
3298
- const row = (a) => (0, import_react2.createElement)(
3803
+ const row = (a) => (0, import_react3.createElement)(
3299
3804
  RN3.Pressable,
3300
3805
  { key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "button", style: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 11, paddingHorizontal: 8 } },
3301
3806
  k.text(a.title, { flex: 1 }),
3302
3807
  glyph("next", p.accent, 14)
3303
3808
  );
3304
- const card = (children, style = {}) => (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: raised, borderRadius: r.card, padding: 16, ...surf, ...style } }, ...children);
3305
- const searchRow = (onPress) => (0, import_react2.createElement)(
3809
+ const card = (children, style = {}) => (0, import_react3.createElement)(RN3.View, { style: { backgroundColor: raised, borderRadius: r.card, padding: 16, ...surf, ...style } }, ...children);
3810
+ const searchRow = (onPress) => (0, import_react3.createElement)(
3306
3811
  RN3.Pressable,
3307
3812
  { key: "s", onPress, accessibilityRole: "search", style: { flexDirection: "row", alignItems: "center", height: 44, borderRadius: r.field, paddingHorizontal: 14, backgroundColor: hu.def } },
3308
3813
  k.muted("Search for help", { flex: 1, fontSize: 15 }),
@@ -3311,12 +3816,12 @@ var MessengerSheet = ({
3311
3816
  const search = k.field({ value: query, onChange: setQuery, placeholder: "Search for help", max: 200, style: { borderWidth: 0, borderRadius: r.field, backgroundColor: raised, ...surf } });
3312
3817
  const first = client.getUser()?.name?.trim().split(/\s+/)[0];
3313
3818
  const rest = greeting.replace(/^(hi|hello|hey|hallo)( there)?\s*[!,.]*\s*/i, "").trim() || "How can we help you today?";
3314
- const hero = (fg, sub) => (0, import_react2.createElement)(
3819
+ const hero = (fg, sub) => (0, import_react3.createElement)(
3315
3820
  RN3.Text,
3316
3821
  { key: "hero", accessibilityRole: "header", style: { color: fg, fontSize: 25, fontWeight: "600", letterSpacing: -0.6, lineHeight: 30 } },
3317
3822
  first ? `Hi, ${first} \u{1F44B}
3318
3823
  ` : "Hi there \u{1F44B}\n",
3319
- (0, import_react2.createElement)(RN3.Text, { style: { color: sub, fontWeight: "500" } }, rest)
3824
+ (0, import_react3.createElement)(RN3.Text, { style: { color: sub, fontWeight: "500" } }, rest)
3320
3825
  );
3321
3826
  const last = thread?.messages.at(-1);
3322
3827
  const showHelp = helpOn && arts.any !== false;
@@ -3324,11 +3829,11 @@ var MessengerSheet = ({
3324
3829
  if (view === "home") {
3325
3830
  const cards = [
3326
3831
  // "Ask a question": the words go into the chat's field and are sent as the chat opens.
3327
- (0, import_react2.createElement)(
3832
+ (0, import_react3.createElement)(
3328
3833
  RN3.View,
3329
3834
  { key: "ask", style: { flexDirection: "row", alignItems: "center", gap: 8, height: 48, borderRadius: 24, paddingLeft: 18, paddingRight: 6, backgroundColor: hu.surface, ...surf } },
3330
3835
  k.field({ value: body, onChange: setBody, placeholder: "Ask a question", max: 4e3, style: { flex: 1, borderWidth: 0, paddingHorizontal: 0, backgroundColor: "transparent" } }),
3331
- (0, import_react2.createElement)(
3836
+ (0, import_react3.createElement)(
3332
3837
  RN3.Pressable,
3333
3838
  {
3334
3839
  onPress: () => {
@@ -3342,59 +3847,60 @@ var MessengerSheet = ({
3342
3847
  glyph("up", body.trim() ? ink : hu.muted, 18)
3343
3848
  )
3344
3849
  ),
3345
- last ? (0, import_react2.createElement)(
3850
+ last ? (0, import_react3.createElement)(
3346
3851
  RN3.Pressable,
3347
3852
  { key: "recent", onPress: () => setView("messages"), accessibilityRole: "button" },
3348
3853
  card([
3349
- (0, import_react2.createElement)(
3854
+ (0, import_react3.createElement)(
3350
3855
  RN3.View,
3351
3856
  { key: "r", style: { flexDirection: "row", alignItems: "center", gap: 10 } },
3352
3857
  title ? teamMark(title, alpha(p.accent, 0.16), p.accent, 26) : null,
3353
- (0, import_react2.createElement)(RN3.View, { style: { flex: 1, gap: 1 } }, (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.fg, fontSize: 14, fontWeight: "500" } }, last.body || (last.attachments?.length ? `Sent ${last.attachments.length === 1 ? last.attachments[0].name : `${last.attachments.length} files`}` : "")), k.muted(`${last.from === "visitor" ? "You" : who(last.from, o.agentName)} \xB7 ${timeAgo(last.at)}`, { fontSize: 11.5 })),
3354
- chat.unseen.length ? (0, import_react2.createElement)(RN3.View, { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : glyph("next", p.muted, 14)
3858
+ (0, import_react3.createElement)(RN3.View, { style: { flex: 1, gap: 1 } }, (0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.fg, fontSize: 14, fontWeight: "500" } }, last.body || (last.attachments?.length ? `Sent ${last.attachments.length === 1 ? last.attachments[0].name : `${last.attachments.length} files`}` : "")), k.muted(`${last.from === "visitor" ? "You" : who(last.from, o.agentName)} \xB7 ${timeAgo(last.at)}`, { fontSize: 11.5 })),
3859
+ chat.unseen.length ? (0, import_react3.createElement)(RN3.View, { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : glyph("next", p.muted, 14)
3355
3860
  )
3356
3861
  ], { paddingVertical: 10, paddingHorizontal: 12 })
3357
3862
  ) : null,
3358
3863
  showHelp ? card([searchRow(() => setView("help")), ...(arts.list ?? []).slice(0, 4).map(row)], { padding: 8, gap: 2 }) : null
3359
3864
  ];
3360
- content = plain ? (0, import_react2.createElement)(
3865
+ content = plain ? (0, import_react3.createElement)(
3361
3866
  RN3.ScrollView,
3362
3867
  { key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 24, gap: 12 }, keyboardShouldPersistTaps: "handled" },
3363
- (0, import_react2.createElement)(RN3.View, { key: "top", style: { paddingHorizontal: 6, paddingBottom: 10 } }, hero(p.fg, p.muted)),
3868
+ (0, import_react3.createElement)(RN3.View, { key: "top", style: { paddingHorizontal: 6, paddingBottom: 10 } }, hero(p.fg, p.muted)),
3364
3869
  ...cards
3365
- ) : (0, import_react2.createElement)(
3870
+ ) : (0, import_react3.createElement)(
3366
3871
  RN3.ScrollView,
3367
3872
  { key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingBottom: 24 }, keyboardShouldPersistTaps: "handled" },
3368
- (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, hero(ink, alpha(ink, 0.8))),
3369
- (0, import_react2.createElement)(RN3.View, { style: { marginTop: -48, paddingHorizontal: 16, gap: 12 } }, ...cards)
3873
+ (0, import_react3.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, hero(ink, alpha(ink, 0.8))),
3874
+ (0, import_react3.createElement)(RN3.View, { style: { marginTop: -48, paddingHorizontal: 16, gap: 12 } }, ...cards)
3370
3875
  );
3371
3876
  } else if (view === "messages")
3372
- content = (0, import_react2.createElement)(
3877
+ content = (0, import_react3.createElement)(
3373
3878
  RN3.View,
3374
3879
  { key: "messages", style: { flex: 1, backgroundColor: hu.bg } },
3375
- (0, import_react2.createElement)(
3880
+ (0, import_react3.createElement)(
3376
3881
  RN3.ScrollView,
3377
3882
  { ref: scroll, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 } },
3378
- bubble({ id: "greeting", from: "team", body: greeting, at: "" }),
3883
+ // The greeting opens an empty chat only, never above the visitor's first words (2026-10-30).
3884
+ !thread?.messages.length && !pending ? bubble({ id: "greeting", from: "team", body: greeting, at: "" }) : null,
3379
3885
  !chat.loaded ? k.muted("Loading\u2026") : null,
3380
3886
  ...(thread?.messages ?? []).map((m) => bubble(m)),
3381
3887
  pending ? bubble(pending, true) : null,
3382
3888
  thread?.conversation?.status === "solved" && !pending ? k.muted("This conversation is closed. Write to start a new one.", { textAlign: "center" }) : null
3383
3889
  ),
3384
- (0, import_react2.createElement)(
3890
+ (0, import_react3.createElement)(
3385
3891
  RN3.View,
3386
3892
  { style: { paddingHorizontal: 12, paddingTop: 8, paddingBottom: 6, gap: 8 } },
3387
3893
  chat.error ? k.text(chat.error, { color: p.danger, fontSize: 12 }) : null,
3388
3894
  // Photos picked for the next message, each with a way to take it out again.
3389
- photos.length ? (0, import_react2.createElement)(
3895
+ photos.length ? (0, import_react3.createElement)(
3390
3896
  RN3.View,
3391
3897
  { style: { flexDirection: "row", flexWrap: "wrap", gap: 10, paddingTop: 6, paddingHorizontal: 2 } },
3392
3898
  ...photos.map(
3393
- (f) => (0, import_react2.createElement)(
3899
+ (f) => (0, import_react3.createElement)(
3394
3900
  RN3.View,
3395
3901
  { key: f.key, style: { width: 56, height: 56 } },
3396
- (0, import_react2.createElement)(RN3.Image, { source: { uri: f.uri }, resizeMode: "cover", accessibilityLabel: f.id ? f.name : `${f.name}, uploading`, style: { width: 56, height: 56, borderRadius: Math.max(4, r.card - 4), backgroundColor: p.soft, opacity: f.id ? 1 : 0.5 } }),
3397
- (0, import_react2.createElement)(
3902
+ (0, import_react3.createElement)(RN3.Image, { source: { uri: f.uri }, resizeMode: "cover", accessibilityLabel: f.id ? f.name : `${f.name}, uploading`, style: { width: 56, height: 56, borderRadius: Math.max(4, r.card - 4), backgroundColor: p.soft, opacity: f.id ? 1 : 0.5 } }),
3903
+ (0, import_react3.createElement)(
3398
3904
  RN3.Pressable,
3399
3905
  {
3400
3906
  onPress: () => setPhotos((list) => list.filter((x) => x.key !== f.key)),
@@ -3409,7 +3915,7 @@ var MessengerSheet = ({
3409
3915
  )
3410
3916
  ) : null,
3411
3917
  // The message field on top, the paperclip (when the app has expo-image-picker) and the send button under it.
3412
- (0, import_react2.createElement)(
3918
+ (0, import_react3.createElement)(
3413
3919
  RN3.View,
3414
3920
  { style: { gap: 2, borderRadius: 20, paddingTop: 10, paddingBottom: 6, paddingRight: 6, paddingLeft: 16, backgroundColor: hu.surface, ...surf } },
3415
3921
  // The optional email: the box's first line, under a hairline the message follows.
@@ -3422,16 +3928,16 @@ var MessengerSheet = ({
3422
3928
  style: { minHeight: 0, marginRight: 10, marginBottom: 4, paddingHorizontal: 0, paddingTop: 0, paddingBottom: 8, borderWidth: 0, borderRadius: 0, borderBottomWidth: 1, borderBottomColor: emailOk ? p.line : p.danger, backgroundColor: "transparent", fontSize: 13.5 }
3423
3929
  }) : null,
3424
3930
  k.field({ value: body, onChange: setBody, placeholder: "Write a message\u2026", multiline: true, max: 4e3, style: { minHeight: 28, maxHeight: 120, borderWidth: 0, paddingHorizontal: 0, paddingVertical: 4, backgroundColor: "transparent" } }),
3425
- (0, import_react2.createElement)(
3931
+ (0, import_react3.createElement)(
3426
3932
  RN3.View,
3427
3933
  { style: { flexDirection: "row", alignItems: "center", marginLeft: -8 } },
3428
- imagePicker?.launchImageLibraryAsync ? (0, import_react2.createElement)(
3934
+ imagePicker?.launchImageLibraryAsync ? (0, import_react3.createElement)(
3429
3935
  RN3.Pressable,
3430
3936
  { onPress: () => void pickPhotos(), disabled: !!pending, accessibilityRole: "button", accessibilityLabel: "Attach a photo or screenshot", hitSlop: 6, style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center" } },
3431
3937
  glyph("clip", hu.muted, 19)
3432
3938
  ) : null,
3433
- (0, import_react2.createElement)(RN3.View, { style: { flex: 1 } }),
3434
- (0, import_react2.createElement)(
3939
+ (0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
3940
+ (0, import_react3.createElement)(
3435
3941
  RN3.Pressable,
3436
3942
  {
3437
3943
  onPress: () => void send(),
@@ -3449,24 +3955,24 @@ var MessengerSheet = ({
3449
3955
  else if (view === "help") {
3450
3956
  const groups = /* @__PURE__ */ new Map();
3451
3957
  for (const a of arts.list ?? []) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
3452
- content = (0, import_react2.createElement)(
3958
+ content = (0, import_react3.createElement)(
3453
3959
  RN3.ScrollView,
3454
3960
  { key: "help", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 }, keyboardShouldPersistTaps: "handled" },
3455
3961
  search,
3456
3962
  arts.list === null ? k.muted("Loading\u2026") : null,
3457
- arts.list?.length === 0 ? (0, import_react2.createElement)(RN3.View, { style: { gap: 10, alignItems: "center", paddingVertical: 24 } }, k.muted(query.trim() ? `Nothing found for \u201C${query.trim()}\u201D.` : "No help articles yet."), k.btn("Send us a message", "primary", () => setView("messages"))) : null,
3458
- ...query.trim() ? arts.list?.length ? [card((arts.list ?? []).map(row), { padding: 8 })] : [] : [...groups].map(([name, items]) => (0, import_react2.createElement)(RN3.View, { key: name, style: { gap: 6 } }, k.muted(`${name} \xB7 ${items.length}`, { fontSize: 12, fontWeight: "600", paddingHorizontal: 2 }), card(items.map(row), { padding: 8 })))
3963
+ arts.list?.length === 0 ? (0, import_react3.createElement)(RN3.View, { style: { gap: 10, alignItems: "center", paddingVertical: 24 } }, k.muted(query.trim() ? `Nothing found for \u201C${query.trim()}\u201D.` : "No help articles yet."), k.btn("Send us a message", "primary", () => setView("messages"))) : null,
3964
+ ...query.trim() ? arts.list?.length ? [card((arts.list ?? []).map(row), { padding: 8 })] : [] : [...groups].map(([name, items]) => (0, import_react3.createElement)(RN3.View, { key: name, style: { gap: 6 } }, k.muted(`${name} \xB7 ${items.length}`, { fontSize: 12, fontWeight: "600", paddingHorizontal: 2 }), card(items.map(row), { padding: 8 })))
3459
3965
  );
3460
3966
  } else
3461
- content = (0, import_react2.createElement)(
3967
+ content = (0, import_react3.createElement)(
3462
3968
  RN3.ScrollView,
3463
3969
  { key: `a-${view.article}`, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 32 } },
3464
- (0, import_react2.createElement)(ArticleView, { client, p, id: view.article, onAsk: () => setView("messages") })
3970
+ (0, import_react3.createElement)(ArticleView, { client, p, id: view.article, onAsk: () => setView("messages") })
3465
3971
  );
3466
3972
  const current = typeof view === "string" ? view : "help";
3467
3973
  const tab = (v, name, icon, badge = 0) => {
3468
3974
  const on = current === v;
3469
- return (0, import_react2.createElement)(
3975
+ return (0, import_react3.createElement)(
3470
3976
  RN3.Pressable,
3471
3977
  {
3472
3978
  key: v,
@@ -3479,48 +3985,48 @@ var MessengerSheet = ({
3479
3985
  },
3480
3986
  glyph(icon, on ? hu.fg : hu.muted, 17),
3481
3987
  on ? k.text(name, { fontSize: 13.5, fontWeight: "500", color: hu.fg }) : null,
3482
- badge ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: 7, right: 12, width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : null
3988
+ badge ? (0, import_react3.createElement)(RN3.View, { style: { position: "absolute", top: 7, right: 12, width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : null
3483
3989
  );
3484
3990
  };
3485
3991
  const onAccent = view === "home" && !plain;
3486
3992
  const ground = onAccent ? p.accent : hu.bg;
3487
- const back = (to, label) => (0, import_react2.createElement)(
3993
+ const back = (to, label) => (0, import_react3.createElement)(
3488
3994
  RN3.Pressable,
3489
3995
  { key: "back", onPress: () => setView(to), accessibilityRole: "button", accessibilityLabel: label, hitSlop: 10, style: { width: 32, height: 32, alignItems: "center", justifyContent: "center", transform: [{ rotate: "180deg" }] } },
3490
3996
  glyph("next", hu.fg, 18)
3491
3997
  );
3492
- const head = (0, import_react2.createElement)(
3998
+ const head = (0, import_react3.createElement)(
3493
3999
  RN3.View,
3494
4000
  { key: "head", style: { flexDirection: "row", alignItems: "center", gap: 8, paddingLeft: 12, paddingRight: 12, paddingTop: 12, paddingBottom: 6, backgroundColor: ground } },
3495
- typeof view !== "string" ? back("help", "All articles") : view === "messages" ? back("home", "Back to home") : view === "home" && (o.logo || title) ? o.logo ? (0, import_react2.createElement)(RN3.Image, { key: "logo", source: { uri: o.logo }, accessibilityIgnoresInvertColors: true, resizeMode: "contain", style: { height: 28, aspectRatio: 1, maxWidth: 132, borderRadius: 8, marginLeft: 4 } }) : teamMark(title, alpha(p.accent, 0.16), p.accent, 28) : null,
4001
+ typeof view !== "string" ? back("help", "All articles") : view === "messages" ? back("home", "Back to home") : view === "home" && (o.logo || title) ? o.logo ? (0, import_react3.createElement)(RN3.Image, { key: "logo", source: { uri: o.logo }, accessibilityIgnoresInvertColors: true, resizeMode: "contain", style: { height: 28, aspectRatio: 1, maxWidth: 132, borderRadius: 8, marginLeft: 4 } }) : teamMark(title, alpha(p.accent, 0.16), p.accent, 28) : null,
3496
4002
  // In the chat: who answers, in the middle.
3497
- view === "messages" && title ? (0, import_react2.createElement)(
4003
+ view === "messages" && title ? (0, import_react3.createElement)(
3498
4004
  RN3.View,
3499
4005
  { style: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8 } },
3500
4006
  teamMark(title, alpha(p.accent, 0.16), p.accent, 28),
3501
- (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { flexShrink: 1, color: hu.fg, fontSize: 15, fontWeight: "600" } }, title)
3502
- ) : (0, import_react2.createElement)(RN3.View, { style: { flex: 1 } }),
3503
- (0, import_react2.createElement)(RN3.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: "Close", hitSlop: 12, style: { width: 32, height: 32, borderRadius: 10, alignItems: "center", justifyContent: "center" } }, glyph("cross", onAccent ? ink : p.muted, 18))
4007
+ (0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { flexShrink: 1, color: hu.fg, fontSize: 15, fontWeight: "600" } }, title)
4008
+ ) : (0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
4009
+ (0, import_react3.createElement)(RN3.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: "Close", hitSlop: 12, style: { width: 32, height: 32, borderRadius: 10, alignItems: "center", justifyContent: "center" } }, glyph("cross", onAccent ? ink : p.muted, 18))
3504
4010
  );
3505
- const powered = brand ? (0, import_react2.createElement)(
4011
+ const powered = brand ? (0, import_react3.createElement)(
3506
4012
  RN3.View,
3507
4013
  { key: "brand", style: { paddingTop: 2, paddingBottom: 6, alignItems: "center", backgroundColor: hu.bg } },
3508
- (0, import_react2.createElement)(
4014
+ (0, import_react3.createElement)(
3509
4015
  RN3.Text,
3510
4016
  { onPress: () => void RN3.Linking.openURL(BRAND_URL).catch(() => void 0), accessibilityRole: "link", hitSlop: 8, style: { color: p.muted, fontSize: 11.5 } },
3511
4017
  "Powered by ",
3512
- (0, import_react2.createElement)(RN3.Text, { style: { fontWeight: "600" } }, "MRRocket")
4018
+ (0, import_react3.createElement)(RN3.Text, { style: { fontWeight: "600" } }, "MRRocket")
3513
4019
  )
3514
4020
  ) : null;
3515
- return (0, import_react2.createElement)(
4021
+ return (0, import_react3.createElement)(
3516
4022
  RN3.Modal,
3517
4023
  { visible: true, animationType: "slide", presentationStyle: "pageSheet", onRequestClose: close },
3518
- (0, import_react2.createElement)(
4024
+ (0, import_react3.createElement)(
3519
4025
  RN3.KeyboardAvoidingView,
3520
4026
  { behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: hu.bg } },
3521
4027
  head,
3522
4028
  content,
3523
- view === "messages" ? null : (0, import_react2.createElement)(
4029
+ view === "messages" ? null : (0, import_react3.createElement)(
3524
4030
  RN3.View,
3525
4031
  { key: "tabs", accessibilityRole: "tablist", style: { flexDirection: "row", marginHorizontal: 12, marginTop: 6, marginBottom: 6, padding: 4, borderRadius: 22, backgroundColor: hu.surface } },
3526
4032
  tab("home", "Home", "home"),
@@ -3528,13 +4034,13 @@ var MessengerSheet = ({
3528
4034
  showHelp ? tab("help", "Help", "help") : null
3529
4035
  ),
3530
4036
  powered,
3531
- (0, import_react2.createElement)(RN3.View, { style: { height: 16 } })
4037
+ (0, import_react3.createElement)(RN3.View, { style: { height: 16 } })
3532
4038
  )
3533
4039
  );
3534
4040
  };
3535
4041
  var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
3536
- const [look, setLook] = (0, import_react2.useState)(null);
3537
- (0, import_react2.useEffect)(() => {
4042
+ const [look, setLook] = (0, import_react3.useState)(null);
4043
+ (0, import_react3.useEffect)(() => {
3538
4044
  const ctrl = new AbortController();
3539
4045
  void client.ready.then(
3540
4046
  () => client.api()?.messengerLook(ctrl.signal).then(setLook).catch(() => void 0)
@@ -3562,9 +4068,9 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
3562
4068
  const p = usePalette({ theme: appearance?.theme ?? look?.theme, accent: appearance?.accent ?? look?.accent });
3563
4069
  const k = kit(p);
3564
4070
  const open = messengerOpen.useOpen();
3565
- const [view, setViewState] = (0, import_react2.useState)(msgView);
3566
- const [banner, setBanner] = (0, import_react2.useState)(null);
3567
- (0, import_react2.useEffect)(() => {
4071
+ const [view, setViewState] = (0, import_react3.useState)(msgView);
4072
+ const [banner, setBanner] = (0, import_react3.useState)(null);
4073
+ (0, import_react3.useEffect)(() => {
3568
4074
  if (open) setViewState(msgView);
3569
4075
  }, [open]);
3570
4076
  const setView = (v) => {
@@ -3573,13 +4079,13 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
3573
4079
  };
3574
4080
  const chat = useChat(client, options, open, view);
3575
4081
  const latest = chat.unseen.at(-1);
3576
- (0, import_react2.useEffect)(() => {
4082
+ (0, import_react3.useEffect)(() => {
3577
4083
  if (!latest || open || options.preview === false) return;
3578
4084
  setBanner(latest);
3579
4085
  const t = setTimeout(() => setBanner(null), 6e3);
3580
4086
  return () => clearTimeout(t);
3581
4087
  }, [latest, open, options.preview]);
3582
- (0, import_react2.useEffect)(() => {
4088
+ (0, import_react3.useEffect)(() => {
3583
4089
  if (open) setBanner(null);
3584
4090
  }, [open]);
3585
4091
  const unseen = chat.unseen.length;
@@ -3591,10 +4097,10 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
3591
4097
  ...edge === "top" ? { top: 60 } : edge === "bottom" ? { bottom: 34 } : { top: 0, bottom: 0, justifyContent: "center" },
3592
4098
  ...side === "left" ? { left: 20 } : side === "right" ? { right: 20 } : { left: 0, right: 0, alignItems: "center" }
3593
4099
  };
3594
- const launcher = options.launcher ? (0, import_react2.createElement)(
4100
+ const launcher = options.launcher ? (0, import_react3.createElement)(
3595
4101
  RN3.View,
3596
4102
  { key: "launcher", pointerEvents: "box-none", style: { position: "absolute", ...place } },
3597
- (0, import_react2.createElement)(
4103
+ (0, import_react3.createElement)(
3598
4104
  RN3.Pressable,
3599
4105
  {
3600
4106
  onPress: () => openAt(unseen ? "messages" : "home"),
@@ -3619,10 +4125,10 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
3619
4125
  },
3620
4126
  glyph("chat", ink, label ? 20 : 26),
3621
4127
  label ? k.text(label, { color: ink, fontWeight: "700" }) : null,
3622
- unseen ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: p.danger, alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: p.bg } }, k.text(String(Math.min(unseen, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
4128
+ unseen ? (0, import_react3.createElement)(RN3.View, { style: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: p.danger, alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: p.bg } }, k.text(String(Math.min(unseen, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
3623
4129
  )
3624
4130
  ) : null;
3625
- const toast = banner ? (0, import_react2.createElement)(
4131
+ const toast = banner ? (0, import_react3.createElement)(
3626
4132
  RN3.Pressable,
3627
4133
  {
3628
4134
  key: "banner",
@@ -3647,29 +4153,29 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
3647
4153
  }
3648
4154
  },
3649
4155
  k.muted(`${who(banner.from, options.agentName)}${unseen > 1 ? ` \xB7 ${unseen} new` : ""}`, { fontWeight: "600", fontSize: 12 }),
3650
- (0, import_react2.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15, lineHeight: 20 } }, banner.body)
4156
+ (0, import_react3.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15, lineHeight: 20 } }, banner.body)
3651
4157
  ) : null;
3652
4158
  if (!launcher && !toast && !open) return null;
3653
- return (0, import_react2.createElement)(
4159
+ return (0, import_react3.createElement)(
3654
4160
  RN3.View,
3655
4161
  { pointerEvents: "box-none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 } },
3656
4162
  launcher,
3657
4163
  toast,
3658
- open ? (0, import_react2.createElement)(MessengerSheet, { client, p, o: options, chat, view, setView, brand: look?.branding !== false }) : null
4164
+ open ? (0, import_react3.createElement)(MessengerSheet, { client, p, o: options, chat, view, setView, brand: look?.branding !== false }) : null
3659
4165
  );
3660
4166
  };
3661
4167
 
3662
4168
  // src/native/index.tsx
3663
4169
  var RN4 = require("react-native");
3664
4170
  var MRRocketMask = ({ children, style }) => {
3665
- const ref = (0, import_react3.useRef)(null);
3666
- (0, import_react3.useEffect)(() => {
4171
+ const ref = (0, import_react4.useRef)(null);
4172
+ (0, import_react4.useEffect)(() => {
3667
4173
  maskedViews.add(ref);
3668
4174
  return () => {
3669
4175
  maskedViews.delete(ref);
3670
4176
  };
3671
4177
  }, []);
3672
- return (0, import_react3.createElement)(RN4.View, { ref, collapsable: false, style }, children);
4178
+ return (0, import_react4.createElement)(RN4.View, { ref, collapsable: false, style }, children);
3673
4179
  };
3674
4180
  var shared = null;
3675
4181
  var sharedKey = "";
@@ -3686,16 +4192,16 @@ var createMRRocket = (options) => {
3686
4192
  return shared;
3687
4193
  };
3688
4194
  var getMRRocket = () => shared;
3689
- var Ctx = (0, import_react3.createContext)(null);
4195
+ var Ctx = (0, import_react4.createContext)(null);
3690
4196
  var MRRocketProvider = ({ client: given, children, consent, flows = true, appearance, surveys = true, messenger = {}, feedback = {}, heatmaps = true, ...options }) => {
3691
- const [client] = (0, import_react3.useState)(() => given ?? createMRRocket({ ...options, consent }));
3692
- (0, import_react3.useEffect)(() => {
4197
+ const [client] = (0, import_react4.useState)(() => given ?? createMRRocket({ ...options, consent }));
4198
+ (0, import_react4.useEffect)(() => {
3693
4199
  if (consent) client.setConsent(consent);
3694
4200
  }, [client, consent]);
3695
- (0, import_react3.useEffect)(() => {
4201
+ (0, import_react4.useEffect)(() => {
3696
4202
  if (options.replay !== void 0) client.setReplay(options.replay);
3697
4203
  }, [client, options.replay]);
3698
- const start = (0, import_react3.useRef)(null);
4204
+ const start = (0, import_react4.useRef)(null);
3699
4205
  const touch = (e, end) => {
3700
4206
  const n = e.nativeEvent;
3701
4207
  const x = n.pageX ?? n.changedTouches?.[0]?.pageX;
@@ -3704,22 +4210,22 @@ var MRRocketProvider = ({ client: given, children, consent, flows = true, appear
3704
4210
  if (!end) start.current = { x, y, t: Date.now() };
3705
4211
  else if (start.current && Math.hypot(x - start.current.x, y - start.current.y) < 10 && Date.now() - start.current.t < 500) client.tap(x, y);
3706
4212
  };
3707
- return (0, import_react3.createElement)(
4213
+ return (0, import_react4.createElement)(
3708
4214
  Ctx.Provider,
3709
4215
  { value: client },
3710
- heatmaps ? (0, import_react3.createElement)(RN4.View, { style: { flex: 1 }, onTouchStartCapture: (e) => touch(e, false), onTouchEndCapture: (e) => touch(e, true) }, children) : children,
3711
- flows ? (0, import_react3.createElement)(MRRocketFlows, { flows: client.flows, paywall: client.paywall, appearance }) : null,
3712
- surveys ? (0, import_react3.createElement)(MRRocketSurveys, { client, appearance }) : null,
3713
- feedback ? (0, import_react3.createElement)(MRRocketFeedback, { client, appearance, options: feedback }) : null,
3714
- messenger ? (0, import_react3.createElement)(MRRocketMessenger, { client, appearance, options: messenger }) : null
4216
+ heatmaps ? (0, import_react4.createElement)(RN4.View, { style: { flex: 1 }, onTouchStartCapture: (e) => touch(e, false), onTouchEndCapture: (e) => touch(e, true) }, children) : children,
4217
+ flows ? (0, import_react4.createElement)(MRRocketFlows, { flows: client.flows, paywall: client.paywall, appearance }) : null,
4218
+ surveys ? (0, import_react4.createElement)(MRRocketSurveys, { client, appearance }) : null,
4219
+ feedback ? (0, import_react4.createElement)(MRRocketFeedback, { client, appearance, options: feedback }) : null,
4220
+ messenger ? (0, import_react4.createElement)(MRRocketMessenger, { client, appearance, options: messenger }) : null
3715
4221
  );
3716
4222
  };
3717
4223
  var useClient = () => {
3718
- const c = (0, import_react3.useContext)(Ctx);
4224
+ const c = (0, import_react4.useContext)(Ctx);
3719
4225
  if (!c) throw new Error("MRRocket: wrap your app in <MRRocketProvider apiKey=\u2026>.");
3720
4226
  return c;
3721
4227
  };
3722
- var useVersion = (client) => (0, import_react3.useSyncExternalStore)(client.subscribe, client.getVersion, client.getVersion);
4228
+ var useVersion = (client) => (0, import_react4.useSyncExternalStore)(client.subscribe, client.getVersion, client.getVersion);
3723
4229
  var useMRRocket = () => {
3724
4230
  const client = useClient();
3725
4231
  useVersion(client);
@@ -3753,9 +4259,9 @@ var useReview = () => {
3753
4259
  const client = useClient();
3754
4260
  return { request: client.requestReview };
3755
4261
  };
3756
- var useOnboarding = () => {
4262
+ var useOnboarding = (id) => {
3757
4263
  const client = useClient();
3758
- return { start: client.startOnboarding };
4264
+ return useOnboardingItem(client.flows, id, client.startOnboarding);
3759
4265
  };
3760
4266
  var useFlag = (key) => {
3761
4267
  const client = useClient();
@@ -3772,7 +4278,7 @@ var useVariant = (key) => {
3772
4278
  useVersion(client);
3773
4279
  const config = client.getConfig();
3774
4280
  const variant = client.peekVariant(key);
3775
- (0, import_react3.useEffect)(() => {
4281
+ (0, import_react4.useEffect)(() => {
3776
4282
  if (config) client.expose(key, variant);
3777
4283
  }, [client, config, key, variant]);
3778
4284
  return variant;
@@ -3783,7 +4289,7 @@ var useText = (id, fallback) => {
3783
4289
  const config = client.getConfig();
3784
4290
  const text2 = client.peekText(id);
3785
4291
  const person = client.getDistinctId();
3786
- (0, import_react3.useEffect)(() => {
4292
+ (0, import_react4.useEffect)(() => {
3787
4293
  if (config) client.exposeText(id);
3788
4294
  }, [client, config, id, person]);
3789
4295
  return text2 ?? fallback;
@@ -3808,13 +4314,13 @@ var useExpoRouterTracking = () => {
3808
4314
  console.warn("[MRRocket] useExpoRouterTracking needs expo-router. With React Navigation use useNavigationTracking.");
3809
4315
  }
3810
4316
  const pathname = (router?.usePathname ?? noPathname)();
3811
- (0, import_react3.useEffect)(() => {
4317
+ (0, import_react4.useEffect)(() => {
3812
4318
  if (pathname) client.screen(pathname);
3813
4319
  }, [client, pathname]);
3814
4320
  };
3815
4321
  var useNavigationTracking = () => {
3816
4322
  const client = useClient();
3817
- return (0, import_react3.useCallback)(
4323
+ return (0, import_react4.useCallback)(
3818
4324
  (state) => {
3819
4325
  const name = activeRouteName(state);
3820
4326
  if (name) client.screen(name);