@revenuecat/purchases-ui-js 0.0.18 → 0.0.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.
@@ -2,12 +2,18 @@
2
2
  import type { FooterProps } from "../../data/entities";
3
3
  import Stack from "../stack/Stack.svelte";
4
4
 
5
- const { stack, id, labels, purchaseState, variableDictionary }: FooterProps =
6
- $props();
5
+ const {
6
+ stack,
7
+ id,
8
+ labels,
9
+ purchaseState,
10
+ variableDictionary,
11
+ onAction,
12
+ }: FooterProps = $props();
7
13
  </script>
8
14
 
9
15
  <div class="rc-pw-footer" {id}>
10
- <Stack {...stack} {labels} {purchaseState} {variableDictionary} />
16
+ <Stack {...stack} {labels} {purchaseState} {variableDictionary} {onAction} />
11
17
  </div>
12
18
 
13
19
  <style>
@@ -64,6 +64,38 @@
64
64
  mask_shape: { type: "rectangle" },
65
65
  }}
66
66
  />
67
+
68
+ <!-- Rounded Rectangle story -->
69
+ <Story
70
+ name="Rounded Rectangle"
71
+ args={{
72
+ purchaseState,
73
+ id: "example-id",
74
+ fit_mode: "fit",
75
+ size: {
76
+ width: { type: "fill" },
77
+ height: { type: "fill" },
78
+ },
79
+ source: {
80
+ light: {
81
+ original: "https://placehold.co/600x400",
82
+ heic: "https://placehold.co/600x400",
83
+ heic_low_res: "https://placehold.co/600x400",
84
+ webp: "https://placehold.co/600x400",
85
+ webp_low_res: "https://placehold.co/600x400",
86
+ },
87
+ },
88
+ mask_shape: {
89
+ type: "rectangle",
90
+ corners: {
91
+ top_leading: 32,
92
+ top_trailing: 32,
93
+ bottom_leading: 32,
94
+ bottom_trailing: 32,
95
+ },
96
+ },
97
+ }}
98
+ />
67
99
  <!-- Circle story -->
68
100
  <Story
69
101
  name="Circle"
@@ -133,9 +165,50 @@
133
165
  mask_shape: { type: "convex" },
134
166
  }}
135
167
  />
136
- <!-- Gradient story -->
168
+ <!-- Radial Gradient story -->
169
+ <Story
170
+ name="Overlay Radial Gradient"
171
+ args={{
172
+ purchaseState,
173
+ id: "example-id",
174
+ fit_mode: "fit",
175
+ size: {
176
+ width: { type: "fill" },
177
+ height: { type: "fill" },
178
+ },
179
+ source: {
180
+ light: {
181
+ original: "https://placehold.co/600x400",
182
+ heic: "https://placehold.co/600x400",
183
+ heic_low_res: "https://placehold.co/600x400",
184
+ webp: "https://placehold.co/600x400",
185
+ webp_low_res: "https://placehold.co/600x400",
186
+ },
187
+ },
188
+ color_overlay: {
189
+ dark: {
190
+ type: "hex",
191
+ value: "#000000FF",
192
+ },
193
+ light: {
194
+ points: [
195
+ {
196
+ color: "#020024ff",
197
+ percent: 0,
198
+ },
199
+ {
200
+ color: "#00d4ff00",
201
+ percent: 100,
202
+ },
203
+ ],
204
+ type: "radial",
205
+ },
206
+ },
207
+ }}
208
+ />
209
+ <!-- Linear Gradient story -->
137
210
  <Story
138
- name="Background Gradient"
211
+ name="Overlay Linear Gradient"
139
212
  args={{
140
213
  purchaseState,
141
214
  id: "example-id",
@@ -153,19 +226,57 @@
153
226
  webp_low_res: "https://placehold.co/600x400",
154
227
  },
155
228
  },
156
- gradient_colors: [
157
- {
158
- dark: { type: "hex", value: "#FFFFFF80" },
159
- light: { type: "hex", value: "#FFFFFF80" },
229
+ color_overlay: {
230
+ dark: {
231
+ type: "hex",
232
+ value: "#000000FF",
233
+ },
234
+ light: {
235
+ degrees: 180,
236
+ points: [
237
+ {
238
+ color: "#020024ff",
239
+ percent: 0,
240
+ },
241
+ {
242
+ color: "#00d4ff00",
243
+ percent: 100,
244
+ },
245
+ ],
246
+ type: "linear",
247
+ },
248
+ },
249
+ }}
250
+ />
251
+ <!-- Solid overlay -->
252
+ <Story
253
+ name="Overlay Solid"
254
+ args={{
255
+ purchaseState,
256
+ id: "example-id",
257
+ fit_mode: "fit",
258
+ size: {
259
+ width: { type: "fill" },
260
+ height: { type: "fill" },
261
+ },
262
+ source: {
263
+ light: {
264
+ original: "https://placehold.co/600x400",
265
+ heic: "https://placehold.co/600x400",
266
+ heic_low_res: "https://placehold.co/600x400",
267
+ webp: "https://placehold.co/600x400",
268
+ webp_low_res: "https://placehold.co/600x400",
160
269
  },
161
- {
162
- dark: { type: "hex", value: "#FFFFFF80" },
163
- light: { type: "hex", value: "#FFFFFF80" },
270
+ },
271
+ color_overlay: {
272
+ dark: {
273
+ type: "hex",
274
+ value: "#000000FF",
164
275
  },
165
- {
166
- dark: { type: "hex", value: "#000000" },
167
- light: { type: "hex", value: "#000000" },
276
+ light: {
277
+ type: "hex",
278
+ value: "#e7c00069",
168
279
  },
169
- ],
280
+ },
170
281
  }}
171
282
  />
@@ -4,13 +4,23 @@
4
4
 
5
5
  const { id, source, purchaseState, ...restProps }: ImageProps = $props();
6
6
 
7
- const { gradientStyles, imageStyles } = $derived(
7
+ let imageAspectRatio = $state(0);
8
+ let imageElement: HTMLImageElement | null;
9
+
10
+ // Calculate aspect ratio once image loads
11
+ function onImageLoad() {
12
+ if (imageElement) {
13
+ imageAspectRatio = imageElement.naturalHeight / imageElement.naturalWidth;
14
+ }
15
+ }
16
+ const { imageStyles, maskPath, linearGradientAngle } = $derived(
8
17
  getImageComponentStyles({
9
18
  id,
10
19
  colorMode: purchaseState.colorMode,
11
20
  source,
12
21
  purchaseState,
13
22
  ...restProps,
23
+ imageAspectRatio,
14
24
  }),
15
25
  );
16
26
 
@@ -24,24 +34,110 @@
24
34
  });
25
35
  </script>
26
36
 
27
- <div
37
+ <img
38
+ src={imageSource}
39
+ bind:this={imageElement}
40
+ onload={onImageLoad}
41
+ style="display: none;"
42
+ alt=""
43
+ />
44
+
45
+ <svg
28
46
  class="rc-pw-image-container"
29
47
  id={`rc-pw-image-container-${id}`}
30
48
  style={imageStyles}
49
+ preserveAspectRatio="xMidYMid slice"
50
+ viewBox={`0 0 100 ${imageAspectRatio * 100}`}
31
51
  >
32
- <img class="rc-pw-image" src={imageSource} alt="" {id} />
33
- <span class="rc-pw-image-overlay" style={gradientStyles}></span>
34
- </div>
52
+ <defs>
53
+ <clipPath id={`clip-path-${id}`}>
54
+ {#if restProps.mask_shape?.type === "circle"}
55
+ <ellipse
56
+ cx="50"
57
+ cy={imageAspectRatio * 50}
58
+ rx="50"
59
+ ry={imageAspectRatio * 50}
60
+ />
61
+ {:else}
62
+ <path d={maskPath} />
63
+ {/if}
64
+ </clipPath>
65
+
66
+ {#if restProps.color_overlay?.[purchaseState.colorMode]?.type === "linear"}
67
+ <linearGradient
68
+ id={`gradient-${id}`}
69
+ x1={linearGradientAngle.x1}
70
+ y1={linearGradientAngle.y1}
71
+ x2={linearGradientAngle.x2}
72
+ y2={linearGradientAngle.y2}
73
+ >
74
+ {#each restProps.color_overlay?.[purchaseState.colorMode]?.points || restProps.color_overlay?.["light"]?.points || [] as stop}
75
+ <stop
76
+ offset={`${stop.percent}%`}
77
+ style={`stop-color: ${stop.color}`}
78
+ />
79
+ {/each}
80
+ </linearGradient>
81
+ {:else if restProps.color_overlay?.[purchaseState.colorMode]?.type === "radial"}
82
+ <radialGradient
83
+ id={`gradient-${id}`}
84
+ cx="50%"
85
+ cy="50%"
86
+ r="50%"
87
+ fx="50%"
88
+ fy="50%"
89
+ >
90
+ {#each restProps.color_overlay?.[purchaseState.colorMode]?.points || restProps.color_overlay?.["light"]?.points || [] as stop}
91
+ <stop
92
+ offset={`${stop.percent}%`}
93
+ style={`stop-color: ${stop.color}`}
94
+ />
95
+ {/each}
96
+ </radialGradient>
97
+ {:else if restProps.color_overlay?.[purchaseState.colorMode]?.type === "hex"}
98
+ <linearGradient id={`gradient-${id}`}>
99
+ <stop
100
+ offset="0%"
101
+ style={`stop-color: ${
102
+ restProps.color_overlay[purchaseState.colorMode]?.value ||
103
+ restProps.color_overlay?.["light"]?.value
104
+ }`}
105
+ />
106
+ </linearGradient>
107
+ {/if}
108
+ </defs>
109
+
110
+ <image
111
+ class="rc-pw-image"
112
+ href={imageSource}
113
+ x="0"
114
+ y="0"
115
+ width="100"
116
+ height={imageAspectRatio * 100}
117
+ clip-path={`url(#clip-path-${id})`}
118
+ preserveAspectRatio="xMidYMid slice"
119
+ {id}
120
+ />
121
+
122
+ <rect
123
+ class="rc-pw-image-overlay"
124
+ x="0"
125
+ y="0"
126
+ width="100"
127
+ height={imageAspectRatio * 100}
128
+ clip-path={`url(#clip-path-${id})`}
129
+ fill={`url(#gradient-${id})`}
130
+ />
131
+ </svg>
35
132
 
36
133
  <style>
37
134
  .rc-pw-image-container {
38
- position: relative;
39
- overflow: hidden;
40
135
  border-end-start-radius: var(--image-border-end-start-radius, 0px);
41
136
  border-end-end-radius: var(--image-border-end-end-radius, 0px);
42
137
  border-start-start-radius: var(--image-border-start-start-radius, 0px);
43
138
  border-start-end-radius: var(--image-border-start-end-radius, 0px);
44
- clip-path: var(--image-clip-path, initial);
139
+ position: relative;
140
+ overflow: hidden;
45
141
  display: flex;
46
142
  flex: var(--image-flex, 1 1 auto);
47
143
  position: var(--image-position, relative);
@@ -61,6 +157,5 @@
61
157
  .rc-pw-image-overlay {
62
158
  position: absolute;
63
159
  inset: 0;
64
- background: var(--image-background, none);
65
160
  }
66
161
  </style>
@@ -4,7 +4,15 @@ import type { ImageProps } from "../../data/entities";
4
4
  * @param props - Image component properties including gradient, mask and size
5
5
  * @returns Object containing image style variables and gradient style variables
6
6
  */
7
- export declare const getImageComponentStyles: (props: ImageProps) => {
7
+ export declare const getImageComponentStyles: (props: ImageProps & {
8
+ imageAspectRatio: number;
9
+ }) => {
8
10
  imageStyles: string;
9
- gradientStyles: string;
11
+ maskPath: string;
12
+ linearGradientAngle: {
13
+ x1: string;
14
+ y1: string;
15
+ x2: string;
16
+ y2: string;
17
+ };
10
18
  };
@@ -1,14 +1,11 @@
1
- import { getActiveStateProps, getGradientStyle, getMaskStyle, getSizeStyle, prefixObject, stringifyStyles, } from "../../utils/style-utils";
1
+ import { getActiveStateProps, getLinearGradientAngle, getMaskPath, getMaskStyle, getSizeStyle, prefixObject, stringifyStyles, } from "../../utils/style-utils";
2
2
  /**
3
3
  * Generates comprehensive styles for image components by combining gradient and size styles
4
4
  * @param props - Image component properties including gradient, mask and size
5
5
  * @returns Object containing image style variables and gradient style variables
6
6
  */
7
7
  export const getImageComponentStyles = (props) => {
8
- const { gradient_colors, mask_shape, size,
9
- // max_height, // TODO: implement this. still waiting on spec
10
- // override_source_lid, // TODO: Implement this once structure is defined
11
- overrides, componentState, purchaseState, zStackChildStyles, } = props;
8
+ const { size, overrides, componentState, zStackChildStyles } = props;
12
9
  const imageStyles = {
13
10
  "--height": "unset",
14
11
  "--width": "unset",
@@ -21,21 +18,16 @@ export const getImageComponentStyles = (props) => {
21
18
  "--inset": "0",
22
19
  "--transform": "initial",
23
20
  };
24
- Object.assign(imageStyles, zStackChildStyles);
25
- const backgroundStyles = {
26
- "--background": "none",
27
- };
28
21
  const activeStateProps = getActiveStateProps(overrides, componentState);
29
- Object.assign(backgroundStyles, getGradientStyle(purchaseState.colorMode, activeStateProps?.gradient_colors || gradient_colors));
22
+ Object.assign(imageStyles, zStackChildStyles);
30
23
  Object.assign(imageStyles, getSizeStyle({ ...size, ...activeStateProps }));
31
- Object.assign(imageStyles, getMaskStyle({
32
- ...mask_shape,
33
- ...activeStateProps,
34
- }));
24
+ Object.assign(imageStyles, getMaskStyle(props.mask_shape));
35
25
  const prefixedImageStyles = prefixObject(imageStyles, "image");
36
- const prefixedGradientStyles = prefixObject(backgroundStyles, "image");
26
+ const maskPath = getMaskPath(props);
27
+ const linearGradientAngle = getLinearGradientAngle(props);
37
28
  return {
38
29
  imageStyles: stringifyStyles(prefixedImageStyles),
39
- gradientStyles: stringifyStyles(prefixedGradientStyles),
30
+ maskPath,
31
+ linearGradientAngle,
40
32
  };
41
33
  };
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  type ActionsProps,
5
5
  type ComponentLocalizations,
6
+ type ComponentState,
6
7
  type Extra,
7
8
  type PaywallComponent,
8
9
  type PurchaseStateProps,
@@ -39,6 +40,7 @@
39
40
  onAction?: (action: SupportedActions, data?: Extra) => void;
40
41
  variableDictionary?: VariableDictionary;
41
42
  zStackChildStyles?: ZStackChildStyles;
43
+ componentState?: ComponentState;
42
44
  }
43
45
 
44
46
  const ComponentTypes = {
@@ -116,6 +118,7 @@
116
118
  {onAction}
117
119
  {purchaseState}
118
120
  {variableDictionary}
121
+ {...restProps}
119
122
  />
120
123
  {/each}
121
124
  </ComponentToRender>
@@ -1,4 +1,4 @@
1
- import { type ActionsProps, type ComponentLocalizations, type Extra, type PaywallComponent, type PurchaseStateProps, type SupportedActions } from "../../data/entities";
1
+ import { type ActionsProps, type ComponentLocalizations, type ComponentState, type Extra, type PaywallComponent, type PurchaseStateProps, type SupportedActions } from "../../data/entities";
2
2
  import type { VariableDictionary } from "../../utils/variable-utils";
3
3
  import type { ZStackChildStyles } from "../stack/stack-utils";
4
4
  import TextNode from "../text/TextNode.svelte";
@@ -12,6 +12,7 @@ interface Props extends ActionsProps, PurchaseStateProps {
12
12
  onAction?: (action: SupportedActions, data?: Extra) => void;
13
13
  variableDictionary?: VariableDictionary;
14
14
  zStackChildStyles?: ZStackChildStyles;
15
+ componentState?: ComponentState;
15
16
  }
16
17
  declare const Node: Component<Props, {
17
18
  /**
@@ -10,7 +10,6 @@
10
10
  paywallData,
11
11
  gradientPaywallData,
12
12
  variablesPastaPaywallData,
13
- stateTemplate,
14
13
  posterMakerTemplate,
15
14
  e2eTestTemplate,
16
15
  zStackTemplate,
@@ -145,7 +144,7 @@
145
144
  <Story
146
145
  name="Dynamic state style overrides"
147
146
  args={{
148
- paywallData: stateTemplate,
147
+ paywallData: e2eTestTemplate,
149
148
  onPurchaseClicked: fn(),
150
149
  onBackClicked: fn(),
151
150
  onNavigateToUrlClicked: fn(),
@@ -157,11 +156,11 @@
157
156
  name="Dynamic state style overrides with background image"
158
157
  args={{
159
158
  paywallData: {
160
- ...stateTemplate,
159
+ ...e2eTestTemplate,
161
160
  components_config: {
162
- ...stateTemplate.components_config,
161
+ ...e2eTestTemplate.components_config,
163
162
  base: {
164
- ...stateTemplate.components_config.base,
163
+ ...e2eTestTemplate.components_config.base,
165
164
  background: {
166
165
  type: "image",
167
166
  value: {
@@ -205,6 +204,68 @@
205
204
  onBackClicked: fn(),
206
205
  onNavigateToUrlClicked: fn(),
207
206
  onRestorePurchasesClicked: fn(),
207
+ variablesPerPackage: {
208
+ $rc_annual: {
209
+ product_name: "Yearly sub",
210
+ price: "$19.99",
211
+ price_per_period: "$19.99/1yr",
212
+ price_per_period_full: "$19.99/1year",
213
+ total_price_and_per_month: "$19.99/1yr($1.67/mo)",
214
+ total_price_and_per_month_full: "$19.99/1year($1.67/month)",
215
+ sub_price_per_month: "$1.67",
216
+ sub_price_per_week: "$0.42",
217
+ sub_duration: "1 year",
218
+ sub_duration_in_months: "12 months",
219
+ sub_period: "yearly",
220
+ sub_period_length: "year",
221
+ sub_period_abbreviated: "yr",
222
+ sub_relative_discount: "33% off",
223
+ sub_offer_duration: "undefined",
224
+ sub_offer_duration_2: "undefined",
225
+ sub_offer_price: "undefined",
226
+ sub_offer_price_2: "undefined",
227
+ },
228
+ $rc_monthly: {
229
+ product_name: "Monthly sub",
230
+ price: "$30.00",
231
+ price_per_period: "$30.00/1mo",
232
+ price_per_period_full: "$30.00/1month",
233
+ total_price_and_per_month: "$30.00",
234
+ total_price_and_per_month_full: "$30.00",
235
+ sub_price_per_month: "$30.00",
236
+ sub_price_per_week: "$30.00",
237
+ sub_duration: "1 month",
238
+ sub_duration_in_months: "1 month",
239
+ sub_period: "monthly",
240
+ sub_period_length: "month",
241
+ sub_period_abbreviated: "mo",
242
+ sub_relative_discount: "",
243
+ sub_offer_duration: "undefined",
244
+ sub_offer_duration_2: "undefined",
245
+ sub_offer_price: "undefined",
246
+ sub_offer_price_2: "undefined",
247
+ },
248
+ $rc_weekly: {
249
+ product_name: "Weekly no trial",
250
+ price: "$1.25",
251
+ price_per_period: "$1.25/1wk",
252
+ price_per_period_full: "$1.25/1week",
253
+ total_price_and_per_month: "$1.25/1wk($5.00/mo)",
254
+ total_price_and_per_month_full: "$1.25/1week($5.00/month)",
255
+ sub_price_per_month: "$5.00",
256
+ sub_price_per_week: "$1.25",
257
+ sub_duration: "1 week",
258
+ sub_duration_in_months: "1 week",
259
+ sub_period: "weekly",
260
+ sub_period_length: "week",
261
+ sub_period_abbreviated: "wk",
262
+ sub_relative_discount: "96% off",
263
+ sub_offer_duration: "undefined",
264
+ sub_offer_duration_2: "undefined",
265
+ sub_offer_price: "undefined",
266
+ sub_offer_price_2: "undefined",
267
+ },
268
+ },
208
269
  }}
209
270
  />
210
271
 
@@ -103,7 +103,6 @@
103
103
  }
104
104
  onPurchaseClicked?.(purchaseState.selectedPackageId);
105
105
  };
106
-
107
106
  const onAction = (action: SupportedActions, data?: Extra) => {
108
107
  switch (action.type) {
109
108
  case "select_package":
@@ -153,6 +152,7 @@
153
152
  labels={paywallData.components_localizations}
154
153
  {purchaseState}
155
154
  {variableDictionary}
155
+ {onAction}
156
156
  />
157
157
  {/if}
158
158
  {#if paywallData.components_config.base.background?.type === "color"}
@@ -3,6 +3,11 @@
3
3
  import PurchaseButton from "./PurchaseButton.svelte";
4
4
  import type { PurchaseState } from "../../data/state";
5
5
  import type { ColorMode, DimensionType } from "../../types";
6
+ import type { SupportedActions } from "../../data/entities";
7
+
8
+ const onAction = (action: SupportedActions) => {
9
+ alert(action.type);
10
+ };
6
11
 
7
12
  const { Story } = defineMeta({
8
13
  title: "Components/PurchaseButton",
@@ -135,5 +140,6 @@
135
140
  },
136
141
  labels: labelsData,
137
142
  type: "purchase_button",
143
+ onAction,
138
144
  }}
139
145
  />
@@ -96,6 +96,7 @@
96
96
  {onAction}
97
97
  {purchaseState}
98
98
  {variableDictionary}
99
+ componentState={restProps.componentState}
99
100
  zStackChildStyles={index > 0 ? zStackChildrenStyles : undefined}
100
101
  />
101
102
  {:else}
@@ -105,6 +106,7 @@
105
106
  {onAction}
106
107
  {purchaseState}
107
108
  {variableDictionary}
109
+ componentState={restProps.componentState}
108
110
  />
109
111
  {/if}
110
112
  {/each}
@@ -22,9 +22,9 @@ export declare function getStackBadgeTextStyles(props: StackProps): {
22
22
  tagToRender: string;
23
23
  textStyles: string;
24
24
  };
25
+ export declare function getZStackChildStyles(props: StackProps): ZStackChildStyles | undefined;
25
26
  export type ZStackChildStyles = {
26
27
  "--inset": string;
27
28
  "--transform": string;
28
29
  "--position": string;
29
30
  };
30
- export declare function getZStackChildStyles(props: StackProps): ZStackChildStyles | undefined;
@@ -37,7 +37,7 @@ export const getStackComponentStyles = (props) => {
37
37
  };
38
38
  const activeStateProps = getActiveStateProps(overrides, componentState);
39
39
  Object.assign(stackStyles, getComponentStyles({
40
- backgroundColor: background_color,
40
+ background_color,
41
41
  margin,
42
42
  padding,
43
43
  border,
@@ -91,8 +91,8 @@ export function getBadgeStyles(props) {
91
91
  alignment: badge.alignment,
92
92
  }), getComponentStyles({
93
93
  ...badge,
94
- backgroundColor: badge.background_color,
95
- textColor: badge.color,
94
+ background_color: badge.background_color,
95
+ color: badge.color,
96
96
  colorMode: props.purchaseState.colorMode,
97
97
  }));
98
98
  if (badge.style === "overlay") {
@@ -43,8 +43,8 @@ export const getTextComponentStyles = (props) => {
43
43
  const tagToRender = getTextComponentTag(font_size);
44
44
  const textStyles = getTextStyles({ ...props, ...activeStateProps }, purchaseState.colorMode);
45
45
  const componentStyles = getComponentStyles({
46
- backgroundColor: background_color,
47
- textColor: color,
46
+ background_color,
47
+ color,
48
48
  margin,
49
49
  padding,
50
50
  colorMode: purchaseState.colorMode,
@@ -88,7 +88,7 @@ export function getTextWrapperStyles(restProps, size, background_color) {
88
88
  "--transform": "initial",
89
89
  };
90
90
  Object.assign(styles, getComponentStyles({
91
- backgroundColor: background_color,
91
+ background_color,
92
92
  ...restProps,
93
93
  }), getSizeStyle(size));
94
94
  Object.assign(styles, restProps.zStackChildStyles);