@revenuecat/purchases-ui-js 3.11.4 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -127,7 +127,7 @@
127
127
  componentType: "package_selection_sheet",
128
128
  componentName: action.sheet?.name ?? props.name,
129
129
  componentValue: "open",
130
- currentPackageIdentifier: $selectedPackageId,
130
+ currentPackageId: $selectedPackageId,
131
131
  });
132
132
 
133
133
  const onclick = () => {
@@ -10,7 +10,7 @@
10
10
  const props: InputOptionProps = $props();
11
11
  const { stack, option_id } = props;
12
12
 
13
- const { onButtonAction } = getPaywallContext();
13
+ const { onButtonAction, onInputChanged } = getPaywallContext();
14
14
  const inputChoiceContext = getInputChoiceContext();
15
15
 
16
16
  // Derive selected state from context (similar to Package)
@@ -41,6 +41,15 @@
41
41
  // Update selection state
42
42
  inputChoiceContext?.selectOption(option_id);
43
43
 
44
+ // Notify host (e.g. workflows) so selections can be persisted when every option shares
45
+ // the same workflow `on_press` id (e.g. `btn_*`); `onActionTriggered` alone is ambiguous.
46
+ // The trailing actionId lets hosts skip this callback when their own action path
47
+ // already persists the selection (e.g. a legacy input_option_* action handler).
48
+ const fieldId = inputChoiceContext?.fieldId;
49
+ if (fieldId) {
50
+ onInputChanged?.(fieldId, option_id, actionId);
51
+ }
52
+
44
53
  // Trigger navigation
45
54
  onButtonAction({ type: "workflow" }, actionId);
46
55
  };
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import { setPaywallContext } from "../../stores/paywall";
3
+ import {
4
+ createInputChoiceContext,
5
+ setInputChoiceContext,
6
+ } from "../../stores/inputChoice";
7
+ import { readable, writable } from "svelte/store";
8
+ import InputOption from "./InputOption.svelte";
9
+ import type { InputOptionProps } from "../../types/components/options";
10
+
11
+ interface Props extends InputOptionProps {
12
+ fieldId?: string;
13
+ selectionMode?: "single" | "multiple";
14
+ onButtonAction?: (action: { type: string }, actionId?: string) => void;
15
+ onInputChanged?: (
16
+ fieldId: string,
17
+ value: string,
18
+ actionId?: string,
19
+ ) => void;
20
+ }
21
+
22
+ const {
23
+ fieldId,
24
+ selectionMode = "single",
25
+ onButtonAction = () => {},
26
+ onInputChanged,
27
+ ...optionProps
28
+ }: Props = $props();
29
+
30
+ if (fieldId !== undefined) {
31
+ setInputChoiceContext(createInputChoiceContext(fieldId, selectionMode));
32
+ }
33
+
34
+ setPaywallContext({
35
+ selectedPackageId: writable(undefined),
36
+ variablesPerPackage: readable(undefined),
37
+ baseVariables: readable(undefined),
38
+ infoPerPackage: readable(undefined),
39
+ onPurchase: () => {},
40
+ onButtonAction: onButtonAction as never,
41
+ onInputChanged,
42
+ uiConfig: {} as never,
43
+ hideBackButtons: false,
44
+ });
45
+ </script>
46
+
47
+ <InputOption {...optionProps} />
@@ -0,0 +1,12 @@
1
+ import type { InputOptionProps } from "../../types/components/options";
2
+ interface Props extends InputOptionProps {
3
+ fieldId?: string;
4
+ selectionMode?: "single" | "multiple";
5
+ onButtonAction?: (action: {
6
+ type: string;
7
+ }, actionId?: string) => void;
8
+ onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
9
+ }
10
+ declare const InputOptionTestWrapper: import("svelte").Component<Props, {}, "">;
11
+ type InputOptionTestWrapper = ReturnType<typeof InputOptionTestWrapper>;
12
+ export default InputOptionTestWrapper;
@@ -55,9 +55,9 @@
55
55
  componentType: "package",
56
56
  componentName: name,
57
57
  componentValue: package_id,
58
- originPackageIdentifier: originPackageId,
59
- destinationPackageIdentifier: package_id,
60
- defaultPackageIdentifier: defaultPackageId,
58
+ originPackageId,
59
+ destinationPackageId: package_id,
60
+ defaultPackageId,
61
61
  });
62
62
  };
63
63
 
@@ -82,7 +82,11 @@
82
82
  onError?: (error: unknown) => void;
83
83
  hideBackButtons?: boolean;
84
84
  walletButtonRender?: WalletButtonRender;
85
- onInputChanged?: (fieldId: string, value: string) => void;
85
+ onInputChanged?: (
86
+ fieldId: string,
87
+ value: string,
88
+ actionId?: string,
89
+ ) => void;
86
90
  maxContentWidth?: string;
87
91
  initialInputSelections?: InitialInputSelections;
88
92
  /**
@@ -165,7 +169,7 @@
165
169
 
166
170
  let sheet = $state<{
167
171
  props: SheetProps;
168
- rootSelectedPackageIdentifier?: string;
172
+ rootSelectedPackageId?: string;
169
173
  }>();
170
174
 
171
175
  const onButtonAction = (action: Action, actionId?: string) => {
@@ -214,20 +218,19 @@
214
218
  if (action.sheet) {
215
219
  sheet = {
216
220
  props: action.sheet,
217
- rootSelectedPackageIdentifier: $selectedPackageId,
221
+ rootSelectedPackageId: $selectedPackageId,
218
222
  };
219
223
  } else {
220
224
  if (sheet) {
221
- const resultingPackageIdentifier =
222
- sheet.rootSelectedPackageIdentifier;
225
+ const resultingPackageId = sheet.rootSelectedPackageId;
223
226
  emitComponentInteraction({
224
227
  componentType: "package_selection_sheet",
225
228
  componentName: sheet.props.name,
226
229
  componentValue: "close",
227
- currentPackageIdentifier: $selectedPackageId,
228
- resultingPackageIdentifier,
230
+ currentPackageId: $selectedPackageId,
231
+ resultingPackageId,
229
232
  });
230
- selectedPackageId.set(resultingPackageIdentifier);
233
+ selectedPackageId.set(resultingPackageId);
231
234
  }
232
235
  sheet = undefined;
233
236
  }
@@ -39,7 +39,7 @@ interface Props {
39
39
  onError?: (error: unknown) => void;
40
40
  hideBackButtons?: boolean;
41
41
  walletButtonRender?: WalletButtonRender;
42
- onInputChanged?: (fieldId: string, value: string) => void;
42
+ onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
43
43
  maxContentWidth?: string;
44
44
  initialInputSelections?: InitialInputSelections;
45
45
  /**
@@ -59,7 +59,7 @@
59
59
  componentName: props.name,
60
60
  componentValue: resolvedMethodType ?? props.action ?? "web_checkout",
61
61
  ...(componentURL ? { componentURL } : {}),
62
- currentPackageIdentifier: packageId,
62
+ currentPackageId: packageId,
63
63
  };
64
64
  };
65
65
 
@@ -5,6 +5,7 @@
5
5
  import { localizations } from "../../stories/fixtures";
6
6
  import { localizationDecorator } from "../../stories/localization-decorator";
7
7
  import type { StackProps } from "../../types/components/stack";
8
+ import type { Component } from "../../types/component";
8
9
  import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
9
10
  import { defineMeta } from "@storybook/addon-svelte-csf";
10
11
 
@@ -58,6 +59,53 @@
58
59
  },
59
60
  ] as unknown as TextNodeProps[];
60
61
 
62
+ /** Z-layer: full-bleed image (layer 0), text label (layer 1). */
63
+ const zlayerImageUnderText: Component[] = [
64
+ {
65
+ type: "image",
66
+ id: "zlayer-bg",
67
+ name: "Background image",
68
+ size: { width: { type: "fill" }, height: { type: "fill" } },
69
+ fit_mode: "fill",
70
+ source: {
71
+ light: {
72
+ width: 600,
73
+ height: 400,
74
+ original:
75
+ "https://placehold.co/600x400/1e293b/94a3b8?text=Layer+0+%28back%29",
76
+ heic: "https://placehold.co/600x400",
77
+ heic_low_res: "https://placehold.co/600x400",
78
+ webp: "https://placehold.co/600x400",
79
+ webp_low_res: "https://placehold.co/600x400",
80
+ },
81
+ dark: null,
82
+ },
83
+ margin: { top: 0, bottom: 0, leading: 0, trailing: 0 },
84
+ padding: { top: 0, bottom: 0, leading: 0, trailing: 0 },
85
+ },
86
+ {
87
+ type: "text",
88
+ id: "zlayer-top",
89
+ name: "Top label",
90
+ text_lid: "id1",
91
+ color: {
92
+ light: { type: "hex", value: "#f8fafc" },
93
+ dark: { type: "hex", value: "#f8fafc" },
94
+ },
95
+ font_name: null,
96
+ font_size: "heading_m",
97
+ font_weight: "semibold",
98
+ horizontal_alignment: "center",
99
+ background_color: {
100
+ light: { type: "hex", value: "#0f172acc" },
101
+ dark: { type: "hex", value: "#0f172acc" },
102
+ },
103
+ margin: { top: 0, bottom: 0, leading: 0, trailing: 0 },
104
+ padding: { top: 12, bottom: 12, leading: 16, trailing: 16 },
105
+ size: { width: { type: "fit" }, height: { type: "fit" } },
106
+ },
107
+ ];
108
+
61
109
  const { Story } = defineMeta({
62
110
  title: "Components/Stack",
63
111
  component: Stack,
@@ -140,6 +188,46 @@
140
188
  }}
141
189
  />
142
190
 
191
+ <Story
192
+ name="Z layer — image under text"
193
+ args={{
194
+ components: zlayerImageUnderText,
195
+ size: {
196
+ width: { type: "fixed", value: 320 },
197
+ height: { type: "fixed", value: 220 },
198
+ },
199
+ dimension: {
200
+ type: "zlayer",
201
+ alignment: "center",
202
+ },
203
+ background: {
204
+ type: "color",
205
+ value: {
206
+ light: { type: "hex", value: "#0f172a" },
207
+ },
208
+ },
209
+ border: {
210
+ width: 1,
211
+ color: {
212
+ light: { type: "hex", value: "#334155" },
213
+ dark: { type: "hex", value: "#334155" },
214
+ },
215
+ },
216
+ shape: {
217
+ type: "rectangle",
218
+ corners: {
219
+ top_leading: 12,
220
+ top_trailing: 12,
221
+ bottom_leading: 12,
222
+ bottom_trailing: 12,
223
+ },
224
+ },
225
+ }}
226
+ parameters={{
227
+ chromatic: { disable: true },
228
+ }}
229
+ />
230
+
143
231
  <Story
144
232
  name="Space Around"
145
233
  args={{
@@ -114,7 +114,10 @@
114
114
  {/if}
115
115
 
116
116
  {#if dimension.type === "zlayer"}
117
- <div style="position: relative; width: 100%; height: 100%;">
117
+ <!-- isolate: layer z-index values only sort within this stack, not the host page -->
118
+ <div
119
+ style="position: relative; width: 100%; height: 100%; isolation: isolate;"
120
+ >
118
121
  {#each components as component, index}
119
122
  <div style={getLayerStyle(index)}>
120
123
  <Node nodeData={component} />
@@ -23,7 +23,11 @@
23
23
  maxContentWidth?: string;
24
24
  variablesPerPackage?: Record<string, VariableDictionary>;
25
25
  initialInputSelections?: InitialInputSelections;
26
- onInputChanged?: (fieldId: string, value: string) => void;
26
+ onInputChanged?: (
27
+ fieldId: string,
28
+ value: string,
29
+ actionId?: string,
30
+ ) => void;
27
31
  onCompleteWorkflowNavigate?: (
28
32
  args: CompleteWorkflowNavigateArgs,
29
33
  ) => void | Promise<void>;
@@ -18,7 +18,7 @@ interface Props {
18
18
  maxContentWidth?: string;
19
19
  variablesPerPackage?: Record<string, VariableDictionary>;
20
20
  initialInputSelections?: InitialInputSelections;
21
- onInputChanged?: (fieldId: string, value: string) => void;
21
+ onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
22
22
  onCompleteWorkflowNavigate?: (args: CompleteWorkflowNavigateArgs) => void | Promise<void>;
23
23
  walletButtonRender?: WalletButtonRender;
24
24
  }
@@ -20,7 +20,7 @@ type PaywallContext = Readonly<{
20
20
  onPurchase: (actionId?: string) => void;
21
21
  emitComponentInteraction: (data: ComponentInteractionData) => void;
22
22
  onNavigateToUrl?: (url: string) => void;
23
- onInputChanged?: (fieldId: string, value: string) => void;
23
+ onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
24
24
  walletButtonRender?: WalletButtonRender;
25
25
  onWalletButtonReady?: (walletButtonAvailable?: boolean) => void;
26
26
  onButtonAction: (action: Action, actionId?: string) => void;
@@ -12,9 +12,9 @@ interface IndexedTransition {
12
12
  defaultIndex: number;
13
13
  }
14
14
  interface PackageTransition {
15
- originPackageIdentifier?: string;
16
- destinationPackageIdentifier: string;
17
- defaultPackageIdentifier?: string;
15
+ originPackageId?: string;
16
+ destinationPackageId: string;
17
+ defaultPackageId?: string;
18
18
  }
19
19
  type ButtonInteractionValue = "workflow" | "navigate_to_url" | "navigate_back" | "restore_purchases" | "navigate_to_customer_center" | "screen_redirect" | "navigate_to_privacy_policy" | "navigate_to_terms" | "navigate_to_sheet" | "navigate_to_offer_code" | "navigate_to_web_paywall_link";
20
20
  export type TabInteractionData = ComponentInteractionBase<"tab"> & IndexedTransition;
@@ -27,22 +27,22 @@ export type TextInteractionData = ComponentInteractionBase<"text", "navigate_to_
27
27
  componentURL: string;
28
28
  };
29
29
  export type PackageInteractionData = ComponentInteractionBase<"package"> & PackageTransition & {
30
- originProductIdentifier?: string;
31
- destinationProductIdentifier?: string;
32
- defaultProductIdentifier?: string;
30
+ originProductId?: string;
31
+ destinationProductId?: string;
32
+ defaultProductId?: string;
33
33
  };
34
34
  export type PurchaseButtonInteractionData = ComponentInteractionBase<"purchase_button", PurchaseButtonMethod["type"]> & {
35
35
  componentURL?: string;
36
- currentPackageIdentifier: string;
37
- currentProductIdentifier?: string;
38
- resultingPackageIdentifier?: string;
39
- resultingProductIdentifier?: string;
36
+ currentPackageId: string;
37
+ currentProductId?: string;
38
+ resultingPackageId?: string;
39
+ resultingProductId?: string;
40
40
  };
41
41
  export type PackageSelectionSheetInteractionData = ComponentInteractionBase<"package_selection_sheet"> & {
42
- currentPackageIdentifier?: string;
43
- resultingPackageIdentifier?: string;
44
- currentProductIdentifier?: string;
45
- resultingProductIdentifier?: string;
42
+ currentPackageId?: string;
43
+ resultingPackageId?: string;
44
+ currentProductId?: string;
45
+ resultingProductId?: string;
46
46
  };
47
47
  export type ComponentInteractionData = TabInteractionData | SwitchInteractionData | CarouselInteractionData | ButtonInteractionData | TextInteractionData | PackageInteractionData | PurchaseButtonInteractionData | PackageSelectionSheetInteractionData;
48
48
  export type ComponentInteractionType = ComponentInteractionData["componentType"];
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": "3.11.4",
5
+ "version": "3.12.0",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },