@appfunnel-dev/sdk 2.0.0-canary.0 → 2.0.0-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities-7_hy5f5G.d.cts +114 -0
- package/dist/capabilities-7_hy5f5G.d.ts +114 -0
- package/dist/checkout-CZmEvWfC.d.cts +317 -0
- package/dist/checkout-DiQvRT5q.d.ts +317 -0
- package/dist/chunk-7UC5VXOR.js +446 -0
- package/dist/chunk-7UC5VXOR.js.map +1 -0
- package/dist/chunk-LJYLGLFS.cjs +153 -0
- package/dist/chunk-LJYLGLFS.cjs.map +1 -0
- package/dist/chunk-UIR6TGEW.js +97 -0
- package/dist/chunk-UIR6TGEW.js.map +1 -0
- package/dist/chunk-VQOD2Z6Q.cjs +104 -0
- package/dist/chunk-VQOD2Z6Q.cjs.map +1 -0
- package/dist/chunk-YY375F2B.js +140 -0
- package/dist/chunk-YY375F2B.js.map +1 -0
- package/dist/chunk-Z3TWO2PW.cjs +475 -0
- package/dist/chunk-Z3TWO2PW.cjs.map +1 -0
- package/dist/driver-paddle.cjs +814 -0
- package/dist/driver-paddle.cjs.map +1 -0
- package/dist/driver-paddle.d.cts +10 -0
- package/dist/driver-paddle.d.ts +10 -0
- package/dist/driver-paddle.js +811 -0
- package/dist/driver-paddle.js.map +1 -0
- package/dist/driver-stripe.cjs +2253 -0
- package/dist/driver-stripe.cjs.map +1 -0
- package/dist/driver-stripe.d.cts +8 -0
- package/dist/driver-stripe.d.ts +8 -0
- package/dist/driver-stripe.js +2247 -0
- package/dist/driver-stripe.js.map +1 -0
- package/dist/index.cjs +1953 -813
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +183 -933
- package/dist/index.d.ts +183 -933
- package/dist/index.js +1674 -655
- package/dist/index.js.map +1 -1
- package/dist/manifest-DQThneiG.d.cts +777 -0
- package/dist/manifest-DQThneiG.d.ts +777 -0
- package/dist/manifest-entry.cjs +203 -0
- package/dist/manifest-entry.cjs.map +1 -0
- package/dist/manifest-entry.d.cts +184 -0
- package/dist/manifest-entry.d.ts +184 -0
- package/dist/manifest-entry.js +98 -0
- package/dist/manifest-entry.js.map +1 -0
- package/package.json +37 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { isRtl, formatProduct, bucketingSeed, resolveExperiments, entryGuard, evaluateGate, expectedPathLength, nextPage, outgoingKeys, buildCatalog, resolveLocale } from './chunk-7UC5VXOR.js';
|
|
2
|
+
export { assignVariant, bucketingSeed, buildCatalog, compileManifest, currencyExponent, defineFunnel, definePage, entryGuard, evaluateCondition, evaluateGate, expectedPathLength, fnv1a, formatMoney, formatProduct, hashToUnit, isRtl, isVariantKey, nextPage, outgoingKeys, pageMeta, parseSlotKey, pickByWeight, resolveExperiments, resolveLocale, resolveProduct, resolveRoute, validateExperiments } from './chunk-7UC5VXOR.js';
|
|
3
|
+
import { checkoutError, isInlineSurface, PROVIDER_PROFILES } from './chunk-YY375F2B.js';
|
|
4
|
+
export { INLINE_SURFACES, PROVIDER_PROFILES, checkoutError, isInlineSurface, isMerchantOfRecord, isOrchestrator, surfacesFor, validateCheckout, validateUpsell } from './chunk-YY375F2B.js';
|
|
5
|
+
import React, { createContext, createElement, useCallback, useContext, useMemo, lazy, useRef, useState, useEffect, useTransition, Suspense, Fragment, isValidElement, cloneElement, useReducer, useSyncExternalStore } from 'react';
|
|
6
|
+
import ReactDOM, { createPortal } from 'react-dom';
|
|
7
|
+
import { jsx } from 'react/jsx-runtime';
|
|
7
8
|
|
|
8
9
|
// src/state/variableStore.ts
|
|
9
10
|
var VariableStore = class {
|
|
@@ -257,8 +258,12 @@ function collectClickIds() {
|
|
|
257
258
|
}
|
|
258
259
|
function readCookie(name) {
|
|
259
260
|
if (typeof document === "undefined") return void 0;
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
try {
|
|
262
|
+
const m = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
|
|
263
|
+
return m ? decodeURIComponent(m[1]) : void 0;
|
|
264
|
+
} catch {
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
262
267
|
}
|
|
263
268
|
function buildAcquisition(context) {
|
|
264
269
|
return {
|
|
@@ -271,41 +276,44 @@ function buildAcquisition(context) {
|
|
|
271
276
|
}
|
|
272
277
|
function buildContext(opts = {}) {
|
|
273
278
|
const raw = detect();
|
|
274
|
-
const
|
|
279
|
+
const now2 = opts.now ?? 0;
|
|
275
280
|
const referrer = typeof document !== "undefined" ? document.referrer : "";
|
|
276
|
-
const
|
|
281
|
+
const base = {
|
|
277
282
|
device: raw.device,
|
|
278
283
|
browser: raw.browser,
|
|
279
284
|
os: raw.os,
|
|
280
285
|
locale: raw.locale,
|
|
281
286
|
utm: collectUtm(),
|
|
282
287
|
clickIds: collectClickIds(),
|
|
283
|
-
page: { key: "", slug: "", index: 0, total: 0, progressPercentage: 0, startedAt:
|
|
284
|
-
session: { id: opts.sessionId ?? null, startedAt:
|
|
288
|
+
page: { key: "", slug: "", index: 0, total: 0, progressPercentage: 0, startedAt: now2 },
|
|
289
|
+
session: { id: opts.sessionId ?? null, startedAt: now2, referrer },
|
|
285
290
|
identity: { customerId: opts.customerId ?? null, visitorId: opts.visitorId ?? null },
|
|
286
291
|
system: {
|
|
287
292
|
funnelId: opts.funnelId ?? "",
|
|
288
293
|
campaignId: opts.campaignId ?? "",
|
|
289
294
|
mode: opts.mode ?? "live",
|
|
290
|
-
now
|
|
295
|
+
now: now2
|
|
291
296
|
}
|
|
292
297
|
};
|
|
293
|
-
if (!opts.overrides) return
|
|
298
|
+
if (!opts.overrides) return base;
|
|
294
299
|
return {
|
|
295
|
-
...
|
|
300
|
+
...base,
|
|
296
301
|
...opts.overrides,
|
|
297
|
-
device: { ...
|
|
298
|
-
browser: { ...
|
|
299
|
-
os: { ...
|
|
300
|
-
locale: { ...
|
|
301
|
-
utm: { ...
|
|
302
|
-
clickIds: { ...
|
|
303
|
-
page: { ...
|
|
304
|
-
session: { ...
|
|
305
|
-
identity: { ...
|
|
306
|
-
system: { ...
|
|
302
|
+
device: { ...base.device, ...opts.overrides.device },
|
|
303
|
+
browser: { ...base.browser, ...opts.overrides.browser },
|
|
304
|
+
os: { ...base.os, ...opts.overrides.os },
|
|
305
|
+
locale: { ...base.locale, ...opts.overrides.locale },
|
|
306
|
+
utm: { ...base.utm, ...opts.overrides.utm },
|
|
307
|
+
clickIds: { ...base.clickIds, ...opts.overrides.clickIds },
|
|
308
|
+
page: { ...base.page, ...opts.overrides.page },
|
|
309
|
+
session: { ...base.session, ...opts.overrides.session },
|
|
310
|
+
identity: { ...base.identity, ...opts.overrides.identity },
|
|
311
|
+
system: { ...base.system, ...opts.overrides.system }
|
|
307
312
|
};
|
|
308
313
|
}
|
|
314
|
+
function exposurePayload(experimentId, variant, version) {
|
|
315
|
+
return { experimentId, variant, ...version !== void 0 ? { version } : {} };
|
|
316
|
+
}
|
|
309
317
|
var _eventIdCounter = 0;
|
|
310
318
|
function newEventId() {
|
|
311
319
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
@@ -344,7 +352,7 @@ function useTracker() {
|
|
|
344
352
|
|
|
345
353
|
// src/tracking/bus.ts
|
|
346
354
|
var MAX_HISTORY = 250;
|
|
347
|
-
function createBus(accessors,
|
|
355
|
+
function createBus(accessors, now2 = () => Date.now()) {
|
|
348
356
|
const listeners = /* @__PURE__ */ new Map();
|
|
349
357
|
const events = [];
|
|
350
358
|
const on = (event, cb) => {
|
|
@@ -357,7 +365,7 @@ function createBus(accessors, now = () => Date.now()) {
|
|
|
357
365
|
listeners.get(event)?.delete(cb);
|
|
358
366
|
};
|
|
359
367
|
const emit = (event, data) => {
|
|
360
|
-
events.push({ event, data, ts:
|
|
368
|
+
events.push({ event, data, ts: now2() });
|
|
361
369
|
if (events.length > MAX_HISTORY) events.shift();
|
|
362
370
|
for (const cb of listeners.get(event) ?? []) {
|
|
363
371
|
try {
|
|
@@ -412,21 +420,6 @@ function attachBus(bus) {
|
|
|
412
420
|
if (window.appfunnel === api) delete window.appfunnel;
|
|
413
421
|
};
|
|
414
422
|
}
|
|
415
|
-
var RTL_LANGS = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur", "ps", "sd", "dv", "yi"]);
|
|
416
|
-
function isRtl(locale) {
|
|
417
|
-
return RTL_LANGS.has(locale.split("-")[0]);
|
|
418
|
-
}
|
|
419
|
-
var base = (l) => l?.split("-")[0];
|
|
420
|
-
function resolveLocale(config, detected, override) {
|
|
421
|
-
const supported = config?.supported ?? (config?.default ? [config.default] : ["en"]);
|
|
422
|
-
const def = config?.default ?? supported[0] ?? "en";
|
|
423
|
-
const pick = (l) => {
|
|
424
|
-
if (!l) return void 0;
|
|
425
|
-
if (supported.includes(l)) return l;
|
|
426
|
-
return supported.find((s) => base(s) === base(l));
|
|
427
|
-
};
|
|
428
|
-
return pick(override) ?? pick(detected) ?? def;
|
|
429
|
-
}
|
|
430
423
|
function interpolate(str, params) {
|
|
431
424
|
if (!params) return str;
|
|
432
425
|
return str.replace(/\{\{(\w+)\}\}/g, (_, k) => k in params ? String(params[k]) : `{{${k}}}`);
|
|
@@ -455,6 +448,9 @@ function LocaleProvider({
|
|
|
455
448
|
children
|
|
456
449
|
}) {
|
|
457
450
|
const [locale, setLocale] = useState(() => resolveLocale(config, detected, override));
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
setLocale(resolveLocale(config, detected, override));
|
|
453
|
+
}, [config, detected, override]);
|
|
458
454
|
const value = useMemo(
|
|
459
455
|
() => ({ locale, setLocale, config, catalog }),
|
|
460
456
|
[locale, config, catalog]
|
|
@@ -483,7 +479,12 @@ function useTranslation() {
|
|
|
483
479
|
return interpolate(raw, params);
|
|
484
480
|
});
|
|
485
481
|
fn.plural = (n, forms, params) => {
|
|
486
|
-
|
|
482
|
+
let rule;
|
|
483
|
+
try {
|
|
484
|
+
rule = new Intl.PluralRules(locale).select(n);
|
|
485
|
+
} catch {
|
|
486
|
+
rule = "other";
|
|
487
|
+
}
|
|
487
488
|
const form = forms[rule] ?? forms.other ?? "";
|
|
488
489
|
return interpolate(form.replace(/#/g, String(n)), params);
|
|
489
490
|
};
|
|
@@ -491,9 +492,27 @@ function useTranslation() {
|
|
|
491
492
|
}, [catalog, chain, locale]);
|
|
492
493
|
const fmt = useMemo(
|
|
493
494
|
() => ({
|
|
494
|
-
number: (n, opts) =>
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
number: (n, opts) => {
|
|
496
|
+
try {
|
|
497
|
+
return new Intl.NumberFormat(locale, opts).format(n);
|
|
498
|
+
} catch {
|
|
499
|
+
return String(n);
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
date: (d, opts) => {
|
|
503
|
+
try {
|
|
504
|
+
return new Intl.DateTimeFormat(locale, opts).format(d);
|
|
505
|
+
} catch {
|
|
506
|
+
return String(d instanceof Date ? d.toISOString() : d);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
percent: (n, opts) => {
|
|
510
|
+
try {
|
|
511
|
+
return new Intl.NumberFormat(locale, { style: "percent", ...opts }).format(n);
|
|
512
|
+
} catch {
|
|
513
|
+
return `${(n * 100).toFixed(0)}%`;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
497
516
|
}),
|
|
498
517
|
[locale]
|
|
499
518
|
);
|
|
@@ -508,84 +527,6 @@ function useTranslation() {
|
|
|
508
527
|
}
|
|
509
528
|
|
|
510
529
|
// src/commerce/catalog.ts
|
|
511
|
-
var PERIOD_DAYS = {
|
|
512
|
-
day: 1,
|
|
513
|
-
week: 7,
|
|
514
|
-
month: 30,
|
|
515
|
-
year: 365,
|
|
516
|
-
one_time: 0
|
|
517
|
-
};
|
|
518
|
-
function periodDays(interval, count) {
|
|
519
|
-
return PERIOD_DAYS[interval] * count;
|
|
520
|
-
}
|
|
521
|
-
function intervalLabel(interval, count) {
|
|
522
|
-
if (interval === "one_time") return { period: "one-time", periodly: "one-time" };
|
|
523
|
-
if (interval === "month" && count === 3) return { period: "quarter", periodly: "quarterly" };
|
|
524
|
-
if (interval === "month" && count === 6) return { period: "6 months", periodly: "semiannually" };
|
|
525
|
-
if (interval === "week" && count === 2) return { period: "2 weeks", periodly: "biweekly" };
|
|
526
|
-
if (count === 1) {
|
|
527
|
-
const periodly = { day: "daily", week: "weekly", month: "monthly", year: "yearly" }[interval];
|
|
528
|
-
return { period: interval, periodly };
|
|
529
|
-
}
|
|
530
|
-
return { period: `${count} ${interval}s`, periodly: `every ${count} ${interval}s` };
|
|
531
|
-
}
|
|
532
|
-
function resolveProduct(input) {
|
|
533
|
-
const interval = input.interval ?? "one_time";
|
|
534
|
-
const count = input.intervalCount ?? 1;
|
|
535
|
-
const days = periodDays(interval, count);
|
|
536
|
-
const perDayMinor = days > 0 ? input.amount / days : input.amount;
|
|
537
|
-
const label = intervalLabel(interval, count);
|
|
538
|
-
return {
|
|
539
|
-
id: input.id,
|
|
540
|
-
name: input.name ?? input.id,
|
|
541
|
-
displayName: input.displayName ?? input.name ?? input.id,
|
|
542
|
-
provider: input.provider ?? "stripe",
|
|
543
|
-
currency: input.currency,
|
|
544
|
-
priceMinor: input.amount,
|
|
545
|
-
perDayMinor,
|
|
546
|
-
perWeekMinor: perDayMinor * 7,
|
|
547
|
-
perMonthMinor: perDayMinor * 30,
|
|
548
|
-
perYearMinor: perDayMinor * 365,
|
|
549
|
-
period: label.period,
|
|
550
|
-
periodly: label.periodly,
|
|
551
|
-
hasTrial: (input.trialDays ?? 0) > 0,
|
|
552
|
-
trialDays: input.trialDays ?? 0,
|
|
553
|
-
trialMinor: input.trialAmount ?? 0
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
function buildCatalog(inputs) {
|
|
557
|
-
return new Map(inputs.map((i) => [i.id, resolveProduct(i)]));
|
|
558
|
-
}
|
|
559
|
-
function formatMoney(minor, currency, locale) {
|
|
560
|
-
const amount = minor / 100;
|
|
561
|
-
const code = currency.toUpperCase();
|
|
562
|
-
let formatted;
|
|
563
|
-
try {
|
|
564
|
-
formatted = new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(amount);
|
|
565
|
-
} catch {
|
|
566
|
-
formatted = `${code} ${amount.toFixed(2)}`;
|
|
567
|
-
}
|
|
568
|
-
return { amount, minor, currency: code, formatted };
|
|
569
|
-
}
|
|
570
|
-
function formatProduct(r, locale) {
|
|
571
|
-
const m = (minor) => formatMoney(minor, r.currency, locale);
|
|
572
|
-
return {
|
|
573
|
-
id: r.id,
|
|
574
|
-
name: r.name,
|
|
575
|
-
displayName: r.displayName,
|
|
576
|
-
provider: r.provider,
|
|
577
|
-
price: m(r.priceMinor),
|
|
578
|
-
period: r.period,
|
|
579
|
-
periodly: r.periodly,
|
|
580
|
-
perDay: m(r.perDayMinor),
|
|
581
|
-
perWeek: m(r.perWeekMinor),
|
|
582
|
-
perMonth: m(r.perMonthMinor),
|
|
583
|
-
perYear: m(r.perYearMinor),
|
|
584
|
-
hasTrial: r.hasTrial,
|
|
585
|
-
trialDays: r.trialDays,
|
|
586
|
-
trialPrice: r.hasTrial ? m(r.trialMinor) : null
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
530
|
var CatalogContext = createContext(null);
|
|
590
531
|
function CatalogProvider({
|
|
591
532
|
catalog,
|
|
@@ -611,223 +552,6 @@ function useProducts() {
|
|
|
611
552
|
[catalog, locale]
|
|
612
553
|
);
|
|
613
554
|
}
|
|
614
|
-
|
|
615
|
-
// src/flow/spine.ts
|
|
616
|
-
function readField(s, field) {
|
|
617
|
-
let cur = s;
|
|
618
|
-
for (const part of field.split(".")) {
|
|
619
|
-
if (cur == null) return void 0;
|
|
620
|
-
cur = cur[part];
|
|
621
|
-
}
|
|
622
|
-
return cur;
|
|
623
|
-
}
|
|
624
|
-
function evaluateCondition(cond, s) {
|
|
625
|
-
const val = readField(s, cond.field);
|
|
626
|
-
const { op, value } = cond;
|
|
627
|
-
switch (op) {
|
|
628
|
-
case "eq":
|
|
629
|
-
return val === value;
|
|
630
|
-
case "neq":
|
|
631
|
-
return val !== value;
|
|
632
|
-
case "in":
|
|
633
|
-
return Array.isArray(value) && value.includes(val);
|
|
634
|
-
case "gt":
|
|
635
|
-
return typeof val === "number" && typeof value === "number" && val > value;
|
|
636
|
-
case "gte":
|
|
637
|
-
return typeof val === "number" && typeof value === "number" && val >= value;
|
|
638
|
-
case "lt":
|
|
639
|
-
return typeof val === "number" && typeof value === "number" && val < value;
|
|
640
|
-
case "lte":
|
|
641
|
-
return typeof val === "number" && typeof value === "number" && val <= value;
|
|
642
|
-
case "contains":
|
|
643
|
-
if (typeof val === "string") return val.includes(String(value));
|
|
644
|
-
if (Array.isArray(val)) return val.includes(value);
|
|
645
|
-
return false;
|
|
646
|
-
case "exists":
|
|
647
|
-
return val !== void 0 && val !== null && val !== "";
|
|
648
|
-
case "empty":
|
|
649
|
-
return val === void 0 || val === null || val === "" || Array.isArray(val) && val.length === 0;
|
|
650
|
-
default:
|
|
651
|
-
return false;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
function evaluateGate(gate, s) {
|
|
655
|
-
return typeof gate === "function" ? gate(s) : evaluateCondition(gate, s);
|
|
656
|
-
}
|
|
657
|
-
function resolveRoute(routes, s) {
|
|
658
|
-
if (!routes) return void 0;
|
|
659
|
-
for (const route of routes) {
|
|
660
|
-
if (!route.when || evaluateGate(route.when, s)) return route.to;
|
|
661
|
-
}
|
|
662
|
-
return void 0;
|
|
663
|
-
}
|
|
664
|
-
function pageMeta(meta) {
|
|
665
|
-
return meta;
|
|
666
|
-
}
|
|
667
|
-
var TYPE_GUARDS = {
|
|
668
|
-
paywall: { field: "user.email", op: "exists" },
|
|
669
|
-
upsell: { field: "user.email", op: "exists" }
|
|
670
|
-
};
|
|
671
|
-
function entryGuard(meta) {
|
|
672
|
-
return meta?.guard ?? (meta?.type ? TYPE_GUARDS[meta.type] : void 0);
|
|
673
|
-
}
|
|
674
|
-
function definePage(component) {
|
|
675
|
-
return component;
|
|
676
|
-
}
|
|
677
|
-
function nextPage(pages, currentKey, s) {
|
|
678
|
-
const idx = pages.findIndex((p) => p.key === currentKey);
|
|
679
|
-
if (idx === -1) return void 0;
|
|
680
|
-
const routed = resolveRoute(pages[idx].meta?.next, s);
|
|
681
|
-
if (routed) return routed;
|
|
682
|
-
return pages[idx + 1]?.key;
|
|
683
|
-
}
|
|
684
|
-
function outgoingKeys(pages, currentKey) {
|
|
685
|
-
const idx = pages.findIndex((p) => p.key === currentKey);
|
|
686
|
-
if (idx === -1) return [];
|
|
687
|
-
const out = /* @__PURE__ */ new Set();
|
|
688
|
-
for (const route of pages[idx].meta?.next ?? []) out.add(route.to);
|
|
689
|
-
const linear = pages[idx + 1]?.key;
|
|
690
|
-
if (linear) out.add(linear);
|
|
691
|
-
return [...out];
|
|
692
|
-
}
|
|
693
|
-
function expectedPathLength(pages, startKey, s) {
|
|
694
|
-
const seen = /* @__PURE__ */ new Set();
|
|
695
|
-
let key = startKey;
|
|
696
|
-
let count = 0;
|
|
697
|
-
while (key && !seen.has(key)) {
|
|
698
|
-
seen.add(key);
|
|
699
|
-
count++;
|
|
700
|
-
if (pages.find((p) => p.key === key)?.meta?.type === "finish") break;
|
|
701
|
-
key = nextPage(pages, key, s);
|
|
702
|
-
}
|
|
703
|
-
return count;
|
|
704
|
-
}
|
|
705
|
-
function defineFunnel(def) {
|
|
706
|
-
return def;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// src/flow/experiments.ts
|
|
710
|
-
function fnv1a(input) {
|
|
711
|
-
let h = 2166136261;
|
|
712
|
-
for (let i = 0; i < input.length; i++) {
|
|
713
|
-
h ^= input.charCodeAt(i);
|
|
714
|
-
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
715
|
-
}
|
|
716
|
-
return h >>> 0;
|
|
717
|
-
}
|
|
718
|
-
function hashToUnit(seed) {
|
|
719
|
-
return fnv1a(seed) / 4294967296;
|
|
720
|
-
}
|
|
721
|
-
function pickByWeight(weights, u) {
|
|
722
|
-
const entries = Object.entries(weights).filter(([, w]) => w > 0);
|
|
723
|
-
if (entries.length === 0) return Object.keys(weights)[0] ?? "";
|
|
724
|
-
const total = entries.reduce((sum, [, w]) => sum + w, 0);
|
|
725
|
-
const target = u * total;
|
|
726
|
-
let acc = 0;
|
|
727
|
-
for (const [key, w] of entries) {
|
|
728
|
-
acc += w;
|
|
729
|
-
if (target < acc) return key;
|
|
730
|
-
}
|
|
731
|
-
return entries[entries.length - 1][0];
|
|
732
|
-
}
|
|
733
|
-
function assignVariant(experimentId, seed, weights) {
|
|
734
|
-
return pickByWeight(weights, hashToUnit(`${experimentId}:${seed}`));
|
|
735
|
-
}
|
|
736
|
-
function bucketingSeed(context) {
|
|
737
|
-
return context.identity.customerId ?? context.identity.visitorId ?? null;
|
|
738
|
-
}
|
|
739
|
-
function parseSlotKey(key) {
|
|
740
|
-
const at = key.indexOf("@");
|
|
741
|
-
return at === -1 ? { slot: key } : { slot: key.slice(0, at), variant: key.slice(at + 1) };
|
|
742
|
-
}
|
|
743
|
-
function isVariantKey(key) {
|
|
744
|
-
return parseSlotKey(key).variant !== void 0;
|
|
745
|
-
}
|
|
746
|
-
function weightsOf(exp) {
|
|
747
|
-
const out = {};
|
|
748
|
-
for (const [label, v] of Object.entries(exp.variants)) out[label] = v.weight;
|
|
749
|
-
return out;
|
|
750
|
-
}
|
|
751
|
-
function resolveExperiments(pages, experiments, seed) {
|
|
752
|
-
const assignments = {};
|
|
753
|
-
const render = {};
|
|
754
|
-
const experimentOf = {};
|
|
755
|
-
for (const exp of experiments) {
|
|
756
|
-
const status = exp.status ?? "running";
|
|
757
|
-
let label;
|
|
758
|
-
if (status === "stopped" && exp.winner) label = exp.winner;
|
|
759
|
-
else if (status === "running" && seed) label = assignVariant(exp.id, seed, weightsOf(exp));
|
|
760
|
-
else continue;
|
|
761
|
-
const arm = exp.variants[label];
|
|
762
|
-
if (!arm) continue;
|
|
763
|
-
assignments[exp.id] = label;
|
|
764
|
-
experimentOf[exp.slot] = exp.id;
|
|
765
|
-
if (arm.page !== exp.slot) render[exp.slot] = arm.page;
|
|
766
|
-
}
|
|
767
|
-
const slotOf = {};
|
|
768
|
-
const activeKeys = [];
|
|
769
|
-
for (const p of pages) {
|
|
770
|
-
const { slot, variant } = parseSlotKey(p.key);
|
|
771
|
-
if (variant !== void 0) slotOf[p.key] = slot;
|
|
772
|
-
else activeKeys.push(p.key);
|
|
773
|
-
}
|
|
774
|
-
return { assignments, activeKeys, render, slotOf, experimentOf };
|
|
775
|
-
}
|
|
776
|
-
function validateExperiments(experiments, pages) {
|
|
777
|
-
const errors = [];
|
|
778
|
-
const warnings = [];
|
|
779
|
-
const pageKeys = new Set(pages.map((p) => p.key));
|
|
780
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
781
|
-
const slotOwner = /* @__PURE__ */ new Map();
|
|
782
|
-
const renderedVariantPages = /* @__PURE__ */ new Set();
|
|
783
|
-
for (const exp of experiments) {
|
|
784
|
-
const err = (code, message) => errors.push({ experimentId: exp.id, code, message });
|
|
785
|
-
const warn = (code, message) => warnings.push({ experimentId: exp.id, code, message });
|
|
786
|
-
if (seenIds.has(exp.id)) err("duplicate_id", `Experiment id "${exp.id}" is used more than once.`);
|
|
787
|
-
seenIds.add(exp.id);
|
|
788
|
-
if (!pageKeys.has(exp.slot)) err("slot_missing", `Slot "${exp.slot}" is not a page in the funnel.`);
|
|
789
|
-
if (isVariantKey(exp.slot)) err("slot_is_variant", `Slot "${exp.slot}" is a variant page; a slot must be a real flow page.`);
|
|
790
|
-
if (slotOwner.has(exp.slot)) {
|
|
791
|
-
err("slot_conflict", `Slot "${exp.slot}" is already targeted by experiment "${slotOwner.get(exp.slot)}".`);
|
|
792
|
-
} else {
|
|
793
|
-
slotOwner.set(exp.slot, exp.id);
|
|
794
|
-
}
|
|
795
|
-
const arms = Object.entries(exp.variants);
|
|
796
|
-
if (arms.length < 2) warn("single_arm", `Experiment "${exp.id}" has fewer than two variants \u2014 nothing to test.`);
|
|
797
|
-
let hasControl = false;
|
|
798
|
-
let totalWeight = 0;
|
|
799
|
-
for (const [label, arm] of arms) {
|
|
800
|
-
if (!pageKeys.has(arm.page)) {
|
|
801
|
-
err("variant_page_missing", `Variant "${label}" references page "${arm.page}", which doesn't exist.`);
|
|
802
|
-
}
|
|
803
|
-
if (arm.page === exp.slot) hasControl = true;
|
|
804
|
-
else if (isVariantKey(arm.page)) {
|
|
805
|
-
renderedVariantPages.add(arm.page);
|
|
806
|
-
if (parseSlotKey(arm.page).slot !== exp.slot) {
|
|
807
|
-
err("variant_slot_mismatch", `Variant "${label}" page "${arm.page}" belongs to a different slot than "${exp.slot}".`);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
if (arm.weight < 0) err("negative_weight", `Variant "${label}" has a negative weight (${arm.weight}).`);
|
|
811
|
-
totalWeight += arm.weight;
|
|
812
|
-
}
|
|
813
|
-
if (!hasControl) err("no_control", `No variant of "${exp.id}" renders the slot "${exp.slot}" itself (the control/baseline).`);
|
|
814
|
-
if (totalWeight <= 0) err("no_traffic", `Experiment "${exp.id}" has no positive weight \u2014 no traffic would enter it.`);
|
|
815
|
-
if (exp.status === "stopped" && exp.winner && !exp.variants[exp.winner]) {
|
|
816
|
-
err("bad_winner", `Stopped experiment "${exp.id}" names winner "${exp.winner}", which isn't one of its variants.`);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
const running = new Set(experiments.filter((e) => (e.status ?? "running") === "running").flatMap(
|
|
820
|
-
(e) => Object.values(e.variants).map((v) => v.page)
|
|
821
|
-
));
|
|
822
|
-
for (const p of pages) {
|
|
823
|
-
if (isVariantKey(p.key) && !running.has(p.key)) {
|
|
824
|
-
warnings.push({ experimentId: "*", code: "orphan_variant", message: `Variant page "${p.key}" is rendered by no running experiment.` });
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
return { ok: errors.length === 0, errors, warnings };
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// src/flow/flow.tsx
|
|
831
555
|
var NavContext = createContext(null);
|
|
832
556
|
var ExperimentContext = createContext({});
|
|
833
557
|
function useExperiment(id) {
|
|
@@ -844,6 +568,39 @@ function pageContextFor(currentKey, slug, index, total, startedAt) {
|
|
|
844
568
|
startedAt
|
|
845
569
|
};
|
|
846
570
|
}
|
|
571
|
+
var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
572
|
+
function usePageExitTimer(tracker, currentKey) {
|
|
573
|
+
const timer = useRef(null);
|
|
574
|
+
const emitExit = useCallback(() => {
|
|
575
|
+
const t = timer.current;
|
|
576
|
+
if (!t) return;
|
|
577
|
+
const at = now();
|
|
578
|
+
const hidden = t.hidden + (t.hiddenSince != null ? at - t.hiddenSince : 0);
|
|
579
|
+
const durationMs = Math.round(at - t.enteredAt);
|
|
580
|
+
tracker.track("page.exit", { pageId: t.key, durationMs, activeMs: Math.max(0, Math.round(durationMs - hidden)) });
|
|
581
|
+
}, [tracker]);
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
if (typeof document === "undefined") return;
|
|
584
|
+
const onVis = () => {
|
|
585
|
+
const t = timer.current;
|
|
586
|
+
if (!t) return;
|
|
587
|
+
if (document.visibilityState === "hidden") t.hiddenSince = now();
|
|
588
|
+
else if (t.hiddenSince != null) {
|
|
589
|
+
t.hidden += now() - t.hiddenSince;
|
|
590
|
+
t.hiddenSince = null;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
document.addEventListener("visibilitychange", onVis);
|
|
594
|
+
return () => document.removeEventListener("visibilitychange", onVis);
|
|
595
|
+
}, []);
|
|
596
|
+
useEffect(() => {
|
|
597
|
+
if (!currentKey) return;
|
|
598
|
+
if (timer.current && timer.current.key !== currentKey) emitExit();
|
|
599
|
+
const hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
600
|
+
timer.current = { key: currentKey, enteredAt: now(), hidden: 0, hiddenSince: hidden ? now() : null };
|
|
601
|
+
}, [currentKey, emitExit]);
|
|
602
|
+
useEffect(() => () => emitExit(), [emitExit]);
|
|
603
|
+
}
|
|
847
604
|
function FunnelView({
|
|
848
605
|
pages,
|
|
849
606
|
initialKey,
|
|
@@ -871,14 +628,18 @@ function FunnelView({
|
|
|
871
628
|
() => new Map(pages.filter((p) => p.load).map((p) => [p.key, p.load])),
|
|
872
629
|
[pages]
|
|
873
630
|
);
|
|
874
|
-
const
|
|
631
|
+
const seedRef = useRef(null);
|
|
632
|
+
if (seedRef.current === null) seedRef.current = bucketingSeed(funnel.context);
|
|
633
|
+
const seed = seedRef.current;
|
|
875
634
|
const experiments = useMemo(
|
|
876
635
|
() => resolveExperiments(pages, funnel.experiments, seed),
|
|
877
636
|
[pages, funnel.experiments, seed]
|
|
878
637
|
);
|
|
638
|
+
const toSlot = useCallback((k) => experiments.slotOf[k] ?? k, [experiments]);
|
|
639
|
+
const toRenderKey = useCallback((k) => experiments.render[k] ?? k, [experiments]);
|
|
879
640
|
const pageByKey = useMemo(() => new Map(pages.map((p) => [p.key, p])), [pages]);
|
|
880
641
|
const flowPages = useMemo(
|
|
881
|
-
() => experiments.activeKeys.map((k) => pageByKey.get(k)).filter(
|
|
642
|
+
() => experiments.activeKeys.map((k) => pageByKey.get(k)).filter((p) => p !== void 0),
|
|
882
643
|
[experiments, pageByKey]
|
|
883
644
|
);
|
|
884
645
|
const flow = useMemo(
|
|
@@ -887,12 +648,13 @@ function FunnelView({
|
|
|
887
648
|
);
|
|
888
649
|
const [history, setHistory] = useState(() => {
|
|
889
650
|
const start = flowPages[0]?.key;
|
|
890
|
-
if (!initialKey || initialKey === start) return [start]
|
|
891
|
-
const target =
|
|
651
|
+
if (!initialKey || initialKey === start) return start ? [start] : [];
|
|
652
|
+
const target = toSlot(initialKey);
|
|
892
653
|
const page2 = flowPages.find((p) => p.key === target);
|
|
893
654
|
const guard = entryGuard(page2?.meta);
|
|
894
655
|
const ok = page2 && (!guard || evaluateGate(guard, funnel.snapshot()));
|
|
895
|
-
|
|
656
|
+
const first = ok ? target : start;
|
|
657
|
+
return first ? [first] : [];
|
|
896
658
|
});
|
|
897
659
|
const currentKey = history[history.length - 1];
|
|
898
660
|
const index = history.length - 1;
|
|
@@ -907,7 +669,9 @@ function FunnelView({
|
|
|
907
669
|
() => pageContextFor(currentKey, slug, index, total, funnel.context.system.now),
|
|
908
670
|
[currentKey, slug, index, total, funnel.context.system.now]
|
|
909
671
|
);
|
|
910
|
-
|
|
672
|
+
useEffect(() => {
|
|
673
|
+
funnel.context.page = page;
|
|
674
|
+
}, [funnel, page]);
|
|
911
675
|
const started = useRef(false);
|
|
912
676
|
useEffect(() => {
|
|
913
677
|
if (started.current) return;
|
|
@@ -926,66 +690,37 @@ function FunnelView({
|
|
|
926
690
|
const expId = experiments.experimentOf[currentKey];
|
|
927
691
|
if (!expId || exposed.current.has(expId)) return;
|
|
928
692
|
exposed.current.add(expId);
|
|
929
|
-
tracker.track("experiment.exposure",
|
|
693
|
+
tracker.track("experiment.exposure", exposurePayload(expId, experiments.assignments[expId], experiments.versionOf[expId]));
|
|
930
694
|
}, [tracker, currentKey, experiments]);
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
const emitExit = useCallback(() => {
|
|
934
|
-
const t = timer.current;
|
|
935
|
-
if (!t) return;
|
|
936
|
-
const at = now();
|
|
937
|
-
const hidden = t.hidden + (t.hiddenSince != null ? at - t.hiddenSince : 0);
|
|
938
|
-
const durationMs = Math.round(at - t.enteredAt);
|
|
939
|
-
tracker.track("page.exit", { pageId: t.key, durationMs, activeMs: Math.max(0, Math.round(durationMs - hidden)) });
|
|
940
|
-
}, [tracker]);
|
|
941
|
-
useEffect(() => {
|
|
942
|
-
if (typeof document === "undefined") return;
|
|
943
|
-
const onVis = () => {
|
|
944
|
-
const t = timer.current;
|
|
945
|
-
if (!t) return;
|
|
946
|
-
if (document.visibilityState === "hidden") t.hiddenSince = now();
|
|
947
|
-
else if (t.hiddenSince != null) {
|
|
948
|
-
t.hidden += now() - t.hiddenSince;
|
|
949
|
-
t.hiddenSince = null;
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
document.addEventListener("visibilitychange", onVis);
|
|
953
|
-
return () => document.removeEventListener("visibilitychange", onVis);
|
|
954
|
-
}, []);
|
|
955
|
-
useEffect(() => {
|
|
956
|
-
if (!currentKey) return;
|
|
957
|
-
if (timer.current && timer.current.key !== currentKey) emitExit();
|
|
958
|
-
const hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
959
|
-
timer.current = { key: currentKey, enteredAt: now(), hidden: 0, hiddenSince: hidden ? now() : null };
|
|
960
|
-
}, [currentKey, emitExit]);
|
|
961
|
-
useEffect(() => () => emitExit(), [emitExit]);
|
|
695
|
+
usePageExitTimer(tracker, currentKey);
|
|
696
|
+
const [isNavigating, startNav] = useTransition();
|
|
962
697
|
const next = useCallback(() => {
|
|
963
698
|
const target = nextPage(flow, currentKey, funnel.snapshot());
|
|
964
|
-
const resolved = target ?
|
|
965
|
-
if (resolved) setHistory((h) => [...h, resolved]);
|
|
966
|
-
}, [flow, currentKey, funnel,
|
|
699
|
+
const resolved = target ? toSlot(target) : target;
|
|
700
|
+
if (resolved) startNav(() => setHistory((h) => [...h, resolved]));
|
|
701
|
+
}, [flow, currentKey, funnel, toSlot]);
|
|
967
702
|
const back = useCallback(
|
|
968
|
-
() => setHistory((h) => h.length > 1 ? h.slice(0, -1) : h),
|
|
703
|
+
() => startNav(() => setHistory((h) => h.length > 1 ? h.slice(0, -1) : h)),
|
|
969
704
|
[]
|
|
970
705
|
);
|
|
971
706
|
const go = useCallback(
|
|
972
|
-
(key) => setHistory((h) => [...h,
|
|
973
|
-
[
|
|
707
|
+
(key) => startNav(() => setHistory((h) => [...h, toSlot(key)])),
|
|
708
|
+
[toSlot]
|
|
974
709
|
);
|
|
975
710
|
const prefetched = useRef(/* @__PURE__ */ new Set());
|
|
976
711
|
const prefetch = useCallback(
|
|
977
712
|
(key) => {
|
|
978
|
-
const target =
|
|
713
|
+
const target = toRenderKey(key);
|
|
979
714
|
const load = loaders.get(target);
|
|
980
715
|
if (!load || prefetched.current.has(target)) return;
|
|
981
716
|
prefetched.current.add(target);
|
|
982
717
|
load().catch(() => prefetched.current.delete(target));
|
|
983
718
|
},
|
|
984
|
-
[loaders,
|
|
719
|
+
[loaders, toRenderKey]
|
|
985
720
|
);
|
|
986
721
|
const nextCandidates = useMemo(
|
|
987
|
-
() => [...new Set(outgoingKeys(flow, currentKey).map(
|
|
988
|
-
[flow, currentKey,
|
|
722
|
+
() => [...new Set(outgoingKeys(flow, currentKey).map(toRenderKey))],
|
|
723
|
+
[flow, currentKey, toRenderKey]
|
|
989
724
|
);
|
|
990
725
|
useEffect(() => {
|
|
991
726
|
if (prefetchMode !== "auto") return;
|
|
@@ -998,9 +733,10 @@ function FunnelView({
|
|
|
998
733
|
go,
|
|
999
734
|
prefetch,
|
|
1000
735
|
nextCandidates,
|
|
1001
|
-
canGoBack: history.length > 1
|
|
736
|
+
canGoBack: history.length > 1,
|
|
737
|
+
isNavigating
|
|
1002
738
|
};
|
|
1003
|
-
const renderKey =
|
|
739
|
+
const renderKey = toRenderKey(currentKey);
|
|
1004
740
|
const Current = components.get(renderKey);
|
|
1005
741
|
const pageEl = createElement(Suspense, { fallback: fallback ?? null }, Current ? createElement(Current) : null);
|
|
1006
742
|
const content = layout ? createElement(layout, null, pageEl) : pageEl;
|
|
@@ -1022,14 +758,20 @@ var symModalId = /* @__PURE__ */ Symbol("AppFunnelModalId");
|
|
|
1022
758
|
var initialState = {};
|
|
1023
759
|
var ModalStateContext = createContext(initialState);
|
|
1024
760
|
var ModalIdContext = createContext(null);
|
|
761
|
+
var ModalDispatchContext = createContext(null);
|
|
1025
762
|
var REGISTRY = {};
|
|
1026
763
|
var ALREADY_MOUNTED = {};
|
|
1027
764
|
var modalCallbacks = {};
|
|
1028
765
|
var hideCallbacks = {};
|
|
1029
766
|
var uid = 0;
|
|
1030
767
|
var getUid = () => `_af_modal_${uid++}`;
|
|
1031
|
-
var
|
|
1032
|
-
|
|
768
|
+
var runtimeStack = [];
|
|
769
|
+
var dispatch = (action) => {
|
|
770
|
+
const top = runtimeStack[runtimeStack.length - 1];
|
|
771
|
+
if (!top) {
|
|
772
|
+
throw new Error("No modal dispatch \u2014 render inside <FunnelProvider> (which mounts the modal runtime).");
|
|
773
|
+
}
|
|
774
|
+
top(action);
|
|
1033
775
|
};
|
|
1034
776
|
var modalReducer = (state = initialState, action) => {
|
|
1035
777
|
const { modalId, args, flags } = action.payload;
|
|
@@ -1072,10 +814,10 @@ function registerModal(id, comp, props) {
|
|
|
1072
814
|
function unregisterModal(id) {
|
|
1073
815
|
delete REGISTRY[id];
|
|
1074
816
|
}
|
|
1075
|
-
function
|
|
817
|
+
function showModalVia(d, modal, args) {
|
|
1076
818
|
const id = idOf(modal);
|
|
1077
819
|
if (typeof modal !== "string" && !REGISTRY[id]) registerModal(id, modal);
|
|
1078
|
-
|
|
820
|
+
d({ type: "show", payload: { modalId: id, args } });
|
|
1079
821
|
if (!modalCallbacks[id]) {
|
|
1080
822
|
let res;
|
|
1081
823
|
let rej;
|
|
@@ -1087,9 +829,12 @@ function showModal(modal, args) {
|
|
|
1087
829
|
}
|
|
1088
830
|
return modalCallbacks[id].promise;
|
|
1089
831
|
}
|
|
1090
|
-
function
|
|
832
|
+
function showModal(modal, args) {
|
|
833
|
+
return showModalVia(dispatch, modal, args);
|
|
834
|
+
}
|
|
835
|
+
function hideModalVia(d, modal) {
|
|
1091
836
|
const id = idOf(modal);
|
|
1092
|
-
|
|
837
|
+
d({ type: "hide", payload: { modalId: id } });
|
|
1093
838
|
delete modalCallbacks[id];
|
|
1094
839
|
if (!hideCallbacks[id]) {
|
|
1095
840
|
let res;
|
|
@@ -1102,18 +847,25 @@ function hideModal(modal) {
|
|
|
1102
847
|
}
|
|
1103
848
|
return hideCallbacks[id].promise;
|
|
1104
849
|
}
|
|
1105
|
-
function
|
|
850
|
+
function hideModal(modal) {
|
|
851
|
+
return hideModalVia(dispatch, modal);
|
|
852
|
+
}
|
|
853
|
+
function removeModalVia(d, modal) {
|
|
1106
854
|
const id = idOf(modal);
|
|
1107
|
-
|
|
855
|
+
d({ type: "remove", payload: { modalId: id } });
|
|
1108
856
|
delete modalCallbacks[id];
|
|
1109
857
|
delete hideCallbacks[id];
|
|
1110
858
|
}
|
|
1111
|
-
function
|
|
1112
|
-
dispatch
|
|
859
|
+
function removeModal(modal) {
|
|
860
|
+
removeModalVia(dispatch, modal);
|
|
861
|
+
}
|
|
862
|
+
function setFlags(d, id, flags) {
|
|
863
|
+
d({ type: "set-flags", payload: { modalId: id, flags } });
|
|
1113
864
|
}
|
|
1114
865
|
function useModal(modal, args) {
|
|
1115
866
|
const modals = useContext(ModalStateContext);
|
|
1116
867
|
const contextId = useContext(ModalIdContext);
|
|
868
|
+
const d = useContext(ModalDispatchContext) ?? dispatch;
|
|
1117
869
|
const isComponent = !!modal && typeof modal !== "string";
|
|
1118
870
|
const id = modal ? idOf(modal) : contextId;
|
|
1119
871
|
if (!id) throw new Error("useModal: no modal id (call inside a defineModal component, or pass an id/component).");
|
|
@@ -1121,9 +873,9 @@ function useModal(modal, args) {
|
|
|
1121
873
|
if (isComponent && !REGISTRY[id]) registerModal(id, modal, args);
|
|
1122
874
|
}, [isComponent, id, modal, args]);
|
|
1123
875
|
const info = modals[id];
|
|
1124
|
-
const show = useCallback((a) =>
|
|
1125
|
-
const hide = useCallback(() =>
|
|
1126
|
-
const remove = useCallback(() =>
|
|
876
|
+
const show = useCallback((a) => showModalVia(d, id, a), [d, id]);
|
|
877
|
+
const hide = useCallback(() => hideModalVia(d, id), [d, id]);
|
|
878
|
+
const remove = useCallback(() => removeModalVia(d, id), [d, id]);
|
|
1127
879
|
const resolve = useCallback((v) => {
|
|
1128
880
|
modalCallbacks[id]?.resolve(v);
|
|
1129
881
|
delete modalCallbacks[id];
|
|
@@ -1156,6 +908,7 @@ function defineModal(Comp) {
|
|
|
1156
908
|
return function Wrapped({ id, defaultVisible, keepMounted, ...props }) {
|
|
1157
909
|
const { args, show } = useModal(id);
|
|
1158
910
|
const modals = useContext(ModalStateContext);
|
|
911
|
+
const d = useContext(ModalDispatchContext) ?? dispatch;
|
|
1159
912
|
const shouldMount = !!modals[id];
|
|
1160
913
|
useEffect(() => {
|
|
1161
914
|
if (defaultVisible) show();
|
|
@@ -1165,8 +918,8 @@ function defineModal(Comp) {
|
|
|
1165
918
|
};
|
|
1166
919
|
}, [id, show, defaultVisible]);
|
|
1167
920
|
useEffect(() => {
|
|
1168
|
-
if (keepMounted) setFlags(id, { keepMounted: true });
|
|
1169
|
-
}, [id, keepMounted]);
|
|
921
|
+
if (keepMounted) setFlags(d, id, { keepMounted: true });
|
|
922
|
+
}, [d, id, keepMounted]);
|
|
1170
923
|
const delayVisible = modals[id]?.delayVisible;
|
|
1171
924
|
useEffect(() => {
|
|
1172
925
|
if (delayVisible) show(args);
|
|
@@ -1190,12 +943,30 @@ function ModalRoot() {
|
|
|
1190
943
|
}
|
|
1191
944
|
function ModalRuntime({ children }) {
|
|
1192
945
|
const [state, localDispatch] = useReducer(modalReducer, initialState);
|
|
1193
|
-
|
|
946
|
+
useEffect(() => {
|
|
947
|
+
runtimeStack.push(localDispatch);
|
|
948
|
+
if (runtimeStack.length > 1) {
|
|
949
|
+
try {
|
|
950
|
+
console.warn(
|
|
951
|
+
`[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.`
|
|
952
|
+
);
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return () => {
|
|
957
|
+
const i = runtimeStack.indexOf(localDispatch);
|
|
958
|
+
if (i !== -1) runtimeStack.splice(i, 1);
|
|
959
|
+
};
|
|
960
|
+
}, []);
|
|
1194
961
|
return createElement(
|
|
1195
|
-
|
|
1196
|
-
{ value:
|
|
1197
|
-
|
|
1198
|
-
|
|
962
|
+
ModalDispatchContext.Provider,
|
|
963
|
+
{ value: localDispatch },
|
|
964
|
+
createElement(
|
|
965
|
+
ModalStateContext.Provider,
|
|
966
|
+
{ value: state },
|
|
967
|
+
children,
|
|
968
|
+
createElement(ModalRoot, null)
|
|
969
|
+
)
|
|
1199
970
|
);
|
|
1200
971
|
}
|
|
1201
972
|
|
|
@@ -1294,103 +1065,6 @@ function Modal(props) {
|
|
|
1294
1065
|
}
|
|
1295
1066
|
|
|
1296
1067
|
// src/commerce/checkout.tsx
|
|
1297
|
-
var INLINE_SURFACES = /* @__PURE__ */ new Set([
|
|
1298
|
-
"express",
|
|
1299
|
-
"element",
|
|
1300
|
-
"embedded"
|
|
1301
|
-
]);
|
|
1302
|
-
function isInlineSurface(surface) {
|
|
1303
|
-
return INLINE_SURFACES.has(surface);
|
|
1304
|
-
}
|
|
1305
|
-
var ON = { purchase: true, wallets: true, offSession: "reliable" };
|
|
1306
|
-
var MANAGED = { purchase: true, wallets: true, offSession: "conditional" };
|
|
1307
|
-
var ALL_UPSELLS = ["subscription", "one-time", "upgrade"];
|
|
1308
|
-
var PROVIDER_PROFILES = {
|
|
1309
|
-
// PSP; you hold the PaymentMethod and fire your own off-session PaymentIntent on
|
|
1310
|
-
// the Elements surfaces (reliable). **Managed** Checkout (`embedded`/`redirect`)
|
|
1311
|
-
// upsells too, but as a direct charge against Stripe's vaulted card → conditional.
|
|
1312
|
-
// No provider "popup" surface.
|
|
1313
|
-
stripe: {
|
|
1314
|
-
isMoR: false,
|
|
1315
|
-
offSessionUpsells: ALL_UPSELLS,
|
|
1316
|
-
tokenModel: "merchant",
|
|
1317
|
-
orchestrator: false,
|
|
1318
|
-
surfaces: { express: ON, element: ON, sheet: ON, embedded: MANAGED, redirect: MANAGED }
|
|
1319
|
-
},
|
|
1320
|
-
// Merchant-of-Record; card entry is always Paddle's frame → no own element/sheet/
|
|
1321
|
-
// express. Off-session via subscription one-time charge / collection_mode, but
|
|
1322
|
-
// **no 2nd concurrent subscription** (upgrade + one-time only), provider-initiated.
|
|
1323
|
-
paddle: {
|
|
1324
|
-
isMoR: true,
|
|
1325
|
-
offSessionUpsells: ["one-time", "upgrade"],
|
|
1326
|
-
tokenModel: "provider",
|
|
1327
|
-
orchestrator: false,
|
|
1328
|
-
surfaces: { embedded: ON, popup: ON, redirect: ON }
|
|
1329
|
-
},
|
|
1330
|
-
// Merchant-of-Record; iframe-only (no raw card field). Off-session works
|
|
1331
|
-
// (provider-initiated, async via webhooks). `sheet` = our chrome hosting their embed.
|
|
1332
|
-
whop: {
|
|
1333
|
-
isMoR: true,
|
|
1334
|
-
offSessionUpsells: ALL_UPSELLS,
|
|
1335
|
-
tokenModel: "provider",
|
|
1336
|
-
orchestrator: false,
|
|
1337
|
-
surfaces: { embedded: ON, redirect: ON, sheet: ON }
|
|
1338
|
-
},
|
|
1339
|
-
// Pure orchestrator (PSP-side); Headless renders express/element/sheet/embedded.
|
|
1340
|
-
// No hosted redirect page, no popup checkout. Off-session is PSP-conditional.
|
|
1341
|
-
primer: {
|
|
1342
|
-
isMoR: false,
|
|
1343
|
-
offSessionUpsells: ALL_UPSELLS,
|
|
1344
|
-
tokenModel: "merchant",
|
|
1345
|
-
orchestrator: true,
|
|
1346
|
-
surfaces: { express: ON, element: ON, sheet: ON, embedded: ON }
|
|
1347
|
-
},
|
|
1348
|
-
// PSP **and** orchestrator (own acquiring + connectors routing across Stripe/Adyen/
|
|
1349
|
-
// PayPal). One Payment Form (element/sheet/embedded) + hosted page + wallet button.
|
|
1350
|
-
// Strong native off-session (Recurring API). No popup.
|
|
1351
|
-
solidgate: {
|
|
1352
|
-
isMoR: false,
|
|
1353
|
-
offSessionUpsells: ALL_UPSELLS,
|
|
1354
|
-
tokenModel: "merchant",
|
|
1355
|
-
orchestrator: true,
|
|
1356
|
-
surfaces: { express: ON, element: ON, sheet: ON, embedded: ON, redirect: ON }
|
|
1357
|
-
}
|
|
1358
|
-
};
|
|
1359
|
-
function surfacesFor(provider) {
|
|
1360
|
-
return Object.keys(PROVIDER_PROFILES[provider]?.surfaces ?? {});
|
|
1361
|
-
}
|
|
1362
|
-
function isMerchantOfRecord(provider) {
|
|
1363
|
-
return PROVIDER_PROFILES[provider]?.isMoR ?? false;
|
|
1364
|
-
}
|
|
1365
|
-
function isOrchestrator(provider) {
|
|
1366
|
-
return PROVIDER_PROFILES[provider]?.orchestrator ?? false;
|
|
1367
|
-
}
|
|
1368
|
-
function validateCheckout(provider, surface) {
|
|
1369
|
-
const cap = PROVIDER_PROFILES[provider]?.surfaces[surface];
|
|
1370
|
-
if (!cap) return { ok: false, reason: `${provider} has no '${surface}' surface` };
|
|
1371
|
-
return cap.purchase ? { ok: true } : { ok: false, reason: `'${surface}' can't take a purchase on ${provider}` };
|
|
1372
|
-
}
|
|
1373
|
-
function validateUpsell(provider, paywallSurface, kind = "subscription") {
|
|
1374
|
-
const profile = PROVIDER_PROFILES[provider];
|
|
1375
|
-
if (!profile) return { ok: false, reason: `unknown provider '${provider}'` };
|
|
1376
|
-
if (!profile.offSessionUpsells.includes(kind)) {
|
|
1377
|
-
const can = profile.offSessionUpsells.length ? ` (it supports: ${profile.offSessionUpsells.join(", ")})` : "";
|
|
1378
|
-
return { ok: false, reason: `${provider} can't charge a '${kind}' upsell off-session${can}` };
|
|
1379
|
-
}
|
|
1380
|
-
const cap = profile.surfaces[paywallSurface];
|
|
1381
|
-
if (!cap) return { ok: false, reason: `${provider} has no '${paywallSurface}' surface to upsell after` };
|
|
1382
|
-
if (cap.offSession === "none") {
|
|
1383
|
-
return { ok: false, reason: `a purchase on '${paywallSurface}' retains no chargeable method for an upsell` };
|
|
1384
|
-
}
|
|
1385
|
-
if (cap.offSession === "conditional") {
|
|
1386
|
-
return { ok: true, note: `off-session upsell after ${provider} '${paywallSurface}' (managed checkout) may decline \u2014 prefer element/sheet/express` };
|
|
1387
|
-
}
|
|
1388
|
-
return { ok: true };
|
|
1389
|
-
}
|
|
1390
|
-
function checkoutError(category, message, extra = {}) {
|
|
1391
|
-
const retryable = extra.retryable ?? (category === "authentication_required" || category === "requires_payment_method" || category === "processing_error");
|
|
1392
|
-
return { category, message, declineCode: extra.declineCode, retryable };
|
|
1393
|
-
}
|
|
1394
1068
|
function createMockDriver(provider = "stripe", options = {}) {
|
|
1395
1069
|
const success = options.result ?? { ok: true, amountMinor: 0, currency: "USD" };
|
|
1396
1070
|
const offError = typeof options.failOffSession === "object" ? options.failOffSession : checkoutError("authentication_required", "off-session charge declined (mock)");
|
|
@@ -1405,10 +1079,23 @@ function createMockDriver(provider = "stripe", options = {}) {
|
|
|
1405
1079
|
{
|
|
1406
1080
|
type: "button",
|
|
1407
1081
|
"data-mock-surface": req.surface,
|
|
1408
|
-
onClick: () =>
|
|
1082
|
+
onClick: () => {
|
|
1083
|
+
cb.onStart?.();
|
|
1084
|
+
if (success.ok) cb.onSuccess(success);
|
|
1085
|
+
else cb.onError(success.error ?? checkoutError("processing_error", "failed (mock)"));
|
|
1086
|
+
}
|
|
1409
1087
|
},
|
|
1410
1088
|
`Pay (mock ${req.surface})`
|
|
1411
|
-
)
|
|
1089
|
+
),
|
|
1090
|
+
resume: () => Promise.resolve(success)
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function driverWithEmail(driver, getEmail) {
|
|
1094
|
+
const enrich = (req) => req.email !== void 0 ? req : { ...req, email: getEmail() };
|
|
1095
|
+
return {
|
|
1096
|
+
...driver,
|
|
1097
|
+
start: (req) => driver.start(enrich(req)),
|
|
1098
|
+
renderInline: driver.renderInline ? (req, cb) => driver.renderInline(enrich(req), cb) : void 0
|
|
1412
1099
|
};
|
|
1413
1100
|
}
|
|
1414
1101
|
var DriverContext = createContext(null);
|
|
@@ -1431,6 +1118,12 @@ function useCheckout(opts = {}) {
|
|
|
1431
1118
|
const intent = opts.intent ?? "purchase";
|
|
1432
1119
|
const kind = opts.kind;
|
|
1433
1120
|
const advance = opts.advanceOnSuccess ?? true;
|
|
1121
|
+
const recovering = useRef(false);
|
|
1122
|
+
const failRef = useRef(() => {
|
|
1123
|
+
});
|
|
1124
|
+
const attemptRef = useRef(
|
|
1125
|
+
async () => ({ ok: false })
|
|
1126
|
+
);
|
|
1434
1127
|
const succeed = useCallback(
|
|
1435
1128
|
(result) => {
|
|
1436
1129
|
setStatus("success");
|
|
@@ -1445,20 +1138,8 @@ function useCheckout(opts = {}) {
|
|
|
1445
1138
|
},
|
|
1446
1139
|
[tracker, nav, advance, opts]
|
|
1447
1140
|
);
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
setStatus(err.category === "authentication_required" ? "requires_action" : "error");
|
|
1451
|
-
setError(err);
|
|
1452
|
-
tracker.track("checkout.failed", {
|
|
1453
|
-
category: err.category,
|
|
1454
|
-
declineCode: err.declineCode,
|
|
1455
|
-
productId: product,
|
|
1456
|
-
intent
|
|
1457
|
-
});
|
|
1458
|
-
opts.onError?.(err);
|
|
1459
|
-
},
|
|
1460
|
-
[tracker, intent, opts]
|
|
1461
|
-
);
|
|
1141
|
+
const succeedRef = useRef(succeed);
|
|
1142
|
+
succeedRef.current = succeed;
|
|
1462
1143
|
const begin = useCallback(
|
|
1463
1144
|
(product, surface) => {
|
|
1464
1145
|
setStatus("loading");
|
|
@@ -1468,53 +1149,146 @@ function useCheckout(opts = {}) {
|
|
|
1468
1149
|
[tracker]
|
|
1469
1150
|
);
|
|
1470
1151
|
const paymentAdded = useCallback(() => tracker.track("checkout.payment_added", {}), [tracker]);
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1152
|
+
const hostInSheet = (surface) => surface === "sheet" || isInlineSurface(surface) && !!PROVIDER_PROFILES[driver.provider]?.surfaces.sheet;
|
|
1153
|
+
const startRecovery = (product, surface) => {
|
|
1154
|
+
recovering.current = true;
|
|
1155
|
+
if (hostInSheet(surface)) {
|
|
1156
|
+
begin(product, "sheet");
|
|
1157
|
+
void showModal(CheckoutSheet, {
|
|
1158
|
+
product,
|
|
1159
|
+
surface: "sheet",
|
|
1160
|
+
intent,
|
|
1161
|
+
kind,
|
|
1162
|
+
onSuccess: (r) => succeedRef.current(r),
|
|
1163
|
+
onError: (e) => failRef.current(e, product),
|
|
1164
|
+
onPaymentAdded: () => paymentAdded()
|
|
1165
|
+
});
|
|
1166
|
+
} else {
|
|
1167
|
+
void attemptRef.current(product, surface);
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
failRef.current = (err, product) => {
|
|
1171
|
+
setStatus(err.category === "authentication_required" ? "requires_action" : "error");
|
|
1172
|
+
setError(err);
|
|
1173
|
+
tracker.track("checkout.failed", {
|
|
1174
|
+
category: err.category,
|
|
1175
|
+
declineCode: err.declineCode,
|
|
1176
|
+
productId: product,
|
|
1177
|
+
intent
|
|
1178
|
+
});
|
|
1179
|
+
opts.onError?.(err);
|
|
1180
|
+
if (err.category === "canceled" && !opts.onFailed?.canceled) return;
|
|
1181
|
+
if (product && !recovering.current && opts.recoverySurface && err.category !== "canceled") {
|
|
1182
|
+
startRecovery(product, opts.recoverySurface);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
let route = opts.onFailed?.[err.category] ?? (err.category === "canceled" ? void 0 : opts.onFailed?.default);
|
|
1186
|
+
if (route && typeof route === "object" && "fallback" in route && recovering.current) {
|
|
1187
|
+
const dflt = opts.onFailed?.default;
|
|
1188
|
+
route = dflt && dflt !== route && !(typeof dflt === "object" && "fallback" in dflt) ? dflt : void 0;
|
|
1189
|
+
}
|
|
1190
|
+
if (!route) return;
|
|
1191
|
+
if (typeof route === "function") route(err);
|
|
1192
|
+
else if ("go" in route) nav.go(route.go);
|
|
1193
|
+
else if ("modal" in route) {
|
|
1194
|
+
const props = {
|
|
1195
|
+
error: err,
|
|
1196
|
+
product,
|
|
1197
|
+
go: (pageKey) => nav.go(pageKey),
|
|
1198
|
+
retry: (surface) => {
|
|
1199
|
+
if (!product) return;
|
|
1200
|
+
recovering.current = true;
|
|
1201
|
+
if (!surface) {
|
|
1202
|
+
if (intent === "upsell") void attemptRef.current(product);
|
|
1203
|
+
else startRecovery(product, PROVIDER_PROFILES[driver.provider]?.surfaces.sheet ? "sheet" : "popup");
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
startRecovery(product, surface);
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
void showModal(route.modal, props);
|
|
1210
|
+
} else if ("fallback" in route) {
|
|
1211
|
+
if (product && !recovering.current) startRecovery(product, route.fallback);
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
attemptRef.current = async (product, surface) => {
|
|
1215
|
+
begin(product, surface);
|
|
1216
|
+
try {
|
|
1217
|
+
const result = await driver.start({ product, intent, surface, kind });
|
|
1218
|
+
if (result.ok) succeedRef.current(result);
|
|
1219
|
+
else failRef.current(result.error ?? checkoutError("unknown", "Checkout failed"), product);
|
|
1220
|
+
return result;
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
const err = checkoutError("processing_error", e instanceof Error ? e.message : "Checkout failed");
|
|
1223
|
+
failRef.current(err, product);
|
|
1224
|
+
return { ok: false, error: err };
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
const open = useCallback(async (product, surface) => {
|
|
1228
|
+
recovering.current = false;
|
|
1229
|
+
return attemptRef.current(product, surface);
|
|
1230
|
+
}, []);
|
|
1487
1231
|
const callbacksFor = useCallback(
|
|
1488
1232
|
(product, surface) => ({
|
|
1489
|
-
onStart: () =>
|
|
1490
|
-
|
|
1491
|
-
|
|
1233
|
+
onStart: () => {
|
|
1234
|
+
recovering.current = false;
|
|
1235
|
+
begin(product, surface);
|
|
1236
|
+
},
|
|
1237
|
+
onSuccess: (result) => succeedRef.current(result),
|
|
1238
|
+
onError: (err) => failRef.current(err, product),
|
|
1492
1239
|
onPaymentAdded: () => paymentAdded()
|
|
1493
1240
|
}),
|
|
1494
|
-
[begin,
|
|
1241
|
+
[begin, paymentAdded]
|
|
1495
1242
|
);
|
|
1496
1243
|
const reset = useCallback(() => {
|
|
1244
|
+
recovering.current = false;
|
|
1497
1245
|
setStatus("idle");
|
|
1498
1246
|
setError(null);
|
|
1499
1247
|
}, []);
|
|
1500
1248
|
return { open, status, error, isLoading: status === "loading", reset, callbacksFor };
|
|
1501
1249
|
}
|
|
1502
|
-
var CheckoutSheet = defineModal(({ product, surface, intent, onSuccess, onError }) => {
|
|
1250
|
+
var CheckoutSheet = defineModal(({ product, surface, intent, kind, onSuccess, onError, onPaymentAdded }) => {
|
|
1503
1251
|
const driver = useDriver();
|
|
1504
1252
|
const modal = useModal();
|
|
1253
|
+
const resolved = useRef(false);
|
|
1254
|
+
const onErrorRef = useRef(onError);
|
|
1255
|
+
onErrorRef.current = onError;
|
|
1256
|
+
const hostable = !!driver.renderInline;
|
|
1257
|
+
useEffect(() => {
|
|
1258
|
+
if (!hostable) {
|
|
1259
|
+
resolved.current = true;
|
|
1260
|
+
onErrorRef.current(
|
|
1261
|
+
checkoutError("processing_error", "This checkout driver has no inline surface to host in the sheet")
|
|
1262
|
+
);
|
|
1263
|
+
void modal.hide();
|
|
1264
|
+
modal.remove();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
return () => {
|
|
1268
|
+
if (!resolved.current) {
|
|
1269
|
+
onErrorRef.current(checkoutError("canceled", "Checkout was dismissed before completing"));
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
}, []);
|
|
1505
1273
|
if (!driver.renderInline) return null;
|
|
1274
|
+
const close = () => {
|
|
1275
|
+
resolved.current = true;
|
|
1276
|
+
void modal.hide();
|
|
1277
|
+
modal.remove();
|
|
1278
|
+
};
|
|
1506
1279
|
return createElement(Sheet, {
|
|
1507
1280
|
children: driver.renderInline(
|
|
1508
|
-
{ product, intent, surface },
|
|
1281
|
+
{ product, intent, surface, kind },
|
|
1509
1282
|
{
|
|
1510
1283
|
onSuccess: (r) => {
|
|
1511
|
-
|
|
1284
|
+
close();
|
|
1512
1285
|
onSuccess(r);
|
|
1513
1286
|
},
|
|
1514
1287
|
onError: (e) => {
|
|
1515
|
-
|
|
1288
|
+
close();
|
|
1516
1289
|
onError(e);
|
|
1517
|
-
}
|
|
1290
|
+
},
|
|
1291
|
+
onPaymentAdded
|
|
1518
1292
|
}
|
|
1519
1293
|
)
|
|
1520
1294
|
});
|
|
@@ -1548,7 +1322,15 @@ function Checkout({
|
|
|
1548
1322
|
const cb = checkout.callbacksFor(product, surface);
|
|
1549
1323
|
const open = () => {
|
|
1550
1324
|
cb.onStart();
|
|
1551
|
-
showModal(CheckoutSheet, {
|
|
1325
|
+
showModal(CheckoutSheet, {
|
|
1326
|
+
product,
|
|
1327
|
+
surface,
|
|
1328
|
+
intent,
|
|
1329
|
+
kind: options.kind,
|
|
1330
|
+
onSuccess: cb.onSuccess,
|
|
1331
|
+
onError: cb.onError,
|
|
1332
|
+
onPaymentAdded: cb.onPaymentAdded
|
|
1333
|
+
});
|
|
1552
1334
|
};
|
|
1553
1335
|
return trigger(open, { asChild, children, className, disabled: checkout.isLoading });
|
|
1554
1336
|
}
|
|
@@ -1560,31 +1342,1129 @@ function Checkout({
|
|
|
1560
1342
|
});
|
|
1561
1343
|
}
|
|
1562
1344
|
function Upsell({ product, fallback, asChild, children, className, ...options }) {
|
|
1563
|
-
const checkout = useCheckout({
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
cb.onStart();
|
|
1570
|
-
showModal(CheckoutSheet, { product, surface: "sheet", intent: "upsell", onSuccess: cb.onSuccess, onError: cb.onError });
|
|
1571
|
-
} else {
|
|
1572
|
-
await checkout.open(product, fallback);
|
|
1573
|
-
}
|
|
1574
|
-
};
|
|
1575
|
-
return trigger(start, {
|
|
1345
|
+
const checkout = useCheckout({
|
|
1346
|
+
...options,
|
|
1347
|
+
intent: "upsell",
|
|
1348
|
+
recoverySurface: fallback ?? options.recoverySurface
|
|
1349
|
+
});
|
|
1350
|
+
return trigger(() => void checkout.open(product), {
|
|
1576
1351
|
asChild,
|
|
1577
1352
|
children: children ?? "Add to my plan",
|
|
1578
1353
|
className,
|
|
1579
1354
|
disabled: checkout.isLoading
|
|
1580
1355
|
});
|
|
1581
1356
|
}
|
|
1357
|
+
var consumedResumes = /* @__PURE__ */ new Set();
|
|
1358
|
+
function CheckoutResume(options = {}) {
|
|
1359
|
+
const driver = useDriver();
|
|
1360
|
+
const checkout = useCheckout(options);
|
|
1361
|
+
const callbacksRef = useRef(checkout.callbacksFor);
|
|
1362
|
+
callbacksRef.current = checkout.callbacksFor;
|
|
1363
|
+
useEffect(() => {
|
|
1364
|
+
if (typeof window === "undefined" || !driver.resume) return;
|
|
1365
|
+
const url = new URL(window.location.href);
|
|
1366
|
+
const correlationId = url.searchParams.get("af_checkout");
|
|
1367
|
+
if (!correlationId || consumedResumes.has(correlationId)) return;
|
|
1368
|
+
consumedResumes.add(correlationId);
|
|
1369
|
+
void driver.resume(correlationId).then((result) => {
|
|
1370
|
+
const cb = callbacksRef.current();
|
|
1371
|
+
if (result.ok) {
|
|
1372
|
+
url.searchParams.delete("af_checkout");
|
|
1373
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
1374
|
+
cb.onSuccess(result);
|
|
1375
|
+
} else {
|
|
1376
|
+
cb.onError(result.error ?? checkoutError("unknown", "The checkout could not be verified"));
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
}, []);
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
function __insertCSS(code) {
|
|
1383
|
+
if (typeof document == "undefined") return;
|
|
1384
|
+
let head = document.head || document.getElementsByTagName("head")[0];
|
|
1385
|
+
let style = document.createElement("style");
|
|
1386
|
+
style.type = "text/css";
|
|
1387
|
+
head.appendChild(style);
|
|
1388
|
+
style.styleSheet ? style.styleSheet.cssText = code : style.appendChild(document.createTextNode(code));
|
|
1389
|
+
}
|
|
1390
|
+
var getAsset = (type) => {
|
|
1391
|
+
switch (type) {
|
|
1392
|
+
case "success":
|
|
1393
|
+
return SuccessIcon;
|
|
1394
|
+
case "info":
|
|
1395
|
+
return InfoIcon;
|
|
1396
|
+
case "warning":
|
|
1397
|
+
return WarningIcon;
|
|
1398
|
+
case "error":
|
|
1399
|
+
return ErrorIcon;
|
|
1400
|
+
default:
|
|
1401
|
+
return null;
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
var bars = Array(12).fill(0);
|
|
1405
|
+
var Loader = ({ visible, className }) => {
|
|
1406
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1407
|
+
className: [
|
|
1408
|
+
"sonner-loading-wrapper",
|
|
1409
|
+
className
|
|
1410
|
+
].filter(Boolean).join(" "),
|
|
1411
|
+
"data-visible": visible
|
|
1412
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
1413
|
+
className: "sonner-spinner"
|
|
1414
|
+
}, bars.map((_, i) => /* @__PURE__ */ React.createElement("div", {
|
|
1415
|
+
className: "sonner-loading-bar",
|
|
1416
|
+
key: `spinner-bar-${i}`
|
|
1417
|
+
}))));
|
|
1418
|
+
};
|
|
1419
|
+
var SuccessIcon = /* @__PURE__ */ React.createElement("svg", {
|
|
1420
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1421
|
+
viewBox: "0 0 20 20",
|
|
1422
|
+
fill: "currentColor",
|
|
1423
|
+
height: "20",
|
|
1424
|
+
width: "20"
|
|
1425
|
+
}, /* @__PURE__ */ React.createElement("path", {
|
|
1426
|
+
fillRule: "evenodd",
|
|
1427
|
+
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",
|
|
1428
|
+
clipRule: "evenodd"
|
|
1429
|
+
}));
|
|
1430
|
+
var WarningIcon = /* @__PURE__ */ React.createElement("svg", {
|
|
1431
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1432
|
+
viewBox: "0 0 24 24",
|
|
1433
|
+
fill: "currentColor",
|
|
1434
|
+
height: "20",
|
|
1435
|
+
width: "20"
|
|
1436
|
+
}, /* @__PURE__ */ React.createElement("path", {
|
|
1437
|
+
fillRule: "evenodd",
|
|
1438
|
+
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",
|
|
1439
|
+
clipRule: "evenodd"
|
|
1440
|
+
}));
|
|
1441
|
+
var InfoIcon = /* @__PURE__ */ React.createElement("svg", {
|
|
1442
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1443
|
+
viewBox: "0 0 20 20",
|
|
1444
|
+
fill: "currentColor",
|
|
1445
|
+
height: "20",
|
|
1446
|
+
width: "20"
|
|
1447
|
+
}, /* @__PURE__ */ React.createElement("path", {
|
|
1448
|
+
fillRule: "evenodd",
|
|
1449
|
+
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",
|
|
1450
|
+
clipRule: "evenodd"
|
|
1451
|
+
}));
|
|
1452
|
+
var ErrorIcon = /* @__PURE__ */ React.createElement("svg", {
|
|
1453
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1454
|
+
viewBox: "0 0 20 20",
|
|
1455
|
+
fill: "currentColor",
|
|
1456
|
+
height: "20",
|
|
1457
|
+
width: "20"
|
|
1458
|
+
}, /* @__PURE__ */ React.createElement("path", {
|
|
1459
|
+
fillRule: "evenodd",
|
|
1460
|
+
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",
|
|
1461
|
+
clipRule: "evenodd"
|
|
1462
|
+
}));
|
|
1463
|
+
var CloseIcon = /* @__PURE__ */ React.createElement("svg", {
|
|
1464
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1465
|
+
width: "12",
|
|
1466
|
+
height: "12",
|
|
1467
|
+
viewBox: "0 0 24 24",
|
|
1468
|
+
fill: "none",
|
|
1469
|
+
stroke: "currentColor",
|
|
1470
|
+
strokeWidth: "1.5",
|
|
1471
|
+
strokeLinecap: "round",
|
|
1472
|
+
strokeLinejoin: "round"
|
|
1473
|
+
}, /* @__PURE__ */ React.createElement("line", {
|
|
1474
|
+
x1: "18",
|
|
1475
|
+
y1: "6",
|
|
1476
|
+
x2: "6",
|
|
1477
|
+
y2: "18"
|
|
1478
|
+
}), /* @__PURE__ */ React.createElement("line", {
|
|
1479
|
+
x1: "6",
|
|
1480
|
+
y1: "6",
|
|
1481
|
+
x2: "18",
|
|
1482
|
+
y2: "18"
|
|
1483
|
+
}));
|
|
1484
|
+
var useIsDocumentHidden = () => {
|
|
1485
|
+
const [isDocumentHidden, setIsDocumentHidden] = React.useState(document.hidden);
|
|
1486
|
+
React.useEffect(() => {
|
|
1487
|
+
const callback = () => {
|
|
1488
|
+
setIsDocumentHidden(document.hidden);
|
|
1489
|
+
};
|
|
1490
|
+
document.addEventListener("visibilitychange", callback);
|
|
1491
|
+
return () => window.removeEventListener("visibilitychange", callback);
|
|
1492
|
+
}, []);
|
|
1493
|
+
return isDocumentHidden;
|
|
1494
|
+
};
|
|
1495
|
+
var toastsCounter = 1;
|
|
1496
|
+
var Observer = class {
|
|
1497
|
+
constructor() {
|
|
1498
|
+
this.subscribe = (subscriber) => {
|
|
1499
|
+
this.subscribers.push(subscriber);
|
|
1500
|
+
return () => {
|
|
1501
|
+
const index = this.subscribers.indexOf(subscriber);
|
|
1502
|
+
this.subscribers.splice(index, 1);
|
|
1503
|
+
};
|
|
1504
|
+
};
|
|
1505
|
+
this.publish = (data) => {
|
|
1506
|
+
this.subscribers.forEach((subscriber) => subscriber(data));
|
|
1507
|
+
};
|
|
1508
|
+
this.addToast = (data) => {
|
|
1509
|
+
this.publish(data);
|
|
1510
|
+
this.toasts = [
|
|
1511
|
+
...this.toasts,
|
|
1512
|
+
data
|
|
1513
|
+
];
|
|
1514
|
+
};
|
|
1515
|
+
this.create = (data) => {
|
|
1516
|
+
var _data_id;
|
|
1517
|
+
const { message, ...rest } = data;
|
|
1518
|
+
const id = typeof (data == null ? void 0 : data.id) === "number" || ((_data_id = data.id) == null ? void 0 : _data_id.length) > 0 ? data.id : toastsCounter++;
|
|
1519
|
+
const alreadyExists = this.toasts.find((toast2) => {
|
|
1520
|
+
return toast2.id === id;
|
|
1521
|
+
});
|
|
1522
|
+
const dismissible = data.dismissible === void 0 ? true : data.dismissible;
|
|
1523
|
+
if (this.dismissedToasts.has(id)) {
|
|
1524
|
+
this.dismissedToasts.delete(id);
|
|
1525
|
+
}
|
|
1526
|
+
if (alreadyExists) {
|
|
1527
|
+
this.toasts = this.toasts.map((toast2) => {
|
|
1528
|
+
if (toast2.id === id) {
|
|
1529
|
+
this.publish({
|
|
1530
|
+
...toast2,
|
|
1531
|
+
...data,
|
|
1532
|
+
id,
|
|
1533
|
+
title: message
|
|
1534
|
+
});
|
|
1535
|
+
return {
|
|
1536
|
+
...toast2,
|
|
1537
|
+
...data,
|
|
1538
|
+
id,
|
|
1539
|
+
dismissible,
|
|
1540
|
+
title: message
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
return toast2;
|
|
1544
|
+
});
|
|
1545
|
+
} else {
|
|
1546
|
+
this.addToast({
|
|
1547
|
+
title: message,
|
|
1548
|
+
...rest,
|
|
1549
|
+
dismissible,
|
|
1550
|
+
id
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
return id;
|
|
1554
|
+
};
|
|
1555
|
+
this.dismiss = (id) => {
|
|
1556
|
+
if (id) {
|
|
1557
|
+
this.dismissedToasts.add(id);
|
|
1558
|
+
requestAnimationFrame(() => this.subscribers.forEach((subscriber) => subscriber({
|
|
1559
|
+
id,
|
|
1560
|
+
dismiss: true
|
|
1561
|
+
})));
|
|
1562
|
+
} else {
|
|
1563
|
+
this.toasts.forEach((toast2) => {
|
|
1564
|
+
this.subscribers.forEach((subscriber) => subscriber({
|
|
1565
|
+
id: toast2.id,
|
|
1566
|
+
dismiss: true
|
|
1567
|
+
}));
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
return id;
|
|
1571
|
+
};
|
|
1572
|
+
this.message = (message, data) => {
|
|
1573
|
+
return this.create({
|
|
1574
|
+
...data,
|
|
1575
|
+
message
|
|
1576
|
+
});
|
|
1577
|
+
};
|
|
1578
|
+
this.error = (message, data) => {
|
|
1579
|
+
return this.create({
|
|
1580
|
+
...data,
|
|
1581
|
+
message,
|
|
1582
|
+
type: "error"
|
|
1583
|
+
});
|
|
1584
|
+
};
|
|
1585
|
+
this.success = (message, data) => {
|
|
1586
|
+
return this.create({
|
|
1587
|
+
...data,
|
|
1588
|
+
type: "success",
|
|
1589
|
+
message
|
|
1590
|
+
});
|
|
1591
|
+
};
|
|
1592
|
+
this.info = (message, data) => {
|
|
1593
|
+
return this.create({
|
|
1594
|
+
...data,
|
|
1595
|
+
type: "info",
|
|
1596
|
+
message
|
|
1597
|
+
});
|
|
1598
|
+
};
|
|
1599
|
+
this.warning = (message, data) => {
|
|
1600
|
+
return this.create({
|
|
1601
|
+
...data,
|
|
1602
|
+
type: "warning",
|
|
1603
|
+
message
|
|
1604
|
+
});
|
|
1605
|
+
};
|
|
1606
|
+
this.loading = (message, data) => {
|
|
1607
|
+
return this.create({
|
|
1608
|
+
...data,
|
|
1609
|
+
type: "loading",
|
|
1610
|
+
message
|
|
1611
|
+
});
|
|
1612
|
+
};
|
|
1613
|
+
this.promise = (promise, data) => {
|
|
1614
|
+
if (!data) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
let id = void 0;
|
|
1618
|
+
if (data.loading !== void 0) {
|
|
1619
|
+
id = this.create({
|
|
1620
|
+
...data,
|
|
1621
|
+
promise,
|
|
1622
|
+
type: "loading",
|
|
1623
|
+
message: data.loading,
|
|
1624
|
+
description: typeof data.description !== "function" ? data.description : void 0
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
const p = Promise.resolve(promise instanceof Function ? promise() : promise);
|
|
1628
|
+
let shouldDismiss = id !== void 0;
|
|
1629
|
+
let result;
|
|
1630
|
+
const originalPromise = p.then(async (response) => {
|
|
1631
|
+
result = [
|
|
1632
|
+
"resolve",
|
|
1633
|
+
response
|
|
1634
|
+
];
|
|
1635
|
+
const isReactElementResponse = React.isValidElement(response);
|
|
1636
|
+
if (isReactElementResponse) {
|
|
1637
|
+
shouldDismiss = false;
|
|
1638
|
+
this.create({
|
|
1639
|
+
id,
|
|
1640
|
+
type: "default",
|
|
1641
|
+
message: response
|
|
1642
|
+
});
|
|
1643
|
+
} else if (isHttpResponse(response) && !response.ok) {
|
|
1644
|
+
shouldDismiss = false;
|
|
1645
|
+
const promiseData = typeof data.error === "function" ? await data.error(`HTTP error! status: ${response.status}`) : data.error;
|
|
1646
|
+
const description = typeof data.description === "function" ? await data.description(`HTTP error! status: ${response.status}`) : data.description;
|
|
1647
|
+
const isExtendedResult = typeof promiseData === "object" && !React.isValidElement(promiseData);
|
|
1648
|
+
const toastSettings = isExtendedResult ? promiseData : {
|
|
1649
|
+
message: promiseData
|
|
1650
|
+
};
|
|
1651
|
+
this.create({
|
|
1652
|
+
id,
|
|
1653
|
+
type: "error",
|
|
1654
|
+
description,
|
|
1655
|
+
...toastSettings
|
|
1656
|
+
});
|
|
1657
|
+
} else if (response instanceof Error) {
|
|
1658
|
+
shouldDismiss = false;
|
|
1659
|
+
const promiseData = typeof data.error === "function" ? await data.error(response) : data.error;
|
|
1660
|
+
const description = typeof data.description === "function" ? await data.description(response) : data.description;
|
|
1661
|
+
const isExtendedResult = typeof promiseData === "object" && !React.isValidElement(promiseData);
|
|
1662
|
+
const toastSettings = isExtendedResult ? promiseData : {
|
|
1663
|
+
message: promiseData
|
|
1664
|
+
};
|
|
1665
|
+
this.create({
|
|
1666
|
+
id,
|
|
1667
|
+
type: "error",
|
|
1668
|
+
description,
|
|
1669
|
+
...toastSettings
|
|
1670
|
+
});
|
|
1671
|
+
} else if (data.success !== void 0) {
|
|
1672
|
+
shouldDismiss = false;
|
|
1673
|
+
const promiseData = typeof data.success === "function" ? await data.success(response) : data.success;
|
|
1674
|
+
const description = typeof data.description === "function" ? await data.description(response) : data.description;
|
|
1675
|
+
const isExtendedResult = typeof promiseData === "object" && !React.isValidElement(promiseData);
|
|
1676
|
+
const toastSettings = isExtendedResult ? promiseData : {
|
|
1677
|
+
message: promiseData
|
|
1678
|
+
};
|
|
1679
|
+
this.create({
|
|
1680
|
+
id,
|
|
1681
|
+
type: "success",
|
|
1682
|
+
description,
|
|
1683
|
+
...toastSettings
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
}).catch(async (error) => {
|
|
1687
|
+
result = [
|
|
1688
|
+
"reject",
|
|
1689
|
+
error
|
|
1690
|
+
];
|
|
1691
|
+
if (data.error !== void 0) {
|
|
1692
|
+
shouldDismiss = false;
|
|
1693
|
+
const promiseData = typeof data.error === "function" ? await data.error(error) : data.error;
|
|
1694
|
+
const description = typeof data.description === "function" ? await data.description(error) : data.description;
|
|
1695
|
+
const isExtendedResult = typeof promiseData === "object" && !React.isValidElement(promiseData);
|
|
1696
|
+
const toastSettings = isExtendedResult ? promiseData : {
|
|
1697
|
+
message: promiseData
|
|
1698
|
+
};
|
|
1699
|
+
this.create({
|
|
1700
|
+
id,
|
|
1701
|
+
type: "error",
|
|
1702
|
+
description,
|
|
1703
|
+
...toastSettings
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
}).finally(() => {
|
|
1707
|
+
if (shouldDismiss) {
|
|
1708
|
+
this.dismiss(id);
|
|
1709
|
+
id = void 0;
|
|
1710
|
+
}
|
|
1711
|
+
data.finally == null ? void 0 : data.finally.call(data);
|
|
1712
|
+
});
|
|
1713
|
+
const unwrap = () => new Promise((resolve, reject) => originalPromise.then(() => result[0] === "reject" ? reject(result[1]) : resolve(result[1])).catch(reject));
|
|
1714
|
+
if (typeof id !== "string" && typeof id !== "number") {
|
|
1715
|
+
return {
|
|
1716
|
+
unwrap
|
|
1717
|
+
};
|
|
1718
|
+
} else {
|
|
1719
|
+
return Object.assign(id, {
|
|
1720
|
+
unwrap
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
this.custom = (jsx2, data) => {
|
|
1725
|
+
const id = (data == null ? void 0 : data.id) || toastsCounter++;
|
|
1726
|
+
this.create({
|
|
1727
|
+
jsx: jsx2(id),
|
|
1728
|
+
id,
|
|
1729
|
+
...data
|
|
1730
|
+
});
|
|
1731
|
+
return id;
|
|
1732
|
+
};
|
|
1733
|
+
this.getActiveToasts = () => {
|
|
1734
|
+
return this.toasts.filter((toast2) => !this.dismissedToasts.has(toast2.id));
|
|
1735
|
+
};
|
|
1736
|
+
this.subscribers = [];
|
|
1737
|
+
this.toasts = [];
|
|
1738
|
+
this.dismissedToasts = /* @__PURE__ */ new Set();
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
var ToastState = new Observer();
|
|
1742
|
+
var toastFunction = (message, data) => {
|
|
1743
|
+
const id = (data == null ? void 0 : data.id) || toastsCounter++;
|
|
1744
|
+
ToastState.addToast({
|
|
1745
|
+
title: message,
|
|
1746
|
+
...data,
|
|
1747
|
+
id
|
|
1748
|
+
});
|
|
1749
|
+
return id;
|
|
1750
|
+
};
|
|
1751
|
+
var isHttpResponse = (data) => {
|
|
1752
|
+
return data && typeof data === "object" && "ok" in data && typeof data.ok === "boolean" && "status" in data && typeof data.status === "number";
|
|
1753
|
+
};
|
|
1754
|
+
var basicToast = toastFunction;
|
|
1755
|
+
var getHistory = () => ToastState.toasts;
|
|
1756
|
+
var getToasts = () => ToastState.getActiveToasts();
|
|
1757
|
+
var toast = Object.assign(basicToast, {
|
|
1758
|
+
success: ToastState.success,
|
|
1759
|
+
info: ToastState.info,
|
|
1760
|
+
warning: ToastState.warning,
|
|
1761
|
+
error: ToastState.error,
|
|
1762
|
+
custom: ToastState.custom,
|
|
1763
|
+
message: ToastState.message,
|
|
1764
|
+
promise: ToastState.promise,
|
|
1765
|
+
dismiss: ToastState.dismiss,
|
|
1766
|
+
loading: ToastState.loading
|
|
1767
|
+
}, {
|
|
1768
|
+
getHistory,
|
|
1769
|
+
getToasts
|
|
1770
|
+
});
|
|
1771
|
+
__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%)}");
|
|
1772
|
+
function isAction(action) {
|
|
1773
|
+
return action.label !== void 0;
|
|
1774
|
+
}
|
|
1775
|
+
var VISIBLE_TOASTS_AMOUNT = 3;
|
|
1776
|
+
var VIEWPORT_OFFSET = "24px";
|
|
1777
|
+
var MOBILE_VIEWPORT_OFFSET = "16px";
|
|
1778
|
+
var TOAST_LIFETIME = 4e3;
|
|
1779
|
+
var TOAST_WIDTH = 356;
|
|
1780
|
+
var GAP = 14;
|
|
1781
|
+
var SWIPE_THRESHOLD = 45;
|
|
1782
|
+
var TIME_BEFORE_UNMOUNT = 200;
|
|
1783
|
+
function cn(...classes) {
|
|
1784
|
+
return classes.filter(Boolean).join(" ");
|
|
1785
|
+
}
|
|
1786
|
+
function getDefaultSwipeDirections(position) {
|
|
1787
|
+
const [y, x] = position.split("-");
|
|
1788
|
+
const directions = [];
|
|
1789
|
+
if (y) {
|
|
1790
|
+
directions.push(y);
|
|
1791
|
+
}
|
|
1792
|
+
if (x) {
|
|
1793
|
+
directions.push(x);
|
|
1794
|
+
}
|
|
1795
|
+
return directions;
|
|
1796
|
+
}
|
|
1797
|
+
var Toast = (props) => {
|
|
1798
|
+
var _toast_classNames, _toast_classNames1, _toast_classNames2, _toast_classNames3, _toast_classNames4, _toast_classNames5, _toast_classNames6, _toast_classNames7, _toast_classNames8;
|
|
1799
|
+
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;
|
|
1800
|
+
const [swipeDirection, setSwipeDirection] = React.useState(null);
|
|
1801
|
+
const [swipeOutDirection, setSwipeOutDirection] = React.useState(null);
|
|
1802
|
+
const [mounted, setMounted] = React.useState(false);
|
|
1803
|
+
const [removed, setRemoved] = React.useState(false);
|
|
1804
|
+
const [swiping, setSwiping] = React.useState(false);
|
|
1805
|
+
const [swipeOut, setSwipeOut] = React.useState(false);
|
|
1806
|
+
const [isSwiped, setIsSwiped] = React.useState(false);
|
|
1807
|
+
const [offsetBeforeRemove, setOffsetBeforeRemove] = React.useState(0);
|
|
1808
|
+
const [initialHeight, setInitialHeight] = React.useState(0);
|
|
1809
|
+
const remainingTime = React.useRef(toast2.duration || durationFromToaster || TOAST_LIFETIME);
|
|
1810
|
+
const dragStartTime = React.useRef(null);
|
|
1811
|
+
const toastRef = React.useRef(null);
|
|
1812
|
+
const isFront = index === 0;
|
|
1813
|
+
const isVisible = index + 1 <= visibleToasts;
|
|
1814
|
+
const toastType = toast2.type;
|
|
1815
|
+
const dismissible = toast2.dismissible !== false;
|
|
1816
|
+
const toastClassname = toast2.className || "";
|
|
1817
|
+
const toastDescriptionClassname = toast2.descriptionClassName || "";
|
|
1818
|
+
const heightIndex = React.useMemo(() => heights.findIndex((height) => height.toastId === toast2.id) || 0, [
|
|
1819
|
+
heights,
|
|
1820
|
+
toast2.id
|
|
1821
|
+
]);
|
|
1822
|
+
const closeButton = React.useMemo(() => {
|
|
1823
|
+
var _toast_closeButton;
|
|
1824
|
+
return (_toast_closeButton = toast2.closeButton) != null ? _toast_closeButton : closeButtonFromToaster;
|
|
1825
|
+
}, [
|
|
1826
|
+
toast2.closeButton,
|
|
1827
|
+
closeButtonFromToaster
|
|
1828
|
+
]);
|
|
1829
|
+
const duration = React.useMemo(() => toast2.duration || durationFromToaster || TOAST_LIFETIME, [
|
|
1830
|
+
toast2.duration,
|
|
1831
|
+
durationFromToaster
|
|
1832
|
+
]);
|
|
1833
|
+
const closeTimerStartTimeRef = React.useRef(0);
|
|
1834
|
+
const offset = React.useRef(0);
|
|
1835
|
+
const lastCloseTimerStartTimeRef = React.useRef(0);
|
|
1836
|
+
const pointerStartRef = React.useRef(null);
|
|
1837
|
+
const [y, x] = position.split("-");
|
|
1838
|
+
const toastsHeightBefore = React.useMemo(() => {
|
|
1839
|
+
return heights.reduce((prev, curr, reducerIndex) => {
|
|
1840
|
+
if (reducerIndex >= heightIndex) {
|
|
1841
|
+
return prev;
|
|
1842
|
+
}
|
|
1843
|
+
return prev + curr.height;
|
|
1844
|
+
}, 0);
|
|
1845
|
+
}, [
|
|
1846
|
+
heights,
|
|
1847
|
+
heightIndex
|
|
1848
|
+
]);
|
|
1849
|
+
const isDocumentHidden = useIsDocumentHidden();
|
|
1850
|
+
const invert = toast2.invert || ToasterInvert;
|
|
1851
|
+
const disabled = toastType === "loading";
|
|
1852
|
+
offset.current = React.useMemo(() => heightIndex * gap + toastsHeightBefore, [
|
|
1853
|
+
heightIndex,
|
|
1854
|
+
toastsHeightBefore
|
|
1855
|
+
]);
|
|
1856
|
+
React.useEffect(() => {
|
|
1857
|
+
remainingTime.current = duration;
|
|
1858
|
+
}, [
|
|
1859
|
+
duration
|
|
1860
|
+
]);
|
|
1861
|
+
React.useEffect(() => {
|
|
1862
|
+
setMounted(true);
|
|
1863
|
+
}, []);
|
|
1864
|
+
React.useEffect(() => {
|
|
1865
|
+
const toastNode = toastRef.current;
|
|
1866
|
+
if (toastNode) {
|
|
1867
|
+
const height = toastNode.getBoundingClientRect().height;
|
|
1868
|
+
setInitialHeight(height);
|
|
1869
|
+
setHeights((h) => [
|
|
1870
|
+
{
|
|
1871
|
+
toastId: toast2.id,
|
|
1872
|
+
height,
|
|
1873
|
+
position: toast2.position
|
|
1874
|
+
},
|
|
1875
|
+
...h
|
|
1876
|
+
]);
|
|
1877
|
+
return () => setHeights((h) => h.filter((height2) => height2.toastId !== toast2.id));
|
|
1878
|
+
}
|
|
1879
|
+
}, [
|
|
1880
|
+
setHeights,
|
|
1881
|
+
toast2.id
|
|
1882
|
+
]);
|
|
1883
|
+
React.useLayoutEffect(() => {
|
|
1884
|
+
if (!mounted) return;
|
|
1885
|
+
const toastNode = toastRef.current;
|
|
1886
|
+
const originalHeight = toastNode.style.height;
|
|
1887
|
+
toastNode.style.height = "auto";
|
|
1888
|
+
const newHeight = toastNode.getBoundingClientRect().height;
|
|
1889
|
+
toastNode.style.height = originalHeight;
|
|
1890
|
+
setInitialHeight(newHeight);
|
|
1891
|
+
setHeights((heights2) => {
|
|
1892
|
+
const alreadyExists = heights2.find((height) => height.toastId === toast2.id);
|
|
1893
|
+
if (!alreadyExists) {
|
|
1894
|
+
return [
|
|
1895
|
+
{
|
|
1896
|
+
toastId: toast2.id,
|
|
1897
|
+
height: newHeight,
|
|
1898
|
+
position: toast2.position
|
|
1899
|
+
},
|
|
1900
|
+
...heights2
|
|
1901
|
+
];
|
|
1902
|
+
} else {
|
|
1903
|
+
return heights2.map((height) => height.toastId === toast2.id ? {
|
|
1904
|
+
...height,
|
|
1905
|
+
height: newHeight
|
|
1906
|
+
} : height);
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
}, [
|
|
1910
|
+
mounted,
|
|
1911
|
+
toast2.title,
|
|
1912
|
+
toast2.description,
|
|
1913
|
+
setHeights,
|
|
1914
|
+
toast2.id,
|
|
1915
|
+
toast2.jsx,
|
|
1916
|
+
toast2.action,
|
|
1917
|
+
toast2.cancel
|
|
1918
|
+
]);
|
|
1919
|
+
const deleteToast = React.useCallback(() => {
|
|
1920
|
+
setRemoved(true);
|
|
1921
|
+
setOffsetBeforeRemove(offset.current);
|
|
1922
|
+
setHeights((h) => h.filter((height) => height.toastId !== toast2.id));
|
|
1923
|
+
setTimeout(() => {
|
|
1924
|
+
removeToast(toast2);
|
|
1925
|
+
}, TIME_BEFORE_UNMOUNT);
|
|
1926
|
+
}, [
|
|
1927
|
+
toast2,
|
|
1928
|
+
removeToast,
|
|
1929
|
+
setHeights,
|
|
1930
|
+
offset
|
|
1931
|
+
]);
|
|
1932
|
+
React.useEffect(() => {
|
|
1933
|
+
if (toast2.promise && toastType === "loading" || toast2.duration === Infinity || toast2.type === "loading") return;
|
|
1934
|
+
let timeoutId;
|
|
1935
|
+
const pauseTimer = () => {
|
|
1936
|
+
if (lastCloseTimerStartTimeRef.current < closeTimerStartTimeRef.current) {
|
|
1937
|
+
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - closeTimerStartTimeRef.current;
|
|
1938
|
+
remainingTime.current = remainingTime.current - elapsedTime;
|
|
1939
|
+
}
|
|
1940
|
+
lastCloseTimerStartTimeRef.current = (/* @__PURE__ */ new Date()).getTime();
|
|
1941
|
+
};
|
|
1942
|
+
const startTimer = () => {
|
|
1943
|
+
if (remainingTime.current === Infinity) return;
|
|
1944
|
+
closeTimerStartTimeRef.current = (/* @__PURE__ */ new Date()).getTime();
|
|
1945
|
+
timeoutId = setTimeout(() => {
|
|
1946
|
+
toast2.onAutoClose == null ? void 0 : toast2.onAutoClose.call(toast2, toast2);
|
|
1947
|
+
deleteToast();
|
|
1948
|
+
}, remainingTime.current);
|
|
1949
|
+
};
|
|
1950
|
+
if (expanded || interacting || isDocumentHidden) {
|
|
1951
|
+
pauseTimer();
|
|
1952
|
+
} else {
|
|
1953
|
+
startTimer();
|
|
1954
|
+
}
|
|
1955
|
+
return () => clearTimeout(timeoutId);
|
|
1956
|
+
}, [
|
|
1957
|
+
expanded,
|
|
1958
|
+
interacting,
|
|
1959
|
+
toast2,
|
|
1960
|
+
toastType,
|
|
1961
|
+
isDocumentHidden,
|
|
1962
|
+
deleteToast
|
|
1963
|
+
]);
|
|
1964
|
+
React.useEffect(() => {
|
|
1965
|
+
if (toast2.delete) {
|
|
1966
|
+
deleteToast();
|
|
1967
|
+
toast2.onDismiss == null ? void 0 : toast2.onDismiss.call(toast2, toast2);
|
|
1968
|
+
}
|
|
1969
|
+
}, [
|
|
1970
|
+
deleteToast,
|
|
1971
|
+
toast2.delete
|
|
1972
|
+
]);
|
|
1973
|
+
function getLoadingIcon() {
|
|
1974
|
+
var _toast_classNames9;
|
|
1975
|
+
if (icons == null ? void 0 : icons.loading) {
|
|
1976
|
+
var _toast_classNames12;
|
|
1977
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1978
|
+
className: cn(classNames == null ? void 0 : classNames.loader, toast2 == null ? void 0 : (_toast_classNames12 = toast2.classNames) == null ? void 0 : _toast_classNames12.loader, "sonner-loader"),
|
|
1979
|
+
"data-visible": toastType === "loading"
|
|
1980
|
+
}, icons.loading);
|
|
1981
|
+
}
|
|
1982
|
+
return /* @__PURE__ */ React.createElement(Loader, {
|
|
1983
|
+
className: cn(classNames == null ? void 0 : classNames.loader, toast2 == null ? void 0 : (_toast_classNames9 = toast2.classNames) == null ? void 0 : _toast_classNames9.loader),
|
|
1984
|
+
visible: toastType === "loading"
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
const icon = toast2.icon || (icons == null ? void 0 : icons[toastType]) || getAsset(toastType);
|
|
1988
|
+
var _toast_richColors, _icons_close;
|
|
1989
|
+
return /* @__PURE__ */ React.createElement("li", {
|
|
1990
|
+
tabIndex: 0,
|
|
1991
|
+
ref: toastRef,
|
|
1992
|
+
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]),
|
|
1993
|
+
"data-sonner-toast": "",
|
|
1994
|
+
"data-rich-colors": (_toast_richColors = toast2.richColors) != null ? _toast_richColors : defaultRichColors,
|
|
1995
|
+
"data-styled": !Boolean(toast2.jsx || toast2.unstyled || unstyled),
|
|
1996
|
+
"data-mounted": mounted,
|
|
1997
|
+
"data-promise": Boolean(toast2.promise),
|
|
1998
|
+
"data-swiped": isSwiped,
|
|
1999
|
+
"data-removed": removed,
|
|
2000
|
+
"data-visible": isVisible,
|
|
2001
|
+
"data-y-position": y,
|
|
2002
|
+
"data-x-position": x,
|
|
2003
|
+
"data-index": index,
|
|
2004
|
+
"data-front": isFront,
|
|
2005
|
+
"data-swiping": swiping,
|
|
2006
|
+
"data-dismissible": dismissible,
|
|
2007
|
+
"data-type": toastType,
|
|
2008
|
+
"data-invert": invert,
|
|
2009
|
+
"data-swipe-out": swipeOut,
|
|
2010
|
+
"data-swipe-direction": swipeOutDirection,
|
|
2011
|
+
"data-expanded": Boolean(expanded || expandByDefault && mounted),
|
|
2012
|
+
"data-testid": toast2.testId,
|
|
2013
|
+
style: {
|
|
2014
|
+
"--index": index,
|
|
2015
|
+
"--toasts-before": index,
|
|
2016
|
+
"--z-index": toasts.length - index,
|
|
2017
|
+
"--offset": `${removed ? offsetBeforeRemove : offset.current}px`,
|
|
2018
|
+
"--initial-height": expandByDefault ? "auto" : `${initialHeight}px`,
|
|
2019
|
+
...style,
|
|
2020
|
+
...toast2.style
|
|
2021
|
+
},
|
|
2022
|
+
onDragEnd: () => {
|
|
2023
|
+
setSwiping(false);
|
|
2024
|
+
setSwipeDirection(null);
|
|
2025
|
+
pointerStartRef.current = null;
|
|
2026
|
+
},
|
|
2027
|
+
onPointerDown: (event) => {
|
|
2028
|
+
if (event.button === 2) return;
|
|
2029
|
+
if (disabled || !dismissible) return;
|
|
2030
|
+
dragStartTime.current = /* @__PURE__ */ new Date();
|
|
2031
|
+
setOffsetBeforeRemove(offset.current);
|
|
2032
|
+
event.target.setPointerCapture(event.pointerId);
|
|
2033
|
+
if (event.target.tagName === "BUTTON") return;
|
|
2034
|
+
setSwiping(true);
|
|
2035
|
+
pointerStartRef.current = {
|
|
2036
|
+
x: event.clientX,
|
|
2037
|
+
y: event.clientY
|
|
2038
|
+
};
|
|
2039
|
+
},
|
|
2040
|
+
onPointerUp: () => {
|
|
2041
|
+
var _toastRef_current, _toastRef_current1, _dragStartTime_current;
|
|
2042
|
+
if (swipeOut || !dismissible) return;
|
|
2043
|
+
pointerStartRef.current = null;
|
|
2044
|
+
const swipeAmountX = Number(((_toastRef_current = toastRef.current) == null ? void 0 : _toastRef_current.style.getPropertyValue("--swipe-amount-x").replace("px", "")) || 0);
|
|
2045
|
+
const swipeAmountY = Number(((_toastRef_current1 = toastRef.current) == null ? void 0 : _toastRef_current1.style.getPropertyValue("--swipe-amount-y").replace("px", "")) || 0);
|
|
2046
|
+
const timeTaken = (/* @__PURE__ */ new Date()).getTime() - ((_dragStartTime_current = dragStartTime.current) == null ? void 0 : _dragStartTime_current.getTime());
|
|
2047
|
+
const swipeAmount = swipeDirection === "x" ? swipeAmountX : swipeAmountY;
|
|
2048
|
+
const velocity = Math.abs(swipeAmount) / timeTaken;
|
|
2049
|
+
if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {
|
|
2050
|
+
setOffsetBeforeRemove(offset.current);
|
|
2051
|
+
toast2.onDismiss == null ? void 0 : toast2.onDismiss.call(toast2, toast2);
|
|
2052
|
+
if (swipeDirection === "x") {
|
|
2053
|
+
setSwipeOutDirection(swipeAmountX > 0 ? "right" : "left");
|
|
2054
|
+
} else {
|
|
2055
|
+
setSwipeOutDirection(swipeAmountY > 0 ? "down" : "up");
|
|
2056
|
+
}
|
|
2057
|
+
deleteToast();
|
|
2058
|
+
setSwipeOut(true);
|
|
2059
|
+
return;
|
|
2060
|
+
} else {
|
|
2061
|
+
var _toastRef_current2, _toastRef_current3;
|
|
2062
|
+
(_toastRef_current2 = toastRef.current) == null ? void 0 : _toastRef_current2.style.setProperty("--swipe-amount-x", `0px`);
|
|
2063
|
+
(_toastRef_current3 = toastRef.current) == null ? void 0 : _toastRef_current3.style.setProperty("--swipe-amount-y", `0px`);
|
|
2064
|
+
}
|
|
2065
|
+
setIsSwiped(false);
|
|
2066
|
+
setSwiping(false);
|
|
2067
|
+
setSwipeDirection(null);
|
|
2068
|
+
},
|
|
2069
|
+
onPointerMove: (event) => {
|
|
2070
|
+
var _window_getSelection, _toastRef_current, _toastRef_current1;
|
|
2071
|
+
if (!pointerStartRef.current || !dismissible) return;
|
|
2072
|
+
const isHighlighted = ((_window_getSelection = window.getSelection()) == null ? void 0 : _window_getSelection.toString().length) > 0;
|
|
2073
|
+
if (isHighlighted) return;
|
|
2074
|
+
const yDelta = event.clientY - pointerStartRef.current.y;
|
|
2075
|
+
const xDelta = event.clientX - pointerStartRef.current.x;
|
|
2076
|
+
var _props_swipeDirections;
|
|
2077
|
+
const swipeDirections = (_props_swipeDirections = props.swipeDirections) != null ? _props_swipeDirections : getDefaultSwipeDirections(position);
|
|
2078
|
+
if (!swipeDirection && (Math.abs(xDelta) > 1 || Math.abs(yDelta) > 1)) {
|
|
2079
|
+
setSwipeDirection(Math.abs(xDelta) > Math.abs(yDelta) ? "x" : "y");
|
|
2080
|
+
}
|
|
2081
|
+
let swipeAmount = {
|
|
2082
|
+
x: 0,
|
|
2083
|
+
y: 0
|
|
2084
|
+
};
|
|
2085
|
+
const getDampening = (delta) => {
|
|
2086
|
+
const factor = Math.abs(delta) / 20;
|
|
2087
|
+
return 1 / (1.5 + factor);
|
|
2088
|
+
};
|
|
2089
|
+
if (swipeDirection === "y") {
|
|
2090
|
+
if (swipeDirections.includes("top") || swipeDirections.includes("bottom")) {
|
|
2091
|
+
if (swipeDirections.includes("top") && yDelta < 0 || swipeDirections.includes("bottom") && yDelta > 0) {
|
|
2092
|
+
swipeAmount.y = yDelta;
|
|
2093
|
+
} else {
|
|
2094
|
+
const dampenedDelta = yDelta * getDampening(yDelta);
|
|
2095
|
+
swipeAmount.y = Math.abs(dampenedDelta) < Math.abs(yDelta) ? dampenedDelta : yDelta;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
} else if (swipeDirection === "x") {
|
|
2099
|
+
if (swipeDirections.includes("left") || swipeDirections.includes("right")) {
|
|
2100
|
+
if (swipeDirections.includes("left") && xDelta < 0 || swipeDirections.includes("right") && xDelta > 0) {
|
|
2101
|
+
swipeAmount.x = xDelta;
|
|
2102
|
+
} else {
|
|
2103
|
+
const dampenedDelta = xDelta * getDampening(xDelta);
|
|
2104
|
+
swipeAmount.x = Math.abs(dampenedDelta) < Math.abs(xDelta) ? dampenedDelta : xDelta;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
if (Math.abs(swipeAmount.x) > 0 || Math.abs(swipeAmount.y) > 0) {
|
|
2109
|
+
setIsSwiped(true);
|
|
2110
|
+
}
|
|
2111
|
+
(_toastRef_current = toastRef.current) == null ? void 0 : _toastRef_current.style.setProperty("--swipe-amount-x", `${swipeAmount.x}px`);
|
|
2112
|
+
(_toastRef_current1 = toastRef.current) == null ? void 0 : _toastRef_current1.style.setProperty("--swipe-amount-y", `${swipeAmount.y}px`);
|
|
2113
|
+
}
|
|
2114
|
+
}, closeButton && !toast2.jsx && toastType !== "loading" ? /* @__PURE__ */ React.createElement("button", {
|
|
2115
|
+
"aria-label": closeButtonAriaLabel,
|
|
2116
|
+
"data-disabled": disabled,
|
|
2117
|
+
"data-close-button": true,
|
|
2118
|
+
onClick: disabled || !dismissible ? () => {
|
|
2119
|
+
} : () => {
|
|
2120
|
+
deleteToast();
|
|
2121
|
+
toast2.onDismiss == null ? void 0 : toast2.onDismiss.call(toast2, toast2);
|
|
2122
|
+
},
|
|
2123
|
+
className: cn(classNames == null ? void 0 : classNames.closeButton, toast2 == null ? void 0 : (_toast_classNames2 = toast2.classNames) == null ? void 0 : _toast_classNames2.closeButton)
|
|
2124
|
+
}, (_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.createElement("div", {
|
|
2125
|
+
"data-icon": "",
|
|
2126
|
+
className: cn(classNames == null ? void 0 : classNames.icon, toast2 == null ? void 0 : (_toast_classNames3 = toast2.classNames) == null ? void 0 : _toast_classNames3.icon)
|
|
2127
|
+
}, toast2.promise || toast2.type === "loading" && !toast2.icon ? toast2.icon || getLoadingIcon() : null, toast2.type !== "loading" ? icon : null) : null, /* @__PURE__ */ React.createElement("div", {
|
|
2128
|
+
"data-content": "",
|
|
2129
|
+
className: cn(classNames == null ? void 0 : classNames.content, toast2 == null ? void 0 : (_toast_classNames4 = toast2.classNames) == null ? void 0 : _toast_classNames4.content)
|
|
2130
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
2131
|
+
"data-title": "",
|
|
2132
|
+
className: cn(classNames == null ? void 0 : classNames.title, toast2 == null ? void 0 : (_toast_classNames5 = toast2.classNames) == null ? void 0 : _toast_classNames5.title)
|
|
2133
|
+
}, toast2.jsx ? toast2.jsx : typeof toast2.title === "function" ? toast2.title() : toast2.title), toast2.description ? /* @__PURE__ */ React.createElement("div", {
|
|
2134
|
+
"data-description": "",
|
|
2135
|
+
className: cn(descriptionClassName, toastDescriptionClassname, classNames == null ? void 0 : classNames.description, toast2 == null ? void 0 : (_toast_classNames6 = toast2.classNames) == null ? void 0 : _toast_classNames6.description)
|
|
2136
|
+
}, typeof toast2.description === "function" ? toast2.description() : toast2.description) : null), /* @__PURE__ */ React.isValidElement(toast2.cancel) ? toast2.cancel : toast2.cancel && isAction(toast2.cancel) ? /* @__PURE__ */ React.createElement("button", {
|
|
2137
|
+
"data-button": true,
|
|
2138
|
+
"data-cancel": true,
|
|
2139
|
+
style: toast2.cancelButtonStyle || cancelButtonStyle,
|
|
2140
|
+
onClick: (event) => {
|
|
2141
|
+
if (!isAction(toast2.cancel)) return;
|
|
2142
|
+
if (!dismissible) return;
|
|
2143
|
+
toast2.cancel.onClick == null ? void 0 : toast2.cancel.onClick.call(toast2.cancel, event);
|
|
2144
|
+
deleteToast();
|
|
2145
|
+
},
|
|
2146
|
+
className: cn(classNames == null ? void 0 : classNames.cancelButton, toast2 == null ? void 0 : (_toast_classNames7 = toast2.classNames) == null ? void 0 : _toast_classNames7.cancelButton)
|
|
2147
|
+
}, toast2.cancel.label) : null, /* @__PURE__ */ React.isValidElement(toast2.action) ? toast2.action : toast2.action && isAction(toast2.action) ? /* @__PURE__ */ React.createElement("button", {
|
|
2148
|
+
"data-button": true,
|
|
2149
|
+
"data-action": true,
|
|
2150
|
+
style: toast2.actionButtonStyle || actionButtonStyle,
|
|
2151
|
+
onClick: (event) => {
|
|
2152
|
+
if (!isAction(toast2.action)) return;
|
|
2153
|
+
toast2.action.onClick == null ? void 0 : toast2.action.onClick.call(toast2.action, event);
|
|
2154
|
+
if (event.defaultPrevented) return;
|
|
2155
|
+
deleteToast();
|
|
2156
|
+
},
|
|
2157
|
+
className: cn(classNames == null ? void 0 : classNames.actionButton, toast2 == null ? void 0 : (_toast_classNames8 = toast2.classNames) == null ? void 0 : _toast_classNames8.actionButton)
|
|
2158
|
+
}, toast2.action.label) : null);
|
|
2159
|
+
};
|
|
2160
|
+
function getDocumentDirection() {
|
|
2161
|
+
if (typeof window === "undefined") return "ltr";
|
|
2162
|
+
if (typeof document === "undefined") return "ltr";
|
|
2163
|
+
const dirAttribute = document.documentElement.getAttribute("dir");
|
|
2164
|
+
if (dirAttribute === "auto" || !dirAttribute) {
|
|
2165
|
+
return window.getComputedStyle(document.documentElement).direction;
|
|
2166
|
+
}
|
|
2167
|
+
return dirAttribute;
|
|
2168
|
+
}
|
|
2169
|
+
function assignOffset(defaultOffset, mobileOffset) {
|
|
2170
|
+
const styles = {};
|
|
2171
|
+
[
|
|
2172
|
+
defaultOffset,
|
|
2173
|
+
mobileOffset
|
|
2174
|
+
].forEach((offset, index) => {
|
|
2175
|
+
const isMobile = index === 1;
|
|
2176
|
+
const prefix = isMobile ? "--mobile-offset" : "--offset";
|
|
2177
|
+
const defaultValue = isMobile ? MOBILE_VIEWPORT_OFFSET : VIEWPORT_OFFSET;
|
|
2178
|
+
function assignAll(offset2) {
|
|
2179
|
+
[
|
|
2180
|
+
"top",
|
|
2181
|
+
"right",
|
|
2182
|
+
"bottom",
|
|
2183
|
+
"left"
|
|
2184
|
+
].forEach((key) => {
|
|
2185
|
+
styles[`${prefix}-${key}`] = typeof offset2 === "number" ? `${offset2}px` : offset2;
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
if (typeof offset === "number" || typeof offset === "string") {
|
|
2189
|
+
assignAll(offset);
|
|
2190
|
+
} else if (typeof offset === "object") {
|
|
2191
|
+
[
|
|
2192
|
+
"top",
|
|
2193
|
+
"right",
|
|
2194
|
+
"bottom",
|
|
2195
|
+
"left"
|
|
2196
|
+
].forEach((key) => {
|
|
2197
|
+
if (offset[key] === void 0) {
|
|
2198
|
+
styles[`${prefix}-${key}`] = defaultValue;
|
|
2199
|
+
} else {
|
|
2200
|
+
styles[`${prefix}-${key}`] = typeof offset[key] === "number" ? `${offset[key]}px` : offset[key];
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
} else {
|
|
2204
|
+
assignAll(defaultValue);
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
return styles;
|
|
2208
|
+
}
|
|
2209
|
+
var Toaster = /* @__PURE__ */ React.forwardRef(function Toaster2(props, ref) {
|
|
2210
|
+
const { id, invert, position = "bottom-right", hotkey = [
|
|
2211
|
+
"altKey",
|
|
2212
|
+
"KeyT"
|
|
2213
|
+
], expand, closeButton, className, offset, mobileOffset, theme = "light", richColors, duration, style, visibleToasts = VISIBLE_TOASTS_AMOUNT, toastOptions, dir = getDocumentDirection(), gap = GAP, icons, containerAriaLabel = "Notifications" } = props;
|
|
2214
|
+
const [toasts, setToasts] = React.useState([]);
|
|
2215
|
+
const filteredToasts = React.useMemo(() => {
|
|
2216
|
+
if (id) {
|
|
2217
|
+
return toasts.filter((toast2) => toast2.toasterId === id);
|
|
2218
|
+
}
|
|
2219
|
+
return toasts.filter((toast2) => !toast2.toasterId);
|
|
2220
|
+
}, [
|
|
2221
|
+
toasts,
|
|
2222
|
+
id
|
|
2223
|
+
]);
|
|
2224
|
+
const possiblePositions = React.useMemo(() => {
|
|
2225
|
+
return Array.from(new Set([
|
|
2226
|
+
position
|
|
2227
|
+
].concat(filteredToasts.filter((toast2) => toast2.position).map((toast2) => toast2.position))));
|
|
2228
|
+
}, [
|
|
2229
|
+
filteredToasts,
|
|
2230
|
+
position
|
|
2231
|
+
]);
|
|
2232
|
+
const [heights, setHeights] = React.useState([]);
|
|
2233
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
2234
|
+
const [interacting, setInteracting] = React.useState(false);
|
|
2235
|
+
const [actualTheme, setActualTheme] = React.useState(theme !== "system" ? theme : typeof window !== "undefined" ? window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : "light");
|
|
2236
|
+
const listRef = React.useRef(null);
|
|
2237
|
+
const hotkeyLabel = hotkey.join("+").replace(/Key/g, "").replace(/Digit/g, "");
|
|
2238
|
+
const lastFocusedElementRef = React.useRef(null);
|
|
2239
|
+
const isFocusWithinRef = React.useRef(false);
|
|
2240
|
+
const removeToast = React.useCallback((toastToRemove) => {
|
|
2241
|
+
setToasts((toasts2) => {
|
|
2242
|
+
var _toasts_find;
|
|
2243
|
+
if (!((_toasts_find = toasts2.find((toast2) => toast2.id === toastToRemove.id)) == null ? void 0 : _toasts_find.delete)) {
|
|
2244
|
+
ToastState.dismiss(toastToRemove.id);
|
|
2245
|
+
}
|
|
2246
|
+
return toasts2.filter(({ id: id2 }) => id2 !== toastToRemove.id);
|
|
2247
|
+
});
|
|
2248
|
+
}, []);
|
|
2249
|
+
React.useEffect(() => {
|
|
2250
|
+
return ToastState.subscribe((toast2) => {
|
|
2251
|
+
if (toast2.dismiss) {
|
|
2252
|
+
requestAnimationFrame(() => {
|
|
2253
|
+
setToasts((toasts2) => toasts2.map((t) => t.id === toast2.id ? {
|
|
2254
|
+
...t,
|
|
2255
|
+
delete: true
|
|
2256
|
+
} : t));
|
|
2257
|
+
});
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
setTimeout(() => {
|
|
2261
|
+
ReactDOM.flushSync(() => {
|
|
2262
|
+
setToasts((toasts2) => {
|
|
2263
|
+
const indexOfExistingToast = toasts2.findIndex((t) => t.id === toast2.id);
|
|
2264
|
+
if (indexOfExistingToast !== -1) {
|
|
2265
|
+
return [
|
|
2266
|
+
...toasts2.slice(0, indexOfExistingToast),
|
|
2267
|
+
{
|
|
2268
|
+
...toasts2[indexOfExistingToast],
|
|
2269
|
+
...toast2
|
|
2270
|
+
},
|
|
2271
|
+
...toasts2.slice(indexOfExistingToast + 1)
|
|
2272
|
+
];
|
|
2273
|
+
}
|
|
2274
|
+
return [
|
|
2275
|
+
toast2,
|
|
2276
|
+
...toasts2
|
|
2277
|
+
];
|
|
2278
|
+
});
|
|
2279
|
+
});
|
|
2280
|
+
});
|
|
2281
|
+
});
|
|
2282
|
+
}, [
|
|
2283
|
+
toasts
|
|
2284
|
+
]);
|
|
2285
|
+
React.useEffect(() => {
|
|
2286
|
+
if (theme !== "system") {
|
|
2287
|
+
setActualTheme(theme);
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
if (theme === "system") {
|
|
2291
|
+
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
2292
|
+
setActualTheme("dark");
|
|
2293
|
+
} else {
|
|
2294
|
+
setActualTheme("light");
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
if (typeof window === "undefined") return;
|
|
2298
|
+
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
2299
|
+
try {
|
|
2300
|
+
darkMediaQuery.addEventListener("change", ({ matches }) => {
|
|
2301
|
+
if (matches) {
|
|
2302
|
+
setActualTheme("dark");
|
|
2303
|
+
} else {
|
|
2304
|
+
setActualTheme("light");
|
|
2305
|
+
}
|
|
2306
|
+
});
|
|
2307
|
+
} catch (error) {
|
|
2308
|
+
darkMediaQuery.addListener(({ matches }) => {
|
|
2309
|
+
try {
|
|
2310
|
+
if (matches) {
|
|
2311
|
+
setActualTheme("dark");
|
|
2312
|
+
} else {
|
|
2313
|
+
setActualTheme("light");
|
|
2314
|
+
}
|
|
2315
|
+
} catch (e) {
|
|
2316
|
+
console.error(e);
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
}, [
|
|
2321
|
+
theme
|
|
2322
|
+
]);
|
|
2323
|
+
React.useEffect(() => {
|
|
2324
|
+
if (toasts.length <= 1) {
|
|
2325
|
+
setExpanded(false);
|
|
2326
|
+
}
|
|
2327
|
+
}, [
|
|
2328
|
+
toasts
|
|
2329
|
+
]);
|
|
2330
|
+
React.useEffect(() => {
|
|
2331
|
+
const handleKeyDown = (event) => {
|
|
2332
|
+
var _listRef_current;
|
|
2333
|
+
const isHotkeyPressed = hotkey.every((key) => event[key] || event.code === key);
|
|
2334
|
+
if (isHotkeyPressed) {
|
|
2335
|
+
var _listRef_current1;
|
|
2336
|
+
setExpanded(true);
|
|
2337
|
+
(_listRef_current1 = listRef.current) == null ? void 0 : _listRef_current1.focus();
|
|
2338
|
+
}
|
|
2339
|
+
if (event.code === "Escape" && (document.activeElement === listRef.current || ((_listRef_current = listRef.current) == null ? void 0 : _listRef_current.contains(document.activeElement)))) {
|
|
2340
|
+
setExpanded(false);
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
2344
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
2345
|
+
}, [
|
|
2346
|
+
hotkey
|
|
2347
|
+
]);
|
|
2348
|
+
React.useEffect(() => {
|
|
2349
|
+
if (listRef.current) {
|
|
2350
|
+
return () => {
|
|
2351
|
+
if (lastFocusedElementRef.current) {
|
|
2352
|
+
lastFocusedElementRef.current.focus({
|
|
2353
|
+
preventScroll: true
|
|
2354
|
+
});
|
|
2355
|
+
lastFocusedElementRef.current = null;
|
|
2356
|
+
isFocusWithinRef.current = false;
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
}, [
|
|
2361
|
+
listRef.current
|
|
2362
|
+
]);
|
|
2363
|
+
return (
|
|
2364
|
+
// Remove item from normal navigation flow, only available via hotkey
|
|
2365
|
+
/* @__PURE__ */ React.createElement("section", {
|
|
2366
|
+
ref,
|
|
2367
|
+
"aria-label": `${containerAriaLabel} ${hotkeyLabel}`,
|
|
2368
|
+
tabIndex: -1,
|
|
2369
|
+
"aria-live": "polite",
|
|
2370
|
+
"aria-relevant": "additions text",
|
|
2371
|
+
"aria-atomic": "false",
|
|
2372
|
+
suppressHydrationWarning: true
|
|
2373
|
+
}, possiblePositions.map((position2, index) => {
|
|
2374
|
+
var _heights_;
|
|
2375
|
+
const [y, x] = position2.split("-");
|
|
2376
|
+
if (!filteredToasts.length) return null;
|
|
2377
|
+
return /* @__PURE__ */ React.createElement("ol", {
|
|
2378
|
+
key: position2,
|
|
2379
|
+
dir: dir === "auto" ? getDocumentDirection() : dir,
|
|
2380
|
+
tabIndex: -1,
|
|
2381
|
+
ref: listRef,
|
|
2382
|
+
className,
|
|
2383
|
+
"data-sonner-toaster": true,
|
|
2384
|
+
"data-sonner-theme": actualTheme,
|
|
2385
|
+
"data-y-position": y,
|
|
2386
|
+
"data-x-position": x,
|
|
2387
|
+
style: {
|
|
2388
|
+
"--front-toast-height": `${((_heights_ = heights[0]) == null ? void 0 : _heights_.height) || 0}px`,
|
|
2389
|
+
"--width": `${TOAST_WIDTH}px`,
|
|
2390
|
+
"--gap": `${gap}px`,
|
|
2391
|
+
...style,
|
|
2392
|
+
...assignOffset(offset, mobileOffset)
|
|
2393
|
+
},
|
|
2394
|
+
onBlur: (event) => {
|
|
2395
|
+
if (isFocusWithinRef.current && !event.currentTarget.contains(event.relatedTarget)) {
|
|
2396
|
+
isFocusWithinRef.current = false;
|
|
2397
|
+
if (lastFocusedElementRef.current) {
|
|
2398
|
+
lastFocusedElementRef.current.focus({
|
|
2399
|
+
preventScroll: true
|
|
2400
|
+
});
|
|
2401
|
+
lastFocusedElementRef.current = null;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
},
|
|
2405
|
+
onFocus: (event) => {
|
|
2406
|
+
const isNotDismissible = event.target instanceof HTMLElement && event.target.dataset.dismissible === "false";
|
|
2407
|
+
if (isNotDismissible) return;
|
|
2408
|
+
if (!isFocusWithinRef.current) {
|
|
2409
|
+
isFocusWithinRef.current = true;
|
|
2410
|
+
lastFocusedElementRef.current = event.relatedTarget;
|
|
2411
|
+
}
|
|
2412
|
+
},
|
|
2413
|
+
onMouseEnter: () => setExpanded(true),
|
|
2414
|
+
onMouseMove: () => setExpanded(true),
|
|
2415
|
+
onMouseLeave: () => {
|
|
2416
|
+
if (!interacting) {
|
|
2417
|
+
setExpanded(false);
|
|
2418
|
+
}
|
|
2419
|
+
},
|
|
2420
|
+
onDragEnd: () => setExpanded(false),
|
|
2421
|
+
onPointerDown: (event) => {
|
|
2422
|
+
const isNotDismissible = event.target instanceof HTMLElement && event.target.dataset.dismissible === "false";
|
|
2423
|
+
if (isNotDismissible) return;
|
|
2424
|
+
setInteracting(true);
|
|
2425
|
+
},
|
|
2426
|
+
onPointerUp: () => setInteracting(false)
|
|
2427
|
+
}, filteredToasts.filter((toast2) => !toast2.position && index === 0 || toast2.position === position2).map((toast2, index2) => {
|
|
2428
|
+
var _toastOptions_duration, _toastOptions_closeButton;
|
|
2429
|
+
return /* @__PURE__ */ React.createElement(Toast, {
|
|
2430
|
+
key: toast2.id,
|
|
2431
|
+
icons,
|
|
2432
|
+
index: index2,
|
|
2433
|
+
toast: toast2,
|
|
2434
|
+
defaultRichColors: richColors,
|
|
2435
|
+
duration: (_toastOptions_duration = toastOptions == null ? void 0 : toastOptions.duration) != null ? _toastOptions_duration : duration,
|
|
2436
|
+
className: toastOptions == null ? void 0 : toastOptions.className,
|
|
2437
|
+
descriptionClassName: toastOptions == null ? void 0 : toastOptions.descriptionClassName,
|
|
2438
|
+
invert,
|
|
2439
|
+
visibleToasts,
|
|
2440
|
+
closeButton: (_toastOptions_closeButton = toastOptions == null ? void 0 : toastOptions.closeButton) != null ? _toastOptions_closeButton : closeButton,
|
|
2441
|
+
interacting,
|
|
2442
|
+
position: position2,
|
|
2443
|
+
style: toastOptions == null ? void 0 : toastOptions.style,
|
|
2444
|
+
unstyled: toastOptions == null ? void 0 : toastOptions.unstyled,
|
|
2445
|
+
classNames: toastOptions == null ? void 0 : toastOptions.classNames,
|
|
2446
|
+
cancelButtonStyle: toastOptions == null ? void 0 : toastOptions.cancelButtonStyle,
|
|
2447
|
+
actionButtonStyle: toastOptions == null ? void 0 : toastOptions.actionButtonStyle,
|
|
2448
|
+
closeButtonAriaLabel: toastOptions == null ? void 0 : toastOptions.closeButtonAriaLabel,
|
|
2449
|
+
removeToast,
|
|
2450
|
+
toasts: filteredToasts.filter((t) => t.position == toast2.position),
|
|
2451
|
+
heights: heights.filter((h) => h.position == toast2.position),
|
|
2452
|
+
setHeights,
|
|
2453
|
+
expandByDefault: expand,
|
|
2454
|
+
gap,
|
|
2455
|
+
expanded,
|
|
2456
|
+
swipeDirections: props.swipeDirections
|
|
2457
|
+
});
|
|
2458
|
+
}));
|
|
2459
|
+
}))
|
|
2460
|
+
);
|
|
2461
|
+
});
|
|
1582
2462
|
function FunnelToaster(props = {}) {
|
|
1583
2463
|
return createElement(Toaster, { position: "top-center", ...props });
|
|
1584
2464
|
}
|
|
1585
2465
|
function renderToaster(toaster) {
|
|
1586
2466
|
if (toaster === false) return null;
|
|
1587
|
-
return
|
|
2467
|
+
return /* @__PURE__ */ jsx(FunnelToaster, { ...toaster === true ? {} : toaster });
|
|
1588
2468
|
}
|
|
1589
2469
|
|
|
1590
2470
|
// src/state/namespaces.ts
|
|
@@ -1683,12 +2563,16 @@ function FunnelProvider({
|
|
|
1683
2563
|
getVisitorId: () => ctx.identity.visitorId
|
|
1684
2564
|
});
|
|
1685
2565
|
const busTracker = withBus(resolvedTracker, bus);
|
|
2566
|
+
const emailForCheckout = () => {
|
|
2567
|
+
const value2 = s.get("user.email");
|
|
2568
|
+
return typeof value2 === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value2) ? value2 : void 0;
|
|
2569
|
+
};
|
|
1686
2570
|
return {
|
|
1687
2571
|
funnel: createFunnel(s, ctx, busTracker, experiments ?? []),
|
|
1688
2572
|
store: s,
|
|
1689
2573
|
context: ctx,
|
|
1690
2574
|
catalog: buildCatalog(products ?? []),
|
|
1691
|
-
driver: checkout ?? createMockDriver(),
|
|
2575
|
+
driver: driverWithEmail(checkout ?? createMockDriver(), emailForCheckout),
|
|
1692
2576
|
tracker: busTracker,
|
|
1693
2577
|
bus
|
|
1694
2578
|
};
|
|
@@ -1791,6 +2675,230 @@ function useContextValue(path) {
|
|
|
1791
2675
|
[ctx, path]
|
|
1792
2676
|
);
|
|
1793
2677
|
}
|
|
2678
|
+
|
|
2679
|
+
// src/tracking/platformTracker.ts
|
|
2680
|
+
function validClickIds(acq) {
|
|
2681
|
+
return Object.entries(acq.clickIds ?? {}).filter(
|
|
2682
|
+
(e) => typeof e[1] === "string" && e[1] !== ""
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
function acquisitionQueryBag(acq) {
|
|
2686
|
+
const q = {};
|
|
2687
|
+
const utm = acq.utm ?? {};
|
|
2688
|
+
for (const [k, v] of Object.entries({
|
|
2689
|
+
utm_source: utm.source,
|
|
2690
|
+
utm_medium: utm.medium,
|
|
2691
|
+
utm_campaign: utm.campaign,
|
|
2692
|
+
utm_term: utm.term,
|
|
2693
|
+
utm_content: utm.content
|
|
2694
|
+
})) if (v) q[k] = v;
|
|
2695
|
+
for (const [k, v] of validClickIds(acq)) q[k] = v;
|
|
2696
|
+
if (acq.cookies?.fbp) q._fbp = acq.cookies.fbp;
|
|
2697
|
+
if (acq.cookies?.fbc) q._fbc = acq.cookies.fbc;
|
|
2698
|
+
return q;
|
|
2699
|
+
}
|
|
2700
|
+
function acquisitionAdAttribution(acq) {
|
|
2701
|
+
const a = {};
|
|
2702
|
+
for (const [k, v] of validClickIds(acq)) a[k] = v;
|
|
2703
|
+
if (acq.cookies?.fbp) a.fbp = acq.cookies.fbp;
|
|
2704
|
+
if (acq.cookies?.fbc) a.fbc = acq.cookies.fbc;
|
|
2705
|
+
else if (acq.clickIds?.fbclid) a.fbc = `fb.1.${Date.now()}.${acq.clickIds.fbclid}`;
|
|
2706
|
+
return a;
|
|
2707
|
+
}
|
|
2708
|
+
function createPlatformTracker(cfg) {
|
|
2709
|
+
if (typeof window === "undefined" || cfg.mode === "test") return createConsoleTracker();
|
|
2710
|
+
const base = cfg.apiBase.replace(/\/+$/, "");
|
|
2711
|
+
const url = `${base}/campaign/${encodeURIComponent(cfg.campaignId)}/event`;
|
|
2712
|
+
const sidKey = `af_sid:${cfg.campaignId}`;
|
|
2713
|
+
let sessionId = null;
|
|
2714
|
+
try {
|
|
2715
|
+
sessionId = window.sessionStorage.getItem(sidKey);
|
|
2716
|
+
} catch {
|
|
2717
|
+
}
|
|
2718
|
+
if (!sessionId && cfg.sessionId) sessionId = cfg.sessionId;
|
|
2719
|
+
let acquisition = null;
|
|
2720
|
+
let acquisitionSent = false;
|
|
2721
|
+
let chain = Promise.resolve();
|
|
2722
|
+
const send = async (event, data, userData) => {
|
|
2723
|
+
let mergedUserData = userData;
|
|
2724
|
+
let adAttribution;
|
|
2725
|
+
let attachingAcquisition = false;
|
|
2726
|
+
if (acquisition && !acquisitionSent) {
|
|
2727
|
+
attachingAcquisition = true;
|
|
2728
|
+
const query = acquisitionQueryBag(acquisition);
|
|
2729
|
+
mergedUserData = Object.keys(query).length ? { ...userData, query } : userData;
|
|
2730
|
+
const ad = acquisitionAdAttribution(acquisition);
|
|
2731
|
+
if (Object.keys(ad).length) adAttribution = ad;
|
|
2732
|
+
}
|
|
2733
|
+
const body = {
|
|
2734
|
+
campaignId: cfg.campaignId,
|
|
2735
|
+
funnelId: cfg.funnelId,
|
|
2736
|
+
event,
|
|
2737
|
+
sessionId: sessionId ?? void 0,
|
|
2738
|
+
visitorId: cfg.visitorId ?? void 0,
|
|
2739
|
+
eventId: typeof data?.eventId === "string" ? data.eventId : void 0,
|
|
2740
|
+
data: data ?? {},
|
|
2741
|
+
metadata: {
|
|
2742
|
+
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
2743
|
+
screen: typeof window.screen !== "undefined" ? `${window.screen.width}x${window.screen.height}` : void 0,
|
|
2744
|
+
timezone: (() => {
|
|
2745
|
+
try {
|
|
2746
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
2747
|
+
} catch {
|
|
2748
|
+
return void 0;
|
|
2749
|
+
}
|
|
2750
|
+
})(),
|
|
2751
|
+
referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0
|
|
2752
|
+
},
|
|
2753
|
+
...mergedUserData ? { userData: mergedUserData } : {},
|
|
2754
|
+
...adAttribution ? { adAttribution } : {}
|
|
2755
|
+
};
|
|
2756
|
+
const res = await fetch(url, {
|
|
2757
|
+
method: "POST",
|
|
2758
|
+
headers: { "content-type": "application/json" },
|
|
2759
|
+
body: JSON.stringify(body),
|
|
2760
|
+
keepalive: true
|
|
2761
|
+
// survives page.exit / unload
|
|
2762
|
+
});
|
|
2763
|
+
if (attachingAcquisition) acquisitionSent = true;
|
|
2764
|
+
const json = await res.json().catch(() => null);
|
|
2765
|
+
if (json?.sessionId && json.sessionId !== sessionId) {
|
|
2766
|
+
sessionId = json.sessionId;
|
|
2767
|
+
try {
|
|
2768
|
+
window.sessionStorage.setItem(sidKey, sessionId);
|
|
2769
|
+
} catch {
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
const enqueue = (event, data, userData) => {
|
|
2774
|
+
const task = () => send(event, data, userData).catch((e) => {
|
|
2775
|
+
if (process.env.NODE_ENV !== "production") console.warn(`[appfunnel] track failed (${event}):`, e);
|
|
2776
|
+
});
|
|
2777
|
+
if (!sessionId) {
|
|
2778
|
+
chain = chain.then(task);
|
|
2779
|
+
} else {
|
|
2780
|
+
void task();
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2783
|
+
const VARS_DEBOUNCE_MS = 800;
|
|
2784
|
+
let pendingVars = null;
|
|
2785
|
+
let varsTimer = null;
|
|
2786
|
+
const flushVars = () => {
|
|
2787
|
+
if (varsTimer) {
|
|
2788
|
+
clearTimeout(varsTimer);
|
|
2789
|
+
varsTimer = null;
|
|
2790
|
+
}
|
|
2791
|
+
if (!pendingVars) return;
|
|
2792
|
+
const doPost = () => {
|
|
2793
|
+
if (!pendingVars || !sessionId) return;
|
|
2794
|
+
const userData = pendingVars;
|
|
2795
|
+
pendingVars = null;
|
|
2796
|
+
void fetch(`${base}/campaign/${encodeURIComponent(cfg.campaignId)}/event/session/data`, {
|
|
2797
|
+
method: "POST",
|
|
2798
|
+
headers: { "content-type": "application/json" },
|
|
2799
|
+
body: JSON.stringify({ sessionId, userData }),
|
|
2800
|
+
keepalive: true
|
|
2801
|
+
}).catch((e) => {
|
|
2802
|
+
if (process.env.NODE_ENV !== "production") console.warn("[appfunnel] variable persist failed:", e);
|
|
2803
|
+
});
|
|
2804
|
+
};
|
|
2805
|
+
if (sessionId) doPost();
|
|
2806
|
+
else chain = chain.then(doPost);
|
|
2807
|
+
};
|
|
2808
|
+
try {
|
|
2809
|
+
window.addEventListener("pagehide", flushVars);
|
|
2810
|
+
} catch {
|
|
2811
|
+
}
|
|
2812
|
+
if (cfg.experiment?.id && cfg.experiment.variant) {
|
|
2813
|
+
enqueue("experiment.exposure", exposurePayload(cfg.experiment.id, cfg.experiment.variant, cfg.experiment.version));
|
|
2814
|
+
}
|
|
2815
|
+
return {
|
|
2816
|
+
track: (event, data, userData) => enqueue(event, data, userData),
|
|
2817
|
+
identify: (email, eventId) => enqueue("user.registered", { email, ...eventId ? { eventId } : {} }),
|
|
2818
|
+
setVariables: (variables) => {
|
|
2819
|
+
pendingVars = variables;
|
|
2820
|
+
if (varsTimer) clearTimeout(varsTimer);
|
|
2821
|
+
varsTimer = setTimeout(flushVars, VARS_DEBOUNCE_MS);
|
|
2822
|
+
},
|
|
2823
|
+
setAcquisition: (acq) => {
|
|
2824
|
+
acquisition = acq;
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// src/flow/mount.tsx
|
|
2830
|
+
var AssetsContext = createContext({});
|
|
2831
|
+
function AssetsProvider({
|
|
2832
|
+
assets,
|
|
2833
|
+
children
|
|
2834
|
+
}) {
|
|
2835
|
+
return createElement(AssetsContext.Provider, { value: assets ?? {} }, children);
|
|
2836
|
+
}
|
|
2837
|
+
function useAssets() {
|
|
2838
|
+
return useContext(AssetsContext);
|
|
2839
|
+
}
|
|
2840
|
+
function useAsset(name) {
|
|
2841
|
+
return useContext(AssetsContext)[name];
|
|
2842
|
+
}
|
|
2843
|
+
function createFunnelTree({
|
|
2844
|
+
config,
|
|
2845
|
+
pages,
|
|
2846
|
+
layout,
|
|
2847
|
+
checkoutDriver
|
|
2848
|
+
}) {
|
|
2849
|
+
return function tree(opts) {
|
|
2850
|
+
const mode = opts.mode ?? "live";
|
|
2851
|
+
const tracker = opts.tracking ? createPlatformTracker({
|
|
2852
|
+
...opts.tracking,
|
|
2853
|
+
visitorId: opts.visitorId,
|
|
2854
|
+
sessionId: opts.sessionId,
|
|
2855
|
+
mode
|
|
2856
|
+
}) : void 0;
|
|
2857
|
+
const checkout = checkoutDriver && opts.tracking ? checkoutDriver({
|
|
2858
|
+
apiBase: opts.tracking.apiBase,
|
|
2859
|
+
campaignId: opts.tracking.campaignId,
|
|
2860
|
+
funnelId: opts.tracking.funnelId,
|
|
2861
|
+
mode,
|
|
2862
|
+
sessionId: opts.sessionId,
|
|
2863
|
+
visitorId: opts.visitorId
|
|
2864
|
+
}) : void 0;
|
|
2865
|
+
const layoutWithResume = function LayoutWithCheckoutResume({
|
|
2866
|
+
children
|
|
2867
|
+
}) {
|
|
2868
|
+
return createElement(
|
|
2869
|
+
Fragment,
|
|
2870
|
+
null,
|
|
2871
|
+
createElement(CheckoutResume, null),
|
|
2872
|
+
layout ? createElement(layout, null, children) : children
|
|
2873
|
+
);
|
|
2874
|
+
};
|
|
2875
|
+
return createElement(AssetsProvider, {
|
|
2876
|
+
assets: opts.assets,
|
|
2877
|
+
children: createElement(FunnelProvider, {
|
|
2878
|
+
config,
|
|
2879
|
+
products: opts.products ?? [],
|
|
2880
|
+
locale: opts.locale,
|
|
2881
|
+
tracker,
|
|
2882
|
+
checkout,
|
|
2883
|
+
sessionValues: opts.sessionValues,
|
|
2884
|
+
experiments: opts.experiments,
|
|
2885
|
+
context: {
|
|
2886
|
+
mode,
|
|
2887
|
+
visitorId: opts.visitorId ?? null,
|
|
2888
|
+
customerId: opts.customerId ?? null,
|
|
2889
|
+
sessionId: opts.sessionId ?? null
|
|
2890
|
+
},
|
|
2891
|
+
children: createElement(FunnelView, {
|
|
2892
|
+
pages,
|
|
2893
|
+
layout: layoutWithResume,
|
|
2894
|
+
initialKey: opts.initialKey,
|
|
2895
|
+
prefetch: "off"
|
|
2896
|
+
// renderer background-modulepreloads all chunks
|
|
2897
|
+
})
|
|
2898
|
+
})
|
|
2899
|
+
});
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
1794
2902
|
function Choice({
|
|
1795
2903
|
bind,
|
|
1796
2904
|
options,
|
|
@@ -1930,95 +3038,6 @@ function Script({
|
|
|
1930
3038
|
return null;
|
|
1931
3039
|
}
|
|
1932
3040
|
|
|
1933
|
-
|
|
1934
|
-
function isVariantNode(page) {
|
|
1935
|
-
return isVariantKey(page.key);
|
|
1936
|
-
}
|
|
1937
|
-
function serializeGate(when) {
|
|
1938
|
-
if (!when) return void 0;
|
|
1939
|
-
if (typeof when === "function") return { kind: "predicate" };
|
|
1940
|
-
return { kind: "declarative", condition: when };
|
|
1941
|
-
}
|
|
1942
|
-
function compileManifest(input) {
|
|
1943
|
-
const { funnel, pages } = input;
|
|
1944
|
-
const keys = new Set(pages.map((p) => p.key));
|
|
1945
|
-
const manifestPages = pages.map((p, index) => ({
|
|
1946
|
-
id: p.key,
|
|
1947
|
-
key: p.key,
|
|
1948
|
-
type: p.meta?.type ?? "default",
|
|
1949
|
-
slug: p.meta?.slug ?? p.key,
|
|
1950
|
-
index,
|
|
1951
|
-
variantOf: isVariantKey(p.key) ? parseSlotKey(p.key).slot : void 0
|
|
1952
|
-
}));
|
|
1953
|
-
const edges = [];
|
|
1954
|
-
const badTargets = [];
|
|
1955
|
-
const nextAnchor = (from) => {
|
|
1956
|
-
for (let j = from + 1; j < pages.length; j++) {
|
|
1957
|
-
if (!isVariantNode(pages[j])) return pages[j];
|
|
1958
|
-
}
|
|
1959
|
-
return void 0;
|
|
1960
|
-
};
|
|
1961
|
-
pages.forEach((p, i) => {
|
|
1962
|
-
if (isVariantNode(p)) return;
|
|
1963
|
-
const routes = p.meta?.next ?? [];
|
|
1964
|
-
let hasUnconditional = false;
|
|
1965
|
-
for (const route of routes) {
|
|
1966
|
-
if (!keys.has(route.to)) {
|
|
1967
|
-
badTargets.push({ from: p.key, to: route.to });
|
|
1968
|
-
continue;
|
|
1969
|
-
}
|
|
1970
|
-
edges.push({ from: p.key, to: route.to, kind: "branch", condition: serializeGate(route.when) });
|
|
1971
|
-
if (!route.when) hasUnconditional = true;
|
|
1972
|
-
}
|
|
1973
|
-
const next = nextAnchor(i);
|
|
1974
|
-
if (!hasUnconditional && next && p.meta?.type !== "finish") {
|
|
1975
|
-
edges.push({ from: p.key, to: next.key, kind: "linear" });
|
|
1976
|
-
}
|
|
1977
|
-
});
|
|
1978
|
-
const startPage = pages.find((p) => !isVariantNode(p));
|
|
1979
|
-
const start = startPage?.key ?? null;
|
|
1980
|
-
const variantKeys = new Set(pages.filter(isVariantNode).map((p) => p.key));
|
|
1981
|
-
const validation = validate(manifestPages, edges, badTargets, start, variantKeys);
|
|
1982
|
-
return {
|
|
1983
|
-
id: funnel.id,
|
|
1984
|
-
pages: manifestPages,
|
|
1985
|
-
flow: { start, nodes: manifestPages.map((p) => p.id), edges },
|
|
1986
|
-
products: funnel.products ?? [],
|
|
1987
|
-
locales: funnel.locales,
|
|
1988
|
-
validation
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
|
-
function validate(pages, edges, badTargets, start, variantKeys) {
|
|
1992
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
1993
|
-
for (const e of edges) outgoing.set(e.from, (outgoing.get(e.from) ?? 0) + 1);
|
|
1994
|
-
const deadEnds = pages.filter((p) => p.type !== "finish" && !variantKeys.has(p.id) && !(outgoing.get(p.id) > 0)).map((p) => p.id);
|
|
1995
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
1996
|
-
if (start) {
|
|
1997
|
-
const adj = /* @__PURE__ */ new Map();
|
|
1998
|
-
for (const e of edges) {
|
|
1999
|
-
const list = adj.get(e.from);
|
|
2000
|
-
if (list) list.push(e.to);
|
|
2001
|
-
else adj.set(e.from, [e.to]);
|
|
2002
|
-
}
|
|
2003
|
-
const stack = [start];
|
|
2004
|
-
while (stack.length) {
|
|
2005
|
-
const id = stack.pop();
|
|
2006
|
-
if (reachable.has(id)) continue;
|
|
2007
|
-
reachable.add(id);
|
|
2008
|
-
for (const to of adj.get(id) ?? []) stack.push(to);
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
const unreachable = pages.filter((p) => !variantKeys.has(p.id) && !reachable.has(p.id)).map((p) => p.id);
|
|
2012
|
-
const missingEmailCapture = !pages.some((p) => p.type === "email-capture");
|
|
2013
|
-
return {
|
|
2014
|
-
ok: deadEnds.length === 0 && unreachable.length === 0 && badTargets.length === 0 && !missingEmailCapture,
|
|
2015
|
-
deadEnds,
|
|
2016
|
-
unreachable,
|
|
2017
|
-
badTargets,
|
|
2018
|
-
missingEmailCapture
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
export { Back, Checkout, Choice, EVENT_CATALOG, FunnelProvider, FunnelToaster, FunnelView, INLINE_SURFACES, Modal, Next, PROVIDER_PROFILES, Script, Sheet, Upsell, VariableStore, assignVariant, attachBus, bucketingSeed, buildAcquisition, buildCatalog, checkoutError, compileManifest, createBus, createConsoleTracker, createFunnelStore, createMockDriver, defineFunnel, defineModal, definePage, entryGuard, evaluateCondition, evaluateGate, expectedPathLength, fnv1a, formatProduct, hashToUnit, hideModal, integrationEvents, isInlineSurface, isIntegrationEvent, isMerchantOfRecord, isOrchestrator, isRtl, isTrackableEvent, isVariantKey, newEventId, nextPage, outgoingKeys, pageMeta, parseSlotKey, pickByWeight, registerModal, removeModal, resolveExperiments, resolveLocale, resolveProduct, resolveRoute, showModal, surfacesFor, unregisterModal, useCheckout, useClickIds, useContextValue, useData, useDevice, useExperiment, useField, useFunnel, useLocale, useModal, useNavigation, usePage, useProduct, useProducts, useResponse, useSystem, useTracker, useTranslation, useUserAttribute, useUtm, validateCheckout, validateExperiments, validateUpsell, withBus };
|
|
3041
|
+
export { AssetsContext, AssetsProvider, Back, Checkout, CheckoutResume, Choice, EVENT_CATALOG, FunnelProvider, FunnelToaster, FunnelView, Modal, Next, Script, Sheet, Upsell, VariableStore, attachBus, buildAcquisition, buildContext, createBus, createConsoleTracker, createFunnelStore, createFunnelTree, createMockDriver, createPlatformTracker, defineModal, driverWithEmail, hideModal, integrationEvents, isIntegrationEvent, isTrackableEvent, newEventId, registerModal, removeModal, showModal, toast, unregisterModal, useActiveLocale, useAsset, useAssets, useCheckout, useClickIds, useContextValue, useData, useDevice, useExperiment, useField, useFunnel, useLocale, useModal, useNavigation, usePage, useProduct, useProducts, useResponse, useSystem, useTracker, useTranslation, useUserAttribute, useUtm, withBus };
|
|
2023
3042
|
//# sourceMappingURL=index.js.map
|
|
2024
3043
|
//# sourceMappingURL=index.js.map
|