@mrrocket/sdk 0.17.0 → 0.21.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 (69) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +24 -1
  3. package/dist/api-client.d.ts +28 -4
  4. package/dist/api-client.js +30 -13
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/browser/mrrocket-flows.js +15 -3
  7. package/dist/browser/mrrocket-messenger.js +38 -5
  8. package/dist/browser/mrrocket.js +1 -1
  9. package/dist/browser.d.ts +2 -2
  10. package/dist/browser.js +2 -1
  11. package/dist/browser.js.map +1 -1
  12. package/dist/client.d.ts +28 -3
  13. package/dist/client.js +37 -5
  14. package/dist/client.js.map +1 -1
  15. package/dist/context.d.ts +3 -1
  16. package/dist/context.js.map +1 -1
  17. package/dist/core.d.ts +7 -1
  18. package/dist/core.js +1 -1
  19. package/dist/core.js.map +1 -1
  20. package/dist/feedback.js +161 -119
  21. package/dist/feedback.js.map +1 -1
  22. package/dist/finish-web.d.ts +18 -0
  23. package/dist/finish-web.js +139 -0
  24. package/dist/finish-web.js.map +1 -0
  25. package/dist/flows-ui.d.ts +1 -10
  26. package/dist/flows-ui.js +175 -48
  27. package/dist/flows-ui.js.map +1 -1
  28. package/dist/flows-web.d.ts +8 -1
  29. package/dist/flows-web.js +10 -1
  30. package/dist/flows-web.js.map +1 -1
  31. package/dist/flows.d.ts +193 -9
  32. package/dist/flows.js +284 -38
  33. package/dist/flows.js.map +1 -1
  34. package/dist/i18n.d.ts +22 -0
  35. package/dist/i18n.js +74 -0
  36. package/dist/i18n.js.map +1 -0
  37. package/dist/icons.d.ts +24 -0
  38. package/dist/icons.js +61 -0
  39. package/dist/icons.js.map +1 -0
  40. package/dist/index.d.ts +6 -2
  41. package/dist/index.js +8 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/messenger-ui.d.ts +9 -3
  44. package/dist/messenger-ui.js +388 -191
  45. package/dist/messenger-ui.js.map +1 -1
  46. package/dist/messenger.d.ts +3 -1
  47. package/dist/messenger.js +3 -2
  48. package/dist/messenger.js.map +1 -1
  49. package/dist/onboarding-state.d.ts +23 -0
  50. package/dist/onboarding-state.js +43 -0
  51. package/dist/onboarding-state.js.map +1 -0
  52. package/dist/paywall.js +2 -1
  53. package/dist/paywall.js.map +1 -1
  54. package/dist/react-native/index.d.cts +239 -19
  55. package/dist/react-native/index.d.ts +239 -19
  56. package/dist/react-native/index.js +1305 -518
  57. package/dist/react-native/index.js.map +1 -1
  58. package/dist/server.d.ts +4 -2
  59. package/dist/server.js +2 -2
  60. package/dist/server.js.map +1 -1
  61. package/dist/styles-css.d.ts +1 -1
  62. package/dist/styles-css.js +35 -2
  63. package/dist/styles-css.js.map +1 -1
  64. package/dist/survey.js +18 -14
  65. package/dist/survey.js.map +1 -1
  66. package/dist/ui.d.ts +2 -0
  67. package/dist/ui.js +15 -12
  68. package/dist/ui.js.map +1 -1
  69. package/package.json +6 -3
package/dist/flows.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/flows.ts
2
+ import { currentLocale, t, tf } from "./i18n.js";
2
3
  var CANCEL_EVENT = "$cancel_flow";
3
4
  var ONBOARDING_EVENT = "$onboarding";
4
5
  var INAPP_EVENT = "$inapp";
@@ -8,23 +9,54 @@ var PLAIN_CANCEL = {
8
9
  question: "",
9
10
  reasons: [],
10
11
  offers: {},
11
- confirm: { title: "Cancel your subscription?", body: "You keep access until the end of the billing period.", button: "Cancel subscription" },
12
+ // Read when it is shown, so it follows the visitor's language.
13
+ get confirm() {
14
+ return { title: t("Cancel your subscription?"), body: t("You keep access until the end of the billing period."), button: t("Cancel subscription") };
15
+ },
12
16
  offersFor: "business"
13
17
  };
14
- var FAILED = "Something went wrong. Please try again.";
15
18
  var PROGRESS_KEY = "mrrocket:onboarding";
16
19
  var eventSlug = (s) => s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
17
20
  var SIGNUP = /* @__PURE__ */ new Set(["signed_up", "signup", "sign_up", "registered", "account_created", "user_signed_up"]);
18
21
  var matchesTrigger = (trigger, event) => {
19
- const t = eventSlug(trigger);
22
+ const t2 = eventSlug(trigger);
20
23
  const e = eventSlug(event);
21
- if (!t || !e) return false;
22
- return t === e || SIGNUP.has(t) && SIGNUP.has(e);
24
+ if (!t2 || !e) return false;
25
+ return t2 === e || SIGNUP.has(t2) && SIGNUP.has(e);
23
26
  };
24
27
  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());
25
28
  var FROM_CHECKLIST = "From a checklist";
26
29
  var isPageTrigger = (trigger) => trigger.trim().startsWith("/");
27
- var manual = (trigger) => eventSlug(trigger) === "from_a_checklist";
30
+ var FROM_CODE = "From your code";
31
+ var NEEDS_NEWER = "Needs tracking code 0.18.0";
32
+ var momentOf = (f) => f.when ?? f.trigger;
33
+ var manual = (trigger) => eventSlug(trigger) === "from_a_checklist" || eventSlug(trigger) === "from_your_code" || eventSlug(trigger) === eventSlug(NEEDS_NEWER);
34
+ var AFTER_PREFIX = "after:";
35
+ var afterOf = (trigger) => {
36
+ const t2 = (trigger ?? "").trim();
37
+ return t2.toLowerCase().startsWith(AFTER_PREFIX) ? t2.slice(AFTER_PREFIX.length).trim() || null : null;
38
+ };
39
+ var opensOf = (step) => step.opens ?? (step.startsTour ? { kind: "tour", id: step.startsTour } : void 0);
40
+ var OTHER_PREFIX = "other:";
41
+ var scaleOf = (q) => {
42
+ const min = q?.min ?? 0;
43
+ const max = q?.max ?? 10;
44
+ return Array.from({ length: Math.max(0, Math.min(max - min, 20) + 1) }, (_, i) => min + i);
45
+ };
46
+ var answerOk = (q, value) => {
47
+ if (!q || value == null) return false;
48
+ if (q.type === "choice") {
49
+ if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim().length > 0;
50
+ return (q.options ?? []).some((o) => o.id === value);
51
+ }
52
+ if (q.type === "scale") return scaleOf(q).some((n) => String(n) === value);
53
+ return value.trim().length > 0 && value.length <= 500;
54
+ };
55
+ var answerLabel = (q, value) => {
56
+ if (q?.type !== "choice") return value.trim();
57
+ if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim();
58
+ return q.options?.find((o) => o.id === value)?.label ?? value;
59
+ };
28
60
  var videoEmbed = (url) => {
29
61
  const u = (url ?? "").trim();
30
62
  if (!/^https?:\/\//i.test(u)) return null;
@@ -46,12 +78,14 @@ var runId = () => `cf_${Math.random().toString(36).slice(2, 10)}${Date.now().toS
46
78
  var discounted = (amount, percent) => Math.round(amount * (1 - percent / 100) * 100) / 100;
47
79
  var money = (amount, currency) => {
48
80
  try {
49
- return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(amount);
81
+ return new Intl.NumberFormat(currentLocale(), { style: "currency", currency }).format(amount);
50
82
  } catch {
51
83
  return `${amount.toFixed(2)} ${currency}`;
52
84
  }
53
85
  };
54
- var offerButton = (o) => o.kind === "contact" ? "Send to the team" : o.kind === "pause" ? "Pause instead" : o.kind === "downgrade" ? "Switch plan" : "Accept offer";
86
+ var offerButton = (o) => o.kind === "contact" ? t("Send to the team") : o.kind === "pause" ? t("Pause instead") : o.kind === "downgrade" ? t("Switch plan") : t("Accept offer");
87
+ var monthsWord = (n) => n === 1 ? t("1 month") : tf("{n} months", { n });
88
+ var forMonths = (n) => tf("for {months}", { months: monthsWord(n) });
55
89
  var safeUrl = (url) => {
56
90
  const u = (url ?? "").trim();
57
91
  return /^https?:\/\//i.test(u) || /^\/(?!\/)/.test(u) ? u : void 0;
@@ -90,7 +124,12 @@ var createFlows = (env) => {
90
124
  const now = () => env.now?.() ?? Date.now();
91
125
  let timer = null;
92
126
  let returnTo = null;
127
+ const answers = /* @__PURE__ */ new Map();
128
+ const drafts = /* @__PURE__ */ new Map();
129
+ const celebrated = /* @__PURE__ */ new Set();
130
+ let finaleNow = null;
93
131
  const flows = () => config?.onboarding ?? [];
132
+ const drawn = () => flows().filter((f) => f.shownBy !== "code");
94
133
  const flowById = (id) => id ? flows().find((f) => f.id === id) : void 0;
95
134
  const prog = (id) => {
96
135
  let p = progress.get(id);
@@ -164,6 +203,14 @@ var createFlows = (env) => {
164
203
  };
165
204
  const keyOf = (pid) => `${PROGRESS_KEY}:${pid ?? "visitor"}`;
166
205
  const eventsKeyOf = (pid) => `${PROGRESS_KEY}:events:${pid ?? "visitor"}`;
206
+ const answersKeyOf = (pid) => `${PROGRESS_KEY}:answers:${pid ?? "visitor"}`;
207
+ const celebrate = (f) => {
208
+ if (!f.finish || f.finish === "none" || f.shownBy === "code" || celebrated.has(f.id)) return;
209
+ const p = progress.get(f.id);
210
+ if (!p || p.dismissed || walkLine(f, nodesOf(f), p, false).at !== "end") return;
211
+ celebrated.add(f.id);
212
+ finaleNow = { kind: f.finish, key: `${f.id}:${now()}` };
213
+ };
167
214
  const save = () => {
168
215
  dirty = false;
169
216
  const store = env.storage?.();
@@ -179,6 +226,7 @@ var createFlows = (env) => {
179
226
  };
180
227
  try {
181
228
  store.set(keyOf(env.personId()), JSON.stringify(out));
229
+ if (answers.size) store.set(answersKeyOf(env.personId()), JSON.stringify(Object.fromEntries(answers)));
182
230
  if (seenEvents.size) store.set(eventsKeyOf(env.personId()), JSON.stringify([...seenEvents].slice(-200)));
183
231
  } catch {
184
232
  }
@@ -231,8 +279,17 @@ var createFlows = (env) => {
231
279
  type: "checklist",
232
280
  flow,
233
281
  items: items.map((s) => {
234
- const tour = s.startsTour && s.startsTour !== flow.id ? flowById(s.startsTour) : void 0;
235
- return { step: s, done: p.done.has(s.id), ...tour && nodesOf(tour).length ? { tour } : {} };
282
+ const o = opensOf(s);
283
+ const t2 = o && (o.kind === "tour" || o.kind === "modal") && o.id !== flow.id ? flowById(o.id) : void 0;
284
+ const target = t2 && t2.shownBy !== "code" && nodesOf(t2).length ? t2 : void 0;
285
+ const opens = o && (o.kind === "page" ? !!o.target.trim() : o.kind === "code" || !!target) ? o : void 0;
286
+ return {
287
+ step: s,
288
+ done: p.done.has(s.id),
289
+ ...target && o?.kind === "tour" ? { tour: target } : {},
290
+ ...opens ? { opens } : {},
291
+ ...target ? { target } : {}
292
+ };
236
293
  })
237
294
  };
238
295
  }
@@ -243,7 +300,16 @@ var createFlows = (env) => {
243
300
  const rest = line.slice(index + 1);
244
301
  const fork = rest.findIndex((n) => n.kind === "condition");
245
302
  const ahead = (fork < 0 ? rest : rest.slice(0, fork)).filter(isStep).length;
246
- return { type: "step", flow, step, index: w.before, total: w.before + 1 + ahead, last: !ahead && fork < 0 };
303
+ return {
304
+ type: "step",
305
+ flow,
306
+ step,
307
+ index: w.before,
308
+ total: w.before + 1 + ahead,
309
+ last: !ahead && fork < 0,
310
+ // 0.18.0: a question's answer so far, and whether there is one before it to go back to.
311
+ ...step.kind === "question" ? { answer: drafts.get(step.id) ?? (step.question ? answers.get(step.question.key) : void 0), back: w.before > 0 } : {}
312
+ };
247
313
  };
248
314
  const view = () => {
249
315
  if (cancel) {
@@ -270,23 +336,25 @@ var createFlows = (env) => {
270
336
  if (ob) return ob;
271
337
  if (active && !holding) {
272
338
  const back = returnTo;
273
- const ended = active;
339
+ const ended2 = active;
274
340
  returnTo = null;
275
341
  active = null;
276
342
  const list = back ? flowById(back.flow) : void 0;
277
343
  if (back && list) {
278
344
  const item = stepsIn(nodesOf(list)).find((s) => s.id === back.step);
279
345
  const p = prog(list.id);
280
- if (item && !item.event && !progress.get(ended)?.dismissed && !p.done.has(item.id)) {
346
+ if (item && !item.event && !progress.get(ended2)?.dismissed && !p.done.has(item.id)) {
281
347
  markDone(p, item.id);
282
348
  record({ flow: list.id, step: item.id, kind: "checklist", action: "completed" });
349
+ celebrate(list);
283
350
  }
284
351
  active = list.id;
285
352
  const again = onboardingView();
286
353
  if (again) return again;
287
354
  if (!holding) active = null;
288
355
  } else if (known) {
289
- const after = flowById(ended)?.trigger;
356
+ const was = flowById(ended2);
357
+ const after = was ? momentOf(was) : void 0;
290
358
  Promise.resolve().then(() => autoStart(after));
291
359
  }
292
360
  }
@@ -314,22 +382,64 @@ var createFlows = (env) => {
314
382
  emit();
315
383
  return true;
316
384
  };
317
- const allowed = (f) => f.audience !== "signed_in" || !!env.personId();
385
+ const allowed = (f) => f.audience === "answered" ? !!f.answered && answers.get(f.answered.question) === f.answered.option : f.audience !== "signed_in" || !!env.personId();
386
+ const predecessorEnded = (id) => {
387
+ const g = flowById(id);
388
+ return !g || finished(g);
389
+ };
390
+ const due = (f, trigger = momentOf(f)) => {
391
+ const path = env.path?.();
392
+ const pred = afterOf(trigger);
393
+ if (pred) return predecessorEnded(pred);
394
+ if (manual(trigger)) return false;
395
+ if (isPageTrigger(trigger)) return path !== void 0 && pageMatches(trigger, path);
396
+ if (startsOnLoad(trigger)) return true;
397
+ for (const e of seenEvents) if (matchesTrigger(trigger, e)) return true;
398
+ return false;
399
+ };
318
400
  const autoStart = (after) => {
319
401
  if (!auto || active || !known) return;
320
402
  if (!env.personId() && !env.storage?.()) return;
321
403
  const path = env.path?.();
322
404
  const same = after && !manual(after) ? eventSlug(after) : "";
323
- const next = flows().find(
324
- (f) => (isPageTrigger(f.trigger) ? path !== void 0 && pageMatches(f.trigger, path) : startsOnLoad(f.trigger) || !!same && eventSlug(f.trigger) === same) && allowed(f) && nodesOf(f).length && !finished(f)
405
+ const next = drawn().find(
406
+ (f) => (afterOf(momentOf(f)) ? predecessorEnded(afterOf(momentOf(f))) : isPageTrigger(momentOf(f)) ? path !== void 0 && pageMatches(momentOf(f), path) : startsOnLoad(momentOf(f)) || !!same && eventSlug(momentOf(f)) === same) && allowed(f) && nodesOf(f).length && !finished(f)
325
407
  );
326
408
  if (next) start(next);
327
409
  };
410
+ const openItem = (list, stepId, target) => {
411
+ const p = prog(target.id);
412
+ p.done.clear();
413
+ p.at.clear();
414
+ p.dismissed = false;
415
+ returnTo = { flow: list.id, step: stepId };
416
+ active = target.id;
417
+ env.track(ONBOARDING_EVENT, { flow: target.id, action: "started", from: list.id });
418
+ save();
419
+ emit();
420
+ };
421
+ const answerStep = (flow, step, raw) => {
422
+ const q = step.question;
423
+ const value = q.type === "text" ? raw.trim().slice(0, 500) : raw.startsWith(OTHER_PREFIX) ? OTHER_PREFIX + raw.slice(OTHER_PREFIX.length).trim().slice(0, 490) : raw;
424
+ answers.set(q.key, value);
425
+ drafts.delete(step.id);
426
+ markDone(prog(flow.id), step.id);
427
+ env.track(ONBOARDING_EVENT, { flow: flow.id, step: step.id, kind: "question", action: "completed", question: q.key, answer: value, label: answerLabel(q, value) });
428
+ celebrate(flow);
429
+ save();
430
+ };
431
+ const ended = (f) => {
432
+ save();
433
+ if (finished(f)) Promise.resolve().then(() => autoStart());
434
+ emit();
435
+ };
328
436
  const refresh = () => {
329
437
  const pid = env.personId();
330
438
  if (progressFor !== null && progressFor !== pid) {
331
439
  progress.clear();
332
440
  seenEvents.clear();
441
+ answers.clear();
442
+ drafts.clear();
333
443
  handled.clear();
334
444
  active = null;
335
445
  returnTo = null;
@@ -346,6 +456,10 @@ var createFlows = (env) => {
346
456
  if (seen) {
347
457
  for (const e of JSON.parse(seen)) if (typeof e === "string") seenEvents.add(e);
348
458
  }
459
+ const said = await store.get(answersKeyOf(pid));
460
+ if (said) {
461
+ for (const [k, v] of Object.entries(JSON.parse(said))) if (typeof v === "string") answers.set(k, v);
462
+ }
349
463
  } catch {
350
464
  }
351
465
  }
@@ -356,6 +470,7 @@ var createFlows = (env) => {
356
470
  if (state && env.personId() === pid) {
357
471
  merge(state.onboarding);
358
472
  for (const e of state.events ?? []) if (typeof e === "string") seenEvents.add(eventSlug(e));
473
+ for (const [k, v] of Object.entries(state.answers ?? {})) if (typeof v === "string" && !answers.has(k)) answers.set(k, v);
359
474
  messages = (state.messages ?? []).filter((m) => m && m.id && m.title);
360
475
  }
361
476
  }
@@ -416,7 +531,7 @@ var createFlows = (env) => {
416
531
  () => finish(c, r),
417
532
  () => {
418
533
  c.busy = false;
419
- c.error = FAILED;
534
+ c.error = t("Something went wrong. Please try again.");
420
535
  emit();
421
536
  }
422
537
  );
@@ -449,27 +564,33 @@ var createFlows = (env) => {
449
564
  if (fresh) seenEvents.add(slug);
450
565
  const flow = flowById(active);
451
566
  let ticked = false;
452
- if (flow) {
453
- const p = prog(flow.id);
454
- for (const s of stepsIn(nodesOf(flow)))
567
+ const lists = [...flow ? [flow] : [], ...flows().filter((f) => f.shownBy === "code" && f.id !== flow?.id)];
568
+ for (const l of lists) {
569
+ const p = prog(l.id);
570
+ let here = false;
571
+ for (const s of stepsIn(nodesOf(l)))
455
572
  if (s.kind === "checklist" && s.event && !p.done.has(s.id) && matchesTrigger(s.event, name)) {
456
573
  markDone(p, s.id);
457
- ticked = true;
458
- env.track(ONBOARDING_EVENT, { flow: flow.id, step: s.id, kind: s.kind, action: "completed" });
574
+ here = true;
575
+ env.track(ONBOARDING_EVENT, { flow: l.id, step: s.id, kind: s.kind, action: "completed" });
459
576
  }
577
+ if (here) {
578
+ ticked = true;
579
+ celebrate(l);
580
+ }
460
581
  }
461
- if (ticked || fresh && flow) {
582
+ if (ticked || fresh && (flow || lists.length)) {
462
583
  save();
463
584
  emit();
464
585
  } else if (fresh) save();
465
586
  if (!auto || !env.personId() && !env.storage?.()) return;
466
- const next = flows().find((f) => nodesOf(f).length && !manual(f.trigger) && matchesTrigger(f.trigger, name) && allowed(f) && !finished(f));
587
+ const next = drawn().find((f) => nodesOf(f).length && !manual(momentOf(f)) && matchesTrigger(momentOf(f), name) && allowed(f) && !finished(f));
467
588
  if (next) start(next);
468
589
  },
469
590
  openCancelFlow,
470
591
  startOnboarding: (id) => {
471
- const f = id ? flowById(id) : flows().find((x) => nodesOf(x).length && !manual(x.trigger) && !finished(x));
472
- if (!f || !nodesOf(f).length) return false;
592
+ const f = id ? flowById(id) : drawn().find((x) => nodesOf(x).length && !manual(momentOf(x)) && !afterOf(momentOf(x)) && !finished(x));
593
+ if (!f || !nodesOf(f).length || f.shownBy === "code") return false;
473
594
  const p = prog(f.id);
474
595
  p.dismissed = false;
475
596
  return start(f);
@@ -490,8 +611,8 @@ var createFlows = (env) => {
490
611
  answer: (text) => {
491
612
  const c = cancel;
492
613
  if (!c || c.busy || c.screen !== "followup") return;
493
- const t = text.slice(0, 500);
494
- c.answer = t.trim() ? t : void 0;
614
+ const t2 = text.slice(0, 500);
615
+ c.answer = t2.trim() ? t2 : void 0;
495
616
  emit();
496
617
  },
497
618
  next: () => {
@@ -560,8 +681,17 @@ var createFlows = (env) => {
560
681
  next: () => {
561
682
  const v = onboardingView();
562
683
  if (v?.type !== "step") return;
684
+ if (v.step.kind === "question") {
685
+ const q = v.step.question;
686
+ const a = drafts.get(v.step.id) ?? (q ? answers.get(q.key) : void 0);
687
+ if (!q || !answerOk(q, a)) return;
688
+ answerStep(v.flow, v.step, a);
689
+ emit();
690
+ return;
691
+ }
563
692
  markDone(prog(v.flow.id), v.step.id);
564
693
  env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: v.step.kind, action: "completed" });
694
+ celebrate(v.flow);
565
695
  save();
566
696
  emit();
567
697
  const url = v.step.kind === "modal" ? safeUrl(v.step.url) : void 0;
@@ -574,6 +704,7 @@ var createFlows = (env) => {
574
704
  if (!item || item.done) return;
575
705
  markDone(prog(v.flow.id), stepId);
576
706
  env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
707
+ celebrate(v.flow);
577
708
  save();
578
709
  emit();
579
710
  },
@@ -590,20 +721,123 @@ var createFlows = (env) => {
590
721
  startTour: (stepId) => {
591
722
  const v = onboardingView();
592
723
  if (v?.type !== "checklist") return false;
593
- const tour = v.items.find((i) => i.step.id === stepId)?.tour;
594
- if (!tour) return false;
595
- const p = prog(tour.id);
596
- p.done.clear();
597
- p.at.clear();
598
- p.dismissed = false;
599
- returnTo = { flow: v.flow.id, step: stepId };
600
- active = tour.id;
601
- env.track(ONBOARDING_EVENT, { flow: tour.id, action: "started", from: v.flow.id });
724
+ const target = v.items.find((i) => i.step.id === stepId)?.target;
725
+ if (!target) return false;
726
+ openItem(v.flow, stepId, target);
727
+ return true;
728
+ },
729
+ open: (stepId) => {
730
+ const v = onboardingView();
731
+ if (v?.type !== "checklist") return false;
732
+ const it = v.items.find((i) => i.step.id === stepId);
733
+ const o = it?.opens;
734
+ if (!it || !o) return false;
735
+ env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "opened", opens: o.kind });
736
+ if ((o.kind === "tour" || o.kind === "modal") && it.target) {
737
+ openItem(v.flow, stepId, it.target);
738
+ return true;
739
+ }
740
+ if (!it.done && !it.step.event) {
741
+ markDone(prog(v.flow.id), stepId);
742
+ env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
743
+ celebrate(v.flow);
744
+ }
602
745
  save();
603
746
  emit();
747
+ if (o.kind === "page") env.navigate?.(o.target.trim());
748
+ else env.task?.(stepId, v.flow);
604
749
  return true;
750
+ },
751
+ answer: (value) => {
752
+ const v = onboardingView();
753
+ if (v?.type !== "step" || v.step.kind !== "question") return;
754
+ drafts.set(v.step.id, String(value).slice(0, 500));
755
+ emit();
756
+ },
757
+ skip: () => {
758
+ const v = onboardingView();
759
+ const q = v?.type === "step" && v.step.kind === "question" ? v.step.question : void 0;
760
+ if (v?.type !== "step" || !q?.optional) return;
761
+ drafts.delete(v.step.id);
762
+ markDone(prog(v.flow.id), v.step.id);
763
+ env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: "question", action: "completed", question: q.key, skipped: true });
764
+ celebrate(v.flow);
765
+ save();
766
+ emit();
767
+ },
768
+ back: () => {
769
+ const v = onboardingView();
770
+ if (v?.type !== "step" || v.step.kind !== "question" || !v.back) return;
771
+ const line = stepsIn(nodesOf(v.flow));
772
+ const at = line.findIndex((s) => s.id === v.step.id);
773
+ const p = prog(v.flow.id);
774
+ for (let i = at - 1; i >= 0; i--)
775
+ if (p.done.has(line[i].id)) {
776
+ p.done.delete(line[i].id);
777
+ p.at.delete(line[i].id);
778
+ break;
779
+ }
780
+ save();
781
+ emit();
782
+ },
783
+ item: (id) => {
784
+ const f = flowById(id);
785
+ if (!f) return null;
786
+ const p = progress.get(f.id);
787
+ const steps = stepsIn(nodesOf(f)).map((s) => {
788
+ const a = s.question ? answers.get(s.question.key) : void 0;
789
+ return { ...s, done: !!p?.done.has(s.id), ...a !== void 0 ? { answer: a } : {} };
790
+ });
791
+ const done = steps.length > 0 && steps.every((s) => s.done);
792
+ const dismissed = !!p?.dismissed;
793
+ return { item: f, steps, done, dismissed, eligible: !done && !dismissed && allowed(f) && due(f) };
794
+ },
795
+ seen: (id) => {
796
+ const f = flowById(id);
797
+ if (!f || f.shownBy !== "code" || !known) return;
798
+ if (!shown.has(`f:${f.id}`)) {
799
+ shown.add(`f:${f.id}`);
800
+ env.track(ONBOARDING_EVENT, { flow: f.id, action: "started" });
801
+ }
802
+ const p = progress.get(f.id);
803
+ for (const s of stepsIn(nodesOf(f)))
804
+ if (!p?.done.has(s.id) && !shown.has(`s:${s.id}`)) {
805
+ shown.add(`s:${s.id}`);
806
+ env.track(ONBOARDING_EVENT, { flow: f.id, step: s.id, kind: s.kind, action: "shown" });
807
+ if (s.kind !== "checklist") break;
808
+ }
809
+ },
810
+ complete: (id, stepId) => {
811
+ const f = flowById(id);
812
+ const s = f && stepsIn(nodesOf(f)).find((x) => x.id === stepId);
813
+ if (!f || !s || s.kind === "question") return;
814
+ const p = prog(f.id);
815
+ if (p.done.has(s.id)) return;
816
+ markDone(p, s.id);
817
+ env.track(ONBOARDING_EVENT, { flow: f.id, step: s.id, kind: s.kind, action: "completed" });
818
+ ended(f);
819
+ },
820
+ dismissItem: (id) => {
821
+ const f = flowById(id);
822
+ if (!f) return;
823
+ const p = prog(f.id);
824
+ if (p.dismissed) return;
825
+ p.dismissed = true;
826
+ env.track(ONBOARDING_EVENT, { flow: f.id, action: "dismissed" });
827
+ if (active === f.id) active = null;
828
+ ended(f);
829
+ },
830
+ answerItem: (id, question, value) => {
831
+ const f = flowById(id);
832
+ const s = f && stepsIn(nodesOf(f)).find((x) => x.kind === "question" && (x.id === question || x.question?.key === question));
833
+ if (!f || !s?.question || !answerOk(s.question, value)) return;
834
+ answerStep(f, s, value);
835
+ ended(f);
605
836
  }
606
837
  },
838
+ finale: () => finaleNow,
839
+ accent: () => config?.onboardingAccent,
840
+ answers: () => Object.fromEntries(answers),
607
841
  message: {
608
842
  click: () => {
609
843
  const v = view();
@@ -626,26 +860,38 @@ var createFlows = (env) => {
626
860
  return controller;
627
861
  };
628
862
  export {
863
+ AFTER_PREFIX,
629
864
  CANCEL_EVENT,
630
865
  FROM_CHECKLIST,
866
+ FROM_CODE,
631
867
  INAPP_EVENT,
868
+ NEEDS_NEWER,
632
869
  ONBOARDING_EVENT,
870
+ OTHER_PREFIX,
633
871
  PLAIN_CANCEL,
634
872
  REVIEW_EVENT,
873
+ afterOf,
874
+ answerLabel,
875
+ answerOk,
635
876
  createFlows,
636
877
  discounted,
637
878
  eventSlug,
879
+ forMonths,
638
880
  isPageTrigger,
639
881
  matchesTrigger,
640
882
  mediaOf,
883
+ momentOf,
641
884
  money,
642
885
  monthsSince,
886
+ monthsWord,
643
887
  nodesOf,
644
888
  offerButton,
889
+ opensOf,
645
890
  pageMatches,
646
891
  ruleMatches,
647
892
  runId,
648
893
  safeUrl,
894
+ scaleOf,
649
895
  startsOnLoad,
650
896
  videoEmbed,
651
897
  waitMs