@revenuecat/purchases-ui-js 3.12.0 → 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.
@@ -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 {
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
+ import { setPackageInfoContext } from "../../stores/packageInfo";
2
3
  import { setPaywallContext } from "../../stores/paywall";
4
+ import { setVariablesContext } from "../../stores/variables";
3
5
  import {
4
6
  createInputChoiceContext,
5
7
  setInputChoiceContext,
@@ -37,11 +39,14 @@
37
39
  baseVariables: readable(undefined),
38
40
  infoPerPackage: readable(undefined),
39
41
  onPurchase: () => {},
42
+ emitComponentInteraction: () => {},
40
43
  onButtonAction: onButtonAction as never,
41
44
  onInputChanged,
42
45
  uiConfig: {} as never,
43
46
  hideBackButtons: false,
44
47
  });
48
+ setPackageInfoContext(readable(undefined));
49
+ setVariablesContext(readable({}));
45
50
  </script>
46
51
 
47
52
  <InputOption {...optionProps} />
@@ -2,7 +2,10 @@
2
2
  import Node from "../paywall/Node.svelte";
3
3
  import { getColorModeContext } from "../../stores/color-mode";
4
4
  import { getHoverStateContext } from "../../stores/hover";
5
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
6
+ import { getPaywallContext } from "../../stores/paywall";
5
7
  import { getSelectedStateContext } from "../../stores/selected";
8
+ import { getOptionalVariablesContext } from "../../stores/variables";
6
9
  import type { StackProps } from "../../types/components/stack";
7
10
  import { mapBackground } from "../../utils/background-utils";
8
11
  import {
@@ -16,6 +19,7 @@
16
19
  type CSS,
17
20
  } from "../../utils/base-utils";
18
21
  import {
22
+ evaluateVisibilityConditions,
19
23
  getActiveStateProps,
20
24
  getHoverStateProps,
21
25
  } from "../../utils/style-utils";
@@ -35,6 +39,9 @@
35
39
 
36
40
  const selectedState = getSelectedStateContext();
37
41
  const hoverState = getHoverStateContext();
42
+ const packageInfo = getOptionalPackageInfoContext();
43
+ const { selectedPackageId } = getPaywallContext();
44
+ const variables = getOptionalVariablesContext();
38
45
  const {
39
46
  components,
40
47
  size,
@@ -61,7 +68,7 @@
61
68
 
62
69
  const stackStyle = $derived(
63
70
  css({
64
- display: props.visible === false ? "none" : "flex",
71
+ display: "flex",
65
72
  position: "relative",
66
73
  width: mapSize(size.width),
67
74
  height: mapSize(size.height),
@@ -97,41 +104,55 @@
97
104
  };
98
105
 
99
106
  const badgeStyle = $derived(css(mapBadge(badge, border?.width)));
107
+
108
+ const isVisible = $derived(
109
+ evaluateVisibilityConditions(
110
+ {
111
+ selectedPackageId: $selectedPackageId,
112
+ packageInfo: $packageInfo,
113
+ variables: $variables ?? {},
114
+ },
115
+ props.overrides,
116
+ props.visible,
117
+ ),
118
+ );
100
119
  </script>
101
120
 
102
- <svelte:element
103
- this={onclick !== undefined ? "button" : "div"}
104
- role={onclick !== undefined ? "button" : undefined}
105
- {onclick}
106
- style={stackStyle}
107
- class={["stack", "rc-gradient-border", classProp].filter(Boolean).join(" ")}
108
- data-testid={testId}
109
- >
110
- {#if badge}
111
- <div style={badgeStyle}>
112
- <Node nodeData={badge.stack} />
113
- </div>
114
- {/if}
121
+ {#if isVisible}
122
+ <svelte:element
123
+ this={onclick !== undefined ? "button" : "div"}
124
+ role={onclick !== undefined ? "button" : undefined}
125
+ {onclick}
126
+ style={stackStyle}
127
+ class={["stack", "rc-gradient-border", classProp].filter(Boolean).join(" ")}
128
+ data-testid={testId}
129
+ >
130
+ {#if badge}
131
+ <div style={badgeStyle}>
132
+ <Node nodeData={badge.stack} />
133
+ </div>
134
+ {/if}
115
135
 
116
- {#if dimension.type === "zlayer"}
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
- >
121
- {#each components as component, index}
122
- <div style={getLayerStyle(index)}>
123
- <Node nodeData={component} />
124
- </div>
136
+ {#if dimension.type === "zlayer"}
137
+ <!-- isolate: layer z-index values only sort within this stack, not the host page -->
138
+ <div
139
+ style="position: relative; width: 100%; height: 100%; isolation: isolate;"
140
+ >
141
+ {#each components as component, index}
142
+ <div style={getLayerStyle(index)}>
143
+ <Node nodeData={component} />
144
+ </div>
145
+ {/each}
146
+ </div>
147
+ {:else}
148
+ {#each components as component}
149
+ <Node nodeData={component} />
125
150
  {/each}
126
- </div>
127
- {:else}
128
- {#each components as component}
129
- <Node nodeData={component} />
130
- {/each}
131
- {/if}
151
+ {/if}
132
152
 
133
- {@render children?.()}
134
- </svelte:element>
153
+ {@render children?.()}
154
+ </svelte:element>
155
+ {/if}
135
156
 
136
157
  <style>
137
158
  .stack {
@@ -1,7 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { getColorModeContext } from "../../stores/color-mode";
3
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
3
4
  import { getPaywallContext } from "../../stores/paywall";
4
5
  import { getSelectedStateContext } from "../../stores/selected";
6
+ import { getOptionalVariablesContext } from "../../stores/variables";
5
7
  import type { TabsProps } from "../../types/components/tabs";
6
8
  import { mapBackground } from "../../utils/background-utils";
7
9
  import {
@@ -12,7 +14,10 @@
12
14
  mapSize,
13
15
  mapSpacing,
14
16
  } from "../../utils/base-utils";
15
- import { getActiveStateProps } from "../../utils/style-utils";
17
+ import {
18
+ evaluateVisibilityConditions,
19
+ getActiveStateProps,
20
+ } from "../../utils/style-utils";
16
21
  import { derived, get, readable, writable } from "svelte/store";
17
22
  import Stack from "../stack/Stack.svelte";
18
23
  import { setTabsContext, type SelectTab } from "./tabs-context";
@@ -21,6 +26,9 @@
21
26
  const { name, control, tabs, default_tab_id } = props;
22
27
 
23
28
  const selectedState = getSelectedStateContext();
29
+ const packageInfo = getOptionalPackageInfoContext();
30
+ const { selectedPackageId } = getPaywallContext();
31
+ const variables = getOptionalVariablesContext();
24
32
  const { size, padding, margin, background, shape, border, shadow } =
25
33
  $derived.by(() => {
26
34
  return {
@@ -94,10 +102,24 @@
94
102
  selectTab,
95
103
  isActive: derived(selectedTabIndex, (value) => value === 1),
96
104
  });
105
+
106
+ const isVisible = $derived(
107
+ evaluateVisibilityConditions(
108
+ {
109
+ selectedPackageId: $selectedPackageId,
110
+ packageInfo: $packageInfo,
111
+ variables: $variables ?? {},
112
+ },
113
+ props.overrides,
114
+ props.visible,
115
+ ),
116
+ );
97
117
  </script>
98
118
 
99
- <div class="rc-gradient-border" style={styles}>
100
- {#if $tab !== undefined}
101
- <Stack {...$tab.stack} />
102
- {/if}
103
- </div>
119
+ {#if isVisible}
120
+ <div class="rc-gradient-border" style={styles}>
121
+ {#if $tab !== undefined}
122
+ <Stack {...$tab.stack} />
123
+ {/if}
124
+ </div>
125
+ {/if}
@@ -9,7 +9,7 @@
9
9
  import { getLocalizationContext } from "../../stores/localization";
10
10
  import { getPaywallContext } from "../../stores/paywall";
11
11
  import { getSelectedStateContext } from "../../stores/selected";
12
- import { getVariablesContext } from "../../stores/variables";
12
+ import { getOptionalVariablesContext } from "../../stores/variables";
13
13
  import type { TextNodeProps } from "../../types/components/text";
14
14
  import {
15
15
  getActiveStateProps,
@@ -18,12 +18,12 @@
18
18
  evaluateVisibilityConditions,
19
19
  } from "../../utils/style-utils";
20
20
  import { replaceVariables } from "../../utils/variable-utils";
21
- import { getPackageInfoContext } from "../../stores/packageInfo";
21
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
22
22
 
23
23
  const props: TextNodeProps = $props();
24
24
 
25
25
  const selectedState = getSelectedStateContext();
26
- const packageInfo = getPackageInfoContext();
26
+ const packageInfo = getOptionalPackageInfoContext();
27
27
  const {
28
28
  uiConfig,
29
29
  selectedPackageId,
@@ -61,7 +61,7 @@
61
61
  ),
62
62
  );
63
63
 
64
- const variables = getVariablesContext();
64
+ const variables = getOptionalVariablesContext();
65
65
  const { getLocalizedString } = getLocalizationContext();
66
66
  const label = $derived(
67
67
  replaceVariables(getLocalizedString(actualProps.text_lid), $variables),
@@ -1,13 +1,22 @@
1
1
  <script lang="ts">
2
2
  import { getTimelineInlineStyles } from "./timeline-utils";
3
3
  import TimelineItem from "./TimelineItem.svelte";
4
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
5
+ import { getPaywallContext } from "../../stores/paywall";
6
+ import { getSelectedStateContext } from "../../stores/selected";
7
+ import { getOptionalVariablesContext } from "../../stores/variables";
4
8
  import type { TimelineProps } from "../../types/components/timeline";
5
9
  import { css } from "../../utils/base-utils";
6
- import { getActiveStateProps } from "../../utils/style-utils";
7
- import { getSelectedStateContext } from "../../stores/selected";
10
+ import {
11
+ evaluateVisibilityConditions,
12
+ getActiveStateProps,
13
+ } from "../../utils/style-utils";
8
14
 
9
15
  const props: TimelineProps = $props();
10
16
  const selectedState = getSelectedStateContext();
17
+ const packageInfo = getOptionalPackageInfoContext();
18
+ const { selectedPackageId } = getPaywallContext();
19
+ const variables = getOptionalVariablesContext();
11
20
 
12
21
  const {
13
22
  items,
@@ -41,9 +50,21 @@
41
50
  }),
42
51
  ),
43
52
  );
53
+
54
+ const isVisible = $derived(
55
+ evaluateVisibilityConditions(
56
+ {
57
+ selectedPackageId: $selectedPackageId,
58
+ packageInfo: $packageInfo,
59
+ variables: $variables ?? {},
60
+ },
61
+ props.overrides,
62
+ props.visible,
63
+ ),
64
+ );
44
65
  </script>
45
66
 
46
- {#if items.length}
67
+ {#if isVisible && items.length}
47
68
  <div style={timelineStyles}>
48
69
  {#each items as item (item.id)}
49
70
  <TimelineItem {...item} {text_spacing} {item_spacing} />
@@ -1,7 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from "svelte";
3
3
  import { getColorModeContext } from "../../stores/color-mode";
4
+ import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
5
+ import { getPaywallContext } from "../../stores/paywall";
4
6
  import { getSelectedStateContext } from "../../stores/selected";
7
+ import { getOptionalVariablesContext } from "../../stores/variables";
5
8
  import type { VideoProps } from "../../types/components/video";
6
9
  import type { VideoFiles, ImageFiles } from "../../types/media";
7
10
  import {
@@ -13,13 +16,19 @@
13
16
  mapSize,
14
17
  mapSpacing,
15
18
  } from "../../utils/base-utils";
16
- import { getActiveStateProps } from "../../utils/style-utils";
19
+ import {
20
+ evaluateVisibilityConditions,
21
+ getActiveStateProps,
22
+ } from "../../utils/style-utils";
17
23
  import ClipPath from "../image/ClipPath.svelte";
18
24
  import Overlay from "../image/Overlay.svelte";
19
25
 
20
26
  const props: VideoProps = $props();
21
27
 
22
28
  const selectedState = getSelectedStateContext();
29
+ const packageInfo = getOptionalPackageInfoContext();
30
+ const { selectedPackageId } = getPaywallContext();
31
+ const variables = getOptionalVariablesContext();
23
32
  const {
24
33
  id,
25
34
  source,
@@ -223,84 +232,98 @@
223
232
  });
224
233
  }
225
234
  });
226
- </script>
227
-
228
- <div class="video-outer" style={wrapperStyle} bind:clientWidth={wrapperWidth}>
229
- <div class="video-media">
230
- <div class="video-clip" style={clipDivStyle}>
231
- {#if shouldShowFallback && fallbackImage}
232
- <img
233
- src={fallbackImage.original}
234
- style="object-fit:{mapFitMode(fit_mode)}"
235
- alt=""
236
- />
237
- {:else if video}
238
- <video
239
- bind:this={videoElement}
240
- src={video.url}
241
- style="object-fit:{mapFitMode(fit_mode)}"
242
- {loop}
243
- muted={mute_audio}
244
- controls={show_controls || showControlsFallback}
245
- playsinline
246
- >
247
- <track kind="captions" />
248
- </video>
249
- {/if}
250
- </div>
251
235
 
252
- <svg
253
- class="video-deco"
254
- bind:contentRect={svgRect}
255
- width="100%"
256
- height="100%"
257
- {viewBox}
258
- >
259
- <defs>
260
- <clipPath id={`${id}-path`}>
261
- <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
262
- </clipPath>
263
-
264
- <g id={`${id}-border`}>
265
- <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
266
- </g>
236
+ const isVisible = $derived(
237
+ evaluateVisibilityConditions(
238
+ {
239
+ selectedPackageId: $selectedPackageId,
240
+ packageInfo: $packageInfo,
241
+ variables: $variables ?? {},
242
+ },
243
+ props.overrides,
244
+ props.visible,
245
+ ),
246
+ );
247
+ </script>
267
248
 
268
- <Overlay id={`${id}-overlay`} {overlay} />
269
- {#if border && border.width > 0}
270
- <Overlay
271
- id={`${id}-border-color`}
272
- overlay={mapColorMode(colorMode, border.color)}
273
- />
274
- {/if}
275
- </defs>
276
-
277
- {#if border && border.width > 0}
278
- <use href={`#${id}-border`} fill="transparent" />
279
- {/if}
280
-
281
- <g clip-path={`url(#${id}-path)`}>
282
- {#if overlay}
283
- <rect
284
- x="0"
285
- y="0"
286
- height="100%"
287
- width="100%"
288
- fill={`url(#${id}-overlay)`}
249
+ {#if isVisible}
250
+ <div class="video-outer" style={wrapperStyle} bind:clientWidth={wrapperWidth}>
251
+ <div class="video-media">
252
+ <div class="video-clip" style={clipDivStyle}>
253
+ {#if shouldShowFallback && fallbackImage}
254
+ <img
255
+ src={fallbackImage.original}
256
+ style="object-fit:{mapFitMode(fit_mode)}"
257
+ alt=""
289
258
  />
259
+ {:else if video}
260
+ <video
261
+ bind:this={videoElement}
262
+ src={video.url}
263
+ style="object-fit:{mapFitMode(fit_mode)}"
264
+ {loop}
265
+ muted={mute_audio}
266
+ controls={show_controls || showControlsFallback}
267
+ playsinline
268
+ >
269
+ <track kind="captions" />
270
+ </video>
290
271
  {/if}
272
+ </div>
273
+
274
+ <svg
275
+ class="video-deco"
276
+ bind:contentRect={svgRect}
277
+ width="100%"
278
+ height="100%"
279
+ {viewBox}
280
+ >
281
+ <defs>
282
+ <clipPath id={`${id}-path`}>
283
+ <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
284
+ </clipPath>
285
+
286
+ <g id={`${id}-border`}>
287
+ <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
288
+ </g>
289
+
290
+ <Overlay id={`${id}-overlay`} {overlay} />
291
+ {#if border && border.width > 0}
292
+ <Overlay
293
+ id={`${id}-border-color`}
294
+ overlay={mapColorMode(colorMode, border.color)}
295
+ />
296
+ {/if}
297
+ </defs>
291
298
 
292
299
  {#if border && border.width > 0}
293
- <use
294
- href={`#${id}-border`}
295
- fill="none"
296
- stroke={`url(#${id}-border-color)`}
297
- stroke-width={border.width * 2}
298
- />
300
+ <use href={`#${id}-border`} fill="transparent" />
299
301
  {/if}
300
- </g>
301
- </svg>
302
+
303
+ <g clip-path={`url(#${id}-path)`}>
304
+ {#if overlay}
305
+ <rect
306
+ x="0"
307
+ y="0"
308
+ height="100%"
309
+ width="100%"
310
+ fill={`url(#${id}-overlay)`}
311
+ />
312
+ {/if}
313
+
314
+ {#if border && border.width > 0}
315
+ <use
316
+ href={`#${id}-border`}
317
+ fill="none"
318
+ stroke={`url(#${id}-border-color)`}
319
+ stroke-width={border.width * 2}
320
+ />
321
+ {/if}
322
+ </g>
323
+ </svg>
324
+ </div>
302
325
  </div>
303
- </div>
326
+ {/if}
304
327
 
305
328
  <style>
306
329
  .video-outer {
@@ -1,20 +1,28 @@
1
1
  <script lang="ts">
2
- import { getPaywallContext } from "../../stores/paywall";
3
- import {
4
- type CSS,
5
- css,
6
- mapSize,
7
- mapSpacing,
8
- px,
9
- } from "../../utils/base-utils";
10
2
  import { mapDimension } from "../stack/stack-utils";
11
- import { DEFAULT_SPACING } from "../../utils/constants";
3
+ import { getColorModeContext } from "../../stores/color-mode";
4
+ import { getPaywallContext } from "../../stores/paywall";
12
5
  import type { WalletButtonProps } from "../../types/components/wallet-button";
6
+ import { css, mapSize, mapSpacing, px } from "../../utils/base-utils";
7
+ import { DEFAULT_SPACING } from "../../utils/constants";
13
8
 
14
9
  const { walletButtonRender, onWalletButtonReady, selectedPackageId } =
15
10
  getPaywallContext();
11
+ const getColorMode = getColorModeContext();
12
+ const colorMode = $derived(getColorMode());
16
13
 
17
- const props: WalletButtonProps & { style?: CSS } = $props();
14
+ const props: WalletButtonProps = $props();
15
+
16
+ const walletButtonTheme = $derived.by(() => {
17
+ const { style } = props;
18
+ if (style == null) {
19
+ return;
20
+ }
21
+
22
+ return colorMode === "dark" && style.dark != null
23
+ ? style.dark
24
+ : style.light;
25
+ });
18
26
 
19
27
  const fallbackSize = {
20
28
  width: { type: "fill" },
@@ -26,22 +34,19 @@
26
34
  distribution: "center",
27
35
  } as const;
28
36
 
29
- const { size, dimension, spacing, margin, style, border, shape, shadow } =
30
- $derived.by(() => {
31
- return {
32
- size: props.size ?? fallbackSize,
33
- dimension: props.dimension ?? fallbackDimension,
34
- spacing: props.spacing ?? 0,
35
- margin: props.margin ?? DEFAULT_SPACING,
36
- border: props.border ?? null,
37
- shape: props.shape ?? null,
38
- shadow: props.shadow ?? null,
39
- style: props.style,
40
- };
41
- });
37
+ const { size, dimension, spacing, margin } = $derived.by(() => {
38
+ return {
39
+ size: props.size ?? fallbackSize,
40
+ dimension: props.dimension ?? fallbackDimension,
41
+ spacing: props.spacing ?? 0,
42
+ margin: props.margin ?? DEFAULT_SPACING,
43
+ border: props.border ?? null,
44
+ shape: props.shape ?? null,
45
+ shadow: props.shadow ?? null,
46
+ };
47
+ });
42
48
 
43
49
  const stackLayoutRules = $derived({
44
- ...style,
45
50
  display: "flex",
46
51
  position: "relative",
47
52
  width: mapSize(size.width),
@@ -64,12 +69,15 @@
64
69
 
65
70
  {#if walletButtonRender && $selectedPackageId}
66
71
  <div style={stackStyle}>
67
- <div
68
- style="flex-grow: 1; width:100%;"
69
- use:walletButtonRender={{
70
- selectedPackageId: $selectedPackageId,
71
- onReady: props.onWalletButtonReady ?? onWalletButtonReady,
72
- }}
73
- ></div>
72
+ {#key walletButtonTheme}
73
+ <div
74
+ style="flex-grow: 1; width:100%;"
75
+ use:walletButtonRender={{
76
+ selectedPackageId: $selectedPackageId,
77
+ onReady: props.onWalletButtonReady ?? onWalletButtonReady,
78
+ walletButtonTheme,
79
+ }}
80
+ ></div>
81
+ {/key}
74
82
  </div>
75
83
  {/if}
@@ -1,8 +1,4 @@
1
- import { type CSS } from "../../utils/base-utils";
2
1
  import type { WalletButtonProps } from "../../types/components/wallet-button";
3
- type $$ComponentProps = WalletButtonProps & {
4
- style?: CSS;
5
- };
6
- declare const WalletButton: import("svelte").Component<$$ComponentProps, {}, "">;
2
+ declare const WalletButton: import("svelte").Component<WalletButtonProps, {}, "">;
7
3
  type WalletButton = ReturnType<typeof WalletButton>;
8
4
  export default WalletButton;
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ export * from "./types";
18
18
  export { type PaywallData } from "./types/paywall";
19
19
  export { type InitialInputSelections } from "./stores/inputValidation";
20
20
  export { type UIConfig } from "./types/ui-config";
21
- export { type WalletButtonRender } from "./types/wallet";
21
+ export { type WalletButtonRender, type WalletButtonTheme, } from "./types/wallet";
22
22
  export { type CustomVariables, CustomVariableValue, type VariableDictionary, type PackageInfo, } from "./types/variables";
23
23
  export type { CompleteWorkflowNavigateArgs, CompleteWorkflowUrlQueryParams, } from "./types/components/button";
24
24
  export * from "./ui/globals";
@@ -3,3 +3,4 @@ import { type Readable } from "svelte/store";
3
3
  export type PackageInfoStore = Readable<PackageInfo | undefined>;
4
4
  export declare function setPackageInfoContext(variables: PackageInfoStore): void;
5
5
  export declare function getPackageInfoContext(): PackageInfoStore;
6
+ export declare function getOptionalPackageInfoContext(): PackageInfoStore;
@@ -1,5 +1,5 @@
1
1
  import { getContext, setContext } from "svelte";
2
- import {} from "svelte/store";
2
+ import { readable } from "svelte/store";
3
3
  const key = Symbol("packageInfo");
4
4
  export function setPackageInfoContext(variables) {
5
5
  setContext(key, variables);
@@ -11,3 +11,7 @@ export function getPackageInfoContext() {
11
11
  }
12
12
  return context;
13
13
  }
14
+ const emptyPackageInfoStore = readable(undefined);
15
+ export function getOptionalPackageInfoContext() {
16
+ return (getContext(key) ?? emptyPackageInfoStore);
17
+ }
@@ -3,3 +3,4 @@ import { type Readable } from "svelte/store";
3
3
  export type VariablesStore = Readable<VariableDictionary | undefined>;
4
4
  export declare function setVariablesContext(variables: VariablesStore): void;
5
5
  export declare function getVariablesContext(): VariablesStore;
6
+ export declare function getOptionalVariablesContext(): VariablesStore;
@@ -1,5 +1,5 @@
1
1
  import { getContext, setContext } from "svelte";
2
- import {} from "svelte/store";
2
+ import { readable } from "svelte/store";
3
3
  const key = Symbol("variables");
4
4
  export function setVariablesContext(variables) {
5
5
  setContext(key, variables);
@@ -11,3 +11,7 @@ export function getVariablesContext() {
11
11
  }
12
12
  return context;
13
13
  }
14
+ const emptyVariablesStore = readable(undefined);
15
+ export function getOptionalVariablesContext() {
16
+ return getContext(key) ?? emptyVariablesStore;
17
+ }
@@ -1,6 +1,11 @@
1
1
  import type { BaseComponent } from "../base";
2
2
  import type { BorderType, ShadowType, ShapeType, SizeType, Spacing } from "../..";
3
3
  import type { Dimension } from "../alignment";
4
+ import type { WalletButtonTheme } from "../wallet";
5
+ export interface WalletButtonStyle {
6
+ light: WalletButtonTheme;
7
+ dark?: WalletButtonTheme | null;
8
+ }
4
9
  export interface WalletButtonProps extends BaseComponent {
5
10
  type: "wallet_button";
6
11
  size?: SizeType;
@@ -9,6 +14,7 @@ export interface WalletButtonProps extends BaseComponent {
9
14
  margin?: Spacing;
10
15
  border?: BorderType | null;
11
16
  shape?: ShapeType | null;
17
+ style?: WalletButtonStyle | null;
12
18
  onWalletButtonReady?: (walletButtonAvailable?: boolean) => void;
13
19
  shadow?: ShadowType | null;
14
20
  }
@@ -1,10 +1,18 @@
1
+ /**
2
+ * Theme for the Stripe wallet button appearance.
3
+ * Matches Apple Pay button styles: 'black', 'white', or 'white-outline'
4
+ * @default "black"
5
+ */
6
+ export type WalletButtonTheme = "black" | "white" | "white-outline";
1
7
  export type WalletButtonRender = (node: HTMLElement, params: {
2
8
  selectedPackageId: string;
3
9
  onReady?: (walletButtonAvailable?: boolean) => void;
10
+ walletButtonTheme?: WalletButtonTheme;
4
11
  }) => {
5
12
  destroy?: () => void;
6
13
  update?: (updateParams: {
7
14
  selectedPackageId: string;
8
15
  onReady?: (walletButtonAvailable?: boolean) => void;
16
+ walletButtonTheme?: WalletButtonTheme;
9
17
  }) => void;
10
18
  };
@@ -1,5 +1,5 @@
1
1
  import type { Component } from "../types/component.js";
2
- import { type Overrides } from "../types/overrides.js";
2
+ import type { Overrides } from "../types/overrides.js";
3
3
  import type { RootPaywall } from "../types/paywall.js";
4
4
  import type { PackageInfo, VariableDictionary } from "../types/variables.js";
5
5
  /**
@@ -21,13 +21,13 @@ export type VisibilityContext = {
21
21
  variables: VariableDictionary;
22
22
  };
23
23
  /**
24
- * Evaluates visibility conditions on an element's overrides.
24
+ * Evaluates visibility for an element.
25
25
  *
26
- * The four structured condition types — selected_package_condition,
27
- * intro_offer_condition, promo_offer_condition, and variable_condition
28
- * determine whether an element should be visible. If an override exists
29
- * containing only these condition types and all of its conditions are met,
30
- * the element is visible. If the conditions are not met, the element is hidden.
31
- * If no visibility override exists, the element defaults to visible.
26
+ * A component is considered visible unless:
27
+ * - baseVisible is explicitly false, or
28
+ * - an override sets `visible: false` in its properties and all of its
29
+ * conditions are satisfied by the current context.
30
+ *
31
+ * If baseVisible is undefined or null, the component defaults to visible.
32
32
  */
33
33
  export declare const evaluateVisibilityConditions: <T extends Component>(context: VisibilityContext, overrides?: Overrides<T>, baseVisible?: boolean | null) => boolean;
@@ -1,4 +1,3 @@
1
- import { RuleKey } from "../types/overrides.js";
2
1
  function getPaywallComponentChildNodes(node) {
3
2
  switch (node.type) {
4
3
  case "stack": {
@@ -134,58 +133,56 @@ export const getPromoOfferStateProps = (hasPromoOffer, overrides) => {
134
133
  const override = overrides?.find((override) => override.conditions.find((condition) => condition.type === "promo_offer"));
135
134
  return override?.properties ?? {};
136
135
  };
137
- const CONDITION_ORDER = [
138
- RuleKey.IntroOfferCondition,
139
- RuleKey.PromoOfferCondition,
140
- RuleKey.SelectedPackageCondition,
141
- RuleKey.VariableCondition,
142
- ];
143
136
  /**
144
- * Evaluates visibility conditions on an element's overrides.
137
+ * Evaluates visibility for an element.
145
138
  *
146
- * The four structured condition types — selected_package_condition,
147
- * intro_offer_condition, promo_offer_condition, and variable_condition
148
- * determine whether an element should be visible. If an override exists
149
- * containing only these condition types and all of its conditions are met,
150
- * the element is visible. If the conditions are not met, the element is hidden.
151
- * If no visibility override exists, the element defaults to visible.
139
+ * A component is considered visible unless:
140
+ * - baseVisible is explicitly false, or
141
+ * - an override sets `visible: false` in its properties and all of its
142
+ * conditions are satisfied by the current context.
143
+ *
144
+ * If baseVisible is undefined or null, the component defaults to visible.
152
145
  */
153
146
  export const evaluateVisibilityConditions = (context, overrides, baseVisible) => {
154
- const VISIBILITY_CONDITION_TYPES = new Set(Object.values(RuleKey));
155
- const visibilityOverride = overrides?.find((override) => override.conditions.every((condition) => VISIBILITY_CONDITION_TYPES.has(condition.type)));
156
- if (!visibilityOverride)
157
- return baseVisible ?? true;
158
- const sortedConditions = [...visibilityOverride.conditions].sort((a, b) => CONDITION_ORDER.indexOf(a.type) - CONDITION_ORDER.indexOf(b.type));
159
- return sortedConditions.every((condition) => {
160
- if (condition.type === "intro_offer_condition") {
161
- const hasIntroOffer = !!context.packageInfo?.hasIntroOffer || !!context.packageInfo?.hasTrial;
162
- if (condition.operator === "=")
163
- return hasIntroOffer === condition.value;
164
- if (condition.operator === "!=")
165
- return hasIntroOffer !== condition.value;
166
- }
167
- if (condition.type === "promo_offer_condition") {
168
- const hasPromoOffer = !!context.packageInfo?.hasPromoOffer;
169
- if (condition.operator === "=")
170
- return hasPromoOffer === condition.value;
171
- if (condition.operator === "!=")
172
- return hasPromoOffer !== condition.value;
173
- }
174
- if (condition.type === "selected_package_condition") {
175
- const selected = context.selectedPackageId ?? "";
176
- if (condition.operator === "in")
177
- return condition.packages.includes(selected);
178
- if (condition.operator === "not in")
179
- return !condition.packages.includes(selected);
180
- }
181
- if (condition.type === "variable_condition") {
182
- const currentValue = context.variables[condition.variable];
183
- const conditionValue = String(condition.value.value);
184
- if (condition.operator === "=")
185
- return currentValue === conditionValue;
186
- if (condition.operator === "!=")
187
- return currentValue !== conditionValue;
188
- }
147
+ if (baseVisible === false)
189
148
  return false;
149
+ const hidingOverride = overrides?.find((override) => {
150
+ const properties = override.properties;
151
+ if (properties.visible !== false)
152
+ return false;
153
+ return override.conditions.every((condition) => conditionMatches(condition, context));
190
154
  });
155
+ return !hidingOverride;
156
+ };
157
+ const conditionMatches = (condition, context) => {
158
+ if (condition.type === "intro_offer_condition") {
159
+ const hasIntroOffer = !!context.packageInfo?.hasIntroOffer || !!context.packageInfo?.hasTrial;
160
+ if (condition.operator === "=")
161
+ return hasIntroOffer === condition.value;
162
+ if (condition.operator === "!=")
163
+ return hasIntroOffer !== condition.value;
164
+ }
165
+ if (condition.type === "promo_offer_condition") {
166
+ const hasPromoOffer = !!context.packageInfo?.hasPromoOffer;
167
+ if (condition.operator === "=")
168
+ return hasPromoOffer === condition.value;
169
+ if (condition.operator === "!=")
170
+ return hasPromoOffer !== condition.value;
171
+ }
172
+ if (condition.type === "selected_package_condition") {
173
+ const selected = context.selectedPackageId ?? "";
174
+ if (condition.operator === "in")
175
+ return condition.packages.includes(selected);
176
+ if (condition.operator === "not in")
177
+ return !condition.packages.includes(selected);
178
+ }
179
+ if (condition.type === "variable_condition") {
180
+ const currentValue = context.variables[condition.variable];
181
+ const conditionValue = String(condition.value.value);
182
+ if (condition.operator === "=")
183
+ return currentValue === conditionValue;
184
+ if (condition.operator === "!=")
185
+ return currentValue !== conditionValue;
186
+ }
187
+ return false;
191
188
  };
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.12.0",
5
+ "version": "3.12.1",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },