@revenuecat/purchases-ui-js 3.11.5 → 3.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/components/button/ButtonNodeTestWrapper.svelte +4 -0
  2. package/dist/components/countdown/Countdown.svelte +5 -1
  3. package/dist/components/express-purchase-button/ExpressPurchaseButton.svelte +14 -5
  4. package/dist/components/icon/Icon.svelte +27 -4
  5. package/dist/components/image/Image.svelte +80 -57
  6. package/dist/components/input-text/InputText.svelte +34 -17
  7. package/dist/components/options/InputOption.svelte +10 -1
  8. package/dist/components/options/InputOptionTestWrapper.svelte +52 -0
  9. package/dist/components/options/InputOptionTestWrapper.svelte.d.ts +12 -0
  10. package/dist/components/paywall/Paywall.svelte +5 -1
  11. package/dist/components/paywall/Paywall.svelte.d.ts +1 -1
  12. package/dist/components/stack/Stack.stories.svelte +88 -0
  13. package/dist/components/stack/Stack.svelte +52 -28
  14. package/dist/components/tabs/Tabs.svelte +28 -6
  15. package/dist/components/text/TextNode.svelte +4 -4
  16. package/dist/components/timeline/Timeline.svelte +24 -3
  17. package/dist/components/video/Video.svelte +94 -71
  18. package/dist/components/wallet-button/WalletButton.svelte +39 -31
  19. package/dist/components/wallet-button/WalletButton.svelte.d.ts +1 -5
  20. package/dist/components/workflows/Screen.svelte +5 -1
  21. package/dist/components/workflows/Screen.svelte.d.ts +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/stores/packageInfo.d.ts +1 -0
  24. package/dist/stores/packageInfo.js +5 -1
  25. package/dist/stores/paywall.d.ts +1 -1
  26. package/dist/stores/variables.d.ts +1 -0
  27. package/dist/stores/variables.js +5 -1
  28. package/dist/types/components/wallet-button.d.ts +6 -0
  29. package/dist/types/wallet.d.ts +8 -0
  30. package/dist/utils/style-utils.d.ts +8 -8
  31. package/dist/utils/style-utils.js +46 -49
  32. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { setLocalizationContext } from "../../stores/localization";
3
+ import { setPackageInfoContext } from "../../stores/packageInfo";
3
4
  import { setPaywallContext } from "../../stores/paywall";
5
+ import { setVariablesContext } from "../../stores/variables";
4
6
  import { writable, readable } from "svelte/store";
5
7
  import ButtonNode from "./ButtonNode.svelte";
6
8
  import type { ButtonProps } from "../../types/components/button";
@@ -30,6 +32,8 @@
30
32
  uiConfig: {} as never,
31
33
  hideBackButtons,
32
34
  });
35
+ setPackageInfoContext(readable(undefined));
36
+ setVariablesContext(readable({}));
33
37
  </script>
34
38
 
35
39
  <ButtonNode {...buttonProps} />
@@ -50,6 +50,10 @@
50
50
  // Otherwise show countdown_stack
51
51
  return countdown_stack;
52
52
  });
53
+
54
+ const isVisible = $derived(props.visible !== false);
53
55
  </script>
54
56
 
55
- <Stack {...activeStack} />
57
+ {#if isVisible}
58
+ <Stack {...activeStack} />
59
+ {/if}
@@ -31,8 +31,8 @@
31
31
  const shouldShowAvailableStack = $derived(
32
32
  Boolean(
33
33
  wallet_available_stack &&
34
- walletButtonAvailable &&
35
- !shouldShowWalletSkeleton,
34
+ walletButtonAvailable &&
35
+ !shouldShowWalletSkeleton,
36
36
  ),
37
37
  );
38
38
 
@@ -52,9 +52,9 @@
52
52
  </script>
53
53
 
54
54
  <Stack {...wrapperStackProps}>
55
- <Stack
56
- {...{ ...wallet_available_stack, visible: shouldShowAvailableStack }}
57
- />
55
+ <div class={shouldShowAvailableStack ? "wallet-slot" : "wallet-slot-hidden"}>
56
+ <Stack {...wallet_available_stack} />
57
+ </div>
58
58
 
59
59
  {#if shouldShowWalletSkeleton}
60
60
  <Stack {...wallet_unknown_stack} />
@@ -62,3 +62,12 @@
62
62
  <Stack {...wallet_unavailable_stack} />
63
63
  {/if}
64
64
  </Stack>
65
+
66
+ <style>
67
+ .wallet-slot {
68
+ display: contents;
69
+ }
70
+ .wallet-slot-hidden {
71
+ display: none;
72
+ }
73
+ </style>
@@ -1,6 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { getColorModeContext } from "../../stores/color-mode";
3
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
4
+ import { getPaywallContext } from "../../stores/paywall";
3
5
  import { getSelectedStateContext } from "../../stores/selected";
6
+ import { getOptionalVariablesContext } from "../../stores/variables";
4
7
  import type { IconProps } from "../../types/components/icon";
5
8
  import {
6
9
  css,
@@ -11,10 +14,16 @@
11
14
  mapSize,
12
15
  mapSpacing,
13
16
  } from "../../utils/base-utils";
14
- import { getActiveStateProps } from "../../utils/style-utils";
17
+ import {
18
+ evaluateVisibilityConditions,
19
+ getActiveStateProps,
20
+ } from "../../utils/style-utils";
15
21
 
16
22
  const props: IconProps = $props();
17
23
  const selectedState = getSelectedStateContext();
24
+ const packageInfo = getOptionalPackageInfoContext();
25
+ const { selectedPackageId } = getPaywallContext();
26
+ const variables = getOptionalVariablesContext();
18
27
  const {
19
28
  base_url,
20
29
  formats,
@@ -66,8 +75,22 @@
66
75
  "-webkit-mask-repeat": "no-repeat",
67
76
  }),
68
77
  );
78
+
79
+ const isVisible = $derived(
80
+ evaluateVisibilityConditions(
81
+ {
82
+ selectedPackageId: $selectedPackageId,
83
+ packageInfo: $packageInfo,
84
+ variables: $variables ?? {},
85
+ },
86
+ props.overrides,
87
+ props.visible,
88
+ ),
89
+ );
69
90
  </script>
70
91
 
71
- <div class="rc-gradient-border" style={container}>
72
- <div style={icon}></div>
73
- </div>
92
+ {#if isVisible}
93
+ <div class="rc-gradient-border" style={container}>
94
+ <div style={icon}></div>
95
+ </div>
96
+ {/if}
@@ -1,6 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { getColorModeContext } from "../../stores/color-mode";
3
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
4
+ import { getPaywallContext } from "../../stores/paywall";
3
5
  import { getSelectedStateContext } from "../../stores/selected";
6
+ import { getOptionalVariablesContext } from "../../stores/variables";
4
7
  import type { ImageProps } from "../../types/components/image";
5
8
  import {
6
9
  css,
@@ -10,13 +13,19 @@
10
13
  mapSize,
11
14
  mapSpacing,
12
15
  } from "../../utils/base-utils";
13
- import { getActiveStateProps } from "../../utils/style-utils";
16
+ import {
17
+ evaluateVisibilityConditions,
18
+ getActiveStateProps,
19
+ } from "../../utils/style-utils";
14
20
  import ClipPath from "./ClipPath.svelte";
15
21
  import Overlay from "./Overlay.svelte";
16
22
 
17
23
  const props: ImageProps = $props();
18
24
 
19
25
  const selectedState = getSelectedStateContext();
26
+ const packageInfo = getOptionalPackageInfoContext();
27
+ const { selectedPackageId } = getPaywallContext();
28
+ const variables = getOptionalVariablesContext();
20
29
  const {
21
30
  id,
22
31
  source,
@@ -112,68 +121,82 @@
112
121
  const overlay = $derived(
113
122
  color_overlay && mapColorMode(colorMode, color_overlay),
114
123
  );
124
+
125
+ const isVisible = $derived(
126
+ evaluateVisibilityConditions(
127
+ {
128
+ selectedPackageId: $selectedPackageId,
129
+ packageInfo: $packageInfo,
130
+ variables: $variables ?? {},
131
+ },
132
+ props.overrides,
133
+ props.visible,
134
+ ),
135
+ );
115
136
  </script>
116
137
 
117
- <div {style} bind:clientWidth={wrapperWidth}>
118
- <svg
119
- bind:contentRect={svgRect}
120
- width="100%"
121
- height="100%"
122
- {viewBox}
123
- style={svgStyle}
124
- >
125
- <defs>
126
- <clipPath id={`${id}-path`}>
127
- <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
128
- </clipPath>
129
-
130
- <g id={`${id}-border`}>
131
- <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
132
- </g>
138
+ {#if isVisible}
139
+ <div {style} bind:clientWidth={wrapperWidth}>
140
+ <svg
141
+ bind:contentRect={svgRect}
142
+ width="100%"
143
+ height="100%"
144
+ {viewBox}
145
+ style={svgStyle}
146
+ >
147
+ <defs>
148
+ <clipPath id={`${id}-path`}>
149
+ <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
150
+ </clipPath>
151
+
152
+ <g id={`${id}-border`}>
153
+ <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
154
+ </g>
155
+
156
+ <Overlay id={`${id}-overlay`} {overlay} />
157
+ {#if border && border.width > 0}
158
+ <Overlay
159
+ id={`${id}-border-color`}
160
+ overlay={mapColorMode(colorMode, border.color)}
161
+ />
162
+ {/if}
163
+ </defs>
133
164
 
134
- <Overlay id={`${id}-overlay`} {overlay} />
135
165
  {#if border && border.width > 0}
136
- <Overlay
137
- id={`${id}-border-color`}
138
- overlay={mapColorMode(colorMode, border.color)}
139
- />
140
- {/if}
141
- </defs>
142
-
143
- {#if border && border.width > 0}
144
- <use href={`#${id}-border`} fill="transparent" />
145
- {/if}
146
-
147
- <g clip-path={`url(#${id}-path)`}>
148
- <foreignObject x="0" y="0" width="100%" height="100%">
149
- <img
150
- src={image.original}
151
- style="object-fit:{mapFitMode(fit_mode)}"
152
- alt=""
153
- />
154
- </foreignObject>
155
-
156
- {#if overlay}
157
- <rect
158
- x="0"
159
- y="0"
160
- height="100%"
161
- width="100%"
162
- fill={`url(#${id}-overlay)`}
163
- />
166
+ <use href={`#${id}-border`} fill="transparent" />
164
167
  {/if}
165
168
 
166
- {#if border && border.width > 0}
167
- <use
168
- href={`#${id}-border`}
169
- fill="none"
170
- stroke={`url(#${id}-border-color)`}
171
- stroke-width={border.width * 2}
172
- />
173
- {/if}
174
- </g>
175
- </svg>
176
- </div>
169
+ <g clip-path={`url(#${id}-path)`}>
170
+ <foreignObject x="0" y="0" width="100%" height="100%">
171
+ <img
172
+ src={image.original}
173
+ style="object-fit:{mapFitMode(fit_mode)}"
174
+ alt=""
175
+ />
176
+ </foreignObject>
177
+
178
+ {#if overlay}
179
+ <rect
180
+ x="0"
181
+ y="0"
182
+ height="100%"
183
+ width="100%"
184
+ fill={`url(#${id}-overlay)`}
185
+ />
186
+ {/if}
187
+
188
+ {#if border && border.width > 0}
189
+ <use
190
+ href={`#${id}-border`}
191
+ fill="none"
192
+ stroke={`url(#${id}-border-color)`}
193
+ stroke-width={border.width * 2}
194
+ />
195
+ {/if}
196
+ </g>
197
+ </svg>
198
+ </div>
199
+ {/if}
177
200
 
178
201
  <style>
179
202
  img {
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { getColorModeContext } from "../../stores/color-mode";
3
3
  import { getLocalizationContext } from "../../stores/localization";
4
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
4
5
  import { getPaywallContext } from "../../stores/paywall";
5
- import { getVariablesContext } from "../../stores/variables";
6
+ import { getOptionalVariablesContext } from "../../stores/variables";
6
7
  import { FontWeights, TextAlignments } from "../../types";
7
8
  import type {
8
9
  InputTextKeyboardType,
@@ -22,6 +23,7 @@
22
23
  isFontRCFMManaged,
23
24
  } from "../../utils/font-utils";
24
25
  import {
26
+ evaluateVisibilityConditions,
25
27
  getErrorStateProps,
26
28
  getFocusStateProps,
27
29
  } from "../../utils/style-utils";
@@ -88,11 +90,12 @@
88
90
 
89
91
  const getColorMode = getColorModeContext();
90
92
  const colorMode = $derived(getColorMode());
91
- const { uiConfig, onInputChanged } = getPaywallContext();
93
+ const { uiConfig, onInputChanged, selectedPackageId } = getPaywallContext();
94
+ const packageInfo = getOptionalPackageInfoContext();
92
95
 
93
96
  const wrapperStyle = $derived(
94
97
  css({
95
- display: props.visible === false ? "none" : "flex",
98
+ display: "flex",
96
99
  width: mapSize(size.width),
97
100
  height: mapSize(size.height),
98
101
  margin: mapSpacing(margin),
@@ -140,7 +143,7 @@
140
143
  });
141
144
  });
142
145
 
143
- const variables = getVariablesContext();
146
+ const variables = getOptionalVariablesContext();
144
147
  const { getLocalizedString } = getLocalizationContext();
145
148
  const placeholder = $derived(
146
149
  replaceVariables(getLocalizedString(placeholder_lid), $variables),
@@ -161,21 +164,35 @@
161
164
  return "url";
162
165
  }
163
166
  });
167
+
168
+ const isVisible = $derived(
169
+ evaluateVisibilityConditions(
170
+ {
171
+ selectedPackageId: $selectedPackageId,
172
+ packageInfo: $packageInfo,
173
+ variables: $variables ?? {},
174
+ },
175
+ props.overrides,
176
+ props.visible,
177
+ ),
178
+ );
164
179
  </script>
165
180
 
166
- <div class="rc-gradient-border" style={wrapperStyle}>
167
- <input
168
- {type}
169
- {placeholder}
170
- {required}
171
- inputmode={keyboard_type}
172
- autocapitalize={capitalize}
173
- style={inputStyle}
174
- {oninput}
175
- onfocus={() => (focused = true)}
176
- {onblur}
177
- />
178
- </div>
181
+ {#if isVisible}
182
+ <div class="rc-gradient-border" style={wrapperStyle}>
183
+ <input
184
+ {type}
185
+ {placeholder}
186
+ {required}
187
+ inputmode={keyboard_type}
188
+ autocapitalize={capitalize}
189
+ style={inputStyle}
190
+ {oninput}
191
+ onfocus={() => (focused = true)}
192
+ {onblur}
193
+ />
194
+ </div>
195
+ {/if}
179
196
 
180
197
  <style>
181
198
  input {
@@ -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,52 @@
1
+ <script lang="ts">
2
+ import { setPackageInfoContext } from "../../stores/packageInfo";
3
+ import { setPaywallContext } from "../../stores/paywall";
4
+ import { setVariablesContext } from "../../stores/variables";
5
+ import {
6
+ createInputChoiceContext,
7
+ setInputChoiceContext,
8
+ } from "../../stores/inputChoice";
9
+ import { readable, writable } from "svelte/store";
10
+ import InputOption from "./InputOption.svelte";
11
+ import type { InputOptionProps } from "../../types/components/options";
12
+
13
+ interface Props extends InputOptionProps {
14
+ fieldId?: string;
15
+ selectionMode?: "single" | "multiple";
16
+ onButtonAction?: (action: { type: string }, actionId?: string) => void;
17
+ onInputChanged?: (
18
+ fieldId: string,
19
+ value: string,
20
+ actionId?: string,
21
+ ) => void;
22
+ }
23
+
24
+ const {
25
+ fieldId,
26
+ selectionMode = "single",
27
+ onButtonAction = () => {},
28
+ onInputChanged,
29
+ ...optionProps
30
+ }: Props = $props();
31
+
32
+ if (fieldId !== undefined) {
33
+ setInputChoiceContext(createInputChoiceContext(fieldId, selectionMode));
34
+ }
35
+
36
+ setPaywallContext({
37
+ selectedPackageId: writable(undefined),
38
+ variablesPerPackage: readable(undefined),
39
+ baseVariables: readable(undefined),
40
+ infoPerPackage: readable(undefined),
41
+ onPurchase: () => {},
42
+ emitComponentInteraction: () => {},
43
+ onButtonAction: onButtonAction as never,
44
+ onInputChanged,
45
+ uiConfig: {} as never,
46
+ hideBackButtons: false,
47
+ });
48
+ setPackageInfoContext(readable(undefined));
49
+ setVariablesContext(readable({}));
50
+ </script>
51
+
52
+ <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;
@@ -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
  /**
@@ -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
  /**
@@ -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={{