@revenuecat/purchases-ui-js 4.8.18 → 4.8.20
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/components/image/Image.svelte +13 -1
- package/dist/components/image/ImageTestWrapper.svelte +24 -0
- package/dist/components/image/ImageTestWrapper.svelte.d.ts +4 -0
- package/dist/components/image/view-box-size.d.ts +14 -0
- package/dist/components/image/view-box-size.js +30 -0
- package/dist/components/options/InputMultipleChoice.svelte +14 -0
- package/dist/components/paywall/Paywall.svelte +65 -4
- package/dist/components/paywall/Paywall.svelte.d.ts +7 -0
- package/dist/components/video/Video.svelte +9 -1
- package/dist/components/web-view/WebView.svelte +58 -1
- package/dist/components/web-view/WebViewTestWrapper.svelte +17 -3
- package/dist/components/web-view/WebViewTestWrapper.svelte.d.ts +5 -0
- package/dist/components/web-view/build-paywall-context.d.ts +45 -0
- package/dist/components/web-view/build-paywall-context.js +104 -0
- package/dist/components/web-view/web-view-sdk.d.ts +6 -5
- package/dist/components/workflows/Screen.svelte +29 -1
- package/dist/components/workflows/Screen.svelte.d.ts +9 -1
- package/dist/components/workflows/Workflow.svelte +33 -1
- package/dist/components/workflows/Workflow.svelte.d.ts +8 -1
- package/dist/index.d.ts +1 -0
- package/dist/stores/paywall.d.ts +13 -0
- package/dist/types/workflow.d.ts +8 -0
- package/dist/utils/style-utils.d.ts +5 -0
- package/dist/utils/style-utils.js +22 -0
- package/package.json +8 -10
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
} from "../../utils/base-utils";
|
|
13
13
|
import { resolveOverrideProperties } from "../../utils/style-utils";
|
|
14
14
|
import ClipPath from "./ClipPath.svelte";
|
|
15
|
+
import { viewBoxSize } from "./view-box-size";
|
|
15
16
|
import Overlay from "./Overlay.svelte";
|
|
16
17
|
|
|
17
18
|
const props: ImageProps = $props();
|
|
@@ -87,13 +88,24 @@
|
|
|
87
88
|
padding: mapSpacing(padding),
|
|
88
89
|
"line-height": 0,
|
|
89
90
|
"flex-shrink": size.width.type === "fixed" ? 0 : 1,
|
|
91
|
+
// min-height: 0 so a seeded viewBox cannot become a flex min-content
|
|
92
|
+
// square that eats the rest of a space-between stack (body copy).
|
|
93
|
+
...(size.width.type === "fixed"
|
|
94
|
+
? {}
|
|
95
|
+
: { "min-width": 0, "min-height": 0 }),
|
|
90
96
|
}),
|
|
91
97
|
);
|
|
92
98
|
|
|
93
99
|
let svgRect = $state<DOMRect | null>(null);
|
|
94
100
|
|
|
95
101
|
const [svgWidth, svgHeight] = $derived.by(() => {
|
|
96
|
-
|
|
102
|
+
// Hug-content on either axis: wait for contentRect. Seeding fill-width ×
|
|
103
|
+
// fit-height from wrapperWidth * aspect locks a column-width square.
|
|
104
|
+
if (size.width.type === "fit" || size.height.type === "fit") {
|
|
105
|
+
return [svgRect?.width ?? 0, svgRect?.height ?? 0];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return viewBoxSize(svgRect, wrapperWidth, height);
|
|
97
109
|
});
|
|
98
110
|
|
|
99
111
|
const viewBox = $derived.by(() => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { readable, writable } from "svelte/store";
|
|
3
|
+
import { setColorModeContext } from "../../stores/color-mode";
|
|
4
|
+
import { setPaywallContext } from "../../stores/paywall";
|
|
5
|
+
import Image from "./Image.svelte";
|
|
6
|
+
import type { ImageProps } from "../../types/components/image";
|
|
7
|
+
|
|
8
|
+
const props: ImageProps = $props();
|
|
9
|
+
|
|
10
|
+
setColorModeContext();
|
|
11
|
+
setPaywallContext({
|
|
12
|
+
selectedPackageId: writable(undefined),
|
|
13
|
+
variablesPerPackage: readable(undefined),
|
|
14
|
+
baseVariables: readable(undefined),
|
|
15
|
+
infoPerPackage: readable(undefined),
|
|
16
|
+
onPurchase: () => {},
|
|
17
|
+
emitComponentInteraction: () => {},
|
|
18
|
+
onButtonAction: () => {},
|
|
19
|
+
uiConfig: { app: { fonts: {} } } as never,
|
|
20
|
+
hideBackButtons: false,
|
|
21
|
+
});
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<Image {...props} />
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** First strictly positive dimension, else 0. Never invent a 1px axis — that becomes a 1:1 viewBox and blows up flex layout. */
|
|
2
|
+
export declare function viewBoxDimension(...values: (number | undefined)[]): number;
|
|
3
|
+
/**
|
|
4
|
+
* Prefer measured contentRect when both axes are known; otherwise seed width/height
|
|
5
|
+
* together from layout (wrapper size / computed height). Seed only when BOTH fallbacks
|
|
6
|
+
* are positive — a single `1` placeholder gives the SVG a real aspect ratio (SSR
|
|
7
|
+
* `height: NaNpx` + `viewBox="0 0 1 1"` → column-width square, body copy squeezed).
|
|
8
|
+
* Do not use natural media dimensions here — that breaks fit-content images whose
|
|
9
|
+
* box is still 0px wide.
|
|
10
|
+
*/
|
|
11
|
+
export declare function viewBoxSize(measured: {
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
} | null | undefined, fallbackWidth: number | undefined, fallbackHeight: number | undefined): [number, number];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** First strictly positive dimension, else 0. Never invent a 1px axis — that becomes a 1:1 viewBox and blows up flex layout. */
|
|
2
|
+
export function viewBoxDimension(...values) {
|
|
3
|
+
for (const value of values) {
|
|
4
|
+
if (value != null && value > 0) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Prefer measured contentRect when both axes are known; otherwise seed width/height
|
|
12
|
+
* together from layout (wrapper size / computed height). Seed only when BOTH fallbacks
|
|
13
|
+
* are positive — a single `1` placeholder gives the SVG a real aspect ratio (SSR
|
|
14
|
+
* `height: NaNpx` + `viewBox="0 0 1 1"` → column-width square, body copy squeezed).
|
|
15
|
+
* Do not use natural media dimensions here — that breaks fit-content images whose
|
|
16
|
+
* box is still 0px wide.
|
|
17
|
+
*/
|
|
18
|
+
export function viewBoxSize(measured, fallbackWidth, fallbackHeight) {
|
|
19
|
+
const measuredWidth = measured?.width ?? 0;
|
|
20
|
+
const measuredHeight = measured?.height ?? 0;
|
|
21
|
+
if (measuredWidth > 0 && measuredHeight > 0) {
|
|
22
|
+
return [measuredWidth, measuredHeight];
|
|
23
|
+
}
|
|
24
|
+
const width = viewBoxDimension(fallbackWidth);
|
|
25
|
+
const height = viewBoxDimension(fallbackHeight);
|
|
26
|
+
if (width > 0 && height > 0) {
|
|
27
|
+
return [width, height];
|
|
28
|
+
}
|
|
29
|
+
return [0, 0];
|
|
30
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Node from "../paywall/Node.svelte";
|
|
3
|
+
import { asBridgeInputArray } from "../web-view/build-paywall-context";
|
|
3
4
|
import {
|
|
4
5
|
createInputChoiceContext,
|
|
5
6
|
setInputChoiceContext,
|
|
@@ -8,6 +9,7 @@
|
|
|
8
9
|
getInputValidationContext,
|
|
9
10
|
getInitialInputSelectionsContext,
|
|
10
11
|
} from "../../stores/inputValidation";
|
|
12
|
+
import { getPaywallContext } from "../../stores/paywall";
|
|
11
13
|
import type { InputMultipleChoiceProps } from "../../types/components/options";
|
|
12
14
|
import { onDestroy } from "svelte";
|
|
13
15
|
|
|
@@ -26,6 +28,18 @@
|
|
|
26
28
|
);
|
|
27
29
|
setInputChoiceContext(inputChoiceContext);
|
|
28
30
|
|
|
31
|
+
// Keep this field a string[] so handleInputChanged toggles option_id
|
|
32
|
+
// instead of replacing the whole selection (including on deselect).
|
|
33
|
+
// Optional: stories and isolated mounts have no paywall host.
|
|
34
|
+
try {
|
|
35
|
+
getPaywallContext().bridgeInputs?.update((current) => ({
|
|
36
|
+
...current,
|
|
37
|
+
[field_id]: asBridgeInputArray(current[field_id] ?? initialSelectedIds),
|
|
38
|
+
}));
|
|
39
|
+
} catch {
|
|
40
|
+
// Paywall context not found
|
|
41
|
+
}
|
|
42
|
+
|
|
29
43
|
const validationContext = getInputValidationContext();
|
|
30
44
|
if (validationContext) {
|
|
31
45
|
const unsubscribe = inputChoiceContext.isSatisfied.subscribe(
|
|
@@ -43,7 +43,10 @@
|
|
|
43
43
|
import { STICKY_OVERLAY_Z_INDEX } from "../../utils/constants";
|
|
44
44
|
import { applyDocumentBackground } from "../../utils/document-background";
|
|
45
45
|
import { registerFonts } from "../../utils/font-utils";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
collectPackageIdsInDisplayOrder,
|
|
48
|
+
findSelectedPackageId,
|
|
49
|
+
} from "../../utils/style-utils";
|
|
47
50
|
import { onMount } from "svelte";
|
|
48
51
|
import { derived, readable, writable } from "svelte/store";
|
|
49
52
|
import Stack from "../stack/Stack.svelte";
|
|
@@ -56,6 +59,17 @@
|
|
|
56
59
|
} from "../../stores/packageInfo";
|
|
57
60
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
58
61
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
62
|
+
import type {
|
|
63
|
+
PaywallOffering,
|
|
64
|
+
PaywallPackage,
|
|
65
|
+
PaywallWorkflow,
|
|
66
|
+
} from "@revenuecat/workflow-web-components-sdk";
|
|
67
|
+
import {
|
|
68
|
+
applyBridgeInputChange,
|
|
69
|
+
buildBridgeInputs,
|
|
70
|
+
typedCustomVariables,
|
|
71
|
+
type BridgeInputs,
|
|
72
|
+
} from "../web-view/build-paywall-context";
|
|
59
73
|
|
|
60
74
|
/**
|
|
61
75
|
* Props are captured once at mount and are not reactive to subsequent changes.
|
|
@@ -130,6 +144,11 @@
|
|
|
130
144
|
* Hosts (e.g. workflow runtimes) pass their workflow-level fallback here.
|
|
131
145
|
*/
|
|
132
146
|
safeAreaFallbackColor?: ColorScheme | null;
|
|
147
|
+
offering?: PaywallOffering | null;
|
|
148
|
+
packages?: PaywallPackage[];
|
|
149
|
+
workflow?: PaywallWorkflow;
|
|
150
|
+
isPreview?: boolean;
|
|
151
|
+
inputs?: BridgeInputs;
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
const {
|
|
@@ -159,6 +178,11 @@
|
|
|
159
178
|
initialInputSelections = {},
|
|
160
179
|
customVariables = {},
|
|
161
180
|
safeAreaFallbackColor,
|
|
181
|
+
offering = null,
|
|
182
|
+
packages = [],
|
|
183
|
+
workflow,
|
|
184
|
+
isPreview = false,
|
|
185
|
+
inputs = {},
|
|
162
186
|
}: Props = $props();
|
|
163
187
|
|
|
164
188
|
const getColorMode = setColorModeContext(() => preferredColorMode);
|
|
@@ -305,22 +329,59 @@
|
|
|
305
329
|
}
|
|
306
330
|
};
|
|
307
331
|
|
|
332
|
+
// Merge this component's own `customVariables` prop with the dashboard
|
|
333
|
+
// defaults. On the standalone path, this is the only source of custom
|
|
334
|
+
// variable values. On the workflow path, the host has already merged its
|
|
335
|
+
// `customVariables` into `globalVariables` — that merge must win here, or
|
|
336
|
+
// this component's defaults-only merge (since `customVariables` is never
|
|
337
|
+
// passed down by the workflow) silently overwrites the host's overrides
|
|
338
|
+
// for any variable that also has a declared dashboard default.
|
|
308
339
|
const mergedCustomVars = mergeCustomVariables(customVariables, uiConfig);
|
|
340
|
+
const typedCustom = typedCustomVariables(customVariables, uiConfig);
|
|
341
|
+
const packageIdsInDisplayOrder = collectPackageIdsInDisplayOrder(base);
|
|
342
|
+
const bridgeInputs = writable(
|
|
343
|
+
buildBridgeInputs({
|
|
344
|
+
inputs,
|
|
345
|
+
initialInputSelections,
|
|
346
|
+
globalVariables,
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const handleInputChanged = (
|
|
351
|
+
fieldId: string,
|
|
352
|
+
value: string,
|
|
353
|
+
actionId?: string,
|
|
354
|
+
) => {
|
|
355
|
+
// InputOption emits the clicked option_id, not the full field. Toggle
|
|
356
|
+
// when the field is already a string[] (multi-choice); replace otherwise.
|
|
357
|
+
bridgeInputs.update((current) =>
|
|
358
|
+
applyBridgeInputChange(current, fieldId, value),
|
|
359
|
+
);
|
|
360
|
+
onInputChanged?.(fieldId, value, actionId);
|
|
361
|
+
};
|
|
309
362
|
|
|
310
363
|
setPaywallContext({
|
|
311
364
|
defaultPackageId,
|
|
312
365
|
selectedPackageId,
|
|
313
366
|
variablesPerPackage: readable(variablesPerPackage),
|
|
314
367
|
baseVariables: readable({
|
|
315
|
-
...globalVariables,
|
|
316
368
|
...mergedCustomVars,
|
|
369
|
+
...globalVariables,
|
|
317
370
|
}),
|
|
318
371
|
infoPerPackage: readable(infoPerPackage),
|
|
372
|
+
offering,
|
|
373
|
+
contextPackages: packages,
|
|
374
|
+
packageIdsInDisplayOrder,
|
|
375
|
+
workflow,
|
|
376
|
+
isPreview,
|
|
377
|
+
selectedLocale: selectedLocale ?? default_locale,
|
|
378
|
+
typedCustom,
|
|
379
|
+
bridgeInputs,
|
|
319
380
|
onPurchase,
|
|
320
381
|
emitComponentInteraction,
|
|
321
382
|
onNavigateToUrl: onNavigateToUrlClicked,
|
|
322
383
|
onButtonAction,
|
|
323
|
-
onInputChanged,
|
|
384
|
+
onInputChanged: handleInputChanged,
|
|
324
385
|
onSensitiveInputChanged,
|
|
325
386
|
onReservedAttributeChanged,
|
|
326
387
|
walletButtonRender,
|
|
@@ -341,8 +402,8 @@
|
|
|
341
402
|
const variables: VariablesStore = derived(selectedPackageId, (packageId) => {
|
|
342
403
|
const packageVars = variablesPerPackage[packageId || ""];
|
|
343
404
|
return {
|
|
344
|
-
...globalVariables,
|
|
345
405
|
...mergedCustomVars,
|
|
406
|
+
...globalVariables,
|
|
346
407
|
...packageVars,
|
|
347
408
|
};
|
|
348
409
|
});
|
|
@@ -8,6 +8,8 @@ import type { UIConfig } from "../../types/ui-config";
|
|
|
8
8
|
import { type CustomVariables, type PackageInfo, type VariableDictionary } from "../../types/variables";
|
|
9
9
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
10
10
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
11
|
+
import type { PaywallOffering, PaywallPackage, PaywallWorkflow } from "@revenuecat/workflow-web-components-sdk";
|
|
12
|
+
import { type BridgeInputs } from "../web-view/build-paywall-context";
|
|
11
13
|
/**
|
|
12
14
|
* Props are captured once at mount and are not reactive to subsequent changes.
|
|
13
15
|
* The paywall should be remounted to reflect new prop values.
|
|
@@ -72,6 +74,11 @@ interface Props {
|
|
|
72
74
|
* Hosts (e.g. workflow runtimes) pass their workflow-level fallback here.
|
|
73
75
|
*/
|
|
74
76
|
safeAreaFallbackColor?: ColorScheme | null;
|
|
77
|
+
offering?: PaywallOffering | null;
|
|
78
|
+
packages?: PaywallPackage[];
|
|
79
|
+
workflow?: PaywallWorkflow;
|
|
80
|
+
isPreview?: boolean;
|
|
81
|
+
inputs?: BridgeInputs;
|
|
75
82
|
}
|
|
76
83
|
declare const Paywall: import("svelte").Component<Props, {}, "">;
|
|
77
84
|
type Paywall = ReturnType<typeof Paywall>;
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
subscribeDecorativeVideoAutoplayAttempts,
|
|
20
20
|
} from "../../utils/video-inline-playback";
|
|
21
21
|
import ClipPath from "../image/ClipPath.svelte";
|
|
22
|
+
import { viewBoxSize } from "../image/view-box-size";
|
|
22
23
|
import Overlay from "../image/Overlay.svelte";
|
|
23
24
|
|
|
24
25
|
const props: VideoProps = $props();
|
|
@@ -157,13 +158,20 @@
|
|
|
157
158
|
padding: mapSpacing(padding),
|
|
158
159
|
"line-height": 0,
|
|
159
160
|
"flex-shrink": size.width.type === "fixed" ? 0 : 1,
|
|
161
|
+
...(size.width.type === "fixed"
|
|
162
|
+
? {}
|
|
163
|
+
: { "min-width": 0, "min-height": 0 }),
|
|
160
164
|
}),
|
|
161
165
|
);
|
|
162
166
|
|
|
163
167
|
let svgRect = $state<DOMRect | null>(null);
|
|
164
168
|
|
|
165
169
|
const [svgWidth, svgHeight] = $derived.by(() => {
|
|
166
|
-
|
|
170
|
+
if (size.width.type === "fit" || size.height.type === "fit") {
|
|
171
|
+
return [svgRect?.width ?? 0, svgRect?.height ?? 0];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return viewBoxSize(svgRect, wrapperWidth, height);
|
|
167
175
|
});
|
|
168
176
|
|
|
169
177
|
const viewBox = $derived.by(() => {
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
RESIZE_MESSAGE,
|
|
12
12
|
} from "./web-view-sdk";
|
|
13
13
|
import type { ResizePayload, WebViewHostBridge } from "./web-view-sdk";
|
|
14
|
+
import { getPaywallContext } from "../../stores/paywall";
|
|
15
|
+
import { getOptionalPackageIdContext } from "../../stores/packageId";
|
|
16
|
+
import { getColorModeContext } from "../../stores/color-mode";
|
|
17
|
+
import { fromStore } from "svelte/store";
|
|
18
|
+
import {
|
|
19
|
+
buildPaywallContext,
|
|
20
|
+
contextFingerprint,
|
|
21
|
+
} from "./build-paywall-context";
|
|
14
22
|
|
|
15
23
|
// Placeholder for a `fit` axis until the content reports its size — a cross-origin
|
|
16
24
|
// iframe exposes no intrinsic size, so `fit` would otherwise collapse to nothing.
|
|
@@ -27,6 +35,32 @@
|
|
|
27
35
|
|
|
28
36
|
const props: WebViewProps = $props();
|
|
29
37
|
const getParentStackDimension = getStackDimensionContext();
|
|
38
|
+
const paywall = getPaywallContext();
|
|
39
|
+
const enclosingPackageIdStore = getOptionalPackageIdContext();
|
|
40
|
+
const getColorMode = getColorModeContext();
|
|
41
|
+
const selectedPackageId = fromStore(paywall.selectedPackageId);
|
|
42
|
+
const enclosingPackageId = enclosingPackageIdStore
|
|
43
|
+
? fromStore(enclosingPackageIdStore)
|
|
44
|
+
: null;
|
|
45
|
+
const liveInputs = paywall.bridgeInputs
|
|
46
|
+
? fromStore(paywall.bridgeInputs)
|
|
47
|
+
: null;
|
|
48
|
+
const darkMode = $derived(getColorMode() === "dark");
|
|
49
|
+
|
|
50
|
+
const paywallContextSnapshot = () =>
|
|
51
|
+
buildPaywallContext({
|
|
52
|
+
custom: paywall.typedCustom,
|
|
53
|
+
offering: paywall.offering,
|
|
54
|
+
packages: paywall.contextPackages,
|
|
55
|
+
packageIdsInDisplayOrder: paywall.packageIdsInDisplayOrder,
|
|
56
|
+
selectedPackageId: selectedPackageId.current,
|
|
57
|
+
enclosingPackageId: enclosingPackageId?.current,
|
|
58
|
+
inputs: liveInputs?.current,
|
|
59
|
+
workflow: paywall.workflow,
|
|
60
|
+
locale: paywall.selectedLocale,
|
|
61
|
+
darkMode,
|
|
62
|
+
isPreview: paywall.isPreview,
|
|
63
|
+
});
|
|
30
64
|
|
|
31
65
|
// Gate the origin guard on a post-mount flag (not `typeof window`) so SSR and the
|
|
32
66
|
// first client render match; otherwise a cross-origin URL mismatches on hydration.
|
|
@@ -106,6 +140,8 @@
|
|
|
106
140
|
// to the plain iframe.
|
|
107
141
|
let iframeEl = $state<HTMLIFrameElement | undefined>(undefined);
|
|
108
142
|
let previousSafeUrl: string | null | undefined;
|
|
143
|
+
let liveBridge = $state<WebViewHostBridge | undefined>(undefined);
|
|
144
|
+
let lastFingerprint = $state<string | undefined>(undefined);
|
|
109
145
|
|
|
110
146
|
$effect(() => {
|
|
111
147
|
const url = safeUrl;
|
|
@@ -119,7 +155,9 @@
|
|
|
119
155
|
// Read every prop the bridge is configured with synchronously: Svelte only
|
|
120
156
|
// tracks reads in the effect body, not inside the async `.then` below, so
|
|
121
157
|
// capturing them here re-runs the effect (rebuilding the bridge) when the
|
|
122
|
-
// component id or `fit` axes change in place.
|
|
158
|
+
// component id or `fit` axes change in place. Do not read package selection
|
|
159
|
+
// or other snapshot fields here — those update via `setContext` without
|
|
160
|
+
// tearing down the iframe.
|
|
123
161
|
const componentId = props.id;
|
|
124
162
|
const protocolVersion = props.protocol_version;
|
|
125
163
|
const widthFit = props.size.width.type === "fit";
|
|
@@ -133,12 +171,16 @@
|
|
|
133
171
|
.then((createHostBridge) => {
|
|
134
172
|
if (disposed) return;
|
|
135
173
|
try {
|
|
174
|
+
const seed = paywallContextSnapshot();
|
|
136
175
|
bridge = createHostBridge({
|
|
137
176
|
iframe,
|
|
138
177
|
allowedOrigin: new URL(url).origin,
|
|
139
178
|
componentId,
|
|
140
179
|
protocolVersion,
|
|
180
|
+
context: seed,
|
|
141
181
|
});
|
|
182
|
+
liveBridge = bridge;
|
|
183
|
+
lastFingerprint = contextFingerprint(seed);
|
|
142
184
|
// Tell the content which axes are `fit` so it hides its own scrollbar there
|
|
143
185
|
// (it would otherwise flash one while we catch up to a new size).
|
|
144
186
|
if (widthFit || heightFit) {
|
|
@@ -166,8 +208,23 @@
|
|
|
166
208
|
return () => {
|
|
167
209
|
disposed = true;
|
|
168
210
|
bridge?.destroy();
|
|
211
|
+
if (liveBridge === bridge) {
|
|
212
|
+
liveBridge = undefined;
|
|
213
|
+
}
|
|
169
214
|
};
|
|
170
215
|
});
|
|
216
|
+
|
|
217
|
+
// Later snapshots replace the whole context. Compare without `updated_at` so
|
|
218
|
+
// the init seed is not also posted as a `context` message.
|
|
219
|
+
$effect(() => {
|
|
220
|
+
const next = paywallContextSnapshot();
|
|
221
|
+
const currentBridge = liveBridge;
|
|
222
|
+
if (!currentBridge?.setContext) return;
|
|
223
|
+
const fingerprint = contextFingerprint(next);
|
|
224
|
+
if (fingerprint === lastFingerprint) return;
|
|
225
|
+
lastFingerprint = fingerprint;
|
|
226
|
+
currentBridge.setContext(next);
|
|
227
|
+
});
|
|
171
228
|
</script>
|
|
172
229
|
|
|
173
230
|
{#if isVisible && safeUrl && canEmbed}
|
|
@@ -7,22 +7,36 @@
|
|
|
7
7
|
type StackDimension,
|
|
8
8
|
} from "../stack/stack-dimension-context";
|
|
9
9
|
import type { WebViewProps } from "../../types/components/web-view";
|
|
10
|
-
import {
|
|
10
|
+
import type {
|
|
11
|
+
PaywallOffering,
|
|
12
|
+
PaywallPackage,
|
|
13
|
+
} from "@revenuecat/workflow-web-components-sdk";
|
|
14
|
+
import { readable, writable, type Writable } from "svelte/store";
|
|
11
15
|
import WebView from "./WebView.svelte";
|
|
12
16
|
|
|
13
17
|
const {
|
|
14
18
|
parentStackDimension,
|
|
19
|
+
selectedPackageId = writable(undefined),
|
|
20
|
+
contextPackages,
|
|
21
|
+
offering,
|
|
15
22
|
...props
|
|
16
|
-
}: WebViewProps & {
|
|
23
|
+
}: WebViewProps & {
|
|
24
|
+
parentStackDimension?: StackDimension;
|
|
25
|
+
selectedPackageId?: Writable<string | undefined>;
|
|
26
|
+
contextPackages?: PaywallPackage[];
|
|
27
|
+
offering?: PaywallOffering | null;
|
|
28
|
+
} = $props();
|
|
17
29
|
|
|
18
30
|
setStackDimensionContext(() => parentStackDimension);
|
|
19
31
|
|
|
20
32
|
setPaywallContext({
|
|
21
33
|
defaultPackageId: undefined,
|
|
22
|
-
selectedPackageId
|
|
34
|
+
selectedPackageId,
|
|
23
35
|
variablesPerPackage: readable(undefined),
|
|
24
36
|
baseVariables: readable(undefined),
|
|
25
37
|
infoPerPackage: readable(undefined),
|
|
38
|
+
offering,
|
|
39
|
+
contextPackages,
|
|
26
40
|
onPurchase: () => {},
|
|
27
41
|
emitComponentInteraction: () => {},
|
|
28
42
|
onButtonAction: () => {},
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { type StackDimension } from "../stack/stack-dimension-context";
|
|
2
2
|
import type { WebViewProps } from "../../types/components/web-view";
|
|
3
|
+
import type { PaywallOffering, PaywallPackage } from "@revenuecat/workflow-web-components-sdk";
|
|
4
|
+
import { type Writable } from "svelte/store";
|
|
3
5
|
type $$ComponentProps = WebViewProps & {
|
|
4
6
|
parentStackDimension?: StackDimension;
|
|
7
|
+
selectedPackageId?: Writable<string | undefined>;
|
|
8
|
+
contextPackages?: PaywallPackage[];
|
|
9
|
+
offering?: PaywallOffering | null;
|
|
5
10
|
};
|
|
6
11
|
declare const WebViewTestWrapper: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
12
|
type WebViewTestWrapper = ReturnType<typeof WebViewTestWrapper>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { InitialInputSelections } from "../../stores/inputValidation";
|
|
2
|
+
import type { UIConfig } from "../../types/ui-config";
|
|
3
|
+
import type { CustomVariables, VariableDictionary } from "../../types/variables";
|
|
4
|
+
import type { PaywallContext as BridgePaywallContext, PaywallOffering, PaywallPackage, PaywallWorkflow } from "@revenuecat/workflow-web-components-sdk";
|
|
5
|
+
export type BridgeCustomValues = Record<string, string | number | boolean>;
|
|
6
|
+
export type BridgeInputs = Record<string, string | string[] | null>;
|
|
7
|
+
export interface BuildPaywallContextParams {
|
|
8
|
+
/** Pre-merged custom values. When set, `customVariables` / `uiConfig` are ignored. */
|
|
9
|
+
custom?: BridgeCustomValues;
|
|
10
|
+
customVariables?: CustomVariables;
|
|
11
|
+
uiConfig?: UIConfig;
|
|
12
|
+
offering?: PaywallOffering | null;
|
|
13
|
+
packages?: PaywallPackage[];
|
|
14
|
+
packageIdsInDisplayOrder?: string[];
|
|
15
|
+
selectedPackageId?: string;
|
|
16
|
+
enclosingPackageId?: string;
|
|
17
|
+
inputs?: BridgeInputs;
|
|
18
|
+
initialInputSelections?: InitialInputSelections;
|
|
19
|
+
globalVariables?: VariableDictionary;
|
|
20
|
+
workflow?: PaywallWorkflow;
|
|
21
|
+
locale?: string;
|
|
22
|
+
darkMode?: boolean;
|
|
23
|
+
isPreview?: boolean;
|
|
24
|
+
updatedAt?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Merge dashboard custom-variable defaults with typed runtime overrides. */
|
|
27
|
+
export declare function typedCustomVariables(customVariables: CustomVariables | undefined, uiConfig: UIConfig | undefined): BridgeCustomValues;
|
|
28
|
+
/**
|
|
29
|
+
* Bare field ids. Host `inputs` win, then current-screen selections, then
|
|
30
|
+
* `input.*` keys from global variables (stringly, from earlier steps).
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildBridgeInputs({ inputs, initialInputSelections, globalVariables, }: Pick<BuildPaywallContextParams, "inputs" | "initialInputSelections" | "globalVariables">): BridgeInputs;
|
|
33
|
+
/** Normalize a stored input to a choice-field `string[]`. */
|
|
34
|
+
export declare function asBridgeInputArray(value: string | string[] | null | undefined): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Apply an `onInputChanged` payload to `bridgeInputs`.
|
|
37
|
+
* Choice options emit a single `option_id`: toggle when the field is already
|
|
38
|
+
* a `string[]` (multi-choice), otherwise replace (text / single-choice).
|
|
39
|
+
*/
|
|
40
|
+
export declare function applyBridgeInputChange(current: BridgeInputs, fieldId: string, value: string): BridgeInputs;
|
|
41
|
+
/** Paywall-tree order, dropping offering packages that never appear on the paywall. */
|
|
42
|
+
export declare function orderPackagesForPaywall(packages: PaywallPackage[], packageIdsInDisplayOrder: string[]): PaywallPackage[];
|
|
43
|
+
export declare function buildPaywallContext(params: BuildPaywallContextParams): BridgePaywallContext;
|
|
44
|
+
/** Snapshot identity ignoring `updated_at`, so init is not also sent as `context`. */
|
|
45
|
+
export declare function contextFingerprint(context: BridgePaywallContext): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Merge dashboard custom-variable defaults with typed runtime overrides. */
|
|
2
|
+
export function typedCustomVariables(customVariables, uiConfig) {
|
|
3
|
+
const custom = {};
|
|
4
|
+
for (const [name, spec] of Object.entries(uiConfig?.custom_variables ?? {})) {
|
|
5
|
+
custom[name] = spec.default_value;
|
|
6
|
+
}
|
|
7
|
+
for (const [name, value] of Object.entries(customVariables ?? {})) {
|
|
8
|
+
custom[name] = value.value;
|
|
9
|
+
}
|
|
10
|
+
return custom;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Bare field ids. Host `inputs` win, then current-screen selections, then
|
|
14
|
+
* `input.*` keys from global variables (stringly, from earlier steps).
|
|
15
|
+
*/
|
|
16
|
+
export function buildBridgeInputs({ inputs, initialInputSelections, globalVariables, }) {
|
|
17
|
+
const result = {};
|
|
18
|
+
for (const [key, value] of Object.entries(globalVariables ?? {})) {
|
|
19
|
+
if (!key.startsWith("input.") || value === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
result[key.slice("input.".length)] = value;
|
|
22
|
+
}
|
|
23
|
+
for (const [fieldId, selectedIds] of Object.entries(initialInputSelections ?? {})) {
|
|
24
|
+
// Length 0 is unanswered: omit so applyBridgeInputChange replaces
|
|
25
|
+
// (single-choice). Do not write [] here; multi-choice seeds [] on mount.
|
|
26
|
+
if (selectedIds.length === 0)
|
|
27
|
+
continue;
|
|
28
|
+
result[fieldId] =
|
|
29
|
+
selectedIds.length === 1 ? (selectedIds[0] ?? null) : selectedIds;
|
|
30
|
+
}
|
|
31
|
+
return { ...result, ...inputs };
|
|
32
|
+
}
|
|
33
|
+
/** Normalize a stored input to a choice-field `string[]`. */
|
|
34
|
+
export function asBridgeInputArray(value) {
|
|
35
|
+
if (Array.isArray(value))
|
|
36
|
+
return [...value];
|
|
37
|
+
if (value == null || value === "")
|
|
38
|
+
return [];
|
|
39
|
+
return [value];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Apply an `onInputChanged` payload to `bridgeInputs`.
|
|
43
|
+
* Choice options emit a single `option_id`: toggle when the field is already
|
|
44
|
+
* a `string[]` (multi-choice), otherwise replace (text / single-choice).
|
|
45
|
+
*/
|
|
46
|
+
export function applyBridgeInputChange(current, fieldId, value) {
|
|
47
|
+
const existing = current[fieldId];
|
|
48
|
+
if (Array.isArray(existing)) {
|
|
49
|
+
const next = existing.includes(value)
|
|
50
|
+
? existing.filter((id) => id !== value)
|
|
51
|
+
: [...existing, value];
|
|
52
|
+
return { ...current, [fieldId]: next };
|
|
53
|
+
}
|
|
54
|
+
return { ...current, [fieldId]: value };
|
|
55
|
+
}
|
|
56
|
+
/** Paywall-tree order, dropping offering packages that never appear on the paywall. */
|
|
57
|
+
export function orderPackagesForPaywall(packages, packageIdsInDisplayOrder) {
|
|
58
|
+
if (packageIdsInDisplayOrder.length === 0) {
|
|
59
|
+
return packages;
|
|
60
|
+
}
|
|
61
|
+
const byId = new Map(packages.map((pkg) => [pkg.identifier, pkg]));
|
|
62
|
+
const ordered = [];
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (const id of packageIdsInDisplayOrder) {
|
|
65
|
+
if (seen.has(id))
|
|
66
|
+
continue;
|
|
67
|
+
seen.add(id);
|
|
68
|
+
ordered.push(byId.get(id) ?? { identifier: id, display_name: id, products: [] });
|
|
69
|
+
}
|
|
70
|
+
return ordered;
|
|
71
|
+
}
|
|
72
|
+
export function buildPaywallContext(params) {
|
|
73
|
+
const packages = orderPackagesForPaywall(params.packages ?? [], params.packageIdsInDisplayOrder ?? []);
|
|
74
|
+
const selectedPackage = packages.find((pkg) => pkg.identifier === params.selectedPackageId) ?? null;
|
|
75
|
+
const placementId = params.enclosingPackageId ?? params.selectedPackageId;
|
|
76
|
+
const placementPackage = packages.find((pkg) => pkg.identifier === placementId) ?? null;
|
|
77
|
+
const context = {
|
|
78
|
+
custom: params.custom !== undefined
|
|
79
|
+
? params.custom
|
|
80
|
+
: typedCustomVariables(params.customVariables, params.uiConfig),
|
|
81
|
+
offering: params.offering ?? null,
|
|
82
|
+
packages,
|
|
83
|
+
package: placementPackage,
|
|
84
|
+
selected_package: selectedPackage,
|
|
85
|
+
inputs: buildBridgeInputs(params),
|
|
86
|
+
device_meta: {
|
|
87
|
+
is_preview: params.isPreview ?? false,
|
|
88
|
+
locale: params.locale ?? "",
|
|
89
|
+
dark_mode: params.darkMode ?? false,
|
|
90
|
+
updated_at: params.updatedAt ?? Date.now(),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
if (params.workflow !== undefined) {
|
|
94
|
+
context.workflow = params.workflow;
|
|
95
|
+
}
|
|
96
|
+
return context;
|
|
97
|
+
}
|
|
98
|
+
/** Snapshot identity ignoring `updated_at`, so init is not also sent as `context`. */
|
|
99
|
+
export function contextFingerprint(context) {
|
|
100
|
+
return JSON.stringify({
|
|
101
|
+
...context,
|
|
102
|
+
device_meta: { ...context.device_meta, updated_at: 0 },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
* ESM sibling at `/v<major>/rc-host.js` remains for other consumers. Types and
|
|
7
7
|
* message constants come from `@revenuecat/workflow-web-components-sdk`.
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type { HostBridge, HostBridgeConfig } from "@revenuecat/workflow-web-components-sdk";
|
|
10
10
|
export { FIT_MESSAGE, RESIZE_MESSAGE, } from "@revenuecat/workflow-web-components-sdk";
|
|
11
11
|
export type { ResizePayload } from "@revenuecat/workflow-web-components-sdk";
|
|
12
12
|
/** Base origin the versioned SDK builds are served from. */
|
|
13
13
|
export declare const SDK_BASE_URL = "https://sdk.revenuecat-static.com";
|
|
14
14
|
/**
|
|
15
|
-
* The subset of the SDK bridge purchases-ui-js uses: subscribe to a
|
|
16
|
-
* a callback once the channel opens, fire-and-forget,
|
|
15
|
+
* The subset of the SDK host bridge purchases-ui-js uses: subscribe to a
|
|
16
|
+
* message, run a callback once the channel opens, fire-and-forget, push a
|
|
17
|
+
* later paywall-context snapshot, and tear down.
|
|
17
18
|
*/
|
|
18
|
-
export type WebViewHostBridge = Pick<
|
|
19
|
-
export type CreateHostBridge = (config: HostBridgeConfig) =>
|
|
19
|
+
export type WebViewHostBridge = Pick<HostBridge, "on" | "onReady" | "send" | "destroy" | "setContext">;
|
|
20
|
+
export type CreateHostBridge = (config: HostBridgeConfig) => HostBridge;
|
|
20
21
|
/** Test factories may return only the bridge surface this package uses. */
|
|
21
22
|
export type CreateHostBridgeForTests = (config: HostBridgeConfig) => WebViewHostBridge;
|
|
22
23
|
declare global {
|
|
@@ -5,10 +5,20 @@
|
|
|
5
5
|
import type { InitialInputSelections } from "../../stores/inputValidation";
|
|
6
6
|
import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
|
|
7
7
|
import type { WorkflowScreen } from "../../types/workflow";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
PackageInfo,
|
|
10
|
+
VariableDictionary,
|
|
11
|
+
CustomVariables,
|
|
12
|
+
} from "../../types/variables";
|
|
9
13
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
10
14
|
import type { UIConfig } from "../../types/ui-config";
|
|
11
15
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
16
|
+
import type {
|
|
17
|
+
PaywallOffering,
|
|
18
|
+
PaywallPackage,
|
|
19
|
+
PaywallWorkflow,
|
|
20
|
+
} from "@revenuecat/workflow-web-components-sdk";
|
|
21
|
+
import type { BridgeInputs } from "../web-view/build-paywall-context";
|
|
12
22
|
interface Props {
|
|
13
23
|
paywallComponents: WorkflowScreen | null | undefined;
|
|
14
24
|
selectedLocale?: string;
|
|
@@ -50,6 +60,12 @@
|
|
|
50
60
|
walletButtonRender?: WalletButtonRender;
|
|
51
61
|
safeAreaFallbackColor?: ColorScheme | null;
|
|
52
62
|
hideBackButtons?: boolean;
|
|
63
|
+
offering?: PaywallOffering | null;
|
|
64
|
+
packages?: PaywallPackage[];
|
|
65
|
+
workflow?: PaywallWorkflow;
|
|
66
|
+
isPreview?: boolean;
|
|
67
|
+
inputs?: BridgeInputs;
|
|
68
|
+
customVariables?: CustomVariables;
|
|
53
69
|
}
|
|
54
70
|
const {
|
|
55
71
|
paywallComponents,
|
|
@@ -76,6 +92,12 @@
|
|
|
76
92
|
walletButtonRender,
|
|
77
93
|
safeAreaFallbackColor,
|
|
78
94
|
hideBackButtons,
|
|
95
|
+
offering,
|
|
96
|
+
packages,
|
|
97
|
+
workflow,
|
|
98
|
+
isPreview,
|
|
99
|
+
inputs,
|
|
100
|
+
customVariables,
|
|
79
101
|
}: Props = $props();
|
|
80
102
|
</script>
|
|
81
103
|
|
|
@@ -113,6 +135,12 @@
|
|
|
113
135
|
{walletButtonRender}
|
|
114
136
|
{safeAreaFallbackColor}
|
|
115
137
|
{hideBackButtons}
|
|
138
|
+
{offering}
|
|
139
|
+
{packages}
|
|
140
|
+
{workflow}
|
|
141
|
+
{isPreview}
|
|
142
|
+
{inputs}
|
|
143
|
+
{customVariables}
|
|
116
144
|
onError={(error) => {
|
|
117
145
|
console.error("Paywall error:", error);
|
|
118
146
|
}}
|
|
@@ -3,10 +3,12 @@ import type { ColorScheme } from "../../types/colors";
|
|
|
3
3
|
import type { InitialInputSelections } from "../../stores/inputValidation";
|
|
4
4
|
import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
|
|
5
5
|
import type { WorkflowScreen } from "../../types/workflow";
|
|
6
|
-
import type { PackageInfo, VariableDictionary } from "../../types/variables";
|
|
6
|
+
import type { PackageInfo, VariableDictionary, CustomVariables } from "../../types/variables";
|
|
7
7
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
8
8
|
import type { UIConfig } from "../../types/ui-config";
|
|
9
9
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
10
|
+
import type { PaywallOffering, PaywallPackage, PaywallWorkflow } from "@revenuecat/workflow-web-components-sdk";
|
|
11
|
+
import type { BridgeInputs } from "../web-view/build-paywall-context";
|
|
10
12
|
interface Props {
|
|
11
13
|
paywallComponents: WorkflowScreen | null | undefined;
|
|
12
14
|
selectedLocale?: string;
|
|
@@ -36,6 +38,12 @@ interface Props {
|
|
|
36
38
|
walletButtonRender?: WalletButtonRender;
|
|
37
39
|
safeAreaFallbackColor?: ColorScheme | null;
|
|
38
40
|
hideBackButtons?: boolean;
|
|
41
|
+
offering?: PaywallOffering | null;
|
|
42
|
+
packages?: PaywallPackage[];
|
|
43
|
+
workflow?: PaywallWorkflow;
|
|
44
|
+
isPreview?: boolean;
|
|
45
|
+
inputs?: BridgeInputs;
|
|
46
|
+
customVariables?: CustomVariables;
|
|
39
47
|
}
|
|
40
48
|
declare const Screen: import("svelte").Component<Props, {}, "">;
|
|
41
49
|
type Screen = ReturnType<typeof Screen>;
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
import type { ColorScheme } from "../../types/colors";
|
|
6
6
|
import type { InitialInputSelections } from "../../stores/inputValidation";
|
|
7
7
|
import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
PackageInfo,
|
|
10
|
+
VariableDictionary,
|
|
11
|
+
CustomVariables,
|
|
12
|
+
} from "../../types/variables";
|
|
9
13
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
10
14
|
import type { UIConfig } from "../../types/ui-config";
|
|
11
15
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
@@ -18,6 +22,11 @@
|
|
|
18
22
|
WorkflowStepTriggerAction,
|
|
19
23
|
WorkflowStep,
|
|
20
24
|
} from "../../types/workflow";
|
|
25
|
+
import type {
|
|
26
|
+
PaywallOffering,
|
|
27
|
+
PaywallPackage,
|
|
28
|
+
} from "@revenuecat/workflow-web-components-sdk";
|
|
29
|
+
import type { BridgeInputs } from "../web-view/build-paywall-context";
|
|
21
30
|
|
|
22
31
|
type TriggerActionValue = WorkflowStep["trigger_actions"][string];
|
|
23
32
|
|
|
@@ -78,6 +87,11 @@
|
|
|
78
87
|
* purchase, error). Used by the host to fire workflow lifecycle events.
|
|
79
88
|
*/
|
|
80
89
|
onStepChanged?: (event: WorkflowStepChangeEvent) => void;
|
|
90
|
+
offering?: PaywallOffering | null;
|
|
91
|
+
packages?: PaywallPackage[];
|
|
92
|
+
isPreview?: boolean;
|
|
93
|
+
inputs?: BridgeInputs;
|
|
94
|
+
customVariables?: CustomVariables;
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
const {
|
|
@@ -106,6 +120,11 @@
|
|
|
106
120
|
onClose,
|
|
107
121
|
onExitBack,
|
|
108
122
|
onStepChanged,
|
|
123
|
+
offering,
|
|
124
|
+
packages,
|
|
125
|
+
isPreview,
|
|
126
|
+
inputs,
|
|
127
|
+
customVariables,
|
|
109
128
|
}: Props = $props();
|
|
110
129
|
|
|
111
130
|
// ── State store ────────────────────────────────────────────────────────────
|
|
@@ -158,6 +177,13 @@
|
|
|
158
177
|
},
|
|
159
178
|
);
|
|
160
179
|
const currentScreen = $derived(workflow.pages[current.pageId]);
|
|
180
|
+
const currentStep = $derived(workflow.steps[current.stepId]);
|
|
181
|
+
const bridgeWorkflow = $derived({
|
|
182
|
+
workflow_id: workflow.workflowId,
|
|
183
|
+
step_id: current.stepId,
|
|
184
|
+
step_type: currentStep?.type ?? "screen",
|
|
185
|
+
screen_type: currentStep?.screen_type ?? [],
|
|
186
|
+
});
|
|
161
187
|
|
|
162
188
|
$effect.pre(() => {
|
|
163
189
|
const navKey = `${workflow.workflowId}:${workflow.initial_page_id}`;
|
|
@@ -365,5 +391,11 @@
|
|
|
365
391
|
});
|
|
366
392
|
onClose?.();
|
|
367
393
|
}}
|
|
394
|
+
{offering}
|
|
395
|
+
{packages}
|
|
396
|
+
workflow={bridgeWorkflow}
|
|
397
|
+
{isPreview}
|
|
398
|
+
{inputs}
|
|
399
|
+
{customVariables}
|
|
368
400
|
/>
|
|
369
401
|
{/key}
|
|
@@ -2,11 +2,13 @@ import type { CompleteWorkflowNavigateArgs } from "../../types/components/button
|
|
|
2
2
|
import type { ColorScheme } from "../../types/colors";
|
|
3
3
|
import type { InitialInputSelections } from "../../stores/inputValidation";
|
|
4
4
|
import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
|
|
5
|
-
import type { PackageInfo, VariableDictionary } from "../../types/variables";
|
|
5
|
+
import type { PackageInfo, VariableDictionary, CustomVariables } from "../../types/variables";
|
|
6
6
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
7
7
|
import type { UIConfig } from "../../types/ui-config";
|
|
8
8
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
9
9
|
import type { WorkflowNavData, WorkflowStepChangeEvent } from "../../types/workflow-nav";
|
|
10
|
+
import type { PaywallOffering, PaywallPackage } from "@revenuecat/workflow-web-components-sdk";
|
|
11
|
+
import type { BridgeInputs } from "../web-view/build-paywall-context";
|
|
10
12
|
interface Props {
|
|
11
13
|
workflow: WorkflowNavData;
|
|
12
14
|
uiConfig: UIConfig;
|
|
@@ -43,6 +45,11 @@ interface Props {
|
|
|
43
45
|
* purchase, error). Used by the host to fire workflow lifecycle events.
|
|
44
46
|
*/
|
|
45
47
|
onStepChanged?: (event: WorkflowStepChangeEvent) => void;
|
|
48
|
+
offering?: PaywallOffering | null;
|
|
49
|
+
packages?: PaywallPackage[];
|
|
50
|
+
isPreview?: boolean;
|
|
51
|
+
inputs?: BridgeInputs;
|
|
52
|
+
customVariables?: CustomVariables;
|
|
46
53
|
}
|
|
47
54
|
declare const Workflow: import("svelte").Component<Props, {}, "">;
|
|
48
55
|
type Workflow = ReturnType<typeof Workflow>;
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export { type WorkflowNavData, type WorkflowStepChangeEvent, workflowDataToNavDa
|
|
|
29
29
|
export { type WorkflowData, type WorkflowStep } from "./types/workflow";
|
|
30
30
|
export { type InitialInputSelections } from "./stores/inputValidation";
|
|
31
31
|
export { type UIConfig } from "./types/ui-config";
|
|
32
|
+
export type { PaywallContext, PaywallOffering, PaywallPackage, PaywallWorkflow, } from "@revenuecat/workflow-web-components-sdk";
|
|
32
33
|
export { type WalletButtonRender, type WalletButtonTheme, } from "./types/wallet";
|
|
33
34
|
export { type CustomVariables, CustomVariableValue, mergeCustomVariables, type VariableDictionary, type PackageInfo, } from "./types/variables";
|
|
34
35
|
export type { Action, CompleteWorkflowNavigateArgs, CompleteWorkflowUrlQueryParams, } from "./types/components/button";
|
package/dist/stores/paywall.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ import type { ComponentInteractionData } from "../types/paywall-component-intera
|
|
|
4
4
|
import type { UIConfig } from "../types/ui-config";
|
|
5
5
|
import type { PackageInfo, VariableDictionary } from "../types/variables";
|
|
6
6
|
import type { WalletButtonRender } from "../types/wallet";
|
|
7
|
+
import type { PaywallOffering, PaywallPackage, PaywallWorkflow } from "@revenuecat/workflow-web-components-sdk";
|
|
7
8
|
import { type Readable, type Writable } from "svelte/store";
|
|
9
|
+
import type { BridgeCustomValues, BridgeInputs } from "../components/web-view/build-paywall-context";
|
|
8
10
|
type PaywallContext = Readonly<{
|
|
9
11
|
defaultPackageId?: string;
|
|
10
12
|
selectedPackageId: Writable<string | undefined>;
|
|
@@ -18,6 +20,17 @@ type PaywallContext = Readonly<{
|
|
|
18
20
|
* */
|
|
19
21
|
baseVariables: Readable<VariableDictionary | undefined>;
|
|
20
22
|
infoPerPackage: Readable<Record<string, PackageInfo> | undefined>;
|
|
23
|
+
/** Host-supplied offering for the web_view bridge. */
|
|
24
|
+
offering?: PaywallOffering | null;
|
|
25
|
+
/** Host-mapped packages (SDK shape) for the web_view bridge. */
|
|
26
|
+
contextPackages?: PaywallPackage[];
|
|
27
|
+
/** Paywall-tree package ids in display order. */
|
|
28
|
+
packageIdsInDisplayOrder?: string[];
|
|
29
|
+
workflow?: PaywallWorkflow;
|
|
30
|
+
isPreview?: boolean;
|
|
31
|
+
selectedLocale?: string;
|
|
32
|
+
typedCustom?: BridgeCustomValues;
|
|
33
|
+
bridgeInputs?: Writable<BridgeInputs>;
|
|
21
34
|
onPurchase: (actionId?: string) => void;
|
|
22
35
|
emitComponentInteraction: (data: ComponentInteractionData) => void;
|
|
23
36
|
onNavigateToUrl?: (url: string) => void;
|
package/dist/types/workflow.d.ts
CHANGED
|
@@ -42,6 +42,14 @@ export interface WorkflowStep {
|
|
|
42
42
|
id: string;
|
|
43
43
|
screen_id?: string;
|
|
44
44
|
type: string;
|
|
45
|
+
/** Editor step name from the published SDK payload (khepri #24244). */
|
|
46
|
+
name?: string | null;
|
|
47
|
+
/**
|
|
48
|
+
* Editor screen-type classification from the published SDK payload
|
|
49
|
+
* (khepri #24244). Missing on unre-published funnels; treat as `[]`.
|
|
50
|
+
* Distinct from `metadata.screen_type` (native fallback-paywall contract).
|
|
51
|
+
*/
|
|
52
|
+
screen_type?: string[];
|
|
45
53
|
param_values: Record<string, unknown>;
|
|
46
54
|
/**
|
|
47
55
|
* Maps action IDs (from `triggers[].action_id`) to the next step to navigate
|
|
@@ -10,6 +10,11 @@ import type { PackageInfo, VariableDictionary } from "../types/variables.js";
|
|
|
10
10
|
* @returns the id of the first package marked as `is_selected_by_default`, otherwise the first package id, or undefined
|
|
11
11
|
*/
|
|
12
12
|
export declare function findSelectedPackageId({ stack, sticky_footer, }: RootPaywall): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Package ids in paywall display order (root stack, then sticky footer).
|
|
15
|
+
* Duplicates keep the first occurrence. Unselected tabs still count.
|
|
16
|
+
*/
|
|
17
|
+
export declare function collectPackageIdsInDisplayOrder({ stack, sticky_footer, }: RootPaywall): string[];
|
|
13
18
|
export type ConditionContext = {
|
|
14
19
|
selectedPackageId: string | undefined;
|
|
15
20
|
packageInfo: PackageInfo | undefined;
|
|
@@ -93,6 +93,28 @@ export function findSelectedPackageId({ stack, sticky_footer, }) {
|
|
|
93
93
|
}
|
|
94
94
|
return undefined;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Package ids in paywall display order (root stack, then sticky footer).
|
|
98
|
+
* Duplicates keep the first occurrence. Unselected tabs still count.
|
|
99
|
+
*/
|
|
100
|
+
export function collectPackageIdsInDisplayOrder({ stack, sticky_footer, }) {
|
|
101
|
+
const ids = [];
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
const walk = (node) => {
|
|
104
|
+
if (node.type === "package" && !seen.has(node.package_id)) {
|
|
105
|
+
seen.add(node.package_id);
|
|
106
|
+
ids.push(node.package_id);
|
|
107
|
+
}
|
|
108
|
+
for (const child of getPaywallComponentChildNodes(node)) {
|
|
109
|
+
walk(child);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
walk(stack);
|
|
113
|
+
if (sticky_footer != null) {
|
|
114
|
+
walk(sticky_footer);
|
|
115
|
+
}
|
|
116
|
+
return ids;
|
|
117
|
+
}
|
|
96
118
|
/**
|
|
97
119
|
* Merges the properties of every override whose conditions all match (AND),
|
|
98
120
|
* applying them in override-array order (later overrides overwrite earlier ones).
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@revenuecat/purchases-ui-js",
|
|
3
3
|
"description": "Web components for Paywalls. Powered by RevenueCat",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "4.8.
|
|
5
|
+
"version": "4.8.20",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "RevenueCat, Inc."
|
|
8
8
|
},
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
36
36
|
"storybook": "storybook dev -p 6006",
|
|
37
37
|
"build-storybook": "storybook build",
|
|
38
|
-
"prepare": "husky",
|
|
39
38
|
"chromatic": "chromatic",
|
|
40
39
|
"format": "prettier --write .",
|
|
41
40
|
"typecheck": "tsc --noEmit",
|
|
@@ -76,7 +75,7 @@
|
|
|
76
75
|
},
|
|
77
76
|
"packageManager": "npm@11.7.0+sha512.c22099a6fff8d5b2286c2a09df5352b4858a7c0c716320f58989d60ad8b29ecf2ce6fdfe97ccb41c23ffb1272e1fa079f868487dd6b81d02a2a9e199c095a117",
|
|
78
77
|
"dependencies": {
|
|
79
|
-
"@revenuecat/workflow-web-components-sdk": "^0.1.
|
|
78
|
+
"@revenuecat/workflow-web-components-sdk": "^0.1.7",
|
|
80
79
|
"qrcode": "^1.5.4"
|
|
81
80
|
},
|
|
82
81
|
"peerDependencies": {
|
|
@@ -105,10 +104,9 @@
|
|
|
105
104
|
"eslint-plugin-storybook": "9.1.17",
|
|
106
105
|
"eslint-plugin-svelte": "3.16.0",
|
|
107
106
|
"globals": "16.4.0",
|
|
108
|
-
"husky": "9.1.7",
|
|
109
107
|
"jsdom": "27.0.1",
|
|
110
108
|
"knip": "5.82.1",
|
|
111
|
-
"
|
|
109
|
+
"lefthook": "^2.1.10",
|
|
112
110
|
"prettier": "3.8.1",
|
|
113
111
|
"prettier-plugin-svelte": "3.5.1",
|
|
114
112
|
"publint": "0.3.18",
|
|
@@ -122,10 +120,10 @@
|
|
|
122
120
|
"vite-plugin-dts": "4.5.4",
|
|
123
121
|
"vitest": "3.2.4"
|
|
124
122
|
},
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
"allowScripts": {
|
|
124
|
+
"esbuild@0.25.12": true,
|
|
125
|
+
"fsevents@2.3.2": true,
|
|
126
|
+
"fsevents@2.3.3": true,
|
|
127
|
+
"lefthook@2.1.10": true
|
|
130
128
|
}
|
|
131
129
|
}
|