@revenuecat/purchases-ui-js 4.8.21 → 4.8.22

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.
@@ -24,6 +24,7 @@
24
24
  import { onMount } from "svelte";
25
25
  import {
26
26
  clamp,
27
+ getDefaultPageIndex,
27
28
  getSwipeTargetPage,
28
29
  getTranslation,
29
30
  mapPageAlignment,
@@ -132,10 +133,6 @@
132
133
  let pageWidth = $derived(carouselWidth - pageOffset);
133
134
  let gestureStartPage = $state<number | null>(null);
134
135
 
135
- function getDefaultPageIndex() {
136
- return pages.at(initial_page_index) !== undefined ? initial_page_index : 0;
137
- }
138
-
139
136
  function normalizePageIndex(index: number) {
140
137
  if (pages.length === 0) {
141
138
  return 0;
@@ -199,7 +196,7 @@
199
196
  }
200
197
 
201
198
  onMount(() => {
202
- const initialPage = getDefaultPageIndex();
199
+ const initialPage = getDefaultPageIndex(pages, initial_page_index);
203
200
  currentPage = initialPage;
204
201
 
205
202
  stopTransition();
@@ -265,7 +262,7 @@
265
262
  destinationIndex: destinationPage,
266
263
  originContextName: getPageContextName(gestureStartPage),
267
264
  destinationContextName: getPageContextName(destinationPage),
268
- defaultIndex: getDefaultPageIndex(),
265
+ defaultIndex: getDefaultPageIndex(pages, initial_page_index),
269
266
  });
270
267
  }
271
268
 
@@ -1,6 +1,12 @@
1
1
  import type { VerticalAlignment } from "../../types/alignment";
2
2
  export declare function mapPageAlignment(alignment: VerticalAlignment): string;
3
3
  export declare function clamp(value: number, min: number, max: number): number;
4
+ /**
5
+ * Resolves the carousel's starting page. Out-of-range indexes (including a
6
+ * missing `pages[i]` hole) fall back to 0. Uses index access rather than
7
+ * `Array.prototype.at` so Chrome < 92 does not throw while mounting.
8
+ */
9
+ export declare function getDefaultPageIndex<T>(pages: readonly T[], initialPageIndex: number): number;
4
10
  /**
5
11
  * Advance one page when the drag passed this fraction of a page's width,
6
12
  * matching the iOS SDK carousel. A shorter drag snaps back to the page the
@@ -11,6 +11,14 @@ export function mapPageAlignment(alignment) {
11
11
  export function clamp(value, min, max) {
12
12
  return Math.max(min, Math.min(value, max));
13
13
  }
14
+ /**
15
+ * Resolves the carousel's starting page. Out-of-range indexes (including a
16
+ * missing `pages[i]` hole) fall back to 0. Uses index access rather than
17
+ * `Array.prototype.at` so Chrome < 92 does not throw while mounting.
18
+ */
19
+ export function getDefaultPageIndex(pages, initialPageIndex) {
20
+ return pages[initialPageIndex] !== undefined ? initialPageIndex : 0;
21
+ }
14
22
  /**
15
23
  * Advance one page when the drag passed this fraction of a page's width,
16
24
  * matching the iOS SDK carousel. A shorter drag snaps back to the page the
@@ -30,6 +30,7 @@
30
30
  import { OVERRIDE_PAYWALL } from "./fixtures/override-paywall";
31
31
  import { SHEET_PAYWALL } from "./fixtures/sheet-paywall";
32
32
  import { SHEET_PAYWALL_VIDEO_STACKING } from "./fixtures/sheet-video-stacking-paywall";
33
+ import { GRADIENT_SHEET_PAYWALL } from "./fixtures/gradient-sheet-paywall";
33
34
  import { STACK_PAYWALL } from "./fixtures/stack-paywall";
34
35
  import { VARIABLES } from "./fixtures/variables";
35
36
  import { CUSTOM_VARIABLES_PAYWALL } from "./fixtures/custom-variables-paywall";
@@ -167,6 +168,19 @@
167
168
  }}
168
169
  />
169
170
 
171
+ <Story
172
+ name="Sheet — gradient background"
173
+ decorators={[viewportDecorator(500, 500, 0)]}
174
+ play={async ({ canvasElement }) => {
175
+ const button = canvasElement.querySelector("button");
176
+ button?.click();
177
+ await waitForAnimations();
178
+ }}
179
+ args={{
180
+ paywallData: GRADIENT_SHEET_PAYWALL,
181
+ }}
182
+ />
183
+
170
184
  <Story
171
185
  name="Background - Color"
172
186
  decorators={[viewportDecorator(500, 500, 0)]}
@@ -442,8 +442,9 @@
442
442
  </script>
443
443
 
444
444
  <svelte:boundary onerror={onError}>
445
+ <!-- Outside the root: filter/transform on `.blur` would clip the fixed backdrop to the paywall box. -->
446
+ <ViewportBackdrop model={viewportBackdropModel} blur={!!sheet} />
445
447
  <div class={paywallClass} style={paywallStyle}>
446
- <ViewportBackdrop model={viewportBackdropModel} />
447
448
  {#if header}
448
449
  <div
449
450
  class="header-wrapper"
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getColorModeContext } from "../../stores/color-mode";
3
3
  import { useConditionContext } from "../../stores/condition-context.svelte";
4
- import { getPaywallContext } from "../../stores/paywall";
4
+ import { getPaywallContext, setPaywallContext } from "../../stores/paywall";
5
5
  import BackgroundVideoSurface from "./BackgroundVideoSurface.svelte";
6
6
  import type { SheetProps } from "../../types/components/sheet";
7
7
  import {
@@ -56,21 +56,34 @@
56
56
  }),
57
57
  );
58
58
 
59
- const { onButtonAction } = getPaywallContext();
59
+ const paywallContext = getPaywallContext();
60
+ const { onButtonAction } = paywallContext;
60
61
 
61
62
  let sheet: HTMLDivElement | undefined;
62
63
  let visible = $state(false);
63
64
 
65
+ const hideSheet = () => {
66
+ visible = false;
67
+ };
68
+
69
+ // navigate_back inside the sheet closes the sheet, not the paywall.
70
+ setPaywallContext({
71
+ ...paywallContext,
72
+ onButtonAction: (action, actionId) => {
73
+ if (action.type === "navigate_back") {
74
+ hideSheet();
75
+ return;
76
+ }
77
+ onButtonAction(action, actionId);
78
+ },
79
+ });
80
+
64
81
  onMount(() => {
65
82
  requestAnimationFrame(() => {
66
83
  visible = true;
67
84
  });
68
85
  });
69
86
 
70
- const hideSheet = () => {
71
- visible = false;
72
- };
73
-
74
87
  const ontransitionend = () => {
75
88
  if (visible) {
76
89
  return;
@@ -6,7 +6,10 @@
6
6
  // through to the safe-area canvas. A position:fixed element that explicitly
7
7
  // fills the viewport — including safe areas — is the only consistent paint
8
8
  // surface for gradients and images on iOS Safari.
9
- const { model }: { model: PaywallRootBackgroundModel } = $props();
9
+ const {
10
+ model,
11
+ blur = false,
12
+ }: { model: PaywallRootBackgroundModel; blur?: boolean } = $props();
10
13
 
11
14
  const backdropStyle = $derived.by((): string => {
12
15
  if (
@@ -46,6 +49,7 @@
46
49
  {#if shouldRenderBackdrop}
47
50
  <div
48
51
  class="viewport-backdrop"
52
+ class:blurred={blur}
49
53
  style={backdropStyle !== "" ? backdropStyle : undefined}
50
54
  >
51
55
  {#if model.kind === "video"}
@@ -77,6 +81,14 @@
77
81
  pointer-events: none;
78
82
  overflow: hidden;
79
83
  isolation: isolate;
84
+ transition:
85
+ filter 0.1s ease-in-out,
86
+ transform 0.1s ease-in-out;
87
+ }
88
+
89
+ .viewport-backdrop.blurred {
90
+ filter: blur(10px) brightness(0.8);
91
+ transform: scale(1.045);
80
92
  }
81
93
 
82
94
  .viewport-backdrop-overlay {
@@ -1,6 +1,7 @@
1
1
  import type { PaywallRootBackgroundModel } from "../../utils/background-utils";
2
2
  type $$ComponentProps = {
3
3
  model: PaywallRootBackgroundModel;
4
+ blur?: boolean;
4
5
  };
5
6
  declare const ViewportBackdrop: import("svelte").Component<$$ComponentProps, {}, "">;
6
7
  type ViewportBackdrop = ReturnType<typeof ViewportBackdrop>;
@@ -0,0 +1,2 @@
1
+ import type { PaywallData } from "../../../types/paywall";
2
+ export declare const GRADIENT_SHEET_PAYWALL: PaywallData;
@@ -0,0 +1,68 @@
1
+ import { createStack, createTextComponent } from "./helpers";
2
+ // Gradient background (painted by ViewportBackdrop) plus a button that opens a sheet.
3
+ export const GRADIENT_SHEET_PAYWALL = {
4
+ id: "gradient-sheet-paywall",
5
+ default_locale: "en_US",
6
+ components_localizations: {
7
+ en_US: { open_sheet: "Open sheet", sheet_title: "Sheet" },
8
+ },
9
+ components_config: {
10
+ base: {
11
+ background: {
12
+ type: "color",
13
+ value: {
14
+ light: {
15
+ type: "linear",
16
+ degrees: 45,
17
+ points: [
18
+ { percent: 0, color: "#010101ff" },
19
+ { percent: 100, color: "#f79e00ff" },
20
+ ],
21
+ },
22
+ },
23
+ },
24
+ stack: createStack({
25
+ id: "root",
26
+ name: "Root",
27
+ components: [
28
+ {
29
+ type: "button",
30
+ id: "open-sheet",
31
+ name: "open-sheet",
32
+ action: {
33
+ type: "navigate_to",
34
+ destination: "sheet",
35
+ sheet: {
36
+ type: "sheet",
37
+ id: "sheet",
38
+ name: "sheet",
39
+ size: { width: { type: "fill" }, height: { type: "fit" } },
40
+ background_blur: true,
41
+ stack: createStack({
42
+ id: "sheet-stack",
43
+ name: "Sheet",
44
+ components: [
45
+ createTextComponent({
46
+ id: "sheet-title",
47
+ textLid: "sheet_title",
48
+ }),
49
+ ],
50
+ }),
51
+ },
52
+ },
53
+ stack: createStack({
54
+ id: "open-sheet-stack",
55
+ name: "open-sheet",
56
+ components: [
57
+ createTextComponent({
58
+ id: "open-sheet-text",
59
+ textLid: "open_sheet",
60
+ }),
61
+ ],
62
+ }),
63
+ },
64
+ ],
65
+ }),
66
+ },
67
+ },
68
+ };
@@ -0,0 +1,2 @@
1
+ import type { PaywallData } from "../../../types/paywall";
2
+ export declare const SHEET_CLOSE_BUTTON_PAYWALL: PaywallData;
@@ -0,0 +1,62 @@
1
+ import { createStack, createTextComponent } from "./helpers";
2
+ function button(id, textLid, action) {
3
+ return {
4
+ type: "button",
5
+ id,
6
+ name: id,
7
+ action,
8
+ stack: createStack({
9
+ id: `${id}-stack`,
10
+ name: id,
11
+ components: [createTextComponent({ id: `${id}-text`, textLid })],
12
+ }),
13
+ };
14
+ }
15
+ // Sheet whose close button uses navigate_back, as the builder configures it.
16
+ export const SHEET_CLOSE_BUTTON_PAYWALL = {
17
+ id: "sheet-close-button-paywall",
18
+ default_locale: "en_US",
19
+ components_localizations: {
20
+ en_US: {
21
+ open_sheet: "Included devices",
22
+ sheet_title: "Devices",
23
+ close_sheet: "Close",
24
+ close_paywall: "Dismiss paywall",
25
+ },
26
+ },
27
+ components_config: {
28
+ base: {
29
+ stack: createStack({
30
+ id: "root",
31
+ name: "Root",
32
+ components: [
33
+ button("close-paywall", "close_paywall", { type: "navigate_back" }),
34
+ button("open-sheet", "open_sheet", {
35
+ type: "navigate_to",
36
+ destination: "sheet",
37
+ sheet: {
38
+ type: "sheet",
39
+ id: "devices-sheet",
40
+ name: "devices_sheet",
41
+ size: { width: { type: "fill" }, height: { type: "fit" } },
42
+ background_blur: true,
43
+ stack: createStack({
44
+ id: "sheet-stack",
45
+ name: "Sheet",
46
+ components: [
47
+ createTextComponent({
48
+ id: "sheet-title",
49
+ textLid: "sheet_title",
50
+ }),
51
+ button("close-sheet", "close_sheet", {
52
+ type: "navigate_back",
53
+ }),
54
+ ],
55
+ }),
56
+ },
57
+ }),
58
+ ],
59
+ }),
60
+ },
61
+ },
62
+ };
@@ -142,7 +142,8 @@
142
142
  for (const [key, declaration] of Object.entries(
143
143
  screen.state_declarations ?? {},
144
144
  )) {
145
- if (!Object.hasOwn(merged, key)) merged[key] = declaration;
145
+ if (!Object.prototype.hasOwnProperty.call(merged, key))
146
+ merged[key] = declaration;
146
147
  }
147
148
  }
148
149
  return merged;
@@ -34,14 +34,14 @@ export class PaywallStateStore {
34
34
  registerDeclarations(declarations) {
35
35
  let changed = false;
36
36
  for (const [key, declaration] of Object.entries(declarations)) {
37
- if (Object.hasOwn(this.#declaredDefaults, key))
37
+ if (Object.prototype.hasOwnProperty.call(this.#declaredDefaults, key))
38
38
  continue;
39
39
  const normalized = this.#normalizeForType(declaration.default, declaration.type);
40
40
  if (normalized === undefined)
41
41
  continue;
42
42
  this.#declaredDefaults[key] = normalized;
43
43
  this.#declaredTypes[key] = declaration.type;
44
- if (!Object.hasOwn(this.#currentValues, key)) {
44
+ if (!Object.prototype.hasOwnProperty.call(this.#currentValues, key)) {
45
45
  this.#currentValues[key] = normalized;
46
46
  changed = true;
47
47
  }
@@ -80,7 +80,7 @@ export function getSafeAreaFallbackColorHeadStyles(paywallData, hostFallbackColo
80
80
  }
81
81
  function colorAtPercent(sortedPoints, percent) {
82
82
  const firstPoint = sortedPoints[0];
83
- const lastPoint = sortedPoints.at(-1);
83
+ const lastPoint = sortedPoints[sortedPoints.length - 1];
84
84
  if (firstPoint == null || lastPoint == null) {
85
85
  return null;
86
86
  }
@@ -206,8 +206,8 @@ const conditionMatches = (condition, context) => {
206
206
  if (condition.type === RuleKey.StateCondition) {
207
207
  const stateValues = context.stateValues ?? {};
208
208
  const stateDefaults = context.stateDefaults ?? {};
209
- const hasCurrentValue = Object.hasOwn(stateValues, condition.name);
210
- const hasDeclaredDefault = Object.hasOwn(stateDefaults, condition.name);
209
+ const hasCurrentValue = Object.prototype.hasOwnProperty.call(stateValues, condition.name);
210
+ const hasDeclaredDefault = Object.prototype.hasOwnProperty.call(stateDefaults, condition.name);
211
211
  // Undeclared key (not in store, not declared): false for BOTH operators.
212
212
  // This diverges from variable_condition (where != matches a missing variable):
213
213
  // state keys are editor-managed, so an undeclared reference is always a bug,
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": "4.8.21",
5
+ "version": "4.8.22",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },