@revenuecat/purchases-ui-js 3.11.0 → 3.11.2

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.
@@ -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,13 @@
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
+ const fieldId = inputChoiceContext?.fieldId;
47
+ if (fieldId) {
48
+ onInputChanged?.(fieldId, option_id);
49
+ }
50
+
44
51
  // Trigger navigation
45
52
  onButtonAction({ type: "workflow" }, actionId);
46
53
  };
@@ -107,6 +107,16 @@
107
107
  }}
108
108
  />
109
109
 
110
+ <Story
111
+ name="Autoplay Unmuted"
112
+ args={{
113
+ show_controls: false,
114
+ auto_play: true,
115
+ mute_audio: false,
116
+ loop: true,
117
+ }}
118
+ />
119
+
110
120
  <Story
111
121
  name="With Fallback Image"
112
122
  args={{
@@ -1,10 +1,12 @@
1
1
  <script lang="ts">
2
+ import { untrack } from "svelte";
2
3
  import { getColorModeContext } from "../../stores/color-mode";
3
4
  import { getSelectedStateContext } from "../../stores/selected";
4
5
  import type { VideoProps } from "../../types/components/video";
5
6
  import type { VideoFiles, ImageFiles } from "../../types/media";
6
7
  import {
7
8
  css,
9
+ mapBorderRadius,
8
10
  mapColor,
9
11
  mapColorMode,
10
12
  mapFitMode,
@@ -59,6 +61,7 @@
59
61
  });
60
62
  let videoElement = $state<HTMLVideoElement | null>(null);
61
63
  let hasVideoError = $state(false);
64
+ let showControlsFallback = $state(false);
62
65
 
63
66
  // Load video or fallback image metadata to get dimensions
64
67
  $effect(() => {
@@ -157,87 +160,162 @@
157
160
 
158
161
  const { x, y, radius, color } = shadow;
159
162
  const shadowColor = mapColor(colorMode, color);
160
- return `filter: drop-shadow(${x}px ${y}px ${radius}px ${shadowColor})`;
163
+ return `filter:drop-shadow(${x}px ${y}px ${radius}px ${shadowColor})`;
161
164
  });
162
165
 
166
+ const wrapperStyle = $derived.by(() => {
167
+ return svgStyle ? `${style};${svgStyle}` : style;
168
+ });
169
+
170
+ const clipDivStyle = $derived(
171
+ css({
172
+ width: "100%",
173
+ height: "100%",
174
+ overflow: "hidden",
175
+ "border-radius": mapBorderRadius(mask_shape),
176
+ "clip-path":
177
+ mask_shape?.type === "concave" || mask_shape?.type === "convex"
178
+ ? `url(#${id}-path)`
179
+ : "none",
180
+ }),
181
+ );
182
+
163
183
  const overlay = $derived(
164
184
  color_overlay && mapColorMode(colorMode, color_overlay),
165
185
  );
166
186
 
167
187
  const shouldShowFallback = $derived(hasVideoError || !video);
168
- </script>
169
188
 
170
- <div {style} bind:clientWidth={wrapperWidth}>
171
- <svg
172
- bind:contentRect={svgRect}
173
- width="100%"
174
- height="100%"
175
- {viewBox}
176
- style={svgStyle}
177
- >
178
- <defs>
179
- <clipPath id={`${id}-path`}>
180
- <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
181
- </clipPath>
182
-
183
- <g id={`${id}-border`}>
184
- <ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
185
- </g>
189
+ // Programmatic autoplay (single path; HTML autoplay omitted to avoid iOS quirks).
190
+ $effect(() => {
191
+ if (!auto_play) {
192
+ untrack(() => {
193
+ showControlsFallback = false;
194
+ });
195
+ return;
196
+ }
186
197
 
187
- <Overlay id={`${id}-overlay`} {overlay} />
188
- </defs>
198
+ if (shouldShowFallback || !video) {
199
+ untrack(() => {
200
+ showControlsFallback = false;
201
+ });
202
+ return;
203
+ }
189
204
 
190
- {#if border && border.width > 0}
191
- <use href={`#${id}-border`} fill="transparent" />
192
- {/if}
205
+ const el = videoElement;
206
+ if (!el) {
207
+ return;
208
+ }
193
209
 
194
- <g clip-path={`url(#${id}-path)`}>
195
- <foreignObject x="0" y="0" width="100%" height="100%">
196
- {#if shouldShowFallback && fallbackImage}
197
- <img
198
- src={fallbackImage.original}
199
- style="object-fit:{mapFitMode(fit_mode)}"
200
- alt=""
201
- />
202
- {:else if video}
203
- <video
204
- bind:this={videoElement}
205
- src={video.url}
206
- style="object-fit:{mapFitMode(fit_mode)}"
207
- autoplay={auto_play}
208
- {loop}
209
- muted={mute_audio}
210
- controls={show_controls}
211
- playsinline
212
- >
213
- <track kind="captions" />
214
- </video>
215
- {/if}
216
- </foreignObject>
217
-
218
- {#if overlay}
219
- <rect
220
- x="0"
221
- y="0"
222
- height="100%"
223
- width="100%"
224
- fill={`url(#${id}-overlay)`}
210
+ void video.url;
211
+ void mute_audio;
212
+
213
+ untrack(() => {
214
+ showControlsFallback = false;
215
+ });
216
+
217
+ const playPromise = el.play();
218
+ if (playPromise !== undefined) {
219
+ playPromise.catch(() => {
220
+ untrack(() => {
221
+ showControlsFallback = true;
222
+ });
223
+ });
224
+ }
225
+ });
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=""
225
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>
226
249
  {/if}
250
+ </div>
251
+
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>
267
+
268
+ <Overlay id={`${id}-overlay`} {overlay} />
269
+ </defs>
227
270
 
228
271
  {#if border && border.width > 0}
229
- <use
230
- href={`#${id}-border`}
231
- fill="none"
232
- stroke={mapColor(colorMode, border.color)}
233
- stroke-width={border.width}
234
- />
272
+ <use href={`#${id}-border`} fill="transparent" />
235
273
  {/if}
236
- </g>
237
- </svg>
274
+
275
+ <g clip-path={`url(#${id}-path)`}>
276
+ {#if overlay}
277
+ <rect
278
+ x="0"
279
+ y="0"
280
+ height="100%"
281
+ width="100%"
282
+ fill={`url(#${id}-overlay)`}
283
+ />
284
+ {/if}
285
+
286
+ {#if border && border.width > 0}
287
+ <use
288
+ href={`#${id}-border`}
289
+ fill="none"
290
+ stroke={mapColor(colorMode, border.color)}
291
+ stroke-width={border.width}
292
+ />
293
+ {/if}
294
+ </g>
295
+ </svg>
296
+ </div>
238
297
  </div>
239
298
 
240
299
  <style>
300
+ .video-outer {
301
+ position: relative;
302
+ }
303
+
304
+ .video-media {
305
+ position: relative;
306
+ width: 100%;
307
+ height: 100%;
308
+ min-height: 0;
309
+ }
310
+
311
+ .video-deco {
312
+ position: absolute;
313
+ inset: 0;
314
+ width: 100%;
315
+ height: 100%;
316
+ pointer-events: none;
317
+ }
318
+
241
319
  video,
242
320
  img {
243
321
  width: 100%;
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.0",
5
+ "version": "3.11.2",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },