@appfunnel-dev/sdk 2.0.0-canary.1 → 2.0.0-canary.10

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 (58) hide show
  1. package/README.md +1 -1
  2. package/dist/capabilities-Dq22RCr_.d.cts +180 -0
  3. package/dist/capabilities-Dq22RCr_.d.ts +180 -0
  4. package/dist/checkout-ClaO5IYV.d.ts +333 -0
  5. package/dist/checkout-DBp4bCpC.d.cts +333 -0
  6. package/dist/chunk-7JLOF6CJ.cjs +172 -0
  7. package/dist/chunk-7JLOF6CJ.cjs.map +1 -0
  8. package/dist/chunk-EMMSS5I5.cjs +37 -0
  9. package/dist/chunk-EMMSS5I5.cjs.map +1 -0
  10. package/dist/chunk-G3PMV62Z.js +33 -0
  11. package/dist/chunk-G3PMV62Z.js.map +1 -0
  12. package/dist/chunk-JAO6AA4R.js +99 -0
  13. package/dist/chunk-JAO6AA4R.js.map +1 -0
  14. package/dist/chunk-OXQBEKZ5.js +157 -0
  15. package/dist/chunk-OXQBEKZ5.js.map +1 -0
  16. package/dist/chunk-VV7TFC64.cjs +106 -0
  17. package/dist/chunk-VV7TFC64.cjs.map +1 -0
  18. package/dist/chunk-VW2HVPR4.js +446 -0
  19. package/dist/chunk-VW2HVPR4.js.map +1 -0
  20. package/dist/chunk-WYUDL4FI.cjs +8 -0
  21. package/dist/chunk-WYUDL4FI.cjs.map +1 -0
  22. package/dist/chunk-Y4YNJ2EX.cjs +476 -0
  23. package/dist/chunk-Y4YNJ2EX.cjs.map +1 -0
  24. package/dist/chunk-ZZJG4EYL.js +6 -0
  25. package/dist/chunk-ZZJG4EYL.js.map +1 -0
  26. package/dist/driver-paddle.cjs +817 -0
  27. package/dist/driver-paddle.cjs.map +1 -0
  28. package/dist/driver-paddle.d.cts +10 -0
  29. package/dist/driver-paddle.d.ts +10 -0
  30. package/dist/driver-paddle.js +814 -0
  31. package/dist/driver-paddle.js.map +1 -0
  32. package/dist/driver-stripe.cjs +2312 -0
  33. package/dist/driver-stripe.cjs.map +1 -0
  34. package/dist/driver-stripe.d.cts +24 -0
  35. package/dist/driver-stripe.d.ts +24 -0
  36. package/dist/driver-stripe.js +2305 -0
  37. package/dist/driver-stripe.js.map +1 -0
  38. package/dist/index.cjs +2650 -841
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +343 -1005
  41. package/dist/index.d.ts +343 -1005
  42. package/dist/index.js +2381 -701
  43. package/dist/index.js.map +1 -1
  44. package/dist/manifest-B3Tdab0M.d.cts +920 -0
  45. package/dist/manifest-B3Tdab0M.d.ts +920 -0
  46. package/dist/manifest-entry.cjs +408 -0
  47. package/dist/manifest-entry.cjs.map +1 -0
  48. package/dist/manifest-entry.d.cts +192 -0
  49. package/dist/manifest-entry.d.ts +192 -0
  50. package/dist/manifest-entry.js +270 -0
  51. package/dist/manifest-entry.js.map +1 -0
  52. package/dist/protocol.cjs +13 -0
  53. package/dist/protocol.cjs.map +1 -0
  54. package/dist/protocol.d.cts +182 -0
  55. package/dist/protocol.d.ts +182 -0
  56. package/dist/protocol.js +4 -0
  57. package/dist/protocol.js.map +1 -0
  58. package/package.json +49 -4
package/dist/index.cjs CHANGED
@@ -1,10 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
4
- var reactDom = require('react-dom');
5
- var sonner = require('sonner');
3
+ var chunkY4YNJ2EX_cjs = require('./chunk-Y4YNJ2EX.cjs');
4
+ var chunkWYUDL4FI_cjs = require('./chunk-WYUDL4FI.cjs');
5
+ var chunk7JLOF6CJ_cjs = require('./chunk-7JLOF6CJ.cjs');
6
+ require('./chunk-EMMSS5I5.cjs');
7
+ var React = require('react');
8
+ var ReactDOM = require('react-dom');
9
+ var jsxRuntime = require('react/jsx-runtime');
6
10
 
7
- // src/state/namespaces.ts
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var React__default = /*#__PURE__*/_interopDefault(React);
14
+ var ReactDOM__default = /*#__PURE__*/_interopDefault(ReactDOM);
8
15
 
9
16
  // src/state/variableStore.ts
10
17
  var VariableStore = class {
@@ -258,8 +265,12 @@ function collectClickIds() {
258
265
  }
259
266
  function readCookie(name) {
260
267
  if (typeof document === "undefined") return void 0;
261
- const m = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
262
- return m ? decodeURIComponent(m[1]) : void 0;
268
+ try {
269
+ const m = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
270
+ return m ? decodeURIComponent(m[1]) : void 0;
271
+ } catch {
272
+ return void 0;
273
+ }
263
274
  }
264
275
  function buildAcquisition(context) {
265
276
  return {
@@ -272,41 +283,44 @@ function buildAcquisition(context) {
272
283
  }
273
284
  function buildContext(opts = {}) {
274
285
  const raw = detect();
275
- const now = opts.now ?? 0;
286
+ const now2 = opts.now ?? 0;
276
287
  const referrer = typeof document !== "undefined" ? document.referrer : "";
277
- const base2 = {
288
+ const base = {
278
289
  device: raw.device,
279
290
  browser: raw.browser,
280
291
  os: raw.os,
281
292
  locale: raw.locale,
282
293
  utm: collectUtm(),
283
294
  clickIds: collectClickIds(),
284
- page: { key: "", slug: "", index: 0, total: 0, progressPercentage: 0, startedAt: now },
285
- session: { id: opts.sessionId ?? null, startedAt: now, referrer },
295
+ page: { key: "", slug: "", index: 0, total: 0, progressPercentage: 0, startedAt: now2 },
296
+ session: { id: opts.sessionId ?? null, startedAt: now2, referrer },
286
297
  identity: { customerId: opts.customerId ?? null, visitorId: opts.visitorId ?? null },
287
298
  system: {
288
299
  funnelId: opts.funnelId ?? "",
289
300
  campaignId: opts.campaignId ?? "",
290
301
  mode: opts.mode ?? "live",
291
- now
302
+ now: now2
292
303
  }
293
304
  };
294
- if (!opts.overrides) return base2;
305
+ if (!opts.overrides) return base;
295
306
  return {
296
- ...base2,
307
+ ...base,
297
308
  ...opts.overrides,
298
- device: { ...base2.device, ...opts.overrides.device },
299
- browser: { ...base2.browser, ...opts.overrides.browser },
300
- os: { ...base2.os, ...opts.overrides.os },
301
- locale: { ...base2.locale, ...opts.overrides.locale },
302
- utm: { ...base2.utm, ...opts.overrides.utm },
303
- clickIds: { ...base2.clickIds, ...opts.overrides.clickIds },
304
- page: { ...base2.page, ...opts.overrides.page },
305
- session: { ...base2.session, ...opts.overrides.session },
306
- identity: { ...base2.identity, ...opts.overrides.identity },
307
- system: { ...base2.system, ...opts.overrides.system }
309
+ device: { ...base.device, ...opts.overrides.device },
310
+ browser: { ...base.browser, ...opts.overrides.browser },
311
+ os: { ...base.os, ...opts.overrides.os },
312
+ locale: { ...base.locale, ...opts.overrides.locale },
313
+ utm: { ...base.utm, ...opts.overrides.utm },
314
+ clickIds: { ...base.clickIds, ...opts.overrides.clickIds },
315
+ page: { ...base.page, ...opts.overrides.page },
316
+ session: { ...base.session, ...opts.overrides.session },
317
+ identity: { ...base.identity, ...opts.overrides.identity },
318
+ system: { ...base.system, ...opts.overrides.system }
308
319
  };
309
320
  }
321
+ function exposurePayload(experimentId, variant, version) {
322
+ return { experimentId, variant, ...version !== void 0 ? { version } : {} };
323
+ }
310
324
  var _eventIdCounter = 0;
311
325
  function newEventId() {
312
326
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
@@ -323,20 +337,20 @@ function createConsoleTracker(opts = {}) {
323
337
  setAcquisition: (acq) => log("acquisition", acq)
324
338
  };
325
339
  }
326
- var TrackerContext = react.createContext(null);
340
+ var TrackerContext = React.createContext(null);
327
341
  function TrackerProvider({
328
342
  tracker,
329
343
  children
330
344
  }) {
331
- return react.createElement(TrackerContext.Provider, { value: tracker }, children);
345
+ return React.createElement(TrackerContext.Provider, { value: tracker }, children);
332
346
  }
333
347
  function useTrackerRef() {
334
- return react.useContext(TrackerContext) ?? FALLBACK;
348
+ return React.useContext(TrackerContext) ?? FALLBACK;
335
349
  }
336
350
  var FALLBACK = createConsoleTracker({ silent: true });
337
351
  function useTracker() {
338
352
  const tracker = useTrackerRef();
339
- const track = react.useCallback(
353
+ const track = React.useCallback(
340
354
  (event, data, userData) => tracker.track(event, data, userData),
341
355
  [tracker]
342
356
  );
@@ -345,7 +359,7 @@ function useTracker() {
345
359
 
346
360
  // src/tracking/bus.ts
347
361
  var MAX_HISTORY = 250;
348
- function createBus(accessors, now = () => Date.now()) {
362
+ function createBus(accessors, now2 = () => Date.now()) {
349
363
  const listeners = /* @__PURE__ */ new Map();
350
364
  const events = [];
351
365
  const on = (event, cb) => {
@@ -358,7 +372,7 @@ function createBus(accessors, now = () => Date.now()) {
358
372
  listeners.get(event)?.delete(cb);
359
373
  };
360
374
  const emit = (event, data) => {
361
- events.push({ event, data, ts: now() });
375
+ events.push({ event, data, ts: now2() });
362
376
  if (events.length > MAX_HISTORY) events.shift();
363
377
  for (const cb of listeners.get(event) ?? []) {
364
378
  try {
@@ -373,7 +387,40 @@ function createBus(accessors, now = () => Date.now()) {
373
387
  }
374
388
  }
375
389
  };
376
- return { on, off, events, emit, ...accessors };
390
+ let nav = null;
391
+ let warnedNoNav = false;
392
+ const currentNav = () => {
393
+ if (!nav && !warnedNoNav) {
394
+ warnedNoNav = true;
395
+ console.warn(
396
+ "[appfunnel] window.appfunnel navigation called before the funnel flow mounted \u2014 ignored"
397
+ );
398
+ }
399
+ return nav;
400
+ };
401
+ const registerNavigation = (n) => {
402
+ nav = n;
403
+ warnedNoNav = false;
404
+ return () => {
405
+ if (nav === n) nav = null;
406
+ };
407
+ };
408
+ const goTo = (pageKey) => currentNav()?.goTo(pageKey) ?? false;
409
+ const goNext = () => currentNav()?.goNext();
410
+ const goPrevious = () => currentNav()?.goPrevious();
411
+ const navigation = () => nav?.snapshot() ?? null;
412
+ return {
413
+ on,
414
+ off,
415
+ events,
416
+ emit,
417
+ registerNavigation,
418
+ goTo,
419
+ goNext,
420
+ goPrevious,
421
+ navigation,
422
+ ...accessors
423
+ };
377
424
  }
378
425
  function withBus(tracker, bus) {
379
426
  return {
@@ -395,7 +442,19 @@ function withBus(tracker, bus) {
395
442
  function attachBus(bus) {
396
443
  if (typeof window === "undefined") return () => {
397
444
  };
398
- const { on, off, getVariable, getVariables, getCurrentPageId, getCustomerId, getVisitorId } = bus;
445
+ const {
446
+ on,
447
+ off,
448
+ getVariable,
449
+ getVariables,
450
+ getCurrentPageId,
451
+ getCustomerId,
452
+ getVisitorId,
453
+ goTo,
454
+ goNext,
455
+ goPrevious,
456
+ navigation
457
+ } = bus;
399
458
  const api = {
400
459
  on,
401
460
  off,
@@ -404,6 +463,11 @@ function attachBus(bus) {
404
463
  getCurrentPageId,
405
464
  getCustomerId,
406
465
  getVisitorId,
466
+ // Flow control (bound closures over the bus's live nav registration).
467
+ goTo,
468
+ goNext,
469
+ goPrevious,
470
+ navigation,
407
471
  get events() {
408
472
  return bus.events;
409
473
  }
@@ -413,21 +477,6 @@ function attachBus(bus) {
413
477
  if (window.appfunnel === api) delete window.appfunnel;
414
478
  };
415
479
  }
416
- var RTL_LANGS = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur", "ps", "sd", "dv", "yi"]);
417
- function isRtl(locale) {
418
- return RTL_LANGS.has(locale.split("-")[0]);
419
- }
420
- var base = (l) => l?.split("-")[0];
421
- function resolveLocale(config, detected, override) {
422
- const supported = config?.supported ?? (config?.default ? [config.default] : ["en"]);
423
- const def = config?.default ?? supported[0] ?? "en";
424
- const pick = (l) => {
425
- if (!l) return void 0;
426
- if (supported.includes(l)) return l;
427
- return supported.find((s) => base(s) === base(l));
428
- };
429
- return pick(override) ?? pick(detected) ?? def;
430
- }
431
480
  function interpolate(str, params) {
432
481
  if (!params) return str;
433
482
  return str.replace(/\{\{(\w+)\}\}/g, (_, k) => k in params ? String(params[k]) : `{{${k}}}`);
@@ -447,7 +496,7 @@ function devWarn(message) {
447
496
  } catch {
448
497
  }
449
498
  }
450
- var LocaleContext = react.createContext(null);
499
+ var LocaleContext = React.createContext(null);
451
500
  function LocaleProvider({
452
501
  config,
453
502
  catalog = {},
@@ -455,26 +504,29 @@ function LocaleProvider({
455
504
  override,
456
505
  children
457
506
  }) {
458
- const [locale, setLocale] = react.useState(() => resolveLocale(config, detected, override));
459
- const value = react.useMemo(
507
+ const [locale, setLocale] = React.useState(() => chunkY4YNJ2EX_cjs.resolveLocale(config, detected, override));
508
+ React.useEffect(() => {
509
+ setLocale(chunkY4YNJ2EX_cjs.resolveLocale(config, detected, override));
510
+ }, [config, detected, override]);
511
+ const value = React.useMemo(
460
512
  () => ({ locale, setLocale, config, catalog }),
461
513
  [locale, config, catalog]
462
514
  );
463
- return react.createElement(LocaleContext.Provider, { value }, children);
515
+ return React.createElement(LocaleContext.Provider, { value }, children);
464
516
  }
465
517
  function useActiveLocale() {
466
- return react.useContext(LocaleContext)?.locale ?? "en";
518
+ return React.useContext(LocaleContext)?.locale ?? "en";
467
519
  }
468
520
  function useTranslation() {
469
- const ctx = react.useContext(LocaleContext);
521
+ const ctx = React.useContext(LocaleContext);
470
522
  if (!ctx) throw new Error("useTranslation must be used inside <FunnelProvider>");
471
523
  const { locale, setLocale, config, catalog } = ctx;
472
- const chain = react.useMemo(() => {
524
+ const chain = React.useMemo(() => {
473
525
  const def = config?.default;
474
526
  const fb = config?.fallback;
475
527
  return Array.from(new Set([locale, fb, def].filter(Boolean)));
476
528
  }, [locale, config]);
477
- const t = react.useMemo(() => {
529
+ const t = React.useMemo(() => {
478
530
  const fn = ((key, params) => {
479
531
  const raw = lookup(catalog, chain, key);
480
532
  if (raw === void 0) {
@@ -484,17 +536,40 @@ function useTranslation() {
484
536
  return interpolate(raw, params);
485
537
  });
486
538
  fn.plural = (n, forms, params) => {
487
- const rule = new Intl.PluralRules(locale).select(n);
539
+ let rule;
540
+ try {
541
+ rule = new Intl.PluralRules(locale).select(n);
542
+ } catch {
543
+ rule = "other";
544
+ }
488
545
  const form = forms[rule] ?? forms.other ?? "";
489
546
  return interpolate(form.replace(/#/g, String(n)), params);
490
547
  };
491
548
  return fn;
492
549
  }, [catalog, chain, locale]);
493
- const fmt = react.useMemo(
550
+ const fmt = React.useMemo(
494
551
  () => ({
495
- number: (n, opts) => new Intl.NumberFormat(locale, opts).format(n),
496
- date: (d, opts) => new Intl.DateTimeFormat(locale, opts).format(d),
497
- percent: (n, opts) => new Intl.NumberFormat(locale, { style: "percent", ...opts }).format(n)
552
+ number: (n, opts) => {
553
+ try {
554
+ return new Intl.NumberFormat(locale, opts).format(n);
555
+ } catch {
556
+ return String(n);
557
+ }
558
+ },
559
+ date: (d, opts) => {
560
+ try {
561
+ return new Intl.DateTimeFormat(locale, opts).format(d);
562
+ } catch {
563
+ return String(d instanceof Date ? d.toISOString() : d);
564
+ }
565
+ },
566
+ percent: (n, opts) => {
567
+ try {
568
+ return new Intl.NumberFormat(locale, { style: "percent", ...opts }).format(n);
569
+ } catch {
570
+ return `${(n * 100).toFixed(0)}%`;
571
+ }
572
+ }
498
573
  }),
499
574
  [locale]
500
575
  );
@@ -503,336 +578,140 @@ function useTranslation() {
503
578
  fmt,
504
579
  locale,
505
580
  setLocale,
506
- dir: isRtl(locale) ? "rtl" : "ltr",
581
+ dir: chunkY4YNJ2EX_cjs.isRtl(locale) ? "rtl" : "ltr",
507
582
  locales: config?.supported ?? [locale]
508
583
  };
509
584
  }
510
585
 
511
586
  // src/commerce/catalog.ts
512
- var PERIOD_DAYS = {
513
- day: 1,
514
- week: 7,
515
- month: 30,
516
- year: 365,
517
- one_time: 0
518
- };
519
- function periodDays(interval, count) {
520
- return PERIOD_DAYS[interval] * count;
521
- }
522
- function intervalLabel(interval, count) {
523
- if (interval === "one_time") return { period: "one-time", periodly: "one-time" };
524
- if (interval === "month" && count === 3) return { period: "quarter", periodly: "quarterly" };
525
- if (interval === "month" && count === 6) return { period: "6 months", periodly: "semiannually" };
526
- if (interval === "week" && count === 2) return { period: "2 weeks", periodly: "biweekly" };
527
- if (count === 1) {
528
- const periodly = { day: "daily", week: "weekly", month: "monthly", year: "yearly" }[interval];
529
- return { period: interval, periodly };
530
- }
531
- return { period: `${count} ${interval}s`, periodly: `every ${count} ${interval}s` };
532
- }
533
- function resolveProduct(input) {
534
- const interval = input.interval ?? "one_time";
535
- const count = input.intervalCount ?? 1;
536
- const days = periodDays(interval, count);
537
- const perDayMinor = days > 0 ? input.amount / days : input.amount;
538
- const label = intervalLabel(interval, count);
539
- return {
540
- id: input.id,
541
- name: input.name ?? input.id,
542
- displayName: input.displayName ?? input.name ?? input.id,
543
- provider: input.provider ?? "stripe",
544
- currency: input.currency,
545
- priceMinor: input.amount,
546
- perDayMinor,
547
- perWeekMinor: perDayMinor * 7,
548
- perMonthMinor: perDayMinor * 30,
549
- perYearMinor: perDayMinor * 365,
550
- period: label.period,
551
- periodly: label.periodly,
552
- hasTrial: (input.trialDays ?? 0) > 0,
553
- trialDays: input.trialDays ?? 0,
554
- trialMinor: input.trialAmount ?? 0
555
- };
556
- }
557
- function buildCatalog(inputs) {
558
- return new Map(inputs.map((i) => [i.id, resolveProduct(i)]));
559
- }
560
- function formatMoney(minor, currency, locale) {
561
- const amount = minor / 100;
562
- const code = currency.toUpperCase();
563
- let formatted;
564
- try {
565
- formatted = new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(amount);
566
- } catch {
567
- formatted = `${code} ${amount.toFixed(2)}`;
568
- }
569
- return { amount, minor, currency: code, formatted };
570
- }
571
- function formatProduct(r, locale) {
572
- const m = (minor) => formatMoney(minor, r.currency, locale);
573
- return {
574
- id: r.id,
575
- name: r.name,
576
- displayName: r.displayName,
577
- provider: r.provider,
578
- price: m(r.priceMinor),
579
- period: r.period,
580
- periodly: r.periodly,
581
- perDay: m(r.perDayMinor),
582
- perWeek: m(r.perWeekMinor),
583
- perMonth: m(r.perMonthMinor),
584
- perYear: m(r.perYearMinor),
585
- hasTrial: r.hasTrial,
586
- trialDays: r.trialDays,
587
- trialPrice: r.hasTrial ? m(r.trialMinor) : null
588
- };
589
- }
590
- var CatalogContext = react.createContext(null);
587
+ var CatalogContext = React.createContext(null);
591
588
  function CatalogProvider({
592
589
  catalog,
593
590
  children
594
591
  }) {
595
- return react.createElement(CatalogContext.Provider, { value: catalog }, children);
592
+ return React.createElement(CatalogContext.Provider, { value: catalog }, children);
596
593
  }
597
594
  function useCatalog() {
598
- return react.useContext(CatalogContext) ?? EMPTY;
595
+ return React.useContext(CatalogContext) ?? EMPTY;
599
596
  }
600
597
  var EMPTY = /* @__PURE__ */ new Map();
601
- function useProduct(id) {
598
+ function useOffering(id) {
602
599
  const catalog = useCatalog();
603
600
  const locale = useActiveLocale();
604
601
  const resolved = id ? catalog.get(id) : void 0;
605
- return react.useMemo(() => resolved ? formatProduct(resolved, locale) : void 0, [resolved, locale]);
602
+ return React.useMemo(
603
+ () => resolved ? chunkY4YNJ2EX_cjs.formatOffering(resolved, locale) : void 0,
604
+ [resolved, locale]
605
+ );
606
606
  }
607
- function useProducts() {
607
+ function useOfferings() {
608
608
  const catalog = useCatalog();
609
609
  const locale = useActiveLocale();
610
- return react.useMemo(
611
- () => Array.from(catalog.values()).map((r) => formatProduct(r, locale)),
610
+ return React.useMemo(
611
+ () => Array.from(catalog.values()).map((r) => chunkY4YNJ2EX_cjs.formatOffering(r, locale)),
612
612
  [catalog, locale]
613
613
  );
614
614
  }
615
615
 
616
- // src/flow/spine.ts
617
- function readField(s, field) {
618
- let cur = s;
619
- for (const part of field.split(".")) {
620
- if (cur == null) return void 0;
621
- cur = cur[part];
616
+ // src/flow/screenshot.ts
617
+ var MIME = { png: "image/png", jpeg: "image/jpeg", webp: "image/webp" };
618
+ var ScreenshotError = class extends Error {
619
+ constructor(code, message) {
620
+ super(message);
621
+ this.code = code;
622
622
  }
623
- return cur;
624
- }
625
- function evaluateCondition(cond, s) {
626
- const val = readField(s, cond.field);
627
- const { op, value } = cond;
628
- switch (op) {
629
- case "eq":
630
- return val === value;
631
- case "neq":
632
- return val !== value;
633
- case "in":
634
- return Array.isArray(value) && value.includes(val);
635
- case "gt":
636
- return typeof val === "number" && typeof value === "number" && val > value;
637
- case "gte":
638
- return typeof val === "number" && typeof value === "number" && val >= value;
639
- case "lt":
640
- return typeof val === "number" && typeof value === "number" && val < value;
641
- case "lte":
642
- return typeof val === "number" && typeof value === "number" && val <= value;
643
- case "contains":
644
- if (typeof val === "string") return val.includes(String(value));
645
- if (Array.isArray(val)) return val.includes(value);
646
- return false;
647
- case "exists":
648
- return val !== void 0 && val !== null && val !== "";
649
- case "empty":
650
- return val === void 0 || val === null || val === "" || Array.isArray(val) && val.length === 0;
651
- default:
652
- return false;
653
- }
654
- }
655
- function evaluateGate(gate, s) {
656
- return typeof gate === "function" ? gate(s) : evaluateCondition(gate, s);
657
- }
658
- function resolveRoute(routes, s) {
659
- if (!routes) return void 0;
660
- for (const route of routes) {
661
- if (!route.when || evaluateGate(route.when, s)) return route.to;
662
- }
663
- return void 0;
664
- }
665
- function pageMeta(meta) {
666
- return meta;
667
- }
668
- var TYPE_GUARDS = {
669
- paywall: { field: "user.email", op: "exists" },
670
- upsell: { field: "user.email", op: "exists" }
671
623
  };
672
- function entryGuard(meta) {
673
- return meta?.guard ?? (meta?.type ? TYPE_GUARDS[meta.type] : void 0);
674
- }
675
- function definePage(component) {
676
- return component;
677
- }
678
- function nextPage(pages, currentKey, s) {
679
- const idx = pages.findIndex((p) => p.key === currentKey);
680
- if (idx === -1) return void 0;
681
- const routed = resolveRoute(pages[idx].meta?.next, s);
682
- if (routed) return routed;
683
- return pages[idx + 1]?.key;
684
- }
685
- function outgoingKeys(pages, currentKey) {
686
- const idx = pages.findIndex((p) => p.key === currentKey);
687
- if (idx === -1) return [];
688
- const out = /* @__PURE__ */ new Set();
689
- for (const route of pages[idx].meta?.next ?? []) out.add(route.to);
690
- const linear = pages[idx + 1]?.key;
691
- if (linear) out.add(linear);
692
- return [...out];
693
- }
694
- function expectedPathLength(pages, startKey, s) {
695
- const seen = /* @__PURE__ */ new Set();
696
- let key = startKey;
697
- let count = 0;
698
- while (key && !seen.has(key)) {
699
- seen.add(key);
700
- count++;
701
- if (pages.find((p) => p.key === key)?.meta?.type === "finish") break;
702
- key = nextPage(pages, key, s);
703
- }
704
- return count;
705
- }
706
- function defineFunnel(def) {
707
- return def;
708
- }
709
-
710
- // src/flow/experiments.ts
711
- function fnv1a(input) {
712
- let h = 2166136261;
713
- for (let i = 0; i < input.length; i++) {
714
- h ^= input.charCodeAt(i);
715
- h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
624
+ function resolveTarget(t) {
625
+ if (!t || t.kind === "page") return document.getElementById("root") ?? document.body;
626
+ if (t.kind === "viewport") return document.body;
627
+ return document.querySelector(t.selector);
628
+ }
629
+ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
630
+ function hideAll(selectors) {
631
+ const touched = [];
632
+ for (const sel of selectors) {
633
+ document.querySelectorAll(sel).forEach((el) => {
634
+ touched.push([el, el.style.visibility]);
635
+ el.style.visibility = "hidden";
636
+ });
716
637
  }
717
- return h >>> 0;
718
- }
719
- function hashToUnit(seed) {
720
- return fnv1a(seed) / 4294967296;
721
- }
722
- function pickByWeight(weights, u) {
723
- const entries = Object.entries(weights).filter(([, w]) => w > 0);
724
- if (entries.length === 0) return Object.keys(weights)[0] ?? "";
725
- const total = entries.reduce((sum, [, w]) => sum + w, 0);
726
- const target = u * total;
727
- let acc = 0;
728
- for (const [key, w] of entries) {
729
- acc += w;
730
- if (target < acc) return key;
638
+ return () => touched.forEach(([el, v]) => el.style.visibility = v);
639
+ }
640
+ async function scrollThrough(el) {
641
+ const prev = el.scrollTop;
642
+ const step = el.clientHeight || window.innerHeight;
643
+ for (let y = 0; y < el.scrollHeight; y += step) {
644
+ el.scrollTop = y;
645
+ await delay(16);
731
646
  }
732
- return entries[entries.length - 1][0];
733
- }
734
- function assignVariant(experimentId, seed, weights) {
735
- return pickByWeight(weights, hashToUnit(`${experimentId}:${seed}`));
736
- }
737
- function bucketingSeed(context) {
738
- return context.identity.customerId ?? context.identity.visitorId ?? null;
739
- }
740
- function parseSlotKey(key) {
741
- const at = key.indexOf("@");
742
- return at === -1 ? { slot: key } : { slot: key.slice(0, at), variant: key.slice(at + 1) };
743
- }
744
- function isVariantKey(key) {
745
- return parseSlotKey(key).variant !== void 0;
746
- }
747
- function weightsOf(exp) {
748
- const out = {};
749
- for (const [label, v] of Object.entries(exp.variants)) out[label] = v.weight;
750
- return out;
647
+ el.scrollTop = prev;
648
+ await delay(16);
649
+ }
650
+ function blobToDataURL(blob) {
651
+ return new Promise((resolve, reject) => {
652
+ const fr = new FileReader();
653
+ fr.onload = () => resolve(String(fr.result));
654
+ fr.onerror = () => reject(fr.error);
655
+ fr.readAsDataURL(blob);
656
+ });
751
657
  }
752
- function resolveExperiments(pages, experiments, seed) {
753
- const assignments = {};
754
- const render = {};
755
- const experimentOf = {};
756
- for (const exp of experiments) {
757
- const status = exp.status ?? "running";
758
- let label;
759
- if (status === "stopped" && exp.winner) label = exp.winner;
760
- else if (status === "running" && seed) label = assignVariant(exp.id, seed, weightsOf(exp));
761
- else continue;
762
- const arm = exp.variants[label];
763
- if (!arm) continue;
764
- assignments[exp.id] = label;
765
- experimentOf[exp.slot] = exp.id;
766
- if (arm.page !== exp.slot) render[exp.slot] = arm.page;
767
- }
768
- const slotOf = {};
769
- const activeKeys = [];
770
- for (const p of pages) {
771
- const { slot, variant } = parseSlotKey(p.key);
772
- if (variant !== void 0) slotOf[p.key] = slot;
773
- else activeKeys.push(p.key);
774
- }
775
- return { assignments, activeKeys, render, slotOf, experimentOf };
776
- }
777
- function validateExperiments(experiments, pages) {
778
- const errors = [];
779
- const warnings = [];
780
- const pageKeys = new Set(pages.map((p) => p.key));
781
- const seenIds = /* @__PURE__ */ new Set();
782
- const slotOwner = /* @__PURE__ */ new Map();
783
- const renderedVariantPages = /* @__PURE__ */ new Set();
784
- for (const exp of experiments) {
785
- const err = (code, message) => errors.push({ experimentId: exp.id, code, message });
786
- const warn = (code, message) => warnings.push({ experimentId: exp.id, code, message });
787
- if (seenIds.has(exp.id)) err("duplicate_id", `Experiment id "${exp.id}" is used more than once.`);
788
- seenIds.add(exp.id);
789
- if (!pageKeys.has(exp.slot)) err("slot_missing", `Slot "${exp.slot}" is not a page in the funnel.`);
790
- if (isVariantKey(exp.slot)) err("slot_is_variant", `Slot "${exp.slot}" is a variant page; a slot must be a real flow page.`);
791
- if (slotOwner.has(exp.slot)) {
792
- err("slot_conflict", `Slot "${exp.slot}" is already targeted by experiment "${slotOwner.get(exp.slot)}".`);
793
- } else {
794
- slotOwner.set(exp.slot, exp.id);
795
- }
796
- const arms = Object.entries(exp.variants);
797
- if (arms.length < 2) warn("single_arm", `Experiment "${exp.id}" has fewer than two variants \u2014 nothing to test.`);
798
- let hasControl = false;
799
- let totalWeight = 0;
800
- for (const [label, arm] of arms) {
801
- if (!pageKeys.has(arm.page)) {
802
- err("variant_page_missing", `Variant "${label}" references page "${arm.page}", which doesn't exist.`);
803
- }
804
- if (arm.page === exp.slot) hasControl = true;
805
- else if (isVariantKey(arm.page)) {
806
- renderedVariantPages.add(arm.page);
807
- if (parseSlotKey(arm.page).slot !== exp.slot) {
808
- err("variant_slot_mismatch", `Variant "${label}" page "${arm.page}" belongs to a different slot than "${exp.slot}".`);
809
- }
810
- }
811
- if (arm.weight < 0) err("negative_weight", `Variant "${label}" has a negative weight (${arm.weight}).`);
812
- totalWeight += arm.weight;
658
+ async function captureScreenshot(params = {}) {
659
+ const el = resolveTarget(params.target);
660
+ if (!el) throw new ScreenshotError("bad_params", "screenshot target not found");
661
+ const restore = params.hideSelectors?.length ? hideAll(params.hideSelectors) : null;
662
+ const scale = Math.min(params.scale ?? 2, params.maxScale ?? 3);
663
+ const format = params.format ?? "png";
664
+ const timeout = params.timeout ?? 1e4;
665
+ try {
666
+ if (params.waitForFonts !== false && document.fonts?.ready) {
667
+ await Promise.race([document.fonts.ready, delay(timeout)]);
813
668
  }
814
- if (!hasControl) err("no_control", `No variant of "${exp.id}" renders the slot "${exp.slot}" itself (the control/baseline).`);
815
- if (totalWeight <= 0) err("no_traffic", `Experiment "${exp.id}" has no positive weight \u2014 no traffic would enter it.`);
816
- if (exp.status === "stopped" && exp.winner && !exp.variants[exp.winner]) {
817
- err("bad_winner", `Stopped experiment "${exp.id}" names winner "${exp.winner}", which isn't one of its variants.`);
669
+ if (params.triggerLazyLoad) await scrollThrough(el);
670
+ if (params.waitForImages !== false) {
671
+ await Promise.all(
672
+ Array.from(el.querySelectorAll("img")).map(
673
+ (img) => typeof img.decode === "function" ? img.decode().catch(() => {
674
+ }) : Promise.resolve()
675
+ )
676
+ );
818
677
  }
819
- }
820
- const running = new Set(experiments.filter((e) => (e.status ?? "running") === "running").flatMap(
821
- (e) => Object.values(e.variants).map((v) => v.page)
822
- ));
823
- for (const p of pages) {
824
- if (isVariantKey(p.key) && !running.has(p.key)) {
825
- warnings.push({ experimentId: "*", code: "orphan_variant", message: `Variant page "${p.key}" is rendered by no running experiment.` });
678
+ const { domToBlob } = await import('modern-screenshot');
679
+ let blob;
680
+ try {
681
+ blob = await domToBlob(el, {
682
+ type: MIME[format],
683
+ quality: params.quality ?? 0.92,
684
+ scale,
685
+ width: params.width,
686
+ height: params.height,
687
+ // `null` = transparent (png/webp); only fall back to white when the caller omitted it. `??`
688
+ // would wrongly collapse an explicit `null` to white.
689
+ backgroundColor: params.backgroundColor === void 0 ? "#ffffff" : params.backgroundColor,
690
+ timeout
691
+ });
692
+ } catch (e) {
693
+ throw new ScreenshotError("failed", `capture failed (an image/font may be served without CORS '*'): ${String(e?.message || e)}`);
826
694
  }
695
+ const result = {
696
+ mime: MIME[format],
697
+ width: Math.round((params.width ?? el.clientWidth) * scale),
698
+ height: Math.round((params.height ?? el.scrollHeight) * scale),
699
+ scale,
700
+ bytesApprox: blob.size
701
+ };
702
+ if ((params.encoding ?? "blob") === "dataURL") result.dataURL = await blobToDataURL(blob);
703
+ else result.blob = blob;
704
+ return result;
705
+ } finally {
706
+ restore?.();
827
707
  }
828
- return { ok: errors.length === 0, errors, warnings };
829
708
  }
830
709
 
831
710
  // src/flow/flow.tsx
832
- var NavContext = react.createContext(null);
833
- var ExperimentContext = react.createContext({});
711
+ var NavContext = React.createContext(null);
712
+ var ExperimentContext = React.createContext({});
834
713
  function useExperiment(id) {
835
- return react.useContext(ExperimentContext)[id];
714
+ return React.useContext(ExperimentContext)[id];
836
715
  }
837
716
  function pageContextFor(currentKey, slug, index, total, startedAt) {
838
717
  const denom = Math.max(total, index + 1);
@@ -845,9 +724,43 @@ function pageContextFor(currentKey, slug, index, total, startedAt) {
845
724
  startedAt
846
725
  };
847
726
  }
727
+ var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
728
+ function usePageExitTimer(tracker, currentKey) {
729
+ const timer = React.useRef(null);
730
+ const emitExit = React.useCallback(() => {
731
+ const t = timer.current;
732
+ if (!t) return;
733
+ const at = now();
734
+ const hidden = t.hidden + (t.hiddenSince != null ? at - t.hiddenSince : 0);
735
+ const durationMs = Math.round(at - t.enteredAt);
736
+ tracker.track("page.exit", { pageId: t.key, durationMs, activeMs: Math.max(0, Math.round(durationMs - hidden)) });
737
+ }, [tracker]);
738
+ React.useEffect(() => {
739
+ if (typeof document === "undefined") return;
740
+ const onVis = () => {
741
+ const t = timer.current;
742
+ if (!t) return;
743
+ if (document.visibilityState === "hidden") t.hiddenSince = now();
744
+ else if (t.hiddenSince != null) {
745
+ t.hidden += now() - t.hiddenSince;
746
+ t.hiddenSince = null;
747
+ }
748
+ };
749
+ document.addEventListener("visibilitychange", onVis);
750
+ return () => document.removeEventListener("visibilitychange", onVis);
751
+ }, []);
752
+ React.useEffect(() => {
753
+ if (!currentKey) return;
754
+ if (timer.current && timer.current.key !== currentKey) emitExit();
755
+ const hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
756
+ timer.current = { key: currentKey, enteredAt: now(), hidden: 0, hiddenSince: hidden ? now() : null };
757
+ }, [currentKey, emitExit]);
758
+ React.useEffect(() => () => emitExit(), [emitExit]);
759
+ }
848
760
  function FunnelView({
849
761
  pages,
850
762
  initialKey,
763
+ trustInitialKey = false,
851
764
  layout,
852
765
  fallback,
853
766
  prefetch: prefetchMode = "auto",
@@ -855,141 +768,123 @@ function FunnelView({
855
768
  }) {
856
769
  const funnel = useFunnel();
857
770
  const tracker = useTrackerRef();
858
- const components = react.useMemo(() => {
771
+ const components = React.useMemo(() => {
859
772
  const map = /* @__PURE__ */ new Map();
860
773
  for (const p of pages) {
861
774
  if (p.Component) map.set(p.key, p.Component);
862
775
  else if (p.load) {
863
776
  const load = p.load;
864
- map.set(p.key, react.lazy(() => Promise.resolve(load()).then(
777
+ map.set(p.key, React.lazy(() => Promise.resolve(load()).then(
865
778
  (m) => typeof m === "function" ? { default: m } : m
866
779
  )));
867
780
  }
868
781
  }
869
782
  return map;
870
783
  }, [pages]);
871
- const loaders = react.useMemo(
784
+ const loaders = React.useMemo(
872
785
  () => new Map(pages.filter((p) => p.load).map((p) => [p.key, p.load])),
873
786
  [pages]
874
787
  );
875
- const seed = bucketingSeed(funnel.context);
876
- const experiments = react.useMemo(
877
- () => resolveExperiments(pages, funnel.experiments, seed),
788
+ const seedRef = React.useRef(null);
789
+ if (seedRef.current === null) seedRef.current = chunkY4YNJ2EX_cjs.bucketingSeed(funnel.context);
790
+ const seed = seedRef.current;
791
+ const experiments = React.useMemo(
792
+ () => chunkY4YNJ2EX_cjs.resolveExperiments(pages, funnel.experiments, seed),
878
793
  [pages, funnel.experiments, seed]
879
794
  );
880
- const pageByKey = react.useMemo(() => new Map(pages.map((p) => [p.key, p])), [pages]);
881
- const flowPages = react.useMemo(
882
- () => experiments.activeKeys.map((k) => pageByKey.get(k)).filter(Boolean),
795
+ const toSlot = React.useCallback((k) => experiments.slotOf[k] ?? k, [experiments]);
796
+ const toRenderKey = React.useCallback((k) => experiments.render[k] ?? k, [experiments]);
797
+ const pageByKey = React.useMemo(() => new Map(pages.map((p) => [p.key, p])), [pages]);
798
+ const flowPages = React.useMemo(
799
+ () => experiments.activeKeys.map((k) => pageByKey.get(k)).filter((p) => p !== void 0),
883
800
  [experiments, pageByKey]
884
801
  );
885
- const flow = react.useMemo(
886
- () => flowPages.map((p) => ({ key: p.key, meta: p.meta })),
887
- [flowPages]
802
+ const flow = React.useMemo(
803
+ () => flowPages.map((p) => {
804
+ const servedVariant = experiments.render[p.key];
805
+ const variantNext = servedVariant ? pageByKey.get(servedVariant)?.meta?.next : void 0;
806
+ return variantNext ? { key: p.key, meta: { ...p.meta ?? {}, next: variantNext } } : { key: p.key, meta: p.meta };
807
+ }),
808
+ [flowPages, experiments, pageByKey]
888
809
  );
889
- const [history, setHistory] = react.useState(() => {
810
+ const [history, setHistory] = React.useState(() => {
890
811
  const start = flowPages[0]?.key;
891
- if (!initialKey || initialKey === start) return [start].filter(Boolean);
892
- const target = experiments.slotOf[initialKey] ?? initialKey;
812
+ if (!initialKey || initialKey === start) return start ? [start] : [];
813
+ const target = toSlot(initialKey);
893
814
  const page2 = flowPages.find((p) => p.key === target);
894
- const guard = entryGuard(page2?.meta);
895
- const ok = page2 && (!guard || evaluateGate(guard, funnel.snapshot()));
896
- return [ok ? target : start].filter(Boolean);
815
+ if (trustInitialKey) return page2 ? [target] : start ? [start] : [];
816
+ const guard = chunkY4YNJ2EX_cjs.entryGuard(page2?.meta);
817
+ const ok = page2 && (!guard || chunkY4YNJ2EX_cjs.evaluateGate(guard, funnel.snapshot()));
818
+ const first = ok ? target : start;
819
+ return first ? [first] : [];
897
820
  });
898
821
  const currentKey = history[history.length - 1];
899
822
  const index = history.length - 1;
900
823
  const startKey = history[0];
901
- const total = react.useMemo(
902
- () => expectedPathLength(flow, startKey, funnel.snapshot()),
824
+ const total = React.useMemo(
825
+ () => chunkY4YNJ2EX_cjs.expectedPathLength(flow, startKey, funnel.snapshot()),
903
826
  // currentKey in deps: re-trace as each step's answers settle the branch.
904
827
  [flow, startKey, currentKey, funnel]
905
828
  );
906
829
  const slug = pageByKey.get(currentKey)?.meta?.slug ?? currentKey;
907
- const page = react.useMemo(
830
+ const page = React.useMemo(
908
831
  () => pageContextFor(currentKey, slug, index, total, funnel.context.system.now),
909
832
  [currentKey, slug, index, total, funnel.context.system.now]
910
833
  );
911
- funnel.context.page = page;
912
- const started = react.useRef(false);
913
- react.useEffect(() => {
834
+ React.useEffect(() => {
835
+ funnel.context.page = page;
836
+ }, [funnel, page]);
837
+ const started = React.useRef(false);
838
+ React.useEffect(() => {
914
839
  if (started.current) return;
915
840
  started.current = true;
916
841
  tracker.track("funnel.start", {});
917
842
  }, [tracker]);
918
- react.useEffect(() => {
843
+ React.useEffect(() => {
919
844
  if (!currentKey) return;
920
845
  tracker.track("page.view", { pageId: currentKey, pageKey: currentKey, isInitial: index === 0, eventId: newEventId() });
921
846
  }, [tracker, currentKey, index]);
922
- react.useEffect(() => {
847
+ React.useEffect(() => {
923
848
  if (currentKey && onNavigate) onNavigate(currentKey, slug);
924
849
  }, [onNavigate, currentKey, slug]);
925
- const exposed = react.useRef(/* @__PURE__ */ new Set());
926
- react.useEffect(() => {
850
+ const exposed = React.useRef(/* @__PURE__ */ new Set());
851
+ React.useEffect(() => {
927
852
  const expId = experiments.experimentOf[currentKey];
928
853
  if (!expId || exposed.current.has(expId)) return;
929
854
  exposed.current.add(expId);
930
- tracker.track("experiment.exposure", { experimentId: expId, variant: experiments.assignments[expId] });
855
+ tracker.track("experiment.exposure", exposurePayload(expId, experiments.assignments[expId], experiments.versionOf[expId]));
931
856
  }, [tracker, currentKey, experiments]);
932
- const timer = react.useRef(null);
933
- const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
934
- const emitExit = react.useCallback(() => {
935
- const t = timer.current;
936
- if (!t) return;
937
- const at = now();
938
- const hidden = t.hidden + (t.hiddenSince != null ? at - t.hiddenSince : 0);
939
- const durationMs = Math.round(at - t.enteredAt);
940
- tracker.track("page.exit", { pageId: t.key, durationMs, activeMs: Math.max(0, Math.round(durationMs - hidden)) });
941
- }, [tracker]);
942
- react.useEffect(() => {
943
- if (typeof document === "undefined") return;
944
- const onVis = () => {
945
- const t = timer.current;
946
- if (!t) return;
947
- if (document.visibilityState === "hidden") t.hiddenSince = now();
948
- else if (t.hiddenSince != null) {
949
- t.hidden += now() - t.hiddenSince;
950
- t.hiddenSince = null;
951
- }
952
- };
953
- document.addEventListener("visibilitychange", onVis);
954
- return () => document.removeEventListener("visibilitychange", onVis);
955
- }, []);
956
- react.useEffect(() => {
957
- if (!currentKey) return;
958
- if (timer.current && timer.current.key !== currentKey) emitExit();
959
- const hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
960
- timer.current = { key: currentKey, enteredAt: now(), hidden: 0, hiddenSince: hidden ? now() : null };
961
- }, [currentKey, emitExit]);
962
- react.useEffect(() => () => emitExit(), [emitExit]);
963
- const [isNavigating, startNav] = react.useTransition();
964
- const next = react.useCallback(() => {
965
- const target = nextPage(flow, currentKey, funnel.snapshot());
966
- const resolved = target ? experiments.slotOf[target] ?? target : target;
857
+ usePageExitTimer(tracker, currentKey);
858
+ const [isNavigating, startNav] = React.useTransition();
859
+ const next = React.useCallback(() => {
860
+ const target = chunkY4YNJ2EX_cjs.nextPage(flow, currentKey, funnel.snapshot());
861
+ const resolved = target ? toSlot(target) : target;
967
862
  if (resolved) startNav(() => setHistory((h) => [...h, resolved]));
968
- }, [flow, currentKey, funnel, experiments]);
969
- const back = react.useCallback(
863
+ }, [flow, currentKey, funnel, toSlot]);
864
+ const back = React.useCallback(
970
865
  () => startNav(() => setHistory((h) => h.length > 1 ? h.slice(0, -1) : h)),
971
866
  []
972
867
  );
973
- const go = react.useCallback(
974
- (key) => startNav(() => setHistory((h) => [...h, experiments.slotOf[key] ?? key])),
975
- [experiments]
868
+ const go = React.useCallback(
869
+ (key) => startNav(() => setHistory((h) => [...h, toSlot(key)])),
870
+ [toSlot]
976
871
  );
977
- const prefetched = react.useRef(/* @__PURE__ */ new Set());
978
- const prefetch = react.useCallback(
872
+ const prefetched = React.useRef(/* @__PURE__ */ new Set());
873
+ const prefetch = React.useCallback(
979
874
  (key) => {
980
- const target = experiments.render[key] ?? key;
875
+ const target = toRenderKey(key);
981
876
  const load = loaders.get(target);
982
877
  if (!load || prefetched.current.has(target)) return;
983
878
  prefetched.current.add(target);
984
879
  load().catch(() => prefetched.current.delete(target));
985
880
  },
986
- [loaders, experiments]
881
+ [loaders, toRenderKey]
987
882
  );
988
- const nextCandidates = react.useMemo(
989
- () => [...new Set(outgoingKeys(flow, currentKey).map((k) => experiments.render[k] ?? k))],
990
- [flow, currentKey, experiments]
883
+ const nextCandidates = React.useMemo(
884
+ () => [...new Set(chunkY4YNJ2EX_cjs.outgoingKeys(flow, currentKey).map(toRenderKey))],
885
+ [flow, currentKey, toRenderKey]
991
886
  );
992
- react.useEffect(() => {
887
+ React.useEffect(() => {
993
888
  if (prefetchMode !== "auto") return;
994
889
  for (const key of nextCandidates) prefetch(key);
995
890
  }, [prefetchMode, nextCandidates, prefetch]);
@@ -1003,36 +898,157 @@ function FunnelView({
1003
898
  canGoBack: history.length > 1,
1004
899
  isNavigating
1005
900
  };
1006
- const renderKey = experiments.render[currentKey] ?? currentKey;
901
+ const bus = useBusInternal();
902
+ const navRef = React.useRef(value);
903
+ navRef.current = value;
904
+ React.useEffect(() => {
905
+ if (!bus) return;
906
+ const control = {
907
+ goTo: (key) => {
908
+ if (!components.has(toRenderKey(key))) return false;
909
+ navRef.current.go(key);
910
+ return true;
911
+ },
912
+ goNext: () => navRef.current.next(),
913
+ goPrevious: () => navRef.current.back(),
914
+ snapshot: () => {
915
+ const v = navRef.current;
916
+ return {
917
+ key: v.page.key,
918
+ index: v.page.index,
919
+ total: v.page.total,
920
+ progressPercentage: v.page.progressPercentage,
921
+ canGoBack: v.canGoBack,
922
+ nextCandidates: v.nextCandidates
923
+ };
924
+ }
925
+ };
926
+ return bus.registerNavigation(control);
927
+ }, [bus]);
928
+ React.useEffect(() => {
929
+ const v = navRef.current;
930
+ bus?.emit("navigation", {
931
+ key: v.page.key,
932
+ index: v.page.index,
933
+ total: v.page.total,
934
+ progressPercentage: v.page.progressPercentage,
935
+ canGoBack: v.canGoBack,
936
+ nextCandidates: v.nextCandidates
937
+ });
938
+ }, [bus, currentKey]);
939
+ const renderKey = toRenderKey(currentKey);
1007
940
  const Current = components.get(renderKey);
1008
- const pageEl = react.createElement(react.Suspense, { fallback: fallback ?? null }, Current ? react.createElement(Current) : null);
1009
- const content = layout ? react.createElement(layout, null, pageEl) : pageEl;
1010
- return react.createElement(
941
+ const pageEl = React.createElement(React.Suspense, { fallback: fallback ?? null }, Current ? React.createElement(Current) : null);
942
+ const content = layout ? React.createElement(layout, null, pageEl) : pageEl;
943
+ return React.createElement(
1011
944
  ExperimentContext.Provider,
1012
945
  { value: experiments.assignments },
1013
- react.createElement(NavContext.Provider, { value }, content)
946
+ React.createElement(NavContext.Provider, { value }, content)
1014
947
  );
1015
948
  }
1016
949
  function useNavigation() {
1017
- const ctx = react.useContext(NavContext);
950
+ const ctx = React.useContext(NavContext);
1018
951
  if (!ctx) throw new Error("useNavigation must be used inside <FunnelView>");
1019
952
  return ctx;
1020
953
  }
954
+ var EMBED_CAPABILITIES = {
955
+ methods: ["nav.snapshot", "nav.goTo", "nav.goNext", "nav.goPrevious", "screenshot"],
956
+ events: ["navigation"]
957
+ };
958
+ function EmbedBridge() {
959
+ const bus = useBusInternal();
960
+ React.useEffect(() => {
961
+ if (!bus || typeof window === "undefined" || window.parent === window) return;
962
+ const parent = window.parent;
963
+ let disposed = false;
964
+ const send = (msg) => {
965
+ try {
966
+ parent.postMessage({ af: chunkWYUDL4FI_cjs.AF_PROTOCOL, source: "af-preview", ...msg }, "*");
967
+ } catch {
968
+ }
969
+ };
970
+ const hello = () => send({ kind: "hello", role: "funnel", protocol: chunkWYUDL4FI_cjs.AF_PROTOCOL, capabilities: EMBED_CAPABILITIES });
971
+ const sendSnapshot = () => {
972
+ const s = bus.navigation();
973
+ if (s) send({ kind: "event", event: "navigation", data: s });
974
+ };
975
+ async function invoke(method, params) {
976
+ switch (method) {
977
+ case "nav.snapshot":
978
+ return bus.navigation();
979
+ case "nav.goTo": {
980
+ const key = params?.key;
981
+ if (typeof key !== "string" || !bus.goTo(key)) throw { code: "bad_params", message: `unknown page key: ${String(key)}` };
982
+ return void 0;
983
+ }
984
+ case "nav.goNext":
985
+ bus.goNext();
986
+ return void 0;
987
+ case "nav.goPrevious":
988
+ bus.goPrevious();
989
+ return void 0;
990
+ case "screenshot":
991
+ return await captureScreenshot(params ?? {});
992
+ default:
993
+ throw { code: "unknown_method", message: `unknown method: ${method}` };
994
+ }
995
+ }
996
+ async function onMsg(e) {
997
+ if (e.source !== parent) return;
998
+ const d = e.data;
999
+ if (!d || typeof d !== "object" || d.af !== chunkWYUDL4FI_cjs.AF_PROTOCOL || d.source !== "af-host") return;
1000
+ if (d.kind === "hello") {
1001
+ hello();
1002
+ sendSnapshot();
1003
+ return;
1004
+ }
1005
+ if (d.kind !== "request" || typeof d.id !== "string") return;
1006
+ try {
1007
+ const result = await invoke(d.method, d.params);
1008
+ if (!disposed) send({ kind: "response", id: d.id, ok: true, result });
1009
+ } catch (err) {
1010
+ const e2 = err;
1011
+ const error = e2 && typeof e2.code === "string" ? { code: e2.code, message: String(e2.message ?? e2.code) } : { code: "failed", message: String(e2?.message ?? err) };
1012
+ if (!disposed) send({ kind: "response", id: d.id, ok: false, error });
1013
+ }
1014
+ }
1015
+ window.addEventListener("message", onMsg);
1016
+ const offNav = bus.on("navigation", (data) => {
1017
+ if (!disposed) send({ kind: "event", event: "navigation", data });
1018
+ });
1019
+ hello();
1020
+ sendSnapshot();
1021
+ return () => {
1022
+ disposed = true;
1023
+ offNav();
1024
+ window.removeEventListener("message", onMsg);
1025
+ };
1026
+ }, [bus]);
1027
+ return null;
1028
+ }
1021
1029
  function usePage() {
1022
1030
  return useNavigation().page;
1023
1031
  }
1024
- var symModalId = /* @__PURE__ */ Symbol("AppFunnelModalId");
1032
+ var symModalId = /* @__PURE__ */ Symbol("AppfunnelModalId");
1025
1033
  var initialState = {};
1026
- var ModalStateContext = react.createContext(initialState);
1027
- var ModalIdContext = react.createContext(null);
1034
+ var ModalStateContext = React.createContext(initialState);
1035
+ var ModalIdContext = React.createContext(null);
1036
+ var ModalDispatchContext = React.createContext(null);
1028
1037
  var REGISTRY = {};
1029
1038
  var ALREADY_MOUNTED = {};
1030
1039
  var modalCallbacks = {};
1031
1040
  var hideCallbacks = {};
1032
1041
  var uid = 0;
1033
1042
  var getUid = () => `_af_modal_${uid++}`;
1034
- var dispatch = () => {
1035
- throw new Error("No modal dispatch \u2014 render inside <FunnelProvider> (which mounts the modal runtime).");
1043
+ var runtimeStack = [];
1044
+ var dispatch = (action) => {
1045
+ const top = runtimeStack[runtimeStack.length - 1];
1046
+ if (!top) {
1047
+ throw new Error(
1048
+ "No modal dispatch \u2014 render inside <FunnelProvider> (which mounts the modal runtime)."
1049
+ );
1050
+ }
1051
+ top(action);
1036
1052
  };
1037
1053
  var modalReducer = (state = initialState, action) => {
1038
1054
  const { modalId, args, flags } = action.payload;
@@ -1075,10 +1091,10 @@ function registerModal(id, comp, props) {
1075
1091
  function unregisterModal(id) {
1076
1092
  delete REGISTRY[id];
1077
1093
  }
1078
- function showModal(modal, args) {
1094
+ function showModalVia(d, modal, args) {
1079
1095
  const id = idOf(modal);
1080
1096
  if (typeof modal !== "string" && !REGISTRY[id]) registerModal(id, modal);
1081
- dispatch({ type: "show", payload: { modalId: id, args } });
1097
+ d({ type: "show", payload: { modalId: id, args } });
1082
1098
  if (!modalCallbacks[id]) {
1083
1099
  let res;
1084
1100
  let rej;
@@ -1090,9 +1106,12 @@ function showModal(modal, args) {
1090
1106
  }
1091
1107
  return modalCallbacks[id].promise;
1092
1108
  }
1093
- function hideModal(modal) {
1109
+ function showModal(modal, args) {
1110
+ return showModalVia(dispatch, modal, args);
1111
+ }
1112
+ function hideModalVia(d, modal) {
1094
1113
  const id = idOf(modal);
1095
- dispatch({ type: "hide", payload: { modalId: id } });
1114
+ d({ type: "hide", payload: { modalId: id } });
1096
1115
  delete modalCallbacks[id];
1097
1116
  if (!hideCallbacks[id]) {
1098
1117
  let res;
@@ -1105,41 +1124,64 @@ function hideModal(modal) {
1105
1124
  }
1106
1125
  return hideCallbacks[id].promise;
1107
1126
  }
1108
- function removeModal(modal) {
1127
+ function hideModal(modal) {
1128
+ return hideModalVia(dispatch, modal);
1129
+ }
1130
+ function removeModalVia(d, modal) {
1109
1131
  const id = idOf(modal);
1110
- dispatch({ type: "remove", payload: { modalId: id } });
1132
+ d({ type: "remove", payload: { modalId: id } });
1111
1133
  delete modalCallbacks[id];
1112
1134
  delete hideCallbacks[id];
1113
1135
  }
1114
- function setFlags(id, flags) {
1115
- dispatch({ type: "set-flags", payload: { modalId: id, flags } });
1136
+ function removeModal(modal) {
1137
+ removeModalVia(dispatch, modal);
1138
+ }
1139
+ function setFlags(d, id, flags) {
1140
+ d({ type: "set-flags", payload: { modalId: id, flags } });
1116
1141
  }
1117
1142
  function useModal(modal, args) {
1118
- const modals = react.useContext(ModalStateContext);
1119
- const contextId = react.useContext(ModalIdContext);
1143
+ const modals = React.useContext(ModalStateContext);
1144
+ const contextId = React.useContext(ModalIdContext);
1145
+ const d = React.useContext(ModalDispatchContext) ?? dispatch;
1120
1146
  const isComponent = !!modal && typeof modal !== "string";
1121
1147
  const id = modal ? idOf(modal) : contextId;
1122
- if (!id) throw new Error("useModal: no modal id (call inside a defineModal component, or pass an id/component).");
1123
- react.useEffect(() => {
1124
- if (isComponent && !REGISTRY[id]) registerModal(id, modal, args);
1148
+ if (!id)
1149
+ throw new Error(
1150
+ "useModal: no modal id (call inside a defineModal component, or pass an id/component)."
1151
+ );
1152
+ React.useEffect(() => {
1153
+ if (isComponent && !REGISTRY[id])
1154
+ registerModal(id, modal, args);
1125
1155
  }, [isComponent, id, modal, args]);
1126
1156
  const info = modals[id];
1127
- const show = react.useCallback((a) => showModal(id, a), [id]);
1128
- const hide = react.useCallback(() => hideModal(id), [id]);
1129
- const remove = react.useCallback(() => removeModal(id), [id]);
1130
- const resolve = react.useCallback((v) => {
1131
- modalCallbacks[id]?.resolve(v);
1132
- delete modalCallbacks[id];
1133
- }, [id]);
1134
- const reject = react.useCallback((v) => {
1135
- modalCallbacks[id]?.reject(v);
1136
- delete modalCallbacks[id];
1137
- }, [id]);
1138
- const resolveHide = react.useCallback((v) => {
1139
- hideCallbacks[id]?.resolve(v);
1140
- delete hideCallbacks[id];
1141
- }, [id]);
1142
- return react.useMemo(
1157
+ const show = React.useCallback(
1158
+ (a) => showModalVia(d, id, a),
1159
+ [d, id]
1160
+ );
1161
+ const hide = React.useCallback(() => hideModalVia(d, id), [d, id]);
1162
+ const remove = React.useCallback(() => removeModalVia(d, id), [d, id]);
1163
+ const resolve = React.useCallback(
1164
+ (v) => {
1165
+ modalCallbacks[id]?.resolve(v);
1166
+ delete modalCallbacks[id];
1167
+ },
1168
+ [id]
1169
+ );
1170
+ const reject = React.useCallback(
1171
+ (v) => {
1172
+ modalCallbacks[id]?.reject(v);
1173
+ delete modalCallbacks[id];
1174
+ },
1175
+ [id]
1176
+ );
1177
+ const resolveHide = React.useCallback(
1178
+ (v) => {
1179
+ hideCallbacks[id]?.resolve(v);
1180
+ delete hideCallbacks[id];
1181
+ },
1182
+ [id]
1183
+ );
1184
+ return React.useMemo(
1143
1185
  () => ({
1144
1186
  id,
1145
1187
  args: info?.args,
@@ -1152,53 +1194,90 @@ function useModal(modal, args) {
1152
1194
  reject,
1153
1195
  resolveHide
1154
1196
  }),
1155
- [id, info?.args, info?.visible, info?.keepMounted, show, hide, remove, resolve, reject, resolveHide]
1197
+ [
1198
+ id,
1199
+ info?.args,
1200
+ info?.visible,
1201
+ info?.keepMounted,
1202
+ show,
1203
+ hide,
1204
+ remove,
1205
+ resolve,
1206
+ reject,
1207
+ resolveHide
1208
+ ]
1156
1209
  );
1157
1210
  }
1158
1211
  function defineModal(Comp) {
1159
- return function Wrapped({ id, defaultVisible, keepMounted, ...props }) {
1212
+ return function Wrapped({
1213
+ id,
1214
+ defaultVisible,
1215
+ keepMounted,
1216
+ ...props
1217
+ }) {
1160
1218
  const { args, show } = useModal(id);
1161
- const modals = react.useContext(ModalStateContext);
1219
+ const modals = React.useContext(ModalStateContext);
1220
+ const d = React.useContext(ModalDispatchContext) ?? dispatch;
1162
1221
  const shouldMount = !!modals[id];
1163
- react.useEffect(() => {
1222
+ React.useEffect(() => {
1164
1223
  if (defaultVisible) show();
1165
1224
  ALREADY_MOUNTED[id] = true;
1166
1225
  return () => {
1167
1226
  delete ALREADY_MOUNTED[id];
1168
1227
  };
1169
1228
  }, [id, show, defaultVisible]);
1170
- react.useEffect(() => {
1171
- if (keepMounted) setFlags(id, { keepMounted: true });
1172
- }, [id, keepMounted]);
1229
+ React.useEffect(() => {
1230
+ if (keepMounted) setFlags(d, id, { keepMounted: true });
1231
+ }, [d, id, keepMounted]);
1173
1232
  const delayVisible = modals[id]?.delayVisible;
1174
- react.useEffect(() => {
1233
+ React.useEffect(() => {
1175
1234
  if (delayVisible) show(args);
1176
1235
  }, [delayVisible, args, show]);
1177
1236
  if (!shouldMount) return null;
1178
- return react.createElement(
1237
+ return React.createElement(
1179
1238
  ModalIdContext.Provider,
1180
1239
  { value: id },
1181
- react.createElement(Comp, { ...props, ...args })
1240
+ React.createElement(Comp, { ...props, ...args })
1182
1241
  );
1183
1242
  };
1184
1243
  }
1185
1244
  function ModalRoot() {
1186
- const modals = react.useContext(ModalStateContext);
1245
+ const modals = React.useContext(ModalStateContext);
1187
1246
  const toRender = Object.keys(modals).filter((id) => REGISTRY[id]).map((id) => ({ id, ...REGISTRY[id] }));
1188
- return react.createElement(
1247
+ return React.createElement(
1189
1248
  "div",
1190
1249
  { "data-appfunnel-modal-root": "" },
1191
- ...toRender.map((t) => react.createElement(t.comp, { key: t.id, id: t.id, ...t.props }))
1250
+ ...toRender.map(
1251
+ (t) => React.createElement(t.comp, { key: t.id, id: t.id, ...t.props })
1252
+ )
1192
1253
  );
1193
1254
  }
1194
1255
  function ModalRuntime({ children }) {
1195
- const [state, localDispatch] = react.useReducer(modalReducer, initialState);
1196
- dispatch = localDispatch;
1197
- return react.createElement(
1198
- ModalStateContext.Provider,
1199
- { value: state },
1200
- children,
1201
- react.createElement(ModalRoot, null)
1256
+ const [state, localDispatch] = React.useReducer(modalReducer, initialState);
1257
+ React.useEffect(() => {
1258
+ runtimeStack.push(localDispatch);
1259
+ if (runtimeStack.length > 1) {
1260
+ try {
1261
+ console.warn(
1262
+ `[appfunnel] ${runtimeStack.length} modal runtimes are mounted \u2014 the imperative showModal()/hideModal() API binds to the most recent one. Use the useModal() hook inside each tree instead.`
1263
+ );
1264
+ } catch {
1265
+ }
1266
+ }
1267
+ return () => {
1268
+ const i = runtimeStack.indexOf(localDispatch);
1269
+ if (i !== -1) runtimeStack.splice(i, 1);
1270
+ };
1271
+ }, []);
1272
+ return React.createElement(
1273
+ ModalDispatchContext.Provider,
1274
+ { value: localDispatch },
1275
+ React.createElement(
1276
+ ModalStateContext.Provider,
1277
+ { value: state },
1278
+ children,
1279
+ React.createElement(ModalRoot, null)
1280
+ )
1202
1281
  );
1203
1282
  }
1204
1283
 
@@ -1225,7 +1304,7 @@ function OverlayShell({
1225
1304
  panel,
1226
1305
  children
1227
1306
  }) {
1228
- react.useEffect(() => {
1307
+ React.useEffect(() => {
1229
1308
  if (!open || typeof document === "undefined") return;
1230
1309
  const onKey = (e) => {
1231
1310
  if (e.key === "Escape") onClose();
@@ -1239,7 +1318,7 @@ function OverlayShell({
1239
1318
  };
1240
1319
  }, [open, onClose]);
1241
1320
  if (!open || typeof document === "undefined") return null;
1242
- const overlay = react.createElement(
1321
+ const overlay = React.createElement(
1243
1322
  "div",
1244
1323
  {
1245
1324
  role: "dialog",
@@ -1248,19 +1327,19 @@ function OverlayShell({
1248
1327
  "data-appfunnel-overlay": "",
1249
1328
  style: { position: "fixed", inset: 0, zIndex: 1e3, display: "flex", ...container }
1250
1329
  },
1251
- react.createElement("div", {
1330
+ React.createElement("div", {
1252
1331
  "data-appfunnel-overlay-backdrop": "",
1253
1332
  className: backdropClassName,
1254
1333
  onClick: dismissOnBackdrop ? onClose : void 0,
1255
1334
  style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" }
1256
1335
  }),
1257
- react.createElement(
1336
+ React.createElement(
1258
1337
  "div",
1259
1338
  { "data-appfunnel-overlay-panel": "", className, style: { position: "relative", background: "#fff", ...panel } },
1260
1339
  children
1261
1340
  )
1262
1341
  );
1263
- return reactDom.createPortal(overlay, document.body);
1342
+ return ReactDOM.createPortal(overlay, document.body);
1264
1343
  }
1265
1344
  var SHEET_LAYOUT = {
1266
1345
  bottom: { container: { justifyContent: "flex-end", alignItems: "stretch" }, panel: { borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 16, maxHeight: "90vh", overflow: "auto" } },
@@ -1271,7 +1350,7 @@ var SHEET_LAYOUT = {
1271
1350
  function Sheet(props) {
1272
1351
  const control = useModalControl();
1273
1352
  const layout = SHEET_LAYOUT[props.side ?? "bottom"];
1274
- return react.createElement(OverlayShell, {
1353
+ return React.createElement(OverlayShell, {
1275
1354
  ...control,
1276
1355
  className: props.className,
1277
1356
  backdropClassName: props.backdropClassName,
@@ -1284,7 +1363,7 @@ function Sheet(props) {
1284
1363
  }
1285
1364
  function Modal(props) {
1286
1365
  const control = useModalControl();
1287
- return react.createElement(OverlayShell, {
1366
+ return React.createElement(OverlayShell, {
1288
1367
  ...control,
1289
1368
  className: props.className,
1290
1369
  backdropClassName: props.backdropClassName,
@@ -1297,263 +1376,412 @@ function Modal(props) {
1297
1376
  }
1298
1377
 
1299
1378
  // src/commerce/checkout.tsx
1300
- var INLINE_SURFACES = /* @__PURE__ */ new Set([
1301
- "express",
1302
- "element",
1303
- "embedded"
1304
- ]);
1305
- function isInlineSurface(surface) {
1306
- return INLINE_SURFACES.has(surface);
1307
- }
1308
- var ON = { purchase: true, wallets: true, offSession: "reliable" };
1309
- var MANAGED = { purchase: true, wallets: true, offSession: "conditional" };
1310
- var ALL_UPSELLS = ["subscription", "one-time", "upgrade"];
1311
- var PROVIDER_PROFILES = {
1312
- // PSP; you hold the PaymentMethod and fire your own off-session PaymentIntent on
1313
- // the Elements surfaces (reliable). **Managed** Checkout (`embedded`/`redirect`)
1314
- // upsells too, but as a direct charge against Stripe's vaulted card → conditional.
1315
- // No provider "popup" surface.
1316
- stripe: {
1317
- isMoR: false,
1318
- offSessionUpsells: ALL_UPSELLS,
1319
- tokenModel: "merchant",
1320
- orchestrator: false,
1321
- surfaces: { express: ON, element: ON, sheet: ON, embedded: MANAGED, redirect: MANAGED }
1322
- },
1323
- // Merchant-of-Record; card entry is always Paddle's frame → no own element/sheet/
1324
- // express. Off-session via subscription one-time charge / collection_mode, but
1325
- // **no 2nd concurrent subscription** (upgrade + one-time only), provider-initiated.
1326
- paddle: {
1327
- isMoR: true,
1328
- offSessionUpsells: ["one-time", "upgrade"],
1329
- tokenModel: "provider",
1330
- orchestrator: false,
1331
- surfaces: { embedded: ON, popup: ON, redirect: ON }
1332
- },
1333
- // Merchant-of-Record; iframe-only (no raw card field). Off-session works
1334
- // (provider-initiated, async via webhooks). `sheet` = our chrome hosting their embed.
1335
- whop: {
1336
- isMoR: true,
1337
- offSessionUpsells: ALL_UPSELLS,
1338
- tokenModel: "provider",
1339
- orchestrator: false,
1340
- surfaces: { embedded: ON, redirect: ON, sheet: ON }
1341
- },
1342
- // Pure orchestrator (PSP-side); Headless renders express/element/sheet/embedded.
1343
- // No hosted redirect page, no popup checkout. Off-session is PSP-conditional.
1344
- primer: {
1345
- isMoR: false,
1346
- offSessionUpsells: ALL_UPSELLS,
1347
- tokenModel: "merchant",
1348
- orchestrator: true,
1349
- surfaces: { express: ON, element: ON, sheet: ON, embedded: ON }
1350
- },
1351
- // PSP **and** orchestrator (own acquiring + connectors routing across Stripe/Adyen/
1352
- // PayPal). One Payment Form (element/sheet/embedded) + hosted page + wallet button.
1353
- // Strong native off-session (Recurring API). No popup.
1354
- solidgate: {
1355
- isMoR: false,
1356
- offSessionUpsells: ALL_UPSELLS,
1357
- tokenModel: "merchant",
1358
- orchestrator: true,
1359
- surfaces: { express: ON, element: ON, sheet: ON, embedded: ON, redirect: ON }
1360
- }
1361
- };
1362
- function surfacesFor(provider) {
1363
- return Object.keys(PROVIDER_PROFILES[provider]?.surfaces ?? {});
1364
- }
1365
- function isMerchantOfRecord(provider) {
1366
- return PROVIDER_PROFILES[provider]?.isMoR ?? false;
1367
- }
1368
- function isOrchestrator(provider) {
1369
- return PROVIDER_PROFILES[provider]?.orchestrator ?? false;
1370
- }
1371
- function validateCheckout(provider, surface) {
1372
- const cap = PROVIDER_PROFILES[provider]?.surfaces[surface];
1373
- if (!cap) return { ok: false, reason: `${provider} has no '${surface}' surface` };
1374
- return cap.purchase ? { ok: true } : { ok: false, reason: `'${surface}' can't take a purchase on ${provider}` };
1375
- }
1376
- function validateUpsell(provider, paywallSurface, kind = "subscription") {
1377
- const profile = PROVIDER_PROFILES[provider];
1378
- if (!profile) return { ok: false, reason: `unknown provider '${provider}'` };
1379
- if (!profile.offSessionUpsells.includes(kind)) {
1380
- const can = profile.offSessionUpsells.length ? ` (it supports: ${profile.offSessionUpsells.join(", ")})` : "";
1381
- return { ok: false, reason: `${provider} can't charge a '${kind}' upsell off-session${can}` };
1382
- }
1383
- const cap = profile.surfaces[paywallSurface];
1384
- if (!cap) return { ok: false, reason: `${provider} has no '${paywallSurface}' surface to upsell after` };
1385
- if (cap.offSession === "none") {
1386
- return { ok: false, reason: `a purchase on '${paywallSurface}' retains no chargeable method for an upsell` };
1387
- }
1388
- if (cap.offSession === "conditional") {
1389
- return { ok: true, note: `off-session upsell after ${provider} '${paywallSurface}' (managed checkout) may decline \u2014 prefer element/sheet/express` };
1390
- }
1391
- return { ok: true };
1392
- }
1393
- function checkoutError(category, message, extra = {}) {
1394
- const retryable = extra.retryable ?? (category === "authentication_required" || category === "requires_payment_method" || category === "processing_error");
1395
- return { category, message, declineCode: extra.declineCode, retryable };
1396
- }
1397
1379
  function createMockDriver(provider = "stripe", options = {}) {
1398
- const success = options.result ?? { ok: true, amountMinor: 0, currency: "USD" };
1399
- const offError = typeof options.failOffSession === "object" ? options.failOffSession : checkoutError("authentication_required", "off-session charge declined (mock)");
1380
+ const success = options.result ?? {
1381
+ ok: true,
1382
+ amountMinor: 0,
1383
+ currency: "USD"
1384
+ };
1385
+ const offError = typeof options.failOffSession === "object" ? options.failOffSession : chunk7JLOF6CJ_cjs.checkoutError(
1386
+ "authentication_required",
1387
+ "off-session charge declined (mock)"
1388
+ );
1400
1389
  return {
1401
1390
  provider,
1402
1391
  start: (req) => {
1403
- if (options.failOffSession && !req.surface) return Promise.resolve({ ok: false, error: offError });
1392
+ if (options.failOffSession && !req.surface)
1393
+ return Promise.resolve({ ok: false, error: offError });
1404
1394
  return Promise.resolve(success);
1405
1395
  },
1406
- renderInline: (req, cb) => react.createElement(
1396
+ renderInline: (req, cb) => React.createElement(
1407
1397
  "button",
1408
1398
  {
1409
1399
  type: "button",
1410
1400
  "data-mock-surface": req.surface,
1411
- onClick: () => success.ok ? cb.onSuccess(success) : cb.onError(success.error ?? checkoutError("processing_error", "failed (mock)"))
1401
+ onClick: () => {
1402
+ cb.onStart?.();
1403
+ if (success.ok) cb.onSuccess(success);
1404
+ else
1405
+ cb.onError(
1406
+ success.error ?? chunk7JLOF6CJ_cjs.checkoutError(
1407
+ "processing_error",
1408
+ "failed (mock)"
1409
+ )
1410
+ );
1411
+ }
1412
1412
  },
1413
1413
  `Pay (mock ${req.surface})`
1414
- )
1414
+ ),
1415
+ resume: () => Promise.resolve(success)
1416
+ };
1417
+ }
1418
+ function driverWithEmail(driver, getEmail) {
1419
+ const enrich = (req) => req.email !== void 0 ? req : { ...req, email: getEmail() };
1420
+ return {
1421
+ ...driver,
1422
+ start: (req) => driver.start(enrich(req)),
1423
+ renderInline: driver.renderInline ? (req, cb) => driver.renderInline(enrich(req), cb) : void 0
1424
+ };
1425
+ }
1426
+ function driverWithCatalog(driver, catalog) {
1427
+ const enrich = (req) => req.catalogKey !== void 0 ? req : { ...req, catalogKey: catalog.get(req.product)?.catalogKey };
1428
+ return {
1429
+ ...driver,
1430
+ start: (req) => driver.start(enrich(req)),
1431
+ renderInline: driver.renderInline ? (req, cb) => driver.renderInline(enrich(req), cb) : void 0
1415
1432
  };
1416
1433
  }
1417
- var DriverContext = react.createContext(null);
1434
+ var DriverContext = React.createContext(null);
1418
1435
  function CheckoutDriverProvider({
1419
1436
  driver,
1420
1437
  children
1421
1438
  }) {
1422
- return react.createElement(DriverContext.Provider, { value: driver }, children);
1439
+ return React.createElement(DriverContext.Provider, { value: driver }, children);
1423
1440
  }
1424
1441
  function useDriver() {
1425
- return react.useContext(DriverContext) ?? FALLBACK_DRIVER;
1442
+ return React.useContext(DriverContext) ?? FALLBACK_DRIVER;
1426
1443
  }
1427
1444
  var FALLBACK_DRIVER = createMockDriver();
1428
1445
  function useCheckout(opts = {}) {
1429
1446
  const driver = useDriver();
1430
1447
  const tracker = useTrackerRef();
1448
+ const catalog = useCatalog();
1431
1449
  const nav = useNavigation();
1432
- const [status, setStatus] = react.useState("idle");
1433
- const [error, setError] = react.useState(null);
1450
+ const [status, setStatus] = React.useState("idle");
1451
+ const [error, setError] = React.useState(null);
1434
1452
  const intent = opts.intent ?? "purchase";
1435
1453
  const kind = opts.kind;
1436
1454
  const advance = opts.advanceOnSuccess ?? true;
1437
- const succeed = react.useCallback(
1438
- (result) => {
1455
+ const productFacts = React.useCallback(
1456
+ (product) => {
1457
+ const resolved = product ? catalog.get(product) : void 0;
1458
+ return {
1459
+ productId: resolved?.catalogKey ?? product,
1460
+ amount: resolved ? resolved.priceMinor / 100 : void 0,
1461
+ currency: resolved?.currency,
1462
+ isSubscription: resolved ? resolved.period !== "one-time" : kind === "subscription" || kind === "upgrade"
1463
+ };
1464
+ },
1465
+ [catalog, kind]
1466
+ );
1467
+ const recovering = React.useRef(false);
1468
+ const failRef = React.useRef(
1469
+ () => {
1470
+ }
1471
+ );
1472
+ const attemptRef = React.useRef(async () => ({ ok: false }));
1473
+ const succeed = React.useCallback(
1474
+ (result, product) => {
1439
1475
  setStatus("success");
1476
+ const { productId, isSubscription } = productFacts(product);
1477
+ const amount = result.amountMinor != null ? result.amountMinor / 100 : void 0;
1440
1478
  tracker.track("purchase.complete", {
1441
- amount: result.amountMinor != null ? result.amountMinor / 100 : void 0,
1479
+ amount,
1442
1480
  currency: result.currency,
1481
+ productId,
1443
1482
  eventId: result.eventId
1444
1483
  // server-minted (browser↔CAPI dedup); undefined if none
1445
1484
  });
1485
+ if (isSubscription) {
1486
+ tracker.track("subscription.created", {
1487
+ amount,
1488
+ currency: result.currency,
1489
+ productId,
1490
+ eventId: result.eventId
1491
+ });
1492
+ }
1446
1493
  opts.onSuccess?.(result);
1447
1494
  if (advance) nav.next();
1448
1495
  },
1449
- [tracker, nav, advance, opts]
1450
- );
1451
- const fail = react.useCallback(
1452
- (err, product) => {
1453
- setStatus(err.category === "authentication_required" ? "requires_action" : "error");
1454
- setError(err);
1455
- tracker.track("checkout.failed", {
1456
- category: err.category,
1457
- declineCode: err.declineCode,
1458
- productId: product,
1459
- intent
1460
- });
1461
- opts.onError?.(err);
1462
- },
1463
- [tracker, intent, opts]
1496
+ [tracker, nav, advance, opts, productFacts]
1464
1497
  );
1465
- const begin = react.useCallback(
1498
+ const succeedRef = React.useRef(succeed);
1499
+ succeedRef.current = succeed;
1500
+ const begin = React.useCallback(
1466
1501
  (product, surface) => {
1467
1502
  setStatus("loading");
1468
1503
  setError(null);
1469
- tracker.track("checkout.start", { productId: product, surface });
1504
+ const { productId, amount, currency } = productFacts(product);
1505
+ tracker.track("checkout.start", {
1506
+ productId,
1507
+ amount,
1508
+ currency,
1509
+ surface,
1510
+ eventId: newEventId()
1511
+ });
1470
1512
  },
1471
- [tracker]
1513
+ [tracker, productFacts]
1514
+ );
1515
+ const paymentAdded = React.useCallback(
1516
+ (product) => {
1517
+ const { productId, amount, currency } = productFacts(product);
1518
+ tracker.track("checkout.payment_added", {
1519
+ productId,
1520
+ amount,
1521
+ currency,
1522
+ eventId: newEventId()
1523
+ });
1524
+ },
1525
+ [tracker, productFacts]
1472
1526
  );
1473
- const paymentAdded = react.useCallback(() => tracker.track("checkout.payment_added", {}), [tracker]);
1474
- const open = react.useCallback(
1527
+ const hostInSheet = (surface) => surface === "sheet" || chunk7JLOF6CJ_cjs.isInlineSurface(surface) && !!chunk7JLOF6CJ_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet;
1528
+ const startRecovery = (product, surface) => {
1529
+ recovering.current = true;
1530
+ if (hostInSheet(surface)) {
1531
+ begin(product, "sheet");
1532
+ void showModal(CheckoutSheet, {
1533
+ product,
1534
+ surface: "sheet",
1535
+ intent,
1536
+ kind,
1537
+ onSuccess: (r) => succeedRef.current(r, product),
1538
+ onError: (e) => failRef.current(e, product),
1539
+ onPaymentAdded: () => paymentAdded(product)
1540
+ });
1541
+ } else {
1542
+ void attemptRef.current(product, surface);
1543
+ }
1544
+ };
1545
+ failRef.current = (err, product) => {
1546
+ setStatus(
1547
+ err.category === "authentication_required" ? "requires_action" : "error"
1548
+ );
1549
+ setError(err);
1550
+ tracker.track("checkout.failed", {
1551
+ category: err.category,
1552
+ declineCode: err.declineCode,
1553
+ productId: product,
1554
+ intent
1555
+ });
1556
+ opts.onError?.(err);
1557
+ if (err.category === "canceled" && !opts.onFailed?.canceled) return;
1558
+ if (product && !recovering.current && opts.recoverySurface && err.category !== "canceled") {
1559
+ startRecovery(product, opts.recoverySurface);
1560
+ return;
1561
+ }
1562
+ let route = opts.onFailed?.[err.category] ?? (err.category === "canceled" ? void 0 : opts.onFailed?.default);
1563
+ if (route && typeof route === "object" && "fallback" in route && recovering.current) {
1564
+ const dflt = opts.onFailed?.default;
1565
+ route = dflt && dflt !== route && !(typeof dflt === "object" && "fallback" in dflt) ? dflt : void 0;
1566
+ }
1567
+ if (!route) return;
1568
+ if (typeof route === "function") route(err);
1569
+ else if ("go" in route) nav.go(route.go);
1570
+ else if ("modal" in route) {
1571
+ const props = {
1572
+ error: err,
1573
+ product,
1574
+ go: (pageKey) => nav.go(pageKey),
1575
+ retry: (surface) => {
1576
+ if (!product) return;
1577
+ recovering.current = true;
1578
+ if (!surface) {
1579
+ if (intent === "upsell")
1580
+ void attemptRef.current(product);
1581
+ else
1582
+ startRecovery(
1583
+ product,
1584
+ chunk7JLOF6CJ_cjs.PROVIDER_PROFILES[driver.provider]?.surfaces.sheet ? "sheet" : "popup"
1585
+ );
1586
+ return;
1587
+ }
1588
+ startRecovery(product, surface);
1589
+ }
1590
+ };
1591
+ void showModal(
1592
+ route.modal,
1593
+ props
1594
+ );
1595
+ } else if ("fallback" in route) {
1596
+ if (product && !recovering.current)
1597
+ startRecovery(product, route.fallback);
1598
+ }
1599
+ };
1600
+ attemptRef.current = async (product, surface) => {
1601
+ begin(product, surface);
1602
+ try {
1603
+ const result = await driver.start({
1604
+ product,
1605
+ intent,
1606
+ surface,
1607
+ kind
1608
+ });
1609
+ if (result.ok) succeedRef.current(result, product);
1610
+ else
1611
+ failRef.current(
1612
+ result.error ?? chunk7JLOF6CJ_cjs.checkoutError("unknown", "Checkout failed"),
1613
+ product
1614
+ );
1615
+ return result;
1616
+ } catch (e) {
1617
+ const err = chunk7JLOF6CJ_cjs.checkoutError(
1618
+ "processing_error",
1619
+ e instanceof Error ? e.message : "Checkout failed"
1620
+ );
1621
+ failRef.current(err, product);
1622
+ return { ok: false, error: err };
1623
+ }
1624
+ };
1625
+ const open = React.useCallback(
1475
1626
  async (product, surface) => {
1476
- begin(product, surface);
1477
- try {
1478
- const result = await driver.start({ product, intent, surface, kind });
1479
- if (result.ok) succeed(result);
1480
- else fail(result.error ?? checkoutError("unknown", "Checkout failed"), product);
1481
- return result;
1482
- } catch (e) {
1483
- const err = checkoutError("processing_error", e instanceof Error ? e.message : "Checkout failed");
1484
- fail(err, product);
1485
- return { ok: false, error: err };
1486
- }
1627
+ recovering.current = false;
1628
+ return attemptRef.current(product, surface);
1487
1629
  },
1488
- [driver, intent, kind, begin, succeed, fail]
1630
+ []
1489
1631
  );
1490
- const callbacksFor = react.useCallback(
1632
+ const callbacksFor = React.useCallback(
1491
1633
  (product, surface) => ({
1492
- onStart: () => begin(product, surface),
1493
- onSuccess: (result) => succeed(result),
1494
- onError: (err) => fail(err, product),
1495
- onPaymentAdded: () => paymentAdded()
1634
+ onStart: () => {
1635
+ recovering.current = false;
1636
+ begin(product, surface);
1637
+ },
1638
+ onSuccess: (result) => succeedRef.current(result, product),
1639
+ onError: (err) => failRef.current(err, product),
1640
+ onPaymentAdded: () => paymentAdded(product)
1496
1641
  }),
1497
- [begin, succeed, fail, paymentAdded]
1642
+ [begin, paymentAdded]
1498
1643
  );
1499
- const reset = react.useCallback(() => {
1644
+ const reset = React.useCallback(() => {
1645
+ recovering.current = false;
1500
1646
  setStatus("idle");
1501
1647
  setError(null);
1502
1648
  }, []);
1503
- return { open, status, error, isLoading: status === "loading", reset, callbacksFor };
1649
+ return {
1650
+ open,
1651
+ status,
1652
+ error,
1653
+ isLoading: status === "loading",
1654
+ reset,
1655
+ callbacksFor
1656
+ };
1504
1657
  }
1505
- var CheckoutSheet = defineModal(({ product, surface, intent, onSuccess, onError }) => {
1658
+ var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, onError, onPaymentAdded }) => {
1506
1659
  const driver = useDriver();
1507
1660
  const modal = useModal();
1661
+ const resolved = React.useRef(false);
1662
+ const onErrorRef = React.useRef(onError);
1663
+ onErrorRef.current = onError;
1664
+ const hostable = !!driver.renderInline;
1665
+ React.useEffect(() => {
1666
+ if (!hostable) {
1667
+ resolved.current = true;
1668
+ onErrorRef.current(
1669
+ chunk7JLOF6CJ_cjs.checkoutError(
1670
+ "processing_error",
1671
+ "This checkout driver has no inline surface to host in the sheet"
1672
+ )
1673
+ );
1674
+ void modal.hide();
1675
+ modal.remove();
1676
+ return;
1677
+ }
1678
+ return () => {
1679
+ if (!resolved.current) {
1680
+ onErrorRef.current(
1681
+ chunk7JLOF6CJ_cjs.checkoutError(
1682
+ "canceled",
1683
+ "Checkout was dismissed before completing"
1684
+ )
1685
+ );
1686
+ }
1687
+ };
1688
+ }, []);
1508
1689
  if (!driver.renderInline) return null;
1509
- return react.createElement(Sheet, {
1690
+ const close = () => {
1691
+ resolved.current = true;
1692
+ void modal.hide();
1693
+ modal.remove();
1694
+ };
1695
+ return React.createElement(Sheet, {
1510
1696
  children: driver.renderInline(
1511
- { product, intent, surface },
1697
+ { product, intent, surface, kind },
1512
1698
  {
1513
1699
  onSuccess: (r) => {
1514
- modal.hide();
1700
+ close();
1515
1701
  onSuccess(r);
1516
1702
  },
1517
1703
  onError: (e) => {
1518
- modal.hide();
1704
+ close();
1519
1705
  onError(e);
1520
- }
1706
+ },
1707
+ onPaymentAdded
1521
1708
  }
1522
1709
  )
1523
1710
  });
1524
1711
  });
1525
- function trigger(onClick, { asChild, children, className, disabled }) {
1526
- if (asChild && react.isValidElement(children)) {
1712
+ function trigger(onClick, {
1713
+ asChild,
1714
+ children,
1715
+ className,
1716
+ disabled
1717
+ }) {
1718
+ if (asChild && React.isValidElement(children)) {
1527
1719
  const child = children;
1528
- return react.cloneElement(child, { onClick });
1720
+ return React.cloneElement(child, { onClick });
1721
+ }
1722
+ return React.createElement(
1723
+ "button",
1724
+ { type: "button", className, disabled, onClick },
1725
+ children ?? "Continue"
1726
+ );
1727
+ }
1728
+ function detectDeviceClass() {
1729
+ if (typeof window === "undefined") return "desktop";
1730
+ const ua = navigator.userAgent?.toLowerCase() ?? "";
1731
+ if (/mobile|android(?!.*tablet)|iphone|ipod/.test(ua)) return "mobile";
1732
+ try {
1733
+ if (window.matchMedia?.("(pointer: coarse)").matches && window.innerWidth > 0 && window.innerWidth < 768) {
1734
+ return "mobile";
1735
+ }
1736
+ } catch {
1529
1737
  }
1530
- return react.createElement("button", { type: "button", className, disabled, onClick }, children ?? "Continue");
1738
+ return "desktop";
1531
1739
  }
1532
1740
  function Checkout({
1533
- product,
1534
- surface,
1741
+ offering,
1742
+ surface: surfacePreference,
1535
1743
  asChild,
1536
1744
  children,
1537
1745
  className,
1538
1746
  ...options
1539
1747
  }) {
1748
+ const product = offering;
1540
1749
  const driver = useDriver();
1750
+ const [surface] = React.useState(
1751
+ () => chunk7JLOF6CJ_cjs.resolveSurface(driver.provider, surfacePreference, detectDeviceClass())
1752
+ );
1541
1753
  const checkout = useCheckout(options);
1542
1754
  const intent = options.intent ?? "purchase";
1543
- if (isInlineSurface(surface)) {
1544
- return driver.renderInline ? react.createElement(
1755
+ if (chunk7JLOF6CJ_cjs.isInlineSurface(surface)) {
1756
+ return driver.renderInline ? React.createElement(
1545
1757
  "div",
1546
1758
  { key: product, className },
1547
- driver.renderInline({ product, intent, surface }, checkout.callbacksFor(product, surface))
1759
+ driver.renderInline(
1760
+ { product, intent, surface },
1761
+ checkout.callbacksFor(product, surface)
1762
+ )
1548
1763
  ) : null;
1549
1764
  }
1550
1765
  if (surface === "sheet") {
1551
1766
  const cb = checkout.callbacksFor(product, surface);
1552
1767
  const open = () => {
1553
1768
  cb.onStart();
1554
- showModal(CheckoutSheet, { product, surface, intent, onSuccess: cb.onSuccess, onError: cb.onError });
1769
+ showModal(CheckoutSheet, {
1770
+ product,
1771
+ surface,
1772
+ intent,
1773
+ kind: options.kind,
1774
+ onSuccess: cb.onSuccess,
1775
+ onError: cb.onError,
1776
+ onPaymentAdded: cb.onPaymentAdded
1777
+ });
1555
1778
  };
1556
- return trigger(open, { asChild, children, className, disabled: checkout.isLoading });
1779
+ return trigger(open, {
1780
+ asChild,
1781
+ children,
1782
+ className,
1783
+ disabled: checkout.isLoading
1784
+ });
1557
1785
  }
1558
1786
  return trigger(() => checkout.open(product, surface), {
1559
1787
  asChild,
@@ -1562,32 +1790,1150 @@ function Checkout({
1562
1790
  disabled: checkout.isLoading
1563
1791
  });
1564
1792
  }
1565
- function Upsell({ product, fallback, asChild, children, className, ...options }) {
1566
- const checkout = useCheckout({ ...options, intent: "upsell" });
1567
- const start = async () => {
1568
- const result = await checkout.open(product);
1569
- if (result.ok || !fallback) return;
1570
- const cb = checkout.callbacksFor(product, fallback);
1571
- if (fallback === "sheet") {
1572
- cb.onStart();
1573
- showModal(CheckoutSheet, { product, surface: "sheet", intent: "upsell", onSuccess: cb.onSuccess, onError: cb.onError });
1793
+ function Upsell({
1794
+ offering,
1795
+ fallback,
1796
+ asChild,
1797
+ children,
1798
+ className,
1799
+ ...options
1800
+ }) {
1801
+ const product = offering;
1802
+ const checkout = useCheckout({
1803
+ ...options,
1804
+ intent: "upsell",
1805
+ recoverySurface: fallback ?? options.recoverySurface
1806
+ });
1807
+ return trigger(
1808
+ () => void checkout.open(product),
1809
+ {
1810
+ asChild,
1811
+ children: children ?? "Add to my plan",
1812
+ className,
1813
+ disabled: checkout.isLoading
1814
+ }
1815
+ );
1816
+ }
1817
+ var consumedResumes = /* @__PURE__ */ new Set();
1818
+ function CheckoutResume(options = {}) {
1819
+ const driver = useDriver();
1820
+ const checkout = useCheckout(options);
1821
+ const callbacksRef = React.useRef(checkout.callbacksFor);
1822
+ callbacksRef.current = checkout.callbacksFor;
1823
+ React.useEffect(() => {
1824
+ if (typeof window === "undefined" || !driver.resume) return;
1825
+ const url = new URL(window.location.href);
1826
+ const correlationId = url.searchParams.get("af_checkout");
1827
+ if (!correlationId || consumedResumes.has(correlationId)) return;
1828
+ consumedResumes.add(correlationId);
1829
+ void driver.resume(correlationId).then((result) => {
1830
+ const cb = callbacksRef.current();
1831
+ if (result.ok) {
1832
+ url.searchParams.delete("af_checkout");
1833
+ window.history.replaceState(
1834
+ window.history.state,
1835
+ "",
1836
+ url.toString()
1837
+ );
1838
+ cb.onSuccess(result);
1839
+ } else {
1840
+ cb.onError(
1841
+ result.error ?? chunk7JLOF6CJ_cjs.checkoutError(
1842
+ "unknown",
1843
+ "The checkout could not be verified"
1844
+ )
1845
+ );
1846
+ }
1847
+ });
1848
+ }, []);
1849
+ return null;
1850
+ }
1851
+ function __insertCSS(code) {
1852
+ if (typeof document == "undefined") return;
1853
+ let head = document.head || document.getElementsByTagName("head")[0];
1854
+ let style = document.createElement("style");
1855
+ style.type = "text/css";
1856
+ head.appendChild(style);
1857
+ style.styleSheet ? style.styleSheet.cssText = code : style.appendChild(document.createTextNode(code));
1858
+ }
1859
+ var getAsset = (type) => {
1860
+ switch (type) {
1861
+ case "success":
1862
+ return SuccessIcon;
1863
+ case "info":
1864
+ return InfoIcon;
1865
+ case "warning":
1866
+ return WarningIcon;
1867
+ case "error":
1868
+ return ErrorIcon;
1869
+ default:
1870
+ return null;
1871
+ }
1872
+ };
1873
+ var bars = Array(12).fill(0);
1874
+ var Loader = ({ visible, className }) => {
1875
+ return /* @__PURE__ */ React__default.default.createElement("div", {
1876
+ className: [
1877
+ "sonner-loading-wrapper",
1878
+ className
1879
+ ].filter(Boolean).join(" "),
1880
+ "data-visible": visible
1881
+ }, /* @__PURE__ */ React__default.default.createElement("div", {
1882
+ className: "sonner-spinner"
1883
+ }, bars.map((_, i) => /* @__PURE__ */ React__default.default.createElement("div", {
1884
+ className: "sonner-loading-bar",
1885
+ key: `spinner-bar-${i}`
1886
+ }))));
1887
+ };
1888
+ var SuccessIcon = /* @__PURE__ */ React__default.default.createElement("svg", {
1889
+ xmlns: "http://www.w3.org/2000/svg",
1890
+ viewBox: "0 0 20 20",
1891
+ fill: "currentColor",
1892
+ height: "20",
1893
+ width: "20"
1894
+ }, /* @__PURE__ */ React__default.default.createElement("path", {
1895
+ fillRule: "evenodd",
1896
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z",
1897
+ clipRule: "evenodd"
1898
+ }));
1899
+ var WarningIcon = /* @__PURE__ */ React__default.default.createElement("svg", {
1900
+ xmlns: "http://www.w3.org/2000/svg",
1901
+ viewBox: "0 0 24 24",
1902
+ fill: "currentColor",
1903
+ height: "20",
1904
+ width: "20"
1905
+ }, /* @__PURE__ */ React__default.default.createElement("path", {
1906
+ fillRule: "evenodd",
1907
+ d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z",
1908
+ clipRule: "evenodd"
1909
+ }));
1910
+ var InfoIcon = /* @__PURE__ */ React__default.default.createElement("svg", {
1911
+ xmlns: "http://www.w3.org/2000/svg",
1912
+ viewBox: "0 0 20 20",
1913
+ fill: "currentColor",
1914
+ height: "20",
1915
+ width: "20"
1916
+ }, /* @__PURE__ */ React__default.default.createElement("path", {
1917
+ fillRule: "evenodd",
1918
+ d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z",
1919
+ clipRule: "evenodd"
1920
+ }));
1921
+ var ErrorIcon = /* @__PURE__ */ React__default.default.createElement("svg", {
1922
+ xmlns: "http://www.w3.org/2000/svg",
1923
+ viewBox: "0 0 20 20",
1924
+ fill: "currentColor",
1925
+ height: "20",
1926
+ width: "20"
1927
+ }, /* @__PURE__ */ React__default.default.createElement("path", {
1928
+ fillRule: "evenodd",
1929
+ d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z",
1930
+ clipRule: "evenodd"
1931
+ }));
1932
+ var CloseIcon = /* @__PURE__ */ React__default.default.createElement("svg", {
1933
+ xmlns: "http://www.w3.org/2000/svg",
1934
+ width: "12",
1935
+ height: "12",
1936
+ viewBox: "0 0 24 24",
1937
+ fill: "none",
1938
+ stroke: "currentColor",
1939
+ strokeWidth: "1.5",
1940
+ strokeLinecap: "round",
1941
+ strokeLinejoin: "round"
1942
+ }, /* @__PURE__ */ React__default.default.createElement("line", {
1943
+ x1: "18",
1944
+ y1: "6",
1945
+ x2: "6",
1946
+ y2: "18"
1947
+ }), /* @__PURE__ */ React__default.default.createElement("line", {
1948
+ x1: "6",
1949
+ y1: "6",
1950
+ x2: "18",
1951
+ y2: "18"
1952
+ }));
1953
+ var useIsDocumentHidden = () => {
1954
+ const [isDocumentHidden, setIsDocumentHidden] = React__default.default.useState(document.hidden);
1955
+ React__default.default.useEffect(() => {
1956
+ const callback = () => {
1957
+ setIsDocumentHidden(document.hidden);
1958
+ };
1959
+ document.addEventListener("visibilitychange", callback);
1960
+ return () => window.removeEventListener("visibilitychange", callback);
1961
+ }, []);
1962
+ return isDocumentHidden;
1963
+ };
1964
+ var toastsCounter = 1;
1965
+ var Observer = class {
1966
+ constructor() {
1967
+ this.subscribe = (subscriber) => {
1968
+ this.subscribers.push(subscriber);
1969
+ return () => {
1970
+ const index = this.subscribers.indexOf(subscriber);
1971
+ this.subscribers.splice(index, 1);
1972
+ };
1973
+ };
1974
+ this.publish = (data) => {
1975
+ this.subscribers.forEach((subscriber) => subscriber(data));
1976
+ };
1977
+ this.addToast = (data) => {
1978
+ this.publish(data);
1979
+ this.toasts = [
1980
+ ...this.toasts,
1981
+ data
1982
+ ];
1983
+ };
1984
+ this.create = (data) => {
1985
+ var _data_id;
1986
+ const { message, ...rest } = data;
1987
+ const id = typeof (data == null ? void 0 : data.id) === "number" || ((_data_id = data.id) == null ? void 0 : _data_id.length) > 0 ? data.id : toastsCounter++;
1988
+ const alreadyExists = this.toasts.find((toast2) => {
1989
+ return toast2.id === id;
1990
+ });
1991
+ const dismissible = data.dismissible === void 0 ? true : data.dismissible;
1992
+ if (this.dismissedToasts.has(id)) {
1993
+ this.dismissedToasts.delete(id);
1994
+ }
1995
+ if (alreadyExists) {
1996
+ this.toasts = this.toasts.map((toast2) => {
1997
+ if (toast2.id === id) {
1998
+ this.publish({
1999
+ ...toast2,
2000
+ ...data,
2001
+ id,
2002
+ title: message
2003
+ });
2004
+ return {
2005
+ ...toast2,
2006
+ ...data,
2007
+ id,
2008
+ dismissible,
2009
+ title: message
2010
+ };
2011
+ }
2012
+ return toast2;
2013
+ });
2014
+ } else {
2015
+ this.addToast({
2016
+ title: message,
2017
+ ...rest,
2018
+ dismissible,
2019
+ id
2020
+ });
2021
+ }
2022
+ return id;
2023
+ };
2024
+ this.dismiss = (id) => {
2025
+ if (id) {
2026
+ this.dismissedToasts.add(id);
2027
+ requestAnimationFrame(() => this.subscribers.forEach((subscriber) => subscriber({
2028
+ id,
2029
+ dismiss: true
2030
+ })));
2031
+ } else {
2032
+ this.toasts.forEach((toast2) => {
2033
+ this.subscribers.forEach((subscriber) => subscriber({
2034
+ id: toast2.id,
2035
+ dismiss: true
2036
+ }));
2037
+ });
2038
+ }
2039
+ return id;
2040
+ };
2041
+ this.message = (message, data) => {
2042
+ return this.create({
2043
+ ...data,
2044
+ message
2045
+ });
2046
+ };
2047
+ this.error = (message, data) => {
2048
+ return this.create({
2049
+ ...data,
2050
+ message,
2051
+ type: "error"
2052
+ });
2053
+ };
2054
+ this.success = (message, data) => {
2055
+ return this.create({
2056
+ ...data,
2057
+ type: "success",
2058
+ message
2059
+ });
2060
+ };
2061
+ this.info = (message, data) => {
2062
+ return this.create({
2063
+ ...data,
2064
+ type: "info",
2065
+ message
2066
+ });
2067
+ };
2068
+ this.warning = (message, data) => {
2069
+ return this.create({
2070
+ ...data,
2071
+ type: "warning",
2072
+ message
2073
+ });
2074
+ };
2075
+ this.loading = (message, data) => {
2076
+ return this.create({
2077
+ ...data,
2078
+ type: "loading",
2079
+ message
2080
+ });
2081
+ };
2082
+ this.promise = (promise, data) => {
2083
+ if (!data) {
2084
+ return;
2085
+ }
2086
+ let id = void 0;
2087
+ if (data.loading !== void 0) {
2088
+ id = this.create({
2089
+ ...data,
2090
+ promise,
2091
+ type: "loading",
2092
+ message: data.loading,
2093
+ description: typeof data.description !== "function" ? data.description : void 0
2094
+ });
2095
+ }
2096
+ const p = Promise.resolve(promise instanceof Function ? promise() : promise);
2097
+ let shouldDismiss = id !== void 0;
2098
+ let result;
2099
+ const originalPromise = p.then(async (response) => {
2100
+ result = [
2101
+ "resolve",
2102
+ response
2103
+ ];
2104
+ const isReactElementResponse = React__default.default.isValidElement(response);
2105
+ if (isReactElementResponse) {
2106
+ shouldDismiss = false;
2107
+ this.create({
2108
+ id,
2109
+ type: "default",
2110
+ message: response
2111
+ });
2112
+ } else if (isHttpResponse(response) && !response.ok) {
2113
+ shouldDismiss = false;
2114
+ const promiseData = typeof data.error === "function" ? await data.error(`HTTP error! status: ${response.status}`) : data.error;
2115
+ const description = typeof data.description === "function" ? await data.description(`HTTP error! status: ${response.status}`) : data.description;
2116
+ const isExtendedResult = typeof promiseData === "object" && !React__default.default.isValidElement(promiseData);
2117
+ const toastSettings = isExtendedResult ? promiseData : {
2118
+ message: promiseData
2119
+ };
2120
+ this.create({
2121
+ id,
2122
+ type: "error",
2123
+ description,
2124
+ ...toastSettings
2125
+ });
2126
+ } else if (response instanceof Error) {
2127
+ shouldDismiss = false;
2128
+ const promiseData = typeof data.error === "function" ? await data.error(response) : data.error;
2129
+ const description = typeof data.description === "function" ? await data.description(response) : data.description;
2130
+ const isExtendedResult = typeof promiseData === "object" && !React__default.default.isValidElement(promiseData);
2131
+ const toastSettings = isExtendedResult ? promiseData : {
2132
+ message: promiseData
2133
+ };
2134
+ this.create({
2135
+ id,
2136
+ type: "error",
2137
+ description,
2138
+ ...toastSettings
2139
+ });
2140
+ } else if (data.success !== void 0) {
2141
+ shouldDismiss = false;
2142
+ const promiseData = typeof data.success === "function" ? await data.success(response) : data.success;
2143
+ const description = typeof data.description === "function" ? await data.description(response) : data.description;
2144
+ const isExtendedResult = typeof promiseData === "object" && !React__default.default.isValidElement(promiseData);
2145
+ const toastSettings = isExtendedResult ? promiseData : {
2146
+ message: promiseData
2147
+ };
2148
+ this.create({
2149
+ id,
2150
+ type: "success",
2151
+ description,
2152
+ ...toastSettings
2153
+ });
2154
+ }
2155
+ }).catch(async (error) => {
2156
+ result = [
2157
+ "reject",
2158
+ error
2159
+ ];
2160
+ if (data.error !== void 0) {
2161
+ shouldDismiss = false;
2162
+ const promiseData = typeof data.error === "function" ? await data.error(error) : data.error;
2163
+ const description = typeof data.description === "function" ? await data.description(error) : data.description;
2164
+ const isExtendedResult = typeof promiseData === "object" && !React__default.default.isValidElement(promiseData);
2165
+ const toastSettings = isExtendedResult ? promiseData : {
2166
+ message: promiseData
2167
+ };
2168
+ this.create({
2169
+ id,
2170
+ type: "error",
2171
+ description,
2172
+ ...toastSettings
2173
+ });
2174
+ }
2175
+ }).finally(() => {
2176
+ if (shouldDismiss) {
2177
+ this.dismiss(id);
2178
+ id = void 0;
2179
+ }
2180
+ data.finally == null ? void 0 : data.finally.call(data);
2181
+ });
2182
+ const unwrap = () => new Promise((resolve, reject) => originalPromise.then(() => result[0] === "reject" ? reject(result[1]) : resolve(result[1])).catch(reject));
2183
+ if (typeof id !== "string" && typeof id !== "number") {
2184
+ return {
2185
+ unwrap
2186
+ };
2187
+ } else {
2188
+ return Object.assign(id, {
2189
+ unwrap
2190
+ });
2191
+ }
2192
+ };
2193
+ this.custom = (jsx2, data) => {
2194
+ const id = (data == null ? void 0 : data.id) || toastsCounter++;
2195
+ this.create({
2196
+ jsx: jsx2(id),
2197
+ id,
2198
+ ...data
2199
+ });
2200
+ return id;
2201
+ };
2202
+ this.getActiveToasts = () => {
2203
+ return this.toasts.filter((toast2) => !this.dismissedToasts.has(toast2.id));
2204
+ };
2205
+ this.subscribers = [];
2206
+ this.toasts = [];
2207
+ this.dismissedToasts = /* @__PURE__ */ new Set();
2208
+ }
2209
+ };
2210
+ var ToastState = new Observer();
2211
+ var toastFunction = (message, data) => {
2212
+ const id = (data == null ? void 0 : data.id) || toastsCounter++;
2213
+ ToastState.addToast({
2214
+ title: message,
2215
+ ...data,
2216
+ id
2217
+ });
2218
+ return id;
2219
+ };
2220
+ var isHttpResponse = (data) => {
2221
+ return data && typeof data === "object" && "ok" in data && typeof data.ok === "boolean" && "status" in data && typeof data.status === "number";
2222
+ };
2223
+ var basicToast = toastFunction;
2224
+ var getHistory = () => ToastState.toasts;
2225
+ var getToasts = () => ToastState.getActiveToasts();
2226
+ var toast = Object.assign(basicToast, {
2227
+ success: ToastState.success,
2228
+ info: ToastState.info,
2229
+ warning: ToastState.warning,
2230
+ error: ToastState.error,
2231
+ custom: ToastState.custom,
2232
+ message: ToastState.message,
2233
+ promise: ToastState.promise,
2234
+ dismiss: ToastState.dismiss,
2235
+ loading: ToastState.loading
2236
+ }, {
2237
+ getHistory,
2238
+ getToasts
2239
+ });
2240
+ __insertCSS("[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)}");
2241
+ function isAction(action) {
2242
+ return action.label !== void 0;
2243
+ }
2244
+ var VISIBLE_TOASTS_AMOUNT = 3;
2245
+ var VIEWPORT_OFFSET = "24px";
2246
+ var MOBILE_VIEWPORT_OFFSET = "16px";
2247
+ var TOAST_LIFETIME = 4e3;
2248
+ var TOAST_WIDTH = 356;
2249
+ var GAP = 14;
2250
+ var SWIPE_THRESHOLD = 45;
2251
+ var TIME_BEFORE_UNMOUNT = 200;
2252
+ function cn(...classes) {
2253
+ return classes.filter(Boolean).join(" ");
2254
+ }
2255
+ function getDefaultSwipeDirections(position) {
2256
+ const [y, x] = position.split("-");
2257
+ const directions = [];
2258
+ if (y) {
2259
+ directions.push(y);
2260
+ }
2261
+ if (x) {
2262
+ directions.push(x);
2263
+ }
2264
+ return directions;
2265
+ }
2266
+ var Toast = (props) => {
2267
+ var _toast_classNames, _toast_classNames1, _toast_classNames2, _toast_classNames3, _toast_classNames4, _toast_classNames5, _toast_classNames6, _toast_classNames7, _toast_classNames8;
2268
+ const { invert: ToasterInvert, toast: toast2, unstyled, interacting, setHeights, visibleToasts, heights, index, toasts, expanded, removeToast, defaultRichColors, closeButton: closeButtonFromToaster, style, cancelButtonStyle, actionButtonStyle, className = "", descriptionClassName = "", duration: durationFromToaster, position, gap, expandByDefault, classNames, icons, closeButtonAriaLabel = "Close toast" } = props;
2269
+ const [swipeDirection, setSwipeDirection] = React__default.default.useState(null);
2270
+ const [swipeOutDirection, setSwipeOutDirection] = React__default.default.useState(null);
2271
+ const [mounted, setMounted] = React__default.default.useState(false);
2272
+ const [removed, setRemoved] = React__default.default.useState(false);
2273
+ const [swiping, setSwiping] = React__default.default.useState(false);
2274
+ const [swipeOut, setSwipeOut] = React__default.default.useState(false);
2275
+ const [isSwiped, setIsSwiped] = React__default.default.useState(false);
2276
+ const [offsetBeforeRemove, setOffsetBeforeRemove] = React__default.default.useState(0);
2277
+ const [initialHeight, setInitialHeight] = React__default.default.useState(0);
2278
+ const remainingTime = React__default.default.useRef(toast2.duration || durationFromToaster || TOAST_LIFETIME);
2279
+ const dragStartTime = React__default.default.useRef(null);
2280
+ const toastRef = React__default.default.useRef(null);
2281
+ const isFront = index === 0;
2282
+ const isVisible = index + 1 <= visibleToasts;
2283
+ const toastType = toast2.type;
2284
+ const dismissible = toast2.dismissible !== false;
2285
+ const toastClassname = toast2.className || "";
2286
+ const toastDescriptionClassname = toast2.descriptionClassName || "";
2287
+ const heightIndex = React__default.default.useMemo(() => heights.findIndex((height) => height.toastId === toast2.id) || 0, [
2288
+ heights,
2289
+ toast2.id
2290
+ ]);
2291
+ const closeButton = React__default.default.useMemo(() => {
2292
+ var _toast_closeButton;
2293
+ return (_toast_closeButton = toast2.closeButton) != null ? _toast_closeButton : closeButtonFromToaster;
2294
+ }, [
2295
+ toast2.closeButton,
2296
+ closeButtonFromToaster
2297
+ ]);
2298
+ const duration = React__default.default.useMemo(() => toast2.duration || durationFromToaster || TOAST_LIFETIME, [
2299
+ toast2.duration,
2300
+ durationFromToaster
2301
+ ]);
2302
+ const closeTimerStartTimeRef = React__default.default.useRef(0);
2303
+ const offset = React__default.default.useRef(0);
2304
+ const lastCloseTimerStartTimeRef = React__default.default.useRef(0);
2305
+ const pointerStartRef = React__default.default.useRef(null);
2306
+ const [y, x] = position.split("-");
2307
+ const toastsHeightBefore = React__default.default.useMemo(() => {
2308
+ return heights.reduce((prev, curr, reducerIndex) => {
2309
+ if (reducerIndex >= heightIndex) {
2310
+ return prev;
2311
+ }
2312
+ return prev + curr.height;
2313
+ }, 0);
2314
+ }, [
2315
+ heights,
2316
+ heightIndex
2317
+ ]);
2318
+ const isDocumentHidden = useIsDocumentHidden();
2319
+ const invert = toast2.invert || ToasterInvert;
2320
+ const disabled = toastType === "loading";
2321
+ offset.current = React__default.default.useMemo(() => heightIndex * gap + toastsHeightBefore, [
2322
+ heightIndex,
2323
+ toastsHeightBefore
2324
+ ]);
2325
+ React__default.default.useEffect(() => {
2326
+ remainingTime.current = duration;
2327
+ }, [
2328
+ duration
2329
+ ]);
2330
+ React__default.default.useEffect(() => {
2331
+ setMounted(true);
2332
+ }, []);
2333
+ React__default.default.useEffect(() => {
2334
+ const toastNode = toastRef.current;
2335
+ if (toastNode) {
2336
+ const height = toastNode.getBoundingClientRect().height;
2337
+ setInitialHeight(height);
2338
+ setHeights((h) => [
2339
+ {
2340
+ toastId: toast2.id,
2341
+ height,
2342
+ position: toast2.position
2343
+ },
2344
+ ...h
2345
+ ]);
2346
+ return () => setHeights((h) => h.filter((height2) => height2.toastId !== toast2.id));
2347
+ }
2348
+ }, [
2349
+ setHeights,
2350
+ toast2.id
2351
+ ]);
2352
+ React__default.default.useLayoutEffect(() => {
2353
+ if (!mounted) return;
2354
+ const toastNode = toastRef.current;
2355
+ const originalHeight = toastNode.style.height;
2356
+ toastNode.style.height = "auto";
2357
+ const newHeight = toastNode.getBoundingClientRect().height;
2358
+ toastNode.style.height = originalHeight;
2359
+ setInitialHeight(newHeight);
2360
+ setHeights((heights2) => {
2361
+ const alreadyExists = heights2.find((height) => height.toastId === toast2.id);
2362
+ if (!alreadyExists) {
2363
+ return [
2364
+ {
2365
+ toastId: toast2.id,
2366
+ height: newHeight,
2367
+ position: toast2.position
2368
+ },
2369
+ ...heights2
2370
+ ];
2371
+ } else {
2372
+ return heights2.map((height) => height.toastId === toast2.id ? {
2373
+ ...height,
2374
+ height: newHeight
2375
+ } : height);
2376
+ }
2377
+ });
2378
+ }, [
2379
+ mounted,
2380
+ toast2.title,
2381
+ toast2.description,
2382
+ setHeights,
2383
+ toast2.id,
2384
+ toast2.jsx,
2385
+ toast2.action,
2386
+ toast2.cancel
2387
+ ]);
2388
+ const deleteToast = React__default.default.useCallback(() => {
2389
+ setRemoved(true);
2390
+ setOffsetBeforeRemove(offset.current);
2391
+ setHeights((h) => h.filter((height) => height.toastId !== toast2.id));
2392
+ setTimeout(() => {
2393
+ removeToast(toast2);
2394
+ }, TIME_BEFORE_UNMOUNT);
2395
+ }, [
2396
+ toast2,
2397
+ removeToast,
2398
+ setHeights,
2399
+ offset
2400
+ ]);
2401
+ React__default.default.useEffect(() => {
2402
+ if (toast2.promise && toastType === "loading" || toast2.duration === Infinity || toast2.type === "loading") return;
2403
+ let timeoutId;
2404
+ const pauseTimer = () => {
2405
+ if (lastCloseTimerStartTimeRef.current < closeTimerStartTimeRef.current) {
2406
+ const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - closeTimerStartTimeRef.current;
2407
+ remainingTime.current = remainingTime.current - elapsedTime;
2408
+ }
2409
+ lastCloseTimerStartTimeRef.current = (/* @__PURE__ */ new Date()).getTime();
2410
+ };
2411
+ const startTimer = () => {
2412
+ if (remainingTime.current === Infinity) return;
2413
+ closeTimerStartTimeRef.current = (/* @__PURE__ */ new Date()).getTime();
2414
+ timeoutId = setTimeout(() => {
2415
+ toast2.onAutoClose == null ? void 0 : toast2.onAutoClose.call(toast2, toast2);
2416
+ deleteToast();
2417
+ }, remainingTime.current);
2418
+ };
2419
+ if (expanded || interacting || isDocumentHidden) {
2420
+ pauseTimer();
1574
2421
  } else {
1575
- await checkout.open(product, fallback);
2422
+ startTimer();
2423
+ }
2424
+ return () => clearTimeout(timeoutId);
2425
+ }, [
2426
+ expanded,
2427
+ interacting,
2428
+ toast2,
2429
+ toastType,
2430
+ isDocumentHidden,
2431
+ deleteToast
2432
+ ]);
2433
+ React__default.default.useEffect(() => {
2434
+ if (toast2.delete) {
2435
+ deleteToast();
2436
+ toast2.onDismiss == null ? void 0 : toast2.onDismiss.call(toast2, toast2);
2437
+ }
2438
+ }, [
2439
+ deleteToast,
2440
+ toast2.delete
2441
+ ]);
2442
+ function getLoadingIcon() {
2443
+ var _toast_classNames9;
2444
+ if (icons == null ? void 0 : icons.loading) {
2445
+ var _toast_classNames12;
2446
+ return /* @__PURE__ */ React__default.default.createElement("div", {
2447
+ className: cn(classNames == null ? void 0 : classNames.loader, toast2 == null ? void 0 : (_toast_classNames12 = toast2.classNames) == null ? void 0 : _toast_classNames12.loader, "sonner-loader"),
2448
+ "data-visible": toastType === "loading"
2449
+ }, icons.loading);
2450
+ }
2451
+ return /* @__PURE__ */ React__default.default.createElement(Loader, {
2452
+ className: cn(classNames == null ? void 0 : classNames.loader, toast2 == null ? void 0 : (_toast_classNames9 = toast2.classNames) == null ? void 0 : _toast_classNames9.loader),
2453
+ visible: toastType === "loading"
2454
+ });
2455
+ }
2456
+ const icon = toast2.icon || (icons == null ? void 0 : icons[toastType]) || getAsset(toastType);
2457
+ var _toast_richColors, _icons_close;
2458
+ return /* @__PURE__ */ React__default.default.createElement("li", {
2459
+ tabIndex: 0,
2460
+ ref: toastRef,
2461
+ className: cn(className, toastClassname, classNames == null ? void 0 : classNames.toast, toast2 == null ? void 0 : (_toast_classNames = toast2.classNames) == null ? void 0 : _toast_classNames.toast, classNames == null ? void 0 : classNames.default, classNames == null ? void 0 : classNames[toastType], toast2 == null ? void 0 : (_toast_classNames1 = toast2.classNames) == null ? void 0 : _toast_classNames1[toastType]),
2462
+ "data-sonner-toast": "",
2463
+ "data-rich-colors": (_toast_richColors = toast2.richColors) != null ? _toast_richColors : defaultRichColors,
2464
+ "data-styled": !Boolean(toast2.jsx || toast2.unstyled || unstyled),
2465
+ "data-mounted": mounted,
2466
+ "data-promise": Boolean(toast2.promise),
2467
+ "data-swiped": isSwiped,
2468
+ "data-removed": removed,
2469
+ "data-visible": isVisible,
2470
+ "data-y-position": y,
2471
+ "data-x-position": x,
2472
+ "data-index": index,
2473
+ "data-front": isFront,
2474
+ "data-swiping": swiping,
2475
+ "data-dismissible": dismissible,
2476
+ "data-type": toastType,
2477
+ "data-invert": invert,
2478
+ "data-swipe-out": swipeOut,
2479
+ "data-swipe-direction": swipeOutDirection,
2480
+ "data-expanded": Boolean(expanded || expandByDefault && mounted),
2481
+ "data-testid": toast2.testId,
2482
+ style: {
2483
+ "--index": index,
2484
+ "--toasts-before": index,
2485
+ "--z-index": toasts.length - index,
2486
+ "--offset": `${removed ? offsetBeforeRemove : offset.current}px`,
2487
+ "--initial-height": expandByDefault ? "auto" : `${initialHeight}px`,
2488
+ ...style,
2489
+ ...toast2.style
2490
+ },
2491
+ onDragEnd: () => {
2492
+ setSwiping(false);
2493
+ setSwipeDirection(null);
2494
+ pointerStartRef.current = null;
2495
+ },
2496
+ onPointerDown: (event) => {
2497
+ if (event.button === 2) return;
2498
+ if (disabled || !dismissible) return;
2499
+ dragStartTime.current = /* @__PURE__ */ new Date();
2500
+ setOffsetBeforeRemove(offset.current);
2501
+ event.target.setPointerCapture(event.pointerId);
2502
+ if (event.target.tagName === "BUTTON") return;
2503
+ setSwiping(true);
2504
+ pointerStartRef.current = {
2505
+ x: event.clientX,
2506
+ y: event.clientY
2507
+ };
2508
+ },
2509
+ onPointerUp: () => {
2510
+ var _toastRef_current, _toastRef_current1, _dragStartTime_current;
2511
+ if (swipeOut || !dismissible) return;
2512
+ pointerStartRef.current = null;
2513
+ const swipeAmountX = Number(((_toastRef_current = toastRef.current) == null ? void 0 : _toastRef_current.style.getPropertyValue("--swipe-amount-x").replace("px", "")) || 0);
2514
+ const swipeAmountY = Number(((_toastRef_current1 = toastRef.current) == null ? void 0 : _toastRef_current1.style.getPropertyValue("--swipe-amount-y").replace("px", "")) || 0);
2515
+ const timeTaken = (/* @__PURE__ */ new Date()).getTime() - ((_dragStartTime_current = dragStartTime.current) == null ? void 0 : _dragStartTime_current.getTime());
2516
+ const swipeAmount = swipeDirection === "x" ? swipeAmountX : swipeAmountY;
2517
+ const velocity = Math.abs(swipeAmount) / timeTaken;
2518
+ if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {
2519
+ setOffsetBeforeRemove(offset.current);
2520
+ toast2.onDismiss == null ? void 0 : toast2.onDismiss.call(toast2, toast2);
2521
+ if (swipeDirection === "x") {
2522
+ setSwipeOutDirection(swipeAmountX > 0 ? "right" : "left");
2523
+ } else {
2524
+ setSwipeOutDirection(swipeAmountY > 0 ? "down" : "up");
2525
+ }
2526
+ deleteToast();
2527
+ setSwipeOut(true);
2528
+ return;
2529
+ } else {
2530
+ var _toastRef_current2, _toastRef_current3;
2531
+ (_toastRef_current2 = toastRef.current) == null ? void 0 : _toastRef_current2.style.setProperty("--swipe-amount-x", `0px`);
2532
+ (_toastRef_current3 = toastRef.current) == null ? void 0 : _toastRef_current3.style.setProperty("--swipe-amount-y", `0px`);
2533
+ }
2534
+ setIsSwiped(false);
2535
+ setSwiping(false);
2536
+ setSwipeDirection(null);
2537
+ },
2538
+ onPointerMove: (event) => {
2539
+ var _window_getSelection, _toastRef_current, _toastRef_current1;
2540
+ if (!pointerStartRef.current || !dismissible) return;
2541
+ const isHighlighted = ((_window_getSelection = window.getSelection()) == null ? void 0 : _window_getSelection.toString().length) > 0;
2542
+ if (isHighlighted) return;
2543
+ const yDelta = event.clientY - pointerStartRef.current.y;
2544
+ const xDelta = event.clientX - pointerStartRef.current.x;
2545
+ var _props_swipeDirections;
2546
+ const swipeDirections = (_props_swipeDirections = props.swipeDirections) != null ? _props_swipeDirections : getDefaultSwipeDirections(position);
2547
+ if (!swipeDirection && (Math.abs(xDelta) > 1 || Math.abs(yDelta) > 1)) {
2548
+ setSwipeDirection(Math.abs(xDelta) > Math.abs(yDelta) ? "x" : "y");
2549
+ }
2550
+ let swipeAmount = {
2551
+ x: 0,
2552
+ y: 0
2553
+ };
2554
+ const getDampening = (delta) => {
2555
+ const factor = Math.abs(delta) / 20;
2556
+ return 1 / (1.5 + factor);
2557
+ };
2558
+ if (swipeDirection === "y") {
2559
+ if (swipeDirections.includes("top") || swipeDirections.includes("bottom")) {
2560
+ if (swipeDirections.includes("top") && yDelta < 0 || swipeDirections.includes("bottom") && yDelta > 0) {
2561
+ swipeAmount.y = yDelta;
2562
+ } else {
2563
+ const dampenedDelta = yDelta * getDampening(yDelta);
2564
+ swipeAmount.y = Math.abs(dampenedDelta) < Math.abs(yDelta) ? dampenedDelta : yDelta;
2565
+ }
2566
+ }
2567
+ } else if (swipeDirection === "x") {
2568
+ if (swipeDirections.includes("left") || swipeDirections.includes("right")) {
2569
+ if (swipeDirections.includes("left") && xDelta < 0 || swipeDirections.includes("right") && xDelta > 0) {
2570
+ swipeAmount.x = xDelta;
2571
+ } else {
2572
+ const dampenedDelta = xDelta * getDampening(xDelta);
2573
+ swipeAmount.x = Math.abs(dampenedDelta) < Math.abs(xDelta) ? dampenedDelta : xDelta;
2574
+ }
2575
+ }
2576
+ }
2577
+ if (Math.abs(swipeAmount.x) > 0 || Math.abs(swipeAmount.y) > 0) {
2578
+ setIsSwiped(true);
2579
+ }
2580
+ (_toastRef_current = toastRef.current) == null ? void 0 : _toastRef_current.style.setProperty("--swipe-amount-x", `${swipeAmount.x}px`);
2581
+ (_toastRef_current1 = toastRef.current) == null ? void 0 : _toastRef_current1.style.setProperty("--swipe-amount-y", `${swipeAmount.y}px`);
2582
+ }
2583
+ }, closeButton && !toast2.jsx && toastType !== "loading" ? /* @__PURE__ */ React__default.default.createElement("button", {
2584
+ "aria-label": closeButtonAriaLabel,
2585
+ "data-disabled": disabled,
2586
+ "data-close-button": true,
2587
+ onClick: disabled || !dismissible ? () => {
2588
+ } : () => {
2589
+ deleteToast();
2590
+ toast2.onDismiss == null ? void 0 : toast2.onDismiss.call(toast2, toast2);
2591
+ },
2592
+ className: cn(classNames == null ? void 0 : classNames.closeButton, toast2 == null ? void 0 : (_toast_classNames2 = toast2.classNames) == null ? void 0 : _toast_classNames2.closeButton)
2593
+ }, (_icons_close = icons == null ? void 0 : icons.close) != null ? _icons_close : CloseIcon) : null, (toastType || toast2.icon || toast2.promise) && toast2.icon !== null && ((icons == null ? void 0 : icons[toastType]) !== null || toast2.icon) ? /* @__PURE__ */ React__default.default.createElement("div", {
2594
+ "data-icon": "",
2595
+ className: cn(classNames == null ? void 0 : classNames.icon, toast2 == null ? void 0 : (_toast_classNames3 = toast2.classNames) == null ? void 0 : _toast_classNames3.icon)
2596
+ }, toast2.promise || toast2.type === "loading" && !toast2.icon ? toast2.icon || getLoadingIcon() : null, toast2.type !== "loading" ? icon : null) : null, /* @__PURE__ */ React__default.default.createElement("div", {
2597
+ "data-content": "",
2598
+ className: cn(classNames == null ? void 0 : classNames.content, toast2 == null ? void 0 : (_toast_classNames4 = toast2.classNames) == null ? void 0 : _toast_classNames4.content)
2599
+ }, /* @__PURE__ */ React__default.default.createElement("div", {
2600
+ "data-title": "",
2601
+ className: cn(classNames == null ? void 0 : classNames.title, toast2 == null ? void 0 : (_toast_classNames5 = toast2.classNames) == null ? void 0 : _toast_classNames5.title)
2602
+ }, toast2.jsx ? toast2.jsx : typeof toast2.title === "function" ? toast2.title() : toast2.title), toast2.description ? /* @__PURE__ */ React__default.default.createElement("div", {
2603
+ "data-description": "",
2604
+ className: cn(descriptionClassName, toastDescriptionClassname, classNames == null ? void 0 : classNames.description, toast2 == null ? void 0 : (_toast_classNames6 = toast2.classNames) == null ? void 0 : _toast_classNames6.description)
2605
+ }, typeof toast2.description === "function" ? toast2.description() : toast2.description) : null), /* @__PURE__ */ React__default.default.isValidElement(toast2.cancel) ? toast2.cancel : toast2.cancel && isAction(toast2.cancel) ? /* @__PURE__ */ React__default.default.createElement("button", {
2606
+ "data-button": true,
2607
+ "data-cancel": true,
2608
+ style: toast2.cancelButtonStyle || cancelButtonStyle,
2609
+ onClick: (event) => {
2610
+ if (!isAction(toast2.cancel)) return;
2611
+ if (!dismissible) return;
2612
+ toast2.cancel.onClick == null ? void 0 : toast2.cancel.onClick.call(toast2.cancel, event);
2613
+ deleteToast();
2614
+ },
2615
+ className: cn(classNames == null ? void 0 : classNames.cancelButton, toast2 == null ? void 0 : (_toast_classNames7 = toast2.classNames) == null ? void 0 : _toast_classNames7.cancelButton)
2616
+ }, toast2.cancel.label) : null, /* @__PURE__ */ React__default.default.isValidElement(toast2.action) ? toast2.action : toast2.action && isAction(toast2.action) ? /* @__PURE__ */ React__default.default.createElement("button", {
2617
+ "data-button": true,
2618
+ "data-action": true,
2619
+ style: toast2.actionButtonStyle || actionButtonStyle,
2620
+ onClick: (event) => {
2621
+ if (!isAction(toast2.action)) return;
2622
+ toast2.action.onClick == null ? void 0 : toast2.action.onClick.call(toast2.action, event);
2623
+ if (event.defaultPrevented) return;
2624
+ deleteToast();
2625
+ },
2626
+ className: cn(classNames == null ? void 0 : classNames.actionButton, toast2 == null ? void 0 : (_toast_classNames8 = toast2.classNames) == null ? void 0 : _toast_classNames8.actionButton)
2627
+ }, toast2.action.label) : null);
2628
+ };
2629
+ function getDocumentDirection() {
2630
+ if (typeof window === "undefined") return "ltr";
2631
+ if (typeof document === "undefined") return "ltr";
2632
+ const dirAttribute = document.documentElement.getAttribute("dir");
2633
+ if (dirAttribute === "auto" || !dirAttribute) {
2634
+ return window.getComputedStyle(document.documentElement).direction;
2635
+ }
2636
+ return dirAttribute;
2637
+ }
2638
+ function assignOffset(defaultOffset, mobileOffset) {
2639
+ const styles = {};
2640
+ [
2641
+ defaultOffset,
2642
+ mobileOffset
2643
+ ].forEach((offset, index) => {
2644
+ const isMobile = index === 1;
2645
+ const prefix = isMobile ? "--mobile-offset" : "--offset";
2646
+ const defaultValue = isMobile ? MOBILE_VIEWPORT_OFFSET : VIEWPORT_OFFSET;
2647
+ function assignAll(offset2) {
2648
+ [
2649
+ "top",
2650
+ "right",
2651
+ "bottom",
2652
+ "left"
2653
+ ].forEach((key) => {
2654
+ styles[`${prefix}-${key}`] = typeof offset2 === "number" ? `${offset2}px` : offset2;
2655
+ });
2656
+ }
2657
+ if (typeof offset === "number" || typeof offset === "string") {
2658
+ assignAll(offset);
2659
+ } else if (typeof offset === "object") {
2660
+ [
2661
+ "top",
2662
+ "right",
2663
+ "bottom",
2664
+ "left"
2665
+ ].forEach((key) => {
2666
+ if (offset[key] === void 0) {
2667
+ styles[`${prefix}-${key}`] = defaultValue;
2668
+ } else {
2669
+ styles[`${prefix}-${key}`] = typeof offset[key] === "number" ? `${offset[key]}px` : offset[key];
2670
+ }
2671
+ });
2672
+ } else {
2673
+ assignAll(defaultValue);
1576
2674
  }
1577
- };
1578
- return trigger(start, {
1579
- asChild,
1580
- children: children ?? "Add to my plan",
1581
- className,
1582
- disabled: checkout.isLoading
1583
2675
  });
1584
- }
2676
+ return styles;
2677
+ }
2678
+ var Toaster = /* @__PURE__ */ React__default.default.forwardRef(function Toaster2(props, ref) {
2679
+ const { id, invert, position = "bottom-right", hotkey = [
2680
+ "altKey",
2681
+ "KeyT"
2682
+ ], expand, closeButton, className, offset, mobileOffset, theme = "light", richColors, duration, style, visibleToasts = VISIBLE_TOASTS_AMOUNT, toastOptions, dir = getDocumentDirection(), gap = GAP, icons, containerAriaLabel = "Notifications" } = props;
2683
+ const [toasts, setToasts] = React__default.default.useState([]);
2684
+ const filteredToasts = React__default.default.useMemo(() => {
2685
+ if (id) {
2686
+ return toasts.filter((toast2) => toast2.toasterId === id);
2687
+ }
2688
+ return toasts.filter((toast2) => !toast2.toasterId);
2689
+ }, [
2690
+ toasts,
2691
+ id
2692
+ ]);
2693
+ const possiblePositions = React__default.default.useMemo(() => {
2694
+ return Array.from(new Set([
2695
+ position
2696
+ ].concat(filteredToasts.filter((toast2) => toast2.position).map((toast2) => toast2.position))));
2697
+ }, [
2698
+ filteredToasts,
2699
+ position
2700
+ ]);
2701
+ const [heights, setHeights] = React__default.default.useState([]);
2702
+ const [expanded, setExpanded] = React__default.default.useState(false);
2703
+ const [interacting, setInteracting] = React__default.default.useState(false);
2704
+ const [actualTheme, setActualTheme] = React__default.default.useState(theme !== "system" ? theme : typeof window !== "undefined" ? window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : "light");
2705
+ const listRef = React__default.default.useRef(null);
2706
+ const hotkeyLabel = hotkey.join("+").replace(/Key/g, "").replace(/Digit/g, "");
2707
+ const lastFocusedElementRef = React__default.default.useRef(null);
2708
+ const isFocusWithinRef = React__default.default.useRef(false);
2709
+ const removeToast = React__default.default.useCallback((toastToRemove) => {
2710
+ setToasts((toasts2) => {
2711
+ var _toasts_find;
2712
+ if (!((_toasts_find = toasts2.find((toast2) => toast2.id === toastToRemove.id)) == null ? void 0 : _toasts_find.delete)) {
2713
+ ToastState.dismiss(toastToRemove.id);
2714
+ }
2715
+ return toasts2.filter(({ id: id2 }) => id2 !== toastToRemove.id);
2716
+ });
2717
+ }, []);
2718
+ React__default.default.useEffect(() => {
2719
+ return ToastState.subscribe((toast2) => {
2720
+ if (toast2.dismiss) {
2721
+ requestAnimationFrame(() => {
2722
+ setToasts((toasts2) => toasts2.map((t) => t.id === toast2.id ? {
2723
+ ...t,
2724
+ delete: true
2725
+ } : t));
2726
+ });
2727
+ return;
2728
+ }
2729
+ setTimeout(() => {
2730
+ ReactDOM__default.default.flushSync(() => {
2731
+ setToasts((toasts2) => {
2732
+ const indexOfExistingToast = toasts2.findIndex((t) => t.id === toast2.id);
2733
+ if (indexOfExistingToast !== -1) {
2734
+ return [
2735
+ ...toasts2.slice(0, indexOfExistingToast),
2736
+ {
2737
+ ...toasts2[indexOfExistingToast],
2738
+ ...toast2
2739
+ },
2740
+ ...toasts2.slice(indexOfExistingToast + 1)
2741
+ ];
2742
+ }
2743
+ return [
2744
+ toast2,
2745
+ ...toasts2
2746
+ ];
2747
+ });
2748
+ });
2749
+ });
2750
+ });
2751
+ }, [
2752
+ toasts
2753
+ ]);
2754
+ React__default.default.useEffect(() => {
2755
+ if (theme !== "system") {
2756
+ setActualTheme(theme);
2757
+ return;
2758
+ }
2759
+ if (theme === "system") {
2760
+ if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
2761
+ setActualTheme("dark");
2762
+ } else {
2763
+ setActualTheme("light");
2764
+ }
2765
+ }
2766
+ if (typeof window === "undefined") return;
2767
+ const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
2768
+ try {
2769
+ darkMediaQuery.addEventListener("change", ({ matches }) => {
2770
+ if (matches) {
2771
+ setActualTheme("dark");
2772
+ } else {
2773
+ setActualTheme("light");
2774
+ }
2775
+ });
2776
+ } catch (error) {
2777
+ darkMediaQuery.addListener(({ matches }) => {
2778
+ try {
2779
+ if (matches) {
2780
+ setActualTheme("dark");
2781
+ } else {
2782
+ setActualTheme("light");
2783
+ }
2784
+ } catch (e) {
2785
+ console.error(e);
2786
+ }
2787
+ });
2788
+ }
2789
+ }, [
2790
+ theme
2791
+ ]);
2792
+ React__default.default.useEffect(() => {
2793
+ if (toasts.length <= 1) {
2794
+ setExpanded(false);
2795
+ }
2796
+ }, [
2797
+ toasts
2798
+ ]);
2799
+ React__default.default.useEffect(() => {
2800
+ const handleKeyDown = (event) => {
2801
+ var _listRef_current;
2802
+ const isHotkeyPressed = hotkey.every((key) => event[key] || event.code === key);
2803
+ if (isHotkeyPressed) {
2804
+ var _listRef_current1;
2805
+ setExpanded(true);
2806
+ (_listRef_current1 = listRef.current) == null ? void 0 : _listRef_current1.focus();
2807
+ }
2808
+ if (event.code === "Escape" && (document.activeElement === listRef.current || ((_listRef_current = listRef.current) == null ? void 0 : _listRef_current.contains(document.activeElement)))) {
2809
+ setExpanded(false);
2810
+ }
2811
+ };
2812
+ document.addEventListener("keydown", handleKeyDown);
2813
+ return () => document.removeEventListener("keydown", handleKeyDown);
2814
+ }, [
2815
+ hotkey
2816
+ ]);
2817
+ React__default.default.useEffect(() => {
2818
+ if (listRef.current) {
2819
+ return () => {
2820
+ if (lastFocusedElementRef.current) {
2821
+ lastFocusedElementRef.current.focus({
2822
+ preventScroll: true
2823
+ });
2824
+ lastFocusedElementRef.current = null;
2825
+ isFocusWithinRef.current = false;
2826
+ }
2827
+ };
2828
+ }
2829
+ }, [
2830
+ listRef.current
2831
+ ]);
2832
+ return (
2833
+ // Remove item from normal navigation flow, only available via hotkey
2834
+ /* @__PURE__ */ React__default.default.createElement("section", {
2835
+ ref,
2836
+ "aria-label": `${containerAriaLabel} ${hotkeyLabel}`,
2837
+ tabIndex: -1,
2838
+ "aria-live": "polite",
2839
+ "aria-relevant": "additions text",
2840
+ "aria-atomic": "false",
2841
+ suppressHydrationWarning: true
2842
+ }, possiblePositions.map((position2, index) => {
2843
+ var _heights_;
2844
+ const [y, x] = position2.split("-");
2845
+ if (!filteredToasts.length) return null;
2846
+ return /* @__PURE__ */ React__default.default.createElement("ol", {
2847
+ key: position2,
2848
+ dir: dir === "auto" ? getDocumentDirection() : dir,
2849
+ tabIndex: -1,
2850
+ ref: listRef,
2851
+ className,
2852
+ "data-sonner-toaster": true,
2853
+ "data-sonner-theme": actualTheme,
2854
+ "data-y-position": y,
2855
+ "data-x-position": x,
2856
+ style: {
2857
+ "--front-toast-height": `${((_heights_ = heights[0]) == null ? void 0 : _heights_.height) || 0}px`,
2858
+ "--width": `${TOAST_WIDTH}px`,
2859
+ "--gap": `${gap}px`,
2860
+ ...style,
2861
+ ...assignOffset(offset, mobileOffset)
2862
+ },
2863
+ onBlur: (event) => {
2864
+ if (isFocusWithinRef.current && !event.currentTarget.contains(event.relatedTarget)) {
2865
+ isFocusWithinRef.current = false;
2866
+ if (lastFocusedElementRef.current) {
2867
+ lastFocusedElementRef.current.focus({
2868
+ preventScroll: true
2869
+ });
2870
+ lastFocusedElementRef.current = null;
2871
+ }
2872
+ }
2873
+ },
2874
+ onFocus: (event) => {
2875
+ const isNotDismissible = event.target instanceof HTMLElement && event.target.dataset.dismissible === "false";
2876
+ if (isNotDismissible) return;
2877
+ if (!isFocusWithinRef.current) {
2878
+ isFocusWithinRef.current = true;
2879
+ lastFocusedElementRef.current = event.relatedTarget;
2880
+ }
2881
+ },
2882
+ onMouseEnter: () => setExpanded(true),
2883
+ onMouseMove: () => setExpanded(true),
2884
+ onMouseLeave: () => {
2885
+ if (!interacting) {
2886
+ setExpanded(false);
2887
+ }
2888
+ },
2889
+ onDragEnd: () => setExpanded(false),
2890
+ onPointerDown: (event) => {
2891
+ const isNotDismissible = event.target instanceof HTMLElement && event.target.dataset.dismissible === "false";
2892
+ if (isNotDismissible) return;
2893
+ setInteracting(true);
2894
+ },
2895
+ onPointerUp: () => setInteracting(false)
2896
+ }, filteredToasts.filter((toast2) => !toast2.position && index === 0 || toast2.position === position2).map((toast2, index2) => {
2897
+ var _toastOptions_duration, _toastOptions_closeButton;
2898
+ return /* @__PURE__ */ React__default.default.createElement(Toast, {
2899
+ key: toast2.id,
2900
+ icons,
2901
+ index: index2,
2902
+ toast: toast2,
2903
+ defaultRichColors: richColors,
2904
+ duration: (_toastOptions_duration = toastOptions == null ? void 0 : toastOptions.duration) != null ? _toastOptions_duration : duration,
2905
+ className: toastOptions == null ? void 0 : toastOptions.className,
2906
+ descriptionClassName: toastOptions == null ? void 0 : toastOptions.descriptionClassName,
2907
+ invert,
2908
+ visibleToasts,
2909
+ closeButton: (_toastOptions_closeButton = toastOptions == null ? void 0 : toastOptions.closeButton) != null ? _toastOptions_closeButton : closeButton,
2910
+ interacting,
2911
+ position: position2,
2912
+ style: toastOptions == null ? void 0 : toastOptions.style,
2913
+ unstyled: toastOptions == null ? void 0 : toastOptions.unstyled,
2914
+ classNames: toastOptions == null ? void 0 : toastOptions.classNames,
2915
+ cancelButtonStyle: toastOptions == null ? void 0 : toastOptions.cancelButtonStyle,
2916
+ actionButtonStyle: toastOptions == null ? void 0 : toastOptions.actionButtonStyle,
2917
+ closeButtonAriaLabel: toastOptions == null ? void 0 : toastOptions.closeButtonAriaLabel,
2918
+ removeToast,
2919
+ toasts: filteredToasts.filter((t) => t.position == toast2.position),
2920
+ heights: heights.filter((h) => h.position == toast2.position),
2921
+ setHeights,
2922
+ expandByDefault: expand,
2923
+ gap,
2924
+ expanded,
2925
+ swipeDirections: props.swipeDirections
2926
+ });
2927
+ }));
2928
+ }))
2929
+ );
2930
+ });
1585
2931
  function FunnelToaster(props = {}) {
1586
- return react.createElement(sonner.Toaster, { position: "top-center", ...props });
2932
+ return React.createElement(Toaster, { position: "top-center", ...props });
1587
2933
  }
1588
2934
  function renderToaster(toaster) {
1589
2935
  if (toaster === false) return null;
1590
- return react.createElement(sonner.Toaster, { position: "top-center", ...toaster === true ? {} : toaster });
2936
+ return /* @__PURE__ */ jsxRuntime.jsx(FunnelToaster, { ...toaster === true ? {} : toaster });
1591
2937
  }
1592
2938
 
1593
2939
  // src/state/namespaces.ts
@@ -1611,7 +2957,8 @@ var Namespace = class {
1611
2957
  const out = {};
1612
2958
  const all = this.store.getState();
1613
2959
  for (const key in all) {
1614
- if (key.startsWith(this.dot)) out[key.slice(this.dot.length)] = all[key];
2960
+ if (key.startsWith(this.dot))
2961
+ out[key.slice(this.dot.length)] = all[key];
1615
2962
  }
1616
2963
  return out;
1617
2964
  }
@@ -1653,13 +3000,14 @@ function createFunnel(store, context, tracker, experiments = []) {
1653
3000
  })
1654
3001
  };
1655
3002
  }
1656
- var FunnelCtx = react.createContext(null);
3003
+ var FunnelCtx = React.createContext(null);
1657
3004
  function FunnelProvider({
1658
3005
  config,
1659
3006
  sessionValues,
3007
+ deferSessionValues = false,
1660
3008
  tracker,
1661
3009
  context: contextOpts,
1662
- products,
3010
+ offerings,
1663
3011
  experiments,
1664
3012
  checkout,
1665
3013
  messages,
@@ -1669,10 +3017,15 @@ function FunnelProvider({
1669
3017
  children
1670
3018
  }) {
1671
3019
  const resolvedTracker = tracker ?? FALLBACK_TRACKER;
1672
- const [value] = react.useState(() => {
3020
+ const [value] = React.useState(() => {
1673
3021
  const s = store ?? createFunnelStore(
1674
3022
  { responses: config?.responses, data: config?.data },
1675
- sessionValues
3023
+ // FLASH-FREE HYDRATION: with deferSessionValues the initial render is seeded with
3024
+ // DEFAULTS ONLY, so the client's first render is byte-identical to the build-time
3025
+ // DEFAULT-state prerender it hydrates against (clean match). The visitor's saved
3026
+ // answers arrive as a post-hydration setMany below. Without the flag, sessionValues
3027
+ // seed the store at construction (SSR + non-prerendered paths).
3028
+ deferSessionValues ? void 0 : sessionValues
1676
3029
  );
1677
3030
  const ctx = buildContext({
1678
3031
  funnelId: config?.id,
@@ -1686,65 +3039,90 @@ function FunnelProvider({
1686
3039
  getVisitorId: () => ctx.identity.visitorId
1687
3040
  });
1688
3041
  const busTracker = withBus(resolvedTracker, bus);
3042
+ const emailForCheckout = () => {
3043
+ const value2 = s.get("user.email");
3044
+ return typeof value2 === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value2) ? value2 : void 0;
3045
+ };
3046
+ const catalog = chunkY4YNJ2EX_cjs.buildCatalog(offerings ?? []);
1689
3047
  return {
1690
3048
  funnel: createFunnel(s, ctx, busTracker, experiments ?? []),
1691
3049
  store: s,
1692
3050
  context: ctx,
1693
- catalog: buildCatalog(products ?? []),
1694
- driver: checkout ?? createMockDriver(),
3051
+ catalog,
3052
+ // Slot → catalog-key resolution wraps the email-enriching driver, so the wire charges the real
3053
+ // catalog key while pages + analytics keep the durable SLOT identity (req.product).
3054
+ driver: driverWithCatalog(
3055
+ driverWithEmail(
3056
+ checkout ?? createMockDriver(),
3057
+ emailForCheckout
3058
+ ),
3059
+ catalog
3060
+ ),
1695
3061
  tracker: busTracker,
1696
3062
  bus
1697
3063
  };
1698
3064
  });
1699
- react.useEffect(() => attachBus(value.bus), [value.bus]);
1700
- react.useEffect(() => {
3065
+ React.useEffect(() => attachBus(value.bus), [value.bus]);
3066
+ React.useEffect(() => {
3067
+ if (!deferSessionValues || !sessionValues) return;
3068
+ const updates = {};
3069
+ for (const [key, v] of Object.entries(sessionValues))
3070
+ if (v !== void 0) updates[key] = v;
3071
+ if (Object.keys(updates).length > 0) value.store.setMany(updates);
3072
+ }, []);
3073
+ React.useEffect(() => {
1701
3074
  value.tracker.setAcquisition?.(buildAcquisition(value.context));
1702
3075
  }, [value]);
1703
- const tree = react.createElement(TrackerProvider, {
3076
+ const tree = React.createElement(TrackerProvider, {
1704
3077
  tracker: value.tracker,
1705
- children: react.createElement(CheckoutDriverProvider, {
3078
+ children: React.createElement(CheckoutDriverProvider, {
1706
3079
  driver: value.driver,
1707
- children: react.createElement(CatalogProvider, {
3080
+ children: React.createElement(CatalogProvider, {
1708
3081
  catalog: value.catalog,
1709
- children: react.createElement(FunnelCtx.Provider, {
3082
+ children: React.createElement(FunnelCtx.Provider, {
1710
3083
  value,
1711
- children: react.createElement(LocaleProvider, {
3084
+ children: React.createElement(LocaleProvider, {
1712
3085
  config: config?.locales,
1713
3086
  catalog: messages,
1714
3087
  detected: value.context.locale.locale,
1715
3088
  override: localeOverride,
1716
- children: react.createElement(ModalRuntime, { children })
3089
+ children: React.createElement(ModalRuntime, { children })
1717
3090
  })
1718
3091
  })
1719
3092
  })
1720
3093
  })
1721
3094
  });
1722
- return react.createElement(react.Fragment, null, tree, renderToaster(toaster));
3095
+ return React.createElement(React.Fragment, null, tree, renderToaster(toaster));
1723
3096
  }
1724
3097
  var FALLBACK_TRACKER = createConsoleTracker({ silent: true });
1725
3098
  function useFunnelCtx() {
1726
- const ctx = react.useContext(FunnelCtx);
3099
+ const ctx = React.useContext(FunnelCtx);
1727
3100
  if (!ctx) {
1728
- throw new Error("useFunnel/useResponse/\u2026 must be used inside <FunnelProvider>");
3101
+ throw new Error(
3102
+ "useFunnel/useResponse/\u2026 must be used inside <FunnelProvider>"
3103
+ );
1729
3104
  }
1730
3105
  return ctx;
1731
3106
  }
1732
3107
  function useFunnel() {
1733
3108
  return useFunnelCtx().funnel;
1734
3109
  }
3110
+ function useBusInternal() {
3111
+ return React.useContext(FunnelCtx)?.bus ?? null;
3112
+ }
1735
3113
  function useStoreKey(store, key) {
1736
- const subscribe = react.useCallback(
3114
+ const subscribe = React.useCallback(
1737
3115
  (cb) => store.subscribe(cb, { keys: [key] }),
1738
3116
  [store, key]
1739
3117
  );
1740
- const getSnapshot = react.useCallback(() => store.get(key), [store, key]);
1741
- return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
3118
+ const getSnapshot = React.useCallback(() => store.get(key), [store, key]);
3119
+ return React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1742
3120
  }
1743
3121
  function makeFieldHook(ns) {
1744
3122
  return function useNamespaceField(key) {
1745
3123
  const { funnel, store } = useFunnelCtx();
1746
3124
  const value = useStoreKey(store, `${NAMESPACE_PREFIX[ns]}.${key}`);
1747
- const set = react.useCallback(
3125
+ const set = React.useCallback(
1748
3126
  (v) => funnel[ns].set({ [key]: v }),
1749
3127
  [funnel, key]
1750
3128
  );
@@ -1760,10 +3138,12 @@ function useField(path) {
1760
3138
  const key = path.slice(dot + 1);
1761
3139
  const { funnel, store } = useFunnelCtx();
1762
3140
  if (!(ns in NAMESPACE_PREFIX)) {
1763
- throw new Error(`useField: "${path}" is not a writable namespace (user/responses/data)`);
3141
+ throw new Error(
3142
+ `useField: "${path}" is not a writable namespace (user/responses/data)`
3143
+ );
1764
3144
  }
1765
3145
  const value = useStoreKey(store, `${NAMESPACE_PREFIX[ns]}.${key}`);
1766
- const set = react.useCallback(
3146
+ const set = React.useCallback(
1767
3147
  (v) => funnel[ns].set({ [key]: v }),
1768
3148
  [funnel, ns, key]
1769
3149
  );
@@ -1789,11 +3169,404 @@ function useSystem() {
1789
3169
  }
1790
3170
  function useContextValue(path) {
1791
3171
  const ctx = useContextObject();
1792
- return react.useMemo(
1793
- () => path.split(".").reduce((acc, k) => acc?.[k], ctx),
3172
+ return React.useMemo(
3173
+ () => path.split(".").reduce(
3174
+ (acc, k) => acc?.[k],
3175
+ ctx
3176
+ ),
1794
3177
  [ctx, path]
1795
3178
  );
1796
3179
  }
3180
+
3181
+ // src/tracking/snapshotPersistence.ts
3182
+ function createSnapshotPersistence(opts) {
3183
+ const baseDelay = opts.baseDelayMs ?? 1e3;
3184
+ const maxDelay = opts.maxDelayMs ?? 3e4;
3185
+ const setT = opts.timers?.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
3186
+ const clearT = opts.timers?.clearTimeout ?? ((h) => clearTimeout(h));
3187
+ const rand = opts.random ?? Math.random;
3188
+ let pending = null;
3189
+ let seq = 0;
3190
+ let pendingSeq = 0;
3191
+ let inFlight = false;
3192
+ let attempt = 0;
3193
+ let retryTimer = null;
3194
+ let disposed = false;
3195
+ let fatalSeq = -1;
3196
+ const clearRetry = () => {
3197
+ if (retryTimer !== null) {
3198
+ clearT(retryTimer);
3199
+ retryTimer = null;
3200
+ }
3201
+ };
3202
+ const buildBody = (snapshot, s) => JSON.stringify({ sessionId: opts.getSessionId(), userData: snapshot, seq: s });
3203
+ const trySend = () => {
3204
+ if (disposed || inFlight || pending === null) return;
3205
+ if (retryTimer !== null) return;
3206
+ const sessionId = opts.getSessionId();
3207
+ if (!sessionId) return;
3208
+ if (typeof navigator !== "undefined" && navigator.onLine === false) return;
3209
+ if (pendingSeq === fatalSeq) return;
3210
+ const snapshot = pending;
3211
+ const sentSeq = pendingSeq;
3212
+ const body = buildBody(snapshot, sentSeq);
3213
+ inFlight = true;
3214
+ void fetch(opts.url, {
3215
+ method: "POST",
3216
+ headers: { "content-type": "application/json" },
3217
+ body,
3218
+ keepalive: true
3219
+ }).then((res) => {
3220
+ inFlight = false;
3221
+ if (disposed) return;
3222
+ if (res.ok) {
3223
+ attempt = 0;
3224
+ if (pendingSeq === sentSeq) pending = null;
3225
+ else trySend();
3226
+ return;
3227
+ }
3228
+ if (res.status >= 400 && res.status < 500) {
3229
+ if (process.env.NODE_ENV !== "production") {
3230
+ console.warn(
3231
+ `[appfunnel] snapshot persist rejected ${res.status} (dropping snapshot; check session/payload)`
3232
+ );
3233
+ }
3234
+ fatalSeq = sentSeq;
3235
+ if (pendingSeq === sentSeq) pending = null;
3236
+ else trySend();
3237
+ return;
3238
+ }
3239
+ scheduleRetry();
3240
+ }).catch(() => {
3241
+ inFlight = false;
3242
+ if (disposed) return;
3243
+ scheduleRetry();
3244
+ });
3245
+ };
3246
+ const scheduleRetry = () => {
3247
+ if (disposed || pending === null) return;
3248
+ const exp = Math.min(maxDelay, baseDelay * 2 ** attempt);
3249
+ const delay2 = Math.round(exp * (0.5 + rand() * 0.5));
3250
+ attempt++;
3251
+ clearRetry();
3252
+ retryTimer = setT(() => {
3253
+ retryTimer = null;
3254
+ trySend();
3255
+ }, delay2);
3256
+ };
3257
+ const push = (snapshot) => {
3258
+ if (disposed) return;
3259
+ pending = snapshot;
3260
+ pendingSeq = ++seq;
3261
+ if (fatalSeq !== -1 && pendingSeq !== fatalSeq) fatalSeq = -1;
3262
+ if (retryTimer === null) trySend();
3263
+ };
3264
+ const flushBeacon = () => {
3265
+ if (disposed || pending === null) return;
3266
+ const sessionId = opts.getSessionId();
3267
+ if (!sessionId) return;
3268
+ const body = buildBody(pending, pendingSeq);
3269
+ let delivered = false;
3270
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
3271
+ try {
3272
+ delivered = navigator.sendBeacon(
3273
+ opts.url,
3274
+ new Blob([body], { type: "application/json" })
3275
+ );
3276
+ } catch {
3277
+ delivered = false;
3278
+ }
3279
+ }
3280
+ if (!delivered) {
3281
+ try {
3282
+ void fetch(opts.url, {
3283
+ method: "POST",
3284
+ headers: { "content-type": "application/json" },
3285
+ body,
3286
+ keepalive: true
3287
+ }).catch(() => {
3288
+ });
3289
+ delivered = true;
3290
+ } catch {
3291
+ delivered = false;
3292
+ }
3293
+ }
3294
+ if (delivered) pending = null;
3295
+ };
3296
+ const onHidden = () => {
3297
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
3298
+ flushBeacon();
3299
+ }
3300
+ };
3301
+ const onPageHide = () => flushBeacon();
3302
+ const onOnline = () => trySend();
3303
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
3304
+ try {
3305
+ document.addEventListener("visibilitychange", onHidden);
3306
+ window.addEventListener("pagehide", onPageHide);
3307
+ window.addEventListener("online", onOnline);
3308
+ } catch {
3309
+ }
3310
+ }
3311
+ const dispose = () => {
3312
+ disposed = true;
3313
+ clearRetry();
3314
+ pending = null;
3315
+ if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
3316
+ try {
3317
+ document.removeEventListener("visibilitychange", onHidden);
3318
+ window.removeEventListener("pagehide", onPageHide);
3319
+ window.removeEventListener("online", onOnline);
3320
+ } catch {
3321
+ }
3322
+ }
3323
+ };
3324
+ return { push, flushBeacon, dispose };
3325
+ }
3326
+
3327
+ // src/tracking/platformTracker.ts
3328
+ function validClickIds(acq) {
3329
+ return Object.entries(acq.clickIds ?? {}).filter(
3330
+ (e) => typeof e[1] === "string" && e[1] !== ""
3331
+ );
3332
+ }
3333
+ function acquisitionQueryBag(acq) {
3334
+ const q = {};
3335
+ const utm = acq.utm ?? {};
3336
+ for (const [k, v] of Object.entries({
3337
+ utm_source: utm.source,
3338
+ utm_medium: utm.medium,
3339
+ utm_campaign: utm.campaign,
3340
+ utm_term: utm.term,
3341
+ utm_content: utm.content
3342
+ })) if (v) q[k] = v;
3343
+ for (const [k, v] of validClickIds(acq)) q[k] = v;
3344
+ if (acq.cookies?.fbp) q._fbp = acq.cookies.fbp;
3345
+ if (acq.cookies?.fbc) q._fbc = acq.cookies.fbc;
3346
+ return q;
3347
+ }
3348
+ function acquisitionAdAttribution(acq) {
3349
+ const a = {};
3350
+ for (const [k, v] of validClickIds(acq)) a[k] = v;
3351
+ if (acq.cookies?.fbp) a.fbp = acq.cookies.fbp;
3352
+ if (acq.cookies?.fbc) a.fbc = acq.cookies.fbc;
3353
+ else if (acq.clickIds?.fbclid) a.fbc = `fb.1.${Date.now()}.${acq.clickIds.fbclid}`;
3354
+ return a;
3355
+ }
3356
+ function createPlatformTracker(cfg) {
3357
+ if (typeof window === "undefined" || cfg.mode === "test") return createConsoleTracker();
3358
+ const base = cfg.apiBase.replace(/\/+$/, "");
3359
+ const url = `${base}/campaign/${encodeURIComponent(cfg.campaignId)}/event`;
3360
+ const landingUrl = window.location.href;
3361
+ const sidKey = `af_sid:${cfg.campaignId}`;
3362
+ let sessionId = null;
3363
+ try {
3364
+ sessionId = window.sessionStorage.getItem(sidKey);
3365
+ } catch {
3366
+ }
3367
+ if (!sessionId && cfg.sessionId) sessionId = cfg.sessionId;
3368
+ let acquisition = null;
3369
+ let acquisitionSent = false;
3370
+ let chain = Promise.resolve();
3371
+ const send = async (event, data, userData) => {
3372
+ let mergedUserData = landingUrl ? { landingUrl, ...userData } : userData;
3373
+ let adAttribution;
3374
+ let attachingAcquisition = false;
3375
+ if (acquisition && !acquisitionSent) {
3376
+ attachingAcquisition = true;
3377
+ const query = acquisitionQueryBag(acquisition);
3378
+ if (Object.keys(query).length) mergedUserData = { ...mergedUserData, query };
3379
+ const ad = acquisitionAdAttribution(acquisition);
3380
+ if (Object.keys(ad).length) adAttribution = ad;
3381
+ }
3382
+ const body = {
3383
+ campaignId: cfg.campaignId,
3384
+ funnelId: cfg.funnelId,
3385
+ event,
3386
+ sessionId: sessionId ?? void 0,
3387
+ visitorId: cfg.visitorId ?? void 0,
3388
+ // Campaign route stamp (campaign-routing pivot): ingestion reads this ONLY at session create
3389
+ // (WebFunnelSession.campaignRouteId, snapshot semantics) — carried on every event but ignored
3390
+ // once the session exists. Undefined ⇒ default funnel / no route matched.
3391
+ campaignRouteId: cfg.route?.id ?? void 0,
3392
+ eventId: typeof data?.eventId === "string" ? data.eventId : void 0,
3393
+ data: data ?? {},
3394
+ metadata: {
3395
+ language: typeof navigator !== "undefined" ? navigator.language : void 0,
3396
+ screen: typeof window.screen !== "undefined" ? `${window.screen.width}x${window.screen.height}` : void 0,
3397
+ timezone: (() => {
3398
+ try {
3399
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
3400
+ } catch {
3401
+ return void 0;
3402
+ }
3403
+ })(),
3404
+ referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0
3405
+ },
3406
+ ...mergedUserData ? { userData: mergedUserData } : {},
3407
+ ...adAttribution ? { adAttribution } : {}
3408
+ };
3409
+ const res = await fetch(url, {
3410
+ method: "POST",
3411
+ headers: { "content-type": "application/json" },
3412
+ body: JSON.stringify(body),
3413
+ keepalive: true
3414
+ // survives page.exit / unload
3415
+ });
3416
+ if (attachingAcquisition) acquisitionSent = true;
3417
+ const json = await res.json().catch(() => null);
3418
+ if (json?.sessionId && json.sessionId !== sessionId) {
3419
+ sessionId = json.sessionId;
3420
+ try {
3421
+ window.sessionStorage.setItem(sidKey, sessionId);
3422
+ } catch {
3423
+ }
3424
+ }
3425
+ };
3426
+ const enqueue = (event, data, userData) => {
3427
+ const task = () => send(event, data, userData).catch((e) => {
3428
+ if (process.env.NODE_ENV !== "production") console.warn(`[appfunnel] track failed (${event}):`, e);
3429
+ });
3430
+ if (!sessionId) {
3431
+ chain = chain.then(task);
3432
+ } else {
3433
+ void task();
3434
+ }
3435
+ };
3436
+ const VARS_DEBOUNCE_MS = 800;
3437
+ const dataUrl = `${base}/campaign/${encodeURIComponent(cfg.campaignId)}/event/session/data`;
3438
+ let pendingVars = null;
3439
+ let varsTimer = null;
3440
+ const persistence = createSnapshotPersistence({
3441
+ url: dataUrl,
3442
+ getSessionId: () => sessionId
3443
+ });
3444
+ const flushVars = () => {
3445
+ if (varsTimer) {
3446
+ clearTimeout(varsTimer);
3447
+ varsTimer = null;
3448
+ }
3449
+ if (!pendingVars) return;
3450
+ const snapshot = pendingVars;
3451
+ pendingVars = null;
3452
+ persistence.push(snapshot);
3453
+ if (!sessionId) chain = chain.then(() => {
3454
+ persistence.push(snapshot);
3455
+ });
3456
+ };
3457
+ const drainOnExit = () => {
3458
+ if (pendingVars) {
3459
+ flushVars();
3460
+ persistence.flushBeacon();
3461
+ }
3462
+ };
3463
+ try {
3464
+ window.addEventListener("visibilitychange", drainOnExit);
3465
+ window.addEventListener("pagehide", drainOnExit);
3466
+ } catch {
3467
+ }
3468
+ if (cfg.experiment?.id && cfg.experiment.variant) {
3469
+ enqueue("experiment.exposure", exposurePayload(cfg.experiment.id, cfg.experiment.variant, cfg.experiment.version));
3470
+ }
3471
+ return {
3472
+ track: (event, data, userData) => enqueue(event, data, userData),
3473
+ identify: (email, eventId) => enqueue("user.registered", { email, ...eventId ? { eventId } : {} }),
3474
+ setVariables: (variables) => {
3475
+ pendingVars = variables;
3476
+ if (varsTimer) clearTimeout(varsTimer);
3477
+ varsTimer = setTimeout(flushVars, VARS_DEBOUNCE_MS);
3478
+ },
3479
+ setAcquisition: (acq) => {
3480
+ acquisition = acq;
3481
+ }
3482
+ };
3483
+ }
3484
+
3485
+ // src/flow/mount.tsx
3486
+ var AssetsContext = React.createContext({});
3487
+ function AssetsProvider({
3488
+ assets,
3489
+ children
3490
+ }) {
3491
+ return React.createElement(AssetsContext.Provider, { value: assets ?? {} }, children);
3492
+ }
3493
+ function useAssets() {
3494
+ return React.useContext(AssetsContext);
3495
+ }
3496
+ function useAsset(name) {
3497
+ return React.useContext(AssetsContext)[name];
3498
+ }
3499
+ function createFunnelTree({
3500
+ config,
3501
+ pages,
3502
+ layout,
3503
+ checkoutDriver,
3504
+ messages
3505
+ }) {
3506
+ return function tree(opts) {
3507
+ const mode = opts.mode ?? "live";
3508
+ const embedEnabled = opts.embed ?? false;
3509
+ const tracker = opts.tracking ? createPlatformTracker({
3510
+ ...opts.tracking,
3511
+ visitorId: opts.visitorId,
3512
+ sessionId: opts.sessionId,
3513
+ mode
3514
+ }) : void 0;
3515
+ const checkout = checkoutDriver && opts.tracking ? checkoutDriver({
3516
+ apiBase: opts.tracking.apiBase,
3517
+ campaignId: opts.tracking.campaignId,
3518
+ funnelId: opts.tracking.funnelId,
3519
+ mode,
3520
+ sessionId: opts.sessionId,
3521
+ visitorId: opts.visitorId
3522
+ }) : void 0;
3523
+ const layoutWithResume = function LayoutWithCheckoutResume({
3524
+ children
3525
+ }) {
3526
+ return React.createElement(
3527
+ React.Fragment,
3528
+ null,
3529
+ React.createElement(CheckoutResume, null),
3530
+ embedEnabled ? React.createElement(EmbedBridge, null) : null,
3531
+ layout ? React.createElement(layout, null, children) : children
3532
+ );
3533
+ };
3534
+ return React.createElement(AssetsProvider, {
3535
+ assets: opts.assets,
3536
+ children: React.createElement(FunnelProvider, {
3537
+ config,
3538
+ offerings: opts.offerings ?? [],
3539
+ messages: opts.messages ?? messages,
3540
+ locale: opts.locale,
3541
+ tracker,
3542
+ checkout,
3543
+ sessionValues: opts.sessionValues,
3544
+ deferSessionValues: opts.deferSessionValues,
3545
+ experiments: opts.experiments,
3546
+ context: {
3547
+ mode,
3548
+ visitorId: opts.visitorId ?? null,
3549
+ customerId: opts.customerId ?? null,
3550
+ sessionId: opts.sessionId ?? null
3551
+ },
3552
+ children: React.createElement(FunnelView, {
3553
+ pages,
3554
+ layout: layoutWithResume,
3555
+ initialKey: opts.initialKey,
3556
+ // FLASH-FREE HYDRATION (Option B, FIX 1): trust the SERVER-resolved initialKey instead of
3557
+ // re-running the entry guard. The CLIENT sets this via deferSessionValues (its deferred
3558
+ // snapshot is empty, so re-running the guard would bounce a qualified deep-link and
3559
+ // mismatch the markup). The build-time PRERENDER sets `trustInitialKey` explicitly so a
3560
+ // guarded page prerenders its OWN default-state markup (the renderer picks target-vs-bounce
3561
+ // at serve time). Live SSR fallback leaves both off → the guard runs against real values.
3562
+ trustInitialKey: opts.trustInitialKey ?? opts.deferSessionValues,
3563
+ prefetch: "off"
3564
+ // renderer background-modulepreloads all chunks
3565
+ })
3566
+ })
3567
+ });
3568
+ };
3569
+ }
1797
3570
  function Choice({
1798
3571
  bind,
1799
3572
  options,
@@ -1806,7 +3579,7 @@ function Choice({
1806
3579
  return options.map((opt) => {
1807
3580
  const o = typeof opt === "string" ? { value: opt } : opt;
1808
3581
  const selected = value === o.value;
1809
- return react.createElement(
3582
+ return React.createElement(
1810
3583
  "button",
1811
3584
  {
1812
3585
  key: o.value,
@@ -1825,7 +3598,7 @@ function Choice({
1825
3598
  }
1826
3599
  function Next({ children, onClick, ...rest }) {
1827
3600
  const { next } = useNavigation();
1828
- return react.createElement(
3601
+ return React.createElement(
1829
3602
  "button",
1830
3603
  {
1831
3604
  type: "button",
@@ -1841,7 +3614,7 @@ function Next({ children, onClick, ...rest }) {
1841
3614
  function Back({ children, onClick, ...rest }) {
1842
3615
  const { back, canGoBack } = useNavigation();
1843
3616
  if (!canGoBack) return null;
1844
- return react.createElement(
3617
+ return React.createElement(
1845
3618
  "button",
1846
3619
  {
1847
3620
  type: "button",
@@ -1894,7 +3667,7 @@ function Script({
1894
3667
  onLoad,
1895
3668
  onError
1896
3669
  }) {
1897
- react.useEffect(() => {
3670
+ React.useEffect(() => {
1898
3671
  if (typeof document === "undefined") return;
1899
3672
  const key = id ?? src;
1900
3673
  if (injected.has(key)) {
@@ -1933,159 +3706,198 @@ function Script({
1933
3706
  return null;
1934
3707
  }
1935
3708
 
1936
- // src/manifest/manifest.ts
1937
- function isVariantNode(page) {
1938
- return isVariantKey(page.key);
1939
- }
1940
- function serializeGate(when) {
1941
- if (!when) return void 0;
1942
- if (typeof when === "function") return { kind: "predicate" };
1943
- return { kind: "declarative", condition: when };
1944
- }
1945
- function compileManifest(input) {
1946
- const { funnel, pages } = input;
1947
- const keys = new Set(pages.map((p) => p.key));
1948
- const manifestPages = pages.map((p, index) => ({
1949
- id: p.key,
1950
- key: p.key,
1951
- type: p.meta?.type ?? "default",
1952
- slug: p.meta?.slug ?? p.key,
1953
- index,
1954
- variantOf: isVariantKey(p.key) ? parseSlotKey(p.key).slot : void 0
1955
- }));
1956
- const edges = [];
1957
- const badTargets = [];
1958
- const nextAnchor = (from) => {
1959
- for (let j = from + 1; j < pages.length; j++) {
1960
- if (!isVariantNode(pages[j])) return pages[j];
1961
- }
1962
- return void 0;
1963
- };
1964
- pages.forEach((p, i) => {
1965
- if (isVariantNode(p)) return;
1966
- const routes = p.meta?.next ?? [];
1967
- let hasUnconditional = false;
1968
- for (const route of routes) {
1969
- if (!keys.has(route.to)) {
1970
- badTargets.push({ from: p.key, to: route.to });
1971
- continue;
1972
- }
1973
- edges.push({ from: p.key, to: route.to, kind: "branch", condition: serializeGate(route.when) });
1974
- if (!route.when) hasUnconditional = true;
1975
- }
1976
- const next = nextAnchor(i);
1977
- if (!hasUnconditional && next && p.meta?.type !== "finish") {
1978
- edges.push({ from: p.key, to: next.key, kind: "linear" });
1979
- }
1980
- });
1981
- const startPage = pages.find((p) => !isVariantNode(p));
1982
- const start = startPage?.key ?? null;
1983
- const variantKeys = new Set(pages.filter(isVariantNode).map((p) => p.key));
1984
- const validation = validate(manifestPages, edges, badTargets, start, variantKeys);
1985
- return {
1986
- id: funnel.id,
1987
- pages: manifestPages,
1988
- flow: { start, nodes: manifestPages.map((p) => p.id), edges },
1989
- products: funnel.products ?? [],
1990
- locales: funnel.locales,
1991
- validation
1992
- };
1993
- }
1994
- function validate(pages, edges, badTargets, start, variantKeys) {
1995
- const outgoing = /* @__PURE__ */ new Map();
1996
- for (const e of edges) outgoing.set(e.from, (outgoing.get(e.from) ?? 0) + 1);
1997
- const deadEnds = pages.filter((p) => p.type !== "finish" && !variantKeys.has(p.id) && !(outgoing.get(p.id) > 0)).map((p) => p.id);
1998
- const reachable = /* @__PURE__ */ new Set();
1999
- if (start) {
2000
- const adj = /* @__PURE__ */ new Map();
2001
- for (const e of edges) {
2002
- const list = adj.get(e.from);
2003
- if (list) list.push(e.to);
2004
- else adj.set(e.from, [e.to]);
2005
- }
2006
- const stack = [start];
2007
- while (stack.length) {
2008
- const id = stack.pop();
2009
- if (reachable.has(id)) continue;
2010
- reachable.add(id);
2011
- for (const to of adj.get(id) ?? []) stack.push(to);
2012
- }
2013
- }
2014
- const unreachable = pages.filter((p) => !variantKeys.has(p.id) && !reachable.has(p.id)).map((p) => p.id);
2015
- const missingEmailCapture = !pages.some((p) => p.type === "email-capture");
2016
- return {
2017
- ok: deadEnds.length === 0 && unreachable.length === 0 && badTargets.length === 0 && !missingEmailCapture,
2018
- deadEnds,
2019
- unreachable,
2020
- badTargets,
2021
- missingEmailCapture
2022
- };
2023
- }
2024
-
2025
- Object.defineProperty(exports, "toast", {
3709
+ Object.defineProperty(exports, "assignVariant", {
3710
+ enumerable: true,
3711
+ get: function () { return chunkY4YNJ2EX_cjs.assignVariant; }
3712
+ });
3713
+ Object.defineProperty(exports, "bucketingSeed", {
3714
+ enumerable: true,
3715
+ get: function () { return chunkY4YNJ2EX_cjs.bucketingSeed; }
3716
+ });
3717
+ Object.defineProperty(exports, "buildCatalog", {
3718
+ enumerable: true,
3719
+ get: function () { return chunkY4YNJ2EX_cjs.buildCatalog; }
3720
+ });
3721
+ Object.defineProperty(exports, "compileManifest", {
3722
+ enumerable: true,
3723
+ get: function () { return chunkY4YNJ2EX_cjs.compileManifest; }
3724
+ });
3725
+ Object.defineProperty(exports, "currencyExponent", {
3726
+ enumerable: true,
3727
+ get: function () { return chunkY4YNJ2EX_cjs.currencyExponent; }
3728
+ });
3729
+ Object.defineProperty(exports, "defineFunnel", {
3730
+ enumerable: true,
3731
+ get: function () { return chunkY4YNJ2EX_cjs.defineFunnel; }
3732
+ });
3733
+ Object.defineProperty(exports, "definePage", {
3734
+ enumerable: true,
3735
+ get: function () { return chunkY4YNJ2EX_cjs.definePage; }
3736
+ });
3737
+ Object.defineProperty(exports, "entryGuard", {
3738
+ enumerable: true,
3739
+ get: function () { return chunkY4YNJ2EX_cjs.entryGuard; }
3740
+ });
3741
+ Object.defineProperty(exports, "evaluateCondition", {
3742
+ enumerable: true,
3743
+ get: function () { return chunkY4YNJ2EX_cjs.evaluateCondition; }
3744
+ });
3745
+ Object.defineProperty(exports, "evaluateGate", {
3746
+ enumerable: true,
3747
+ get: function () { return chunkY4YNJ2EX_cjs.evaluateGate; }
3748
+ });
3749
+ Object.defineProperty(exports, "expectedPathLength", {
3750
+ enumerable: true,
3751
+ get: function () { return chunkY4YNJ2EX_cjs.expectedPathLength; }
3752
+ });
3753
+ Object.defineProperty(exports, "fnv1a", {
3754
+ enumerable: true,
3755
+ get: function () { return chunkY4YNJ2EX_cjs.fnv1a; }
3756
+ });
3757
+ Object.defineProperty(exports, "formatMoney", {
3758
+ enumerable: true,
3759
+ get: function () { return chunkY4YNJ2EX_cjs.formatMoney; }
3760
+ });
3761
+ Object.defineProperty(exports, "formatOffering", {
3762
+ enumerable: true,
3763
+ get: function () { return chunkY4YNJ2EX_cjs.formatOffering; }
3764
+ });
3765
+ Object.defineProperty(exports, "hashToUnit", {
3766
+ enumerable: true,
3767
+ get: function () { return chunkY4YNJ2EX_cjs.hashToUnit; }
3768
+ });
3769
+ Object.defineProperty(exports, "isRtl", {
3770
+ enumerable: true,
3771
+ get: function () { return chunkY4YNJ2EX_cjs.isRtl; }
3772
+ });
3773
+ Object.defineProperty(exports, "isVariantKey", {
3774
+ enumerable: true,
3775
+ get: function () { return chunkY4YNJ2EX_cjs.isVariantKey; }
3776
+ });
3777
+ Object.defineProperty(exports, "nextPage", {
3778
+ enumerable: true,
3779
+ get: function () { return chunkY4YNJ2EX_cjs.nextPage; }
3780
+ });
3781
+ Object.defineProperty(exports, "outgoingKeys", {
3782
+ enumerable: true,
3783
+ get: function () { return chunkY4YNJ2EX_cjs.outgoingKeys; }
3784
+ });
3785
+ Object.defineProperty(exports, "pageMeta", {
3786
+ enumerable: true,
3787
+ get: function () { return chunkY4YNJ2EX_cjs.pageMeta; }
3788
+ });
3789
+ Object.defineProperty(exports, "parseSlotKey", {
3790
+ enumerable: true,
3791
+ get: function () { return chunkY4YNJ2EX_cjs.parseSlotKey; }
3792
+ });
3793
+ Object.defineProperty(exports, "pickByWeight", {
3794
+ enumerable: true,
3795
+ get: function () { return chunkY4YNJ2EX_cjs.pickByWeight; }
3796
+ });
3797
+ Object.defineProperty(exports, "resolveExperiments", {
3798
+ enumerable: true,
3799
+ get: function () { return chunkY4YNJ2EX_cjs.resolveExperiments; }
3800
+ });
3801
+ Object.defineProperty(exports, "resolveLocale", {
3802
+ enumerable: true,
3803
+ get: function () { return chunkY4YNJ2EX_cjs.resolveLocale; }
3804
+ });
3805
+ Object.defineProperty(exports, "resolveOffering", {
3806
+ enumerable: true,
3807
+ get: function () { return chunkY4YNJ2EX_cjs.resolveOffering; }
3808
+ });
3809
+ Object.defineProperty(exports, "resolveRoute", {
3810
+ enumerable: true,
3811
+ get: function () { return chunkY4YNJ2EX_cjs.resolveRoute; }
3812
+ });
3813
+ Object.defineProperty(exports, "INLINE_SURFACES", {
3814
+ enumerable: true,
3815
+ get: function () { return chunk7JLOF6CJ_cjs.INLINE_SURFACES; }
3816
+ });
3817
+ Object.defineProperty(exports, "PROVIDER_PROFILES", {
3818
+ enumerable: true,
3819
+ get: function () { return chunk7JLOF6CJ_cjs.PROVIDER_PROFILES; }
3820
+ });
3821
+ Object.defineProperty(exports, "checkoutError", {
3822
+ enumerable: true,
3823
+ get: function () { return chunk7JLOF6CJ_cjs.checkoutError; }
3824
+ });
3825
+ Object.defineProperty(exports, "isInlineSurface", {
3826
+ enumerable: true,
3827
+ get: function () { return chunk7JLOF6CJ_cjs.isInlineSurface; }
3828
+ });
3829
+ Object.defineProperty(exports, "isMerchantOfRecord", {
3830
+ enumerable: true,
3831
+ get: function () { return chunk7JLOF6CJ_cjs.isMerchantOfRecord; }
3832
+ });
3833
+ Object.defineProperty(exports, "isOrchestrator", {
3834
+ enumerable: true,
3835
+ get: function () { return chunk7JLOF6CJ_cjs.isOrchestrator; }
3836
+ });
3837
+ Object.defineProperty(exports, "isRenderableSurface", {
3838
+ enumerable: true,
3839
+ get: function () { return chunk7JLOF6CJ_cjs.isRenderableSurface; }
3840
+ });
3841
+ Object.defineProperty(exports, "renderableSurfaces", {
3842
+ enumerable: true,
3843
+ get: function () { return chunk7JLOF6CJ_cjs.renderableSurfaces; }
3844
+ });
3845
+ Object.defineProperty(exports, "resolveSurface", {
3846
+ enumerable: true,
3847
+ get: function () { return chunk7JLOF6CJ_cjs.resolveSurface; }
3848
+ });
3849
+ Object.defineProperty(exports, "surfacesFor", {
3850
+ enumerable: true,
3851
+ get: function () { return chunk7JLOF6CJ_cjs.surfacesFor; }
3852
+ });
3853
+ Object.defineProperty(exports, "validateCheckout", {
3854
+ enumerable: true,
3855
+ get: function () { return chunk7JLOF6CJ_cjs.validateCheckout; }
3856
+ });
3857
+ Object.defineProperty(exports, "validateUpsell", {
2026
3858
  enumerable: true,
2027
- get: function () { return sonner.toast; }
3859
+ get: function () { return chunk7JLOF6CJ_cjs.validateUpsell; }
2028
3860
  });
3861
+ exports.AssetsContext = AssetsContext;
3862
+ exports.AssetsProvider = AssetsProvider;
2029
3863
  exports.Back = Back;
2030
3864
  exports.Checkout = Checkout;
3865
+ exports.CheckoutResume = CheckoutResume;
2031
3866
  exports.Choice = Choice;
2032
3867
  exports.EVENT_CATALOG = EVENT_CATALOG;
2033
3868
  exports.FunnelProvider = FunnelProvider;
2034
3869
  exports.FunnelToaster = FunnelToaster;
2035
3870
  exports.FunnelView = FunnelView;
2036
- exports.INLINE_SURFACES = INLINE_SURFACES;
2037
3871
  exports.Modal = Modal;
2038
3872
  exports.Next = Next;
2039
- exports.PROVIDER_PROFILES = PROVIDER_PROFILES;
2040
3873
  exports.Script = Script;
2041
3874
  exports.Sheet = Sheet;
2042
3875
  exports.Upsell = Upsell;
2043
3876
  exports.VariableStore = VariableStore;
2044
- exports.assignVariant = assignVariant;
2045
3877
  exports.attachBus = attachBus;
2046
- exports.bucketingSeed = bucketingSeed;
2047
3878
  exports.buildAcquisition = buildAcquisition;
2048
- exports.buildCatalog = buildCatalog;
2049
- exports.checkoutError = checkoutError;
2050
- exports.compileManifest = compileManifest;
3879
+ exports.buildContext = buildContext;
2051
3880
  exports.createBus = createBus;
2052
3881
  exports.createConsoleTracker = createConsoleTracker;
2053
3882
  exports.createFunnelStore = createFunnelStore;
3883
+ exports.createFunnelTree = createFunnelTree;
2054
3884
  exports.createMockDriver = createMockDriver;
2055
- exports.defineFunnel = defineFunnel;
3885
+ exports.createPlatformTracker = createPlatformTracker;
2056
3886
  exports.defineModal = defineModal;
2057
- exports.definePage = definePage;
2058
- exports.entryGuard = entryGuard;
2059
- exports.evaluateCondition = evaluateCondition;
2060
- exports.evaluateGate = evaluateGate;
2061
- exports.expectedPathLength = expectedPathLength;
2062
- exports.fnv1a = fnv1a;
2063
- exports.formatProduct = formatProduct;
2064
- exports.hashToUnit = hashToUnit;
3887
+ exports.driverWithEmail = driverWithEmail;
2065
3888
  exports.hideModal = hideModal;
2066
3889
  exports.integrationEvents = integrationEvents;
2067
- exports.isInlineSurface = isInlineSurface;
2068
3890
  exports.isIntegrationEvent = isIntegrationEvent;
2069
- exports.isMerchantOfRecord = isMerchantOfRecord;
2070
- exports.isOrchestrator = isOrchestrator;
2071
- exports.isRtl = isRtl;
2072
3891
  exports.isTrackableEvent = isTrackableEvent;
2073
- exports.isVariantKey = isVariantKey;
2074
3892
  exports.newEventId = newEventId;
2075
- exports.nextPage = nextPage;
2076
- exports.outgoingKeys = outgoingKeys;
2077
- exports.pageMeta = pageMeta;
2078
- exports.parseSlotKey = parseSlotKey;
2079
- exports.pickByWeight = pickByWeight;
2080
3893
  exports.registerModal = registerModal;
2081
3894
  exports.removeModal = removeModal;
2082
- exports.resolveExperiments = resolveExperiments;
2083
- exports.resolveLocale = resolveLocale;
2084
- exports.resolveProduct = resolveProduct;
2085
- exports.resolveRoute = resolveRoute;
2086
3895
  exports.showModal = showModal;
2087
- exports.surfacesFor = surfacesFor;
3896
+ exports.toast = toast;
2088
3897
  exports.unregisterModal = unregisterModal;
3898
+ exports.useActiveLocale = useActiveLocale;
3899
+ exports.useAsset = useAsset;
3900
+ exports.useAssets = useAssets;
2089
3901
  exports.useCheckout = useCheckout;
2090
3902
  exports.useClickIds = useClickIds;
2091
3903
  exports.useContextValue = useContextValue;
@@ -2097,18 +3909,15 @@ exports.useFunnel = useFunnel;
2097
3909
  exports.useLocale = useLocale;
2098
3910
  exports.useModal = useModal;
2099
3911
  exports.useNavigation = useNavigation;
3912
+ exports.useOffering = useOffering;
3913
+ exports.useOfferings = useOfferings;
2100
3914
  exports.usePage = usePage;
2101
- exports.useProduct = useProduct;
2102
- exports.useProducts = useProducts;
2103
3915
  exports.useResponse = useResponse;
2104
3916
  exports.useSystem = useSystem;
2105
3917
  exports.useTracker = useTracker;
2106
3918
  exports.useTranslation = useTranslation;
2107
3919
  exports.useUserAttribute = useUserAttribute;
2108
3920
  exports.useUtm = useUtm;
2109
- exports.validateCheckout = validateCheckout;
2110
- exports.validateExperiments = validateExperiments;
2111
- exports.validateUpsell = validateUpsell;
2112
3921
  exports.withBus = withBus;
2113
3922
  //# sourceMappingURL=index.cjs.map
2114
3923
  //# sourceMappingURL=index.cjs.map