@appfunnel-dev/sdk 2.0.0-canary.0 → 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 +2654 -843
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +351 -1005
  41. package/dist/index.d.ts +351 -1005
  42. package/dist/index.js +2386 -704
  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,140 +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 next = react.useCallback(() => {
964
- const target = nextPage(flow, currentKey, funnel.snapshot());
965
- const resolved = target ? experiments.slotOf[target] ?? target : target;
966
- if (resolved) setHistory((h) => [...h, resolved]);
967
- }, [flow, currentKey, funnel, experiments]);
968
- const back = react.useCallback(
969
- () => setHistory((h) => h.length > 1 ? h.slice(0, -1) : h),
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;
862
+ if (resolved) startNav(() => setHistory((h) => [...h, resolved]));
863
+ }, [flow, currentKey, funnel, toSlot]);
864
+ const back = React.useCallback(
865
+ () => startNav(() => setHistory((h) => h.length > 1 ? h.slice(0, -1) : h)),
970
866
  []
971
867
  );
972
- const go = react.useCallback(
973
- (key) => setHistory((h) => [...h, experiments.slotOf[key] ?? key]),
974
- [experiments]
868
+ const go = React.useCallback(
869
+ (key) => startNav(() => setHistory((h) => [...h, toSlot(key)])),
870
+ [toSlot]
975
871
  );
976
- const prefetched = react.useRef(/* @__PURE__ */ new Set());
977
- const prefetch = react.useCallback(
872
+ const prefetched = React.useRef(/* @__PURE__ */ new Set());
873
+ const prefetch = React.useCallback(
978
874
  (key) => {
979
- const target = experiments.render[key] ?? key;
875
+ const target = toRenderKey(key);
980
876
  const load = loaders.get(target);
981
877
  if (!load || prefetched.current.has(target)) return;
982
878
  prefetched.current.add(target);
983
879
  load().catch(() => prefetched.current.delete(target));
984
880
  },
985
- [loaders, experiments]
881
+ [loaders, toRenderKey]
986
882
  );
987
- const nextCandidates = react.useMemo(
988
- () => [...new Set(outgoingKeys(flow, currentKey).map((k) => experiments.render[k] ?? k))],
989
- [flow, currentKey, experiments]
883
+ const nextCandidates = React.useMemo(
884
+ () => [...new Set(chunkY4YNJ2EX_cjs.outgoingKeys(flow, currentKey).map(toRenderKey))],
885
+ [flow, currentKey, toRenderKey]
990
886
  );
991
- react.useEffect(() => {
887
+ React.useEffect(() => {
992
888
  if (prefetchMode !== "auto") return;
993
889
  for (const key of nextCandidates) prefetch(key);
994
890
  }, [prefetchMode, nextCandidates, prefetch]);
@@ -999,38 +895,160 @@ function FunnelView({
999
895
  go,
1000
896
  prefetch,
1001
897
  nextCandidates,
1002
- canGoBack: history.length > 1
898
+ canGoBack: history.length > 1,
899
+ isNavigating
1003
900
  };
1004
- 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);
1005
940
  const Current = components.get(renderKey);
1006
- const pageEl = react.createElement(react.Suspense, { fallback: fallback ?? null }, Current ? react.createElement(Current) : null);
1007
- const content = layout ? react.createElement(layout, null, pageEl) : pageEl;
1008
- 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(
1009
944
  ExperimentContext.Provider,
1010
945
  { value: experiments.assignments },
1011
- react.createElement(NavContext.Provider, { value }, content)
946
+ React.createElement(NavContext.Provider, { value }, content)
1012
947
  );
1013
948
  }
1014
949
  function useNavigation() {
1015
- const ctx = react.useContext(NavContext);
950
+ const ctx = React.useContext(NavContext);
1016
951
  if (!ctx) throw new Error("useNavigation must be used inside <FunnelView>");
1017
952
  return ctx;
1018
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
+ }
1019
1029
  function usePage() {
1020
1030
  return useNavigation().page;
1021
1031
  }
1022
- var symModalId = /* @__PURE__ */ Symbol("AppFunnelModalId");
1032
+ var symModalId = /* @__PURE__ */ Symbol("AppfunnelModalId");
1023
1033
  var initialState = {};
1024
- var ModalStateContext = react.createContext(initialState);
1025
- var ModalIdContext = react.createContext(null);
1034
+ var ModalStateContext = React.createContext(initialState);
1035
+ var ModalIdContext = React.createContext(null);
1036
+ var ModalDispatchContext = React.createContext(null);
1026
1037
  var REGISTRY = {};
1027
1038
  var ALREADY_MOUNTED = {};
1028
1039
  var modalCallbacks = {};
1029
1040
  var hideCallbacks = {};
1030
1041
  var uid = 0;
1031
1042
  var getUid = () => `_af_modal_${uid++}`;
1032
- var dispatch = () => {
1033
- 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);
1034
1052
  };
1035
1053
  var modalReducer = (state = initialState, action) => {
1036
1054
  const { modalId, args, flags } = action.payload;
@@ -1073,10 +1091,10 @@ function registerModal(id, comp, props) {
1073
1091
  function unregisterModal(id) {
1074
1092
  delete REGISTRY[id];
1075
1093
  }
1076
- function showModal(modal, args) {
1094
+ function showModalVia(d, modal, args) {
1077
1095
  const id = idOf(modal);
1078
1096
  if (typeof modal !== "string" && !REGISTRY[id]) registerModal(id, modal);
1079
- dispatch({ type: "show", payload: { modalId: id, args } });
1097
+ d({ type: "show", payload: { modalId: id, args } });
1080
1098
  if (!modalCallbacks[id]) {
1081
1099
  let res;
1082
1100
  let rej;
@@ -1088,9 +1106,12 @@ function showModal(modal, args) {
1088
1106
  }
1089
1107
  return modalCallbacks[id].promise;
1090
1108
  }
1091
- function hideModal(modal) {
1109
+ function showModal(modal, args) {
1110
+ return showModalVia(dispatch, modal, args);
1111
+ }
1112
+ function hideModalVia(d, modal) {
1092
1113
  const id = idOf(modal);
1093
- dispatch({ type: "hide", payload: { modalId: id } });
1114
+ d({ type: "hide", payload: { modalId: id } });
1094
1115
  delete modalCallbacks[id];
1095
1116
  if (!hideCallbacks[id]) {
1096
1117
  let res;
@@ -1103,41 +1124,64 @@ function hideModal(modal) {
1103
1124
  }
1104
1125
  return hideCallbacks[id].promise;
1105
1126
  }
1106
- function removeModal(modal) {
1127
+ function hideModal(modal) {
1128
+ return hideModalVia(dispatch, modal);
1129
+ }
1130
+ function removeModalVia(d, modal) {
1107
1131
  const id = idOf(modal);
1108
- dispatch({ type: "remove", payload: { modalId: id } });
1132
+ d({ type: "remove", payload: { modalId: id } });
1109
1133
  delete modalCallbacks[id];
1110
1134
  delete hideCallbacks[id];
1111
1135
  }
1112
- function setFlags(id, flags) {
1113
- 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 } });
1114
1141
  }
1115
1142
  function useModal(modal, args) {
1116
- const modals = react.useContext(ModalStateContext);
1117
- const contextId = react.useContext(ModalIdContext);
1143
+ const modals = React.useContext(ModalStateContext);
1144
+ const contextId = React.useContext(ModalIdContext);
1145
+ const d = React.useContext(ModalDispatchContext) ?? dispatch;
1118
1146
  const isComponent = !!modal && typeof modal !== "string";
1119
1147
  const id = modal ? idOf(modal) : contextId;
1120
- if (!id) throw new Error("useModal: no modal id (call inside a defineModal component, or pass an id/component).");
1121
- react.useEffect(() => {
1122
- 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);
1123
1155
  }, [isComponent, id, modal, args]);
1124
1156
  const info = modals[id];
1125
- const show = react.useCallback((a) => showModal(id, a), [id]);
1126
- const hide = react.useCallback(() => hideModal(id), [id]);
1127
- const remove = react.useCallback(() => removeModal(id), [id]);
1128
- const resolve = react.useCallback((v) => {
1129
- modalCallbacks[id]?.resolve(v);
1130
- delete modalCallbacks[id];
1131
- }, [id]);
1132
- const reject = react.useCallback((v) => {
1133
- modalCallbacks[id]?.reject(v);
1134
- delete modalCallbacks[id];
1135
- }, [id]);
1136
- const resolveHide = react.useCallback((v) => {
1137
- hideCallbacks[id]?.resolve(v);
1138
- delete hideCallbacks[id];
1139
- }, [id]);
1140
- 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(
1141
1185
  () => ({
1142
1186
  id,
1143
1187
  args: info?.args,
@@ -1150,53 +1194,90 @@ function useModal(modal, args) {
1150
1194
  reject,
1151
1195
  resolveHide
1152
1196
  }),
1153
- [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
+ ]
1154
1209
  );
1155
1210
  }
1156
1211
  function defineModal(Comp) {
1157
- return function Wrapped({ id, defaultVisible, keepMounted, ...props }) {
1212
+ return function Wrapped({
1213
+ id,
1214
+ defaultVisible,
1215
+ keepMounted,
1216
+ ...props
1217
+ }) {
1158
1218
  const { args, show } = useModal(id);
1159
- const modals = react.useContext(ModalStateContext);
1219
+ const modals = React.useContext(ModalStateContext);
1220
+ const d = React.useContext(ModalDispatchContext) ?? dispatch;
1160
1221
  const shouldMount = !!modals[id];
1161
- react.useEffect(() => {
1222
+ React.useEffect(() => {
1162
1223
  if (defaultVisible) show();
1163
1224
  ALREADY_MOUNTED[id] = true;
1164
1225
  return () => {
1165
1226
  delete ALREADY_MOUNTED[id];
1166
1227
  };
1167
1228
  }, [id, show, defaultVisible]);
1168
- react.useEffect(() => {
1169
- if (keepMounted) setFlags(id, { keepMounted: true });
1170
- }, [id, keepMounted]);
1229
+ React.useEffect(() => {
1230
+ if (keepMounted) setFlags(d, id, { keepMounted: true });
1231
+ }, [d, id, keepMounted]);
1171
1232
  const delayVisible = modals[id]?.delayVisible;
1172
- react.useEffect(() => {
1233
+ React.useEffect(() => {
1173
1234
  if (delayVisible) show(args);
1174
1235
  }, [delayVisible, args, show]);
1175
1236
  if (!shouldMount) return null;
1176
- return react.createElement(
1237
+ return React.createElement(
1177
1238
  ModalIdContext.Provider,
1178
1239
  { value: id },
1179
- react.createElement(Comp, { ...props, ...args })
1240
+ React.createElement(Comp, { ...props, ...args })
1180
1241
  );
1181
1242
  };
1182
1243
  }
1183
1244
  function ModalRoot() {
1184
- const modals = react.useContext(ModalStateContext);
1245
+ const modals = React.useContext(ModalStateContext);
1185
1246
  const toRender = Object.keys(modals).filter((id) => REGISTRY[id]).map((id) => ({ id, ...REGISTRY[id] }));
1186
- return react.createElement(
1247
+ return React.createElement(
1187
1248
  "div",
1188
1249
  { "data-appfunnel-modal-root": "" },
1189
- ...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
+ )
1190
1253
  );
1191
1254
  }
1192
1255
  function ModalRuntime({ children }) {
1193
- const [state, localDispatch] = react.useReducer(modalReducer, initialState);
1194
- dispatch = localDispatch;
1195
- return react.createElement(
1196
- ModalStateContext.Provider,
1197
- { value: state },
1198
- children,
1199
- 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
+ )
1200
1281
  );
1201
1282
  }
1202
1283
 
@@ -1223,7 +1304,7 @@ function OverlayShell({
1223
1304
  panel,
1224
1305
  children
1225
1306
  }) {
1226
- react.useEffect(() => {
1307
+ React.useEffect(() => {
1227
1308
  if (!open || typeof document === "undefined") return;
1228
1309
  const onKey = (e) => {
1229
1310
  if (e.key === "Escape") onClose();
@@ -1237,7 +1318,7 @@ function OverlayShell({
1237
1318
  };
1238
1319
  }, [open, onClose]);
1239
1320
  if (!open || typeof document === "undefined") return null;
1240
- const overlay = react.createElement(
1321
+ const overlay = React.createElement(
1241
1322
  "div",
1242
1323
  {
1243
1324
  role: "dialog",
@@ -1246,19 +1327,19 @@ function OverlayShell({
1246
1327
  "data-appfunnel-overlay": "",
1247
1328
  style: { position: "fixed", inset: 0, zIndex: 1e3, display: "flex", ...container }
1248
1329
  },
1249
- react.createElement("div", {
1330
+ React.createElement("div", {
1250
1331
  "data-appfunnel-overlay-backdrop": "",
1251
1332
  className: backdropClassName,
1252
1333
  onClick: dismissOnBackdrop ? onClose : void 0,
1253
1334
  style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" }
1254
1335
  }),
1255
- react.createElement(
1336
+ React.createElement(
1256
1337
  "div",
1257
1338
  { "data-appfunnel-overlay-panel": "", className, style: { position: "relative", background: "#fff", ...panel } },
1258
1339
  children
1259
1340
  )
1260
1341
  );
1261
- return reactDom.createPortal(overlay, document.body);
1342
+ return ReactDOM.createPortal(overlay, document.body);
1262
1343
  }
1263
1344
  var SHEET_LAYOUT = {
1264
1345
  bottom: { container: { justifyContent: "flex-end", alignItems: "stretch" }, panel: { borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 16, maxHeight: "90vh", overflow: "auto" } },
@@ -1269,7 +1350,7 @@ var SHEET_LAYOUT = {
1269
1350
  function Sheet(props) {
1270
1351
  const control = useModalControl();
1271
1352
  const layout = SHEET_LAYOUT[props.side ?? "bottom"];
1272
- return react.createElement(OverlayShell, {
1353
+ return React.createElement(OverlayShell, {
1273
1354
  ...control,
1274
1355
  className: props.className,
1275
1356
  backdropClassName: props.backdropClassName,
@@ -1282,7 +1363,7 @@ function Sheet(props) {
1282
1363
  }
1283
1364
  function Modal(props) {
1284
1365
  const control = useModalControl();
1285
- return react.createElement(OverlayShell, {
1366
+ return React.createElement(OverlayShell, {
1286
1367
  ...control,
1287
1368
  className: props.className,
1288
1369
  backdropClassName: props.backdropClassName,
@@ -1295,263 +1376,412 @@ function Modal(props) {
1295
1376
  }
1296
1377
 
1297
1378
  // src/commerce/checkout.tsx
1298
- var INLINE_SURFACES = /* @__PURE__ */ new Set([
1299
- "express",
1300
- "element",
1301
- "embedded"
1302
- ]);
1303
- function isInlineSurface(surface) {
1304
- return INLINE_SURFACES.has(surface);
1305
- }
1306
- var ON = { purchase: true, wallets: true, offSession: "reliable" };
1307
- var MANAGED = { purchase: true, wallets: true, offSession: "conditional" };
1308
- var ALL_UPSELLS = ["subscription", "one-time", "upgrade"];
1309
- var PROVIDER_PROFILES = {
1310
- // PSP; you hold the PaymentMethod and fire your own off-session PaymentIntent on
1311
- // the Elements surfaces (reliable). **Managed** Checkout (`embedded`/`redirect`)
1312
- // upsells too, but as a direct charge against Stripe's vaulted card → conditional.
1313
- // No provider "popup" surface.
1314
- stripe: {
1315
- isMoR: false,
1316
- offSessionUpsells: ALL_UPSELLS,
1317
- tokenModel: "merchant",
1318
- orchestrator: false,
1319
- surfaces: { express: ON, element: ON, sheet: ON, embedded: MANAGED, redirect: MANAGED }
1320
- },
1321
- // Merchant-of-Record; card entry is always Paddle's frame → no own element/sheet/
1322
- // express. Off-session via subscription one-time charge / collection_mode, but
1323
- // **no 2nd concurrent subscription** (upgrade + one-time only), provider-initiated.
1324
- paddle: {
1325
- isMoR: true,
1326
- offSessionUpsells: ["one-time", "upgrade"],
1327
- tokenModel: "provider",
1328
- orchestrator: false,
1329
- surfaces: { embedded: ON, popup: ON, redirect: ON }
1330
- },
1331
- // Merchant-of-Record; iframe-only (no raw card field). Off-session works
1332
- // (provider-initiated, async via webhooks). `sheet` = our chrome hosting their embed.
1333
- whop: {
1334
- isMoR: true,
1335
- offSessionUpsells: ALL_UPSELLS,
1336
- tokenModel: "provider",
1337
- orchestrator: false,
1338
- surfaces: { embedded: ON, redirect: ON, sheet: ON }
1339
- },
1340
- // Pure orchestrator (PSP-side); Headless renders express/element/sheet/embedded.
1341
- // No hosted redirect page, no popup checkout. Off-session is PSP-conditional.
1342
- primer: {
1343
- isMoR: false,
1344
- offSessionUpsells: ALL_UPSELLS,
1345
- tokenModel: "merchant",
1346
- orchestrator: true,
1347
- surfaces: { express: ON, element: ON, sheet: ON, embedded: ON }
1348
- },
1349
- // PSP **and** orchestrator (own acquiring + connectors routing across Stripe/Adyen/
1350
- // PayPal). One Payment Form (element/sheet/embedded) + hosted page + wallet button.
1351
- // Strong native off-session (Recurring API). No popup.
1352
- solidgate: {
1353
- isMoR: false,
1354
- offSessionUpsells: ALL_UPSELLS,
1355
- tokenModel: "merchant",
1356
- orchestrator: true,
1357
- surfaces: { express: ON, element: ON, sheet: ON, embedded: ON, redirect: ON }
1358
- }
1359
- };
1360
- function surfacesFor(provider) {
1361
- return Object.keys(PROVIDER_PROFILES[provider]?.surfaces ?? {});
1362
- }
1363
- function isMerchantOfRecord(provider) {
1364
- return PROVIDER_PROFILES[provider]?.isMoR ?? false;
1365
- }
1366
- function isOrchestrator(provider) {
1367
- return PROVIDER_PROFILES[provider]?.orchestrator ?? false;
1368
- }
1369
- function validateCheckout(provider, surface) {
1370
- const cap = PROVIDER_PROFILES[provider]?.surfaces[surface];
1371
- if (!cap) return { ok: false, reason: `${provider} has no '${surface}' surface` };
1372
- return cap.purchase ? { ok: true } : { ok: false, reason: `'${surface}' can't take a purchase on ${provider}` };
1373
- }
1374
- function validateUpsell(provider, paywallSurface, kind = "subscription") {
1375
- const profile = PROVIDER_PROFILES[provider];
1376
- if (!profile) return { ok: false, reason: `unknown provider '${provider}'` };
1377
- if (!profile.offSessionUpsells.includes(kind)) {
1378
- const can = profile.offSessionUpsells.length ? ` (it supports: ${profile.offSessionUpsells.join(", ")})` : "";
1379
- return { ok: false, reason: `${provider} can't charge a '${kind}' upsell off-session${can}` };
1380
- }
1381
- const cap = profile.surfaces[paywallSurface];
1382
- if (!cap) return { ok: false, reason: `${provider} has no '${paywallSurface}' surface to upsell after` };
1383
- if (cap.offSession === "none") {
1384
- return { ok: false, reason: `a purchase on '${paywallSurface}' retains no chargeable method for an upsell` };
1385
- }
1386
- if (cap.offSession === "conditional") {
1387
- return { ok: true, note: `off-session upsell after ${provider} '${paywallSurface}' (managed checkout) may decline \u2014 prefer element/sheet/express` };
1388
- }
1389
- return { ok: true };
1390
- }
1391
- function checkoutError(category, message, extra = {}) {
1392
- const retryable = extra.retryable ?? (category === "authentication_required" || category === "requires_payment_method" || category === "processing_error");
1393
- return { category, message, declineCode: extra.declineCode, retryable };
1394
- }
1395
1379
  function createMockDriver(provider = "stripe", options = {}) {
1396
- const success = options.result ?? { ok: true, amountMinor: 0, currency: "USD" };
1397
- 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
+ );
1398
1389
  return {
1399
1390
  provider,
1400
1391
  start: (req) => {
1401
- 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 });
1402
1394
  return Promise.resolve(success);
1403
1395
  },
1404
- renderInline: (req, cb) => react.createElement(
1396
+ renderInline: (req, cb) => React.createElement(
1405
1397
  "button",
1406
1398
  {
1407
1399
  type: "button",
1408
1400
  "data-mock-surface": req.surface,
1409
- 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
+ }
1410
1412
  },
1411
1413
  `Pay (mock ${req.surface})`
1412
- )
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
1413
1432
  };
1414
1433
  }
1415
- var DriverContext = react.createContext(null);
1434
+ var DriverContext = React.createContext(null);
1416
1435
  function CheckoutDriverProvider({
1417
1436
  driver,
1418
1437
  children
1419
1438
  }) {
1420
- return react.createElement(DriverContext.Provider, { value: driver }, children);
1439
+ return React.createElement(DriverContext.Provider, { value: driver }, children);
1421
1440
  }
1422
1441
  function useDriver() {
1423
- return react.useContext(DriverContext) ?? FALLBACK_DRIVER;
1442
+ return React.useContext(DriverContext) ?? FALLBACK_DRIVER;
1424
1443
  }
1425
1444
  var FALLBACK_DRIVER = createMockDriver();
1426
1445
  function useCheckout(opts = {}) {
1427
1446
  const driver = useDriver();
1428
1447
  const tracker = useTrackerRef();
1448
+ const catalog = useCatalog();
1429
1449
  const nav = useNavigation();
1430
- const [status, setStatus] = react.useState("idle");
1431
- const [error, setError] = react.useState(null);
1450
+ const [status, setStatus] = React.useState("idle");
1451
+ const [error, setError] = React.useState(null);
1432
1452
  const intent = opts.intent ?? "purchase";
1433
1453
  const kind = opts.kind;
1434
1454
  const advance = opts.advanceOnSuccess ?? true;
1435
- const succeed = react.useCallback(
1436
- (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) => {
1437
1475
  setStatus("success");
1476
+ const { productId, isSubscription } = productFacts(product);
1477
+ const amount = result.amountMinor != null ? result.amountMinor / 100 : void 0;
1438
1478
  tracker.track("purchase.complete", {
1439
- amount: result.amountMinor != null ? result.amountMinor / 100 : void 0,
1479
+ amount,
1440
1480
  currency: result.currency,
1481
+ productId,
1441
1482
  eventId: result.eventId
1442
1483
  // server-minted (browser↔CAPI dedup); undefined if none
1443
1484
  });
1485
+ if (isSubscription) {
1486
+ tracker.track("subscription.created", {
1487
+ amount,
1488
+ currency: result.currency,
1489
+ productId,
1490
+ eventId: result.eventId
1491
+ });
1492
+ }
1444
1493
  opts.onSuccess?.(result);
1445
1494
  if (advance) nav.next();
1446
1495
  },
1447
- [tracker, nav, advance, opts]
1448
- );
1449
- const fail = react.useCallback(
1450
- (err, product) => {
1451
- setStatus(err.category === "authentication_required" ? "requires_action" : "error");
1452
- setError(err);
1453
- tracker.track("checkout.failed", {
1454
- category: err.category,
1455
- declineCode: err.declineCode,
1456
- productId: product,
1457
- intent
1458
- });
1459
- opts.onError?.(err);
1460
- },
1461
- [tracker, intent, opts]
1496
+ [tracker, nav, advance, opts, productFacts]
1462
1497
  );
1463
- const begin = react.useCallback(
1498
+ const succeedRef = React.useRef(succeed);
1499
+ succeedRef.current = succeed;
1500
+ const begin = React.useCallback(
1464
1501
  (product, surface) => {
1465
1502
  setStatus("loading");
1466
1503
  setError(null);
1467
- 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
+ });
1468
1512
  },
1469
- [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]
1470
1526
  );
1471
- const paymentAdded = react.useCallback(() => tracker.track("checkout.payment_added", {}), [tracker]);
1472
- 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(
1473
1626
  async (product, surface) => {
1474
- begin(product, surface);
1475
- try {
1476
- const result = await driver.start({ product, intent, surface, kind });
1477
- if (result.ok) succeed(result);
1478
- else fail(result.error ?? checkoutError("unknown", "Checkout failed"), product);
1479
- return result;
1480
- } catch (e) {
1481
- const err = checkoutError("processing_error", e instanceof Error ? e.message : "Checkout failed");
1482
- fail(err, product);
1483
- return { ok: false, error: err };
1484
- }
1627
+ recovering.current = false;
1628
+ return attemptRef.current(product, surface);
1485
1629
  },
1486
- [driver, intent, kind, begin, succeed, fail]
1630
+ []
1487
1631
  );
1488
- const callbacksFor = react.useCallback(
1632
+ const callbacksFor = React.useCallback(
1489
1633
  (product, surface) => ({
1490
- onStart: () => begin(product, surface),
1491
- onSuccess: (result) => succeed(result),
1492
- onError: (err) => fail(err, product),
1493
- 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)
1494
1641
  }),
1495
- [begin, succeed, fail, paymentAdded]
1642
+ [begin, paymentAdded]
1496
1643
  );
1497
- const reset = react.useCallback(() => {
1644
+ const reset = React.useCallback(() => {
1645
+ recovering.current = false;
1498
1646
  setStatus("idle");
1499
1647
  setError(null);
1500
1648
  }, []);
1501
- 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
+ };
1502
1657
  }
1503
- var CheckoutSheet = defineModal(({ product, surface, intent, onSuccess, onError }) => {
1658
+ var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, onError, onPaymentAdded }) => {
1504
1659
  const driver = useDriver();
1505
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
+ }, []);
1506
1689
  if (!driver.renderInline) return null;
1507
- return react.createElement(Sheet, {
1690
+ const close = () => {
1691
+ resolved.current = true;
1692
+ void modal.hide();
1693
+ modal.remove();
1694
+ };
1695
+ return React.createElement(Sheet, {
1508
1696
  children: driver.renderInline(
1509
- { product, intent, surface },
1697
+ { product, intent, surface, kind },
1510
1698
  {
1511
1699
  onSuccess: (r) => {
1512
- modal.hide();
1700
+ close();
1513
1701
  onSuccess(r);
1514
1702
  },
1515
1703
  onError: (e) => {
1516
- modal.hide();
1704
+ close();
1517
1705
  onError(e);
1518
- }
1706
+ },
1707
+ onPaymentAdded
1519
1708
  }
1520
1709
  )
1521
1710
  });
1522
1711
  });
1523
- function trigger(onClick, { asChild, children, className, disabled }) {
1524
- if (asChild && react.isValidElement(children)) {
1712
+ function trigger(onClick, {
1713
+ asChild,
1714
+ children,
1715
+ className,
1716
+ disabled
1717
+ }) {
1718
+ if (asChild && React.isValidElement(children)) {
1525
1719
  const child = children;
1526
- 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 {
1527
1737
  }
1528
- return react.createElement("button", { type: "button", className, disabled, onClick }, children ?? "Continue");
1738
+ return "desktop";
1529
1739
  }
1530
1740
  function Checkout({
1531
- product,
1532
- surface,
1741
+ offering,
1742
+ surface: surfacePreference,
1533
1743
  asChild,
1534
1744
  children,
1535
1745
  className,
1536
1746
  ...options
1537
1747
  }) {
1748
+ const product = offering;
1538
1749
  const driver = useDriver();
1750
+ const [surface] = React.useState(
1751
+ () => chunk7JLOF6CJ_cjs.resolveSurface(driver.provider, surfacePreference, detectDeviceClass())
1752
+ );
1539
1753
  const checkout = useCheckout(options);
1540
1754
  const intent = options.intent ?? "purchase";
1541
- if (isInlineSurface(surface)) {
1542
- return driver.renderInline ? react.createElement(
1755
+ if (chunk7JLOF6CJ_cjs.isInlineSurface(surface)) {
1756
+ return driver.renderInline ? React.createElement(
1543
1757
  "div",
1544
1758
  { key: product, className },
1545
- driver.renderInline({ product, intent, surface }, checkout.callbacksFor(product, surface))
1759
+ driver.renderInline(
1760
+ { product, intent, surface },
1761
+ checkout.callbacksFor(product, surface)
1762
+ )
1546
1763
  ) : null;
1547
1764
  }
1548
1765
  if (surface === "sheet") {
1549
1766
  const cb = checkout.callbacksFor(product, surface);
1550
1767
  const open = () => {
1551
1768
  cb.onStart();
1552
- 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
+ });
1553
1778
  };
1554
- return trigger(open, { asChild, children, className, disabled: checkout.isLoading });
1779
+ return trigger(open, {
1780
+ asChild,
1781
+ children,
1782
+ className,
1783
+ disabled: checkout.isLoading
1784
+ });
1555
1785
  }
1556
1786
  return trigger(() => checkout.open(product, surface), {
1557
1787
  asChild,
@@ -1560,32 +1790,1150 @@ function Checkout({
1560
1790
  disabled: checkout.isLoading
1561
1791
  });
1562
1792
  }
1563
- function Upsell({ product, fallback, asChild, children, className, ...options }) {
1564
- const checkout = useCheckout({ ...options, intent: "upsell" });
1565
- const start = async () => {
1566
- const result = await checkout.open(product);
1567
- if (result.ok || !fallback) return;
1568
- const cb = checkout.callbacksFor(product, fallback);
1569
- if (fallback === "sheet") {
1570
- cb.onStart();
1571
- 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();
1572
2421
  } else {
1573
- 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);
1574
2674
  }
1575
- };
1576
- return trigger(start, {
1577
- asChild,
1578
- children: children ?? "Add to my plan",
1579
- className,
1580
- disabled: checkout.isLoading
1581
2675
  });
1582
- }
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
+ });
1583
2931
  function FunnelToaster(props = {}) {
1584
- return react.createElement(sonner.Toaster, { position: "top-center", ...props });
2932
+ return React.createElement(Toaster, { position: "top-center", ...props });
1585
2933
  }
1586
2934
  function renderToaster(toaster) {
1587
2935
  if (toaster === false) return null;
1588
- return react.createElement(sonner.Toaster, { position: "top-center", ...toaster === true ? {} : toaster });
2936
+ return /* @__PURE__ */ jsxRuntime.jsx(FunnelToaster, { ...toaster === true ? {} : toaster });
1589
2937
  }
1590
2938
 
1591
2939
  // src/state/namespaces.ts
@@ -1609,7 +2957,8 @@ var Namespace = class {
1609
2957
  const out = {};
1610
2958
  const all = this.store.getState();
1611
2959
  for (const key in all) {
1612
- 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];
1613
2962
  }
1614
2963
  return out;
1615
2964
  }
@@ -1651,13 +3000,14 @@ function createFunnel(store, context, tracker, experiments = []) {
1651
3000
  })
1652
3001
  };
1653
3002
  }
1654
- var FunnelCtx = react.createContext(null);
3003
+ var FunnelCtx = React.createContext(null);
1655
3004
  function FunnelProvider({
1656
3005
  config,
1657
3006
  sessionValues,
3007
+ deferSessionValues = false,
1658
3008
  tracker,
1659
3009
  context: contextOpts,
1660
- products,
3010
+ offerings,
1661
3011
  experiments,
1662
3012
  checkout,
1663
3013
  messages,
@@ -1667,10 +3017,15 @@ function FunnelProvider({
1667
3017
  children
1668
3018
  }) {
1669
3019
  const resolvedTracker = tracker ?? FALLBACK_TRACKER;
1670
- const [value] = react.useState(() => {
3020
+ const [value] = React.useState(() => {
1671
3021
  const s = store ?? createFunnelStore(
1672
3022
  { responses: config?.responses, data: config?.data },
1673
- 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
1674
3029
  );
1675
3030
  const ctx = buildContext({
1676
3031
  funnelId: config?.id,
@@ -1684,65 +3039,90 @@ function FunnelProvider({
1684
3039
  getVisitorId: () => ctx.identity.visitorId
1685
3040
  });
1686
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 ?? []);
1687
3047
  return {
1688
3048
  funnel: createFunnel(s, ctx, busTracker, experiments ?? []),
1689
3049
  store: s,
1690
3050
  context: ctx,
1691
- catalog: buildCatalog(products ?? []),
1692
- 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
+ ),
1693
3061
  tracker: busTracker,
1694
3062
  bus
1695
3063
  };
1696
3064
  });
1697
- react.useEffect(() => attachBus(value.bus), [value.bus]);
1698
- 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(() => {
1699
3074
  value.tracker.setAcquisition?.(buildAcquisition(value.context));
1700
3075
  }, [value]);
1701
- const tree = react.createElement(TrackerProvider, {
3076
+ const tree = React.createElement(TrackerProvider, {
1702
3077
  tracker: value.tracker,
1703
- children: react.createElement(CheckoutDriverProvider, {
3078
+ children: React.createElement(CheckoutDriverProvider, {
1704
3079
  driver: value.driver,
1705
- children: react.createElement(CatalogProvider, {
3080
+ children: React.createElement(CatalogProvider, {
1706
3081
  catalog: value.catalog,
1707
- children: react.createElement(FunnelCtx.Provider, {
3082
+ children: React.createElement(FunnelCtx.Provider, {
1708
3083
  value,
1709
- children: react.createElement(LocaleProvider, {
3084
+ children: React.createElement(LocaleProvider, {
1710
3085
  config: config?.locales,
1711
3086
  catalog: messages,
1712
3087
  detected: value.context.locale.locale,
1713
3088
  override: localeOverride,
1714
- children: react.createElement(ModalRuntime, { children })
3089
+ children: React.createElement(ModalRuntime, { children })
1715
3090
  })
1716
3091
  })
1717
3092
  })
1718
3093
  })
1719
3094
  });
1720
- return react.createElement(react.Fragment, null, tree, renderToaster(toaster));
3095
+ return React.createElement(React.Fragment, null, tree, renderToaster(toaster));
1721
3096
  }
1722
3097
  var FALLBACK_TRACKER = createConsoleTracker({ silent: true });
1723
3098
  function useFunnelCtx() {
1724
- const ctx = react.useContext(FunnelCtx);
3099
+ const ctx = React.useContext(FunnelCtx);
1725
3100
  if (!ctx) {
1726
- 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
+ );
1727
3104
  }
1728
3105
  return ctx;
1729
3106
  }
1730
3107
  function useFunnel() {
1731
3108
  return useFunnelCtx().funnel;
1732
3109
  }
3110
+ function useBusInternal() {
3111
+ return React.useContext(FunnelCtx)?.bus ?? null;
3112
+ }
1733
3113
  function useStoreKey(store, key) {
1734
- const subscribe = react.useCallback(
3114
+ const subscribe = React.useCallback(
1735
3115
  (cb) => store.subscribe(cb, { keys: [key] }),
1736
3116
  [store, key]
1737
3117
  );
1738
- const getSnapshot = react.useCallback(() => store.get(key), [store, key]);
1739
- return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
3118
+ const getSnapshot = React.useCallback(() => store.get(key), [store, key]);
3119
+ return React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1740
3120
  }
1741
3121
  function makeFieldHook(ns) {
1742
3122
  return function useNamespaceField(key) {
1743
3123
  const { funnel, store } = useFunnelCtx();
1744
3124
  const value = useStoreKey(store, `${NAMESPACE_PREFIX[ns]}.${key}`);
1745
- const set = react.useCallback(
3125
+ const set = React.useCallback(
1746
3126
  (v) => funnel[ns].set({ [key]: v }),
1747
3127
  [funnel, key]
1748
3128
  );
@@ -1758,10 +3138,12 @@ function useField(path) {
1758
3138
  const key = path.slice(dot + 1);
1759
3139
  const { funnel, store } = useFunnelCtx();
1760
3140
  if (!(ns in NAMESPACE_PREFIX)) {
1761
- 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
+ );
1762
3144
  }
1763
3145
  const value = useStoreKey(store, `${NAMESPACE_PREFIX[ns]}.${key}`);
1764
- const set = react.useCallback(
3146
+ const set = React.useCallback(
1765
3147
  (v) => funnel[ns].set({ [key]: v }),
1766
3148
  [funnel, ns, key]
1767
3149
  );
@@ -1787,11 +3169,404 @@ function useSystem() {
1787
3169
  }
1788
3170
  function useContextValue(path) {
1789
3171
  const ctx = useContextObject();
1790
- return react.useMemo(
1791
- () => path.split(".").reduce((acc, k) => acc?.[k], ctx),
3172
+ return React.useMemo(
3173
+ () => path.split(".").reduce(
3174
+ (acc, k) => acc?.[k],
3175
+ ctx
3176
+ ),
1792
3177
  [ctx, path]
1793
3178
  );
1794
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
+ }
1795
3570
  function Choice({
1796
3571
  bind,
1797
3572
  options,
@@ -1804,7 +3579,7 @@ function Choice({
1804
3579
  return options.map((opt) => {
1805
3580
  const o = typeof opt === "string" ? { value: opt } : opt;
1806
3581
  const selected = value === o.value;
1807
- return react.createElement(
3582
+ return React.createElement(
1808
3583
  "button",
1809
3584
  {
1810
3585
  key: o.value,
@@ -1823,7 +3598,7 @@ function Choice({
1823
3598
  }
1824
3599
  function Next({ children, onClick, ...rest }) {
1825
3600
  const { next } = useNavigation();
1826
- return react.createElement(
3601
+ return React.createElement(
1827
3602
  "button",
1828
3603
  {
1829
3604
  type: "button",
@@ -1839,7 +3614,7 @@ function Next({ children, onClick, ...rest }) {
1839
3614
  function Back({ children, onClick, ...rest }) {
1840
3615
  const { back, canGoBack } = useNavigation();
1841
3616
  if (!canGoBack) return null;
1842
- return react.createElement(
3617
+ return React.createElement(
1843
3618
  "button",
1844
3619
  {
1845
3620
  type: "button",
@@ -1892,7 +3667,7 @@ function Script({
1892
3667
  onLoad,
1893
3668
  onError
1894
3669
  }) {
1895
- react.useEffect(() => {
3670
+ React.useEffect(() => {
1896
3671
  if (typeof document === "undefined") return;
1897
3672
  const key = id ?? src;
1898
3673
  if (injected.has(key)) {
@@ -1931,159 +3706,198 @@ function Script({
1931
3706
  return null;
1932
3707
  }
1933
3708
 
1934
- // src/manifest/manifest.ts
1935
- function isVariantNode(page) {
1936
- return isVariantKey(page.key);
1937
- }
1938
- function serializeGate(when) {
1939
- if (!when) return void 0;
1940
- if (typeof when === "function") return { kind: "predicate" };
1941
- return { kind: "declarative", condition: when };
1942
- }
1943
- function compileManifest(input) {
1944
- const { funnel, pages } = input;
1945
- const keys = new Set(pages.map((p) => p.key));
1946
- const manifestPages = pages.map((p, index) => ({
1947
- id: p.key,
1948
- key: p.key,
1949
- type: p.meta?.type ?? "default",
1950
- slug: p.meta?.slug ?? p.key,
1951
- index,
1952
- variantOf: isVariantKey(p.key) ? parseSlotKey(p.key).slot : void 0
1953
- }));
1954
- const edges = [];
1955
- const badTargets = [];
1956
- const nextAnchor = (from) => {
1957
- for (let j = from + 1; j < pages.length; j++) {
1958
- if (!isVariantNode(pages[j])) return pages[j];
1959
- }
1960
- return void 0;
1961
- };
1962
- pages.forEach((p, i) => {
1963
- if (isVariantNode(p)) return;
1964
- const routes = p.meta?.next ?? [];
1965
- let hasUnconditional = false;
1966
- for (const route of routes) {
1967
- if (!keys.has(route.to)) {
1968
- badTargets.push({ from: p.key, to: route.to });
1969
- continue;
1970
- }
1971
- edges.push({ from: p.key, to: route.to, kind: "branch", condition: serializeGate(route.when) });
1972
- if (!route.when) hasUnconditional = true;
1973
- }
1974
- const next = nextAnchor(i);
1975
- if (!hasUnconditional && next && p.meta?.type !== "finish") {
1976
- edges.push({ from: p.key, to: next.key, kind: "linear" });
1977
- }
1978
- });
1979
- const startPage = pages.find((p) => !isVariantNode(p));
1980
- const start = startPage?.key ?? null;
1981
- const variantKeys = new Set(pages.filter(isVariantNode).map((p) => p.key));
1982
- const validation = validate(manifestPages, edges, badTargets, start, variantKeys);
1983
- return {
1984
- id: funnel.id,
1985
- pages: manifestPages,
1986
- flow: { start, nodes: manifestPages.map((p) => p.id), edges },
1987
- products: funnel.products ?? [],
1988
- locales: funnel.locales,
1989
- validation
1990
- };
1991
- }
1992
- function validate(pages, edges, badTargets, start, variantKeys) {
1993
- const outgoing = /* @__PURE__ */ new Map();
1994
- for (const e of edges) outgoing.set(e.from, (outgoing.get(e.from) ?? 0) + 1);
1995
- const deadEnds = pages.filter((p) => p.type !== "finish" && !variantKeys.has(p.id) && !(outgoing.get(p.id) > 0)).map((p) => p.id);
1996
- const reachable = /* @__PURE__ */ new Set();
1997
- if (start) {
1998
- const adj = /* @__PURE__ */ new Map();
1999
- for (const e of edges) {
2000
- const list = adj.get(e.from);
2001
- if (list) list.push(e.to);
2002
- else adj.set(e.from, [e.to]);
2003
- }
2004
- const stack = [start];
2005
- while (stack.length) {
2006
- const id = stack.pop();
2007
- if (reachable.has(id)) continue;
2008
- reachable.add(id);
2009
- for (const to of adj.get(id) ?? []) stack.push(to);
2010
- }
2011
- }
2012
- const unreachable = pages.filter((p) => !variantKeys.has(p.id) && !reachable.has(p.id)).map((p) => p.id);
2013
- const missingEmailCapture = !pages.some((p) => p.type === "email-capture");
2014
- return {
2015
- ok: deadEnds.length === 0 && unreachable.length === 0 && badTargets.length === 0 && !missingEmailCapture,
2016
- deadEnds,
2017
- unreachable,
2018
- badTargets,
2019
- missingEmailCapture
2020
- };
2021
- }
2022
-
2023
- 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", {
2024
3858
  enumerable: true,
2025
- get: function () { return sonner.toast; }
3859
+ get: function () { return chunk7JLOF6CJ_cjs.validateUpsell; }
2026
3860
  });
3861
+ exports.AssetsContext = AssetsContext;
3862
+ exports.AssetsProvider = AssetsProvider;
2027
3863
  exports.Back = Back;
2028
3864
  exports.Checkout = Checkout;
3865
+ exports.CheckoutResume = CheckoutResume;
2029
3866
  exports.Choice = Choice;
2030
3867
  exports.EVENT_CATALOG = EVENT_CATALOG;
2031
3868
  exports.FunnelProvider = FunnelProvider;
2032
3869
  exports.FunnelToaster = FunnelToaster;
2033
3870
  exports.FunnelView = FunnelView;
2034
- exports.INLINE_SURFACES = INLINE_SURFACES;
2035
3871
  exports.Modal = Modal;
2036
3872
  exports.Next = Next;
2037
- exports.PROVIDER_PROFILES = PROVIDER_PROFILES;
2038
3873
  exports.Script = Script;
2039
3874
  exports.Sheet = Sheet;
2040
3875
  exports.Upsell = Upsell;
2041
3876
  exports.VariableStore = VariableStore;
2042
- exports.assignVariant = assignVariant;
2043
3877
  exports.attachBus = attachBus;
2044
- exports.bucketingSeed = bucketingSeed;
2045
3878
  exports.buildAcquisition = buildAcquisition;
2046
- exports.buildCatalog = buildCatalog;
2047
- exports.checkoutError = checkoutError;
2048
- exports.compileManifest = compileManifest;
3879
+ exports.buildContext = buildContext;
2049
3880
  exports.createBus = createBus;
2050
3881
  exports.createConsoleTracker = createConsoleTracker;
2051
3882
  exports.createFunnelStore = createFunnelStore;
3883
+ exports.createFunnelTree = createFunnelTree;
2052
3884
  exports.createMockDriver = createMockDriver;
2053
- exports.defineFunnel = defineFunnel;
3885
+ exports.createPlatformTracker = createPlatformTracker;
2054
3886
  exports.defineModal = defineModal;
2055
- exports.definePage = definePage;
2056
- exports.entryGuard = entryGuard;
2057
- exports.evaluateCondition = evaluateCondition;
2058
- exports.evaluateGate = evaluateGate;
2059
- exports.expectedPathLength = expectedPathLength;
2060
- exports.fnv1a = fnv1a;
2061
- exports.formatProduct = formatProduct;
2062
- exports.hashToUnit = hashToUnit;
3887
+ exports.driverWithEmail = driverWithEmail;
2063
3888
  exports.hideModal = hideModal;
2064
3889
  exports.integrationEvents = integrationEvents;
2065
- exports.isInlineSurface = isInlineSurface;
2066
3890
  exports.isIntegrationEvent = isIntegrationEvent;
2067
- exports.isMerchantOfRecord = isMerchantOfRecord;
2068
- exports.isOrchestrator = isOrchestrator;
2069
- exports.isRtl = isRtl;
2070
3891
  exports.isTrackableEvent = isTrackableEvent;
2071
- exports.isVariantKey = isVariantKey;
2072
3892
  exports.newEventId = newEventId;
2073
- exports.nextPage = nextPage;
2074
- exports.outgoingKeys = outgoingKeys;
2075
- exports.pageMeta = pageMeta;
2076
- exports.parseSlotKey = parseSlotKey;
2077
- exports.pickByWeight = pickByWeight;
2078
3893
  exports.registerModal = registerModal;
2079
3894
  exports.removeModal = removeModal;
2080
- exports.resolveExperiments = resolveExperiments;
2081
- exports.resolveLocale = resolveLocale;
2082
- exports.resolveProduct = resolveProduct;
2083
- exports.resolveRoute = resolveRoute;
2084
3895
  exports.showModal = showModal;
2085
- exports.surfacesFor = surfacesFor;
3896
+ exports.toast = toast;
2086
3897
  exports.unregisterModal = unregisterModal;
3898
+ exports.useActiveLocale = useActiveLocale;
3899
+ exports.useAsset = useAsset;
3900
+ exports.useAssets = useAssets;
2087
3901
  exports.useCheckout = useCheckout;
2088
3902
  exports.useClickIds = useClickIds;
2089
3903
  exports.useContextValue = useContextValue;
@@ -2095,18 +3909,15 @@ exports.useFunnel = useFunnel;
2095
3909
  exports.useLocale = useLocale;
2096
3910
  exports.useModal = useModal;
2097
3911
  exports.useNavigation = useNavigation;
3912
+ exports.useOffering = useOffering;
3913
+ exports.useOfferings = useOfferings;
2098
3914
  exports.usePage = usePage;
2099
- exports.useProduct = useProduct;
2100
- exports.useProducts = useProducts;
2101
3915
  exports.useResponse = useResponse;
2102
3916
  exports.useSystem = useSystem;
2103
3917
  exports.useTracker = useTracker;
2104
3918
  exports.useTranslation = useTranslation;
2105
3919
  exports.useUserAttribute = useUserAttribute;
2106
3920
  exports.useUtm = useUtm;
2107
- exports.validateCheckout = validateCheckout;
2108
- exports.validateExperiments = validateExperiments;
2109
- exports.validateUpsell = validateUpsell;
2110
3921
  exports.withBus = withBus;
2111
3922
  //# sourceMappingURL=index.cjs.map
2112
3923
  //# sourceMappingURL=index.cjs.map