@revenuecat/purchases-ui-js 4.8.21 → 4.8.23

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 (42) hide show
  1. package/README.md +6 -0
  2. package/dist/components/carousel/Carousel.svelte +3 -6
  3. package/dist/components/carousel/carousel-utils.d.ts +6 -0
  4. package/dist/components/carousel/carousel-utils.js +8 -0
  5. package/dist/components/input-text/InputText.stories.svelte +92 -0
  6. package/dist/components/input-text/InputText.stories.svelte.d.ts +19 -0
  7. package/dist/components/input-text/InputText.svelte +281 -32
  8. package/dist/components/input-text/InputTextTestWrapper.svelte +14 -4
  9. package/dist/components/input-text/InputTextTestWrapper.svelte.d.ts +6 -0
  10. package/dist/components/input-text/pattern.d.ts +16 -0
  11. package/dist/components/input-text/pattern.js +24 -0
  12. package/dist/components/input-text/validity-rule.d.ts +15 -0
  13. package/dist/components/input-text/validity-rule.js +40 -0
  14. package/dist/components/options/InputMultipleChoice.svelte +5 -2
  15. package/dist/components/options/InputSingleChoice.svelte +5 -2
  16. package/dist/components/paywall/Paywall.stories.svelte +40 -0
  17. package/dist/components/paywall/Paywall.svelte +14 -7
  18. package/dist/components/paywall/Sheet.svelte +19 -6
  19. package/dist/components/paywall/ViewportBackdrop.svelte +13 -1
  20. package/dist/components/paywall/ViewportBackdrop.svelte.d.ts +1 -0
  21. package/dist/components/paywall/fixtures/gradient-sheet-paywall.d.ts +2 -0
  22. package/dist/components/paywall/fixtures/gradient-sheet-paywall.js +68 -0
  23. package/dist/components/paywall/fixtures/hero-media-paywall.d.ts +3 -0
  24. package/dist/components/paywall/fixtures/hero-media-paywall.js +182 -0
  25. package/dist/components/paywall/fixtures/sheet-close-button-paywall.d.ts +2 -0
  26. package/dist/components/paywall/fixtures/sheet-close-button-paywall.js +62 -0
  27. package/dist/components/paywall/paywall-utils.d.ts +2 -0
  28. package/dist/components/paywall/paywall-utils.js +22 -0
  29. package/dist/components/text/TextNode.svelte +2 -22
  30. package/dist/components/text/text-utils.d.ts +8 -0
  31. package/dist/components/text/text-utils.js +26 -0
  32. package/dist/components/workflows/Screen.svelte +5 -1
  33. package/dist/components/workflows/Workflow.svelte +2 -1
  34. package/dist/stores/inputValidation.d.ts +6 -3
  35. package/dist/stores/inputValidation.js +27 -4
  36. package/dist/stores/state.js +2 -2
  37. package/dist/types/components/input-text.d.ts +19 -0
  38. package/dist/types/overrides.d.ts +1 -1
  39. package/dist/types.d.ts +1 -1
  40. package/dist/utils/safe-area-background.js +1 -1
  41. package/dist/utils/style-utils.js +2 -2
  42. package/package.json +1 -1
@@ -1,9 +1,15 @@
1
1
  import type { InputTextProps, ReservedAttribute } from "../../types/components/input-text";
2
+ import type { Localizations } from "../../types/localization";
3
+ import type { ComponentInteractionData } from "../../types/paywall-component-interaction";
2
4
  interface Props extends InputTextProps {
3
5
  onInputChanged?: (fieldId: string, value: string) => void;
4
6
  onSensitiveInputChanged?: (fieldId: string, value: string) => void;
5
7
  onReservedAttributeChanged?: (reservedAttribute: ReservedAttribute, value: string) => void;
6
8
  onSatisfactionChange?: (satisfied: boolean) => void;
9
+ onNavigateToUrl?: (url: string) => void;
10
+ onComponentInteraction?: (data: ComponentInteractionData) => void;
11
+ localizations?: Localizations;
12
+ selectedLocale?: string;
7
13
  }
8
14
  declare const InputTextTestWrapper: import("svelte").Component<Props, {}, "">;
9
15
  type InputTextTestWrapper = ReturnType<typeof InputTextTestWrapper>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * How a `validation.pattern` is enforced on the rendered input.
3
+ *
4
+ * Browsers compile the `pattern` attribute as `^(?:pattern)$` with the `v`
5
+ * flag and silently ignore it when that fails, which would drop the constraint
6
+ * altogether. Patterns that are valid `v`-mode regexes are left to the browser
7
+ * (`patternMismatch`); anything else that still compiles as a plain JS regex is
8
+ * matched by the component and reported through `setCustomValidity`.
9
+ */
10
+ export type CompiledPattern = {
11
+ mode: "native";
12
+ } | {
13
+ mode: "custom";
14
+ regex: RegExp;
15
+ };
16
+ export declare function compilePattern(pattern: string | null | undefined): CompiledPattern | null;
@@ -0,0 +1,24 @@
1
+ const FALLBACK_FLAGS = ["u", ""];
2
+ export function compilePattern(pattern) {
3
+ if (!pattern) {
4
+ return null;
5
+ }
6
+ const source = `^(?:${pattern})$`;
7
+ try {
8
+ new RegExp(source, "v");
9
+ return { mode: "native" };
10
+ }
11
+ catch {
12
+ // Not a valid `v`-mode regex; try the more lenient dialects below.
13
+ }
14
+ for (const flags of FALLBACK_FLAGS) {
15
+ try {
16
+ return { mode: "custom", regex: new RegExp(source, flags) };
17
+ }
18
+ catch {
19
+ // Try the next dialect.
20
+ }
21
+ }
22
+ console.warn(`Ignoring invalid input_text validation pattern: ${pattern}`);
23
+ return null;
24
+ }
@@ -0,0 +1,15 @@
1
+ import type { InputTextValidationRule } from "../../types/components/input-text";
2
+ /**
3
+ * Maps a native `ValidityState` to the first failing validation rule, so the
4
+ * per-rule error copy can be picked. Order matters: an empty required field is
5
+ * reported as `required` even if other constraints would also fail.
6
+ *
7
+ * `typeMismatch` lands in `format` even without `validation.format`, since a
8
+ * `keyboard_type: "email"` input is `type="email"` and fails the same way.
9
+ * `stepMismatch` (a non-integer on the `numeric` keyboard) falls back to
10
+ * `format` as well.
11
+ *
12
+ * `customError` is only ever set by the component for a `pattern` the browser
13
+ * cannot compile itself, so it is reported as `pattern`.
14
+ */
15
+ export declare function failingRuleFromValidity(validity: ValidityState): InputTextValidationRule | null;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Maps a native `ValidityState` to the first failing validation rule, so the
3
+ * per-rule error copy can be picked. Order matters: an empty required field is
4
+ * reported as `required` even if other constraints would also fail.
5
+ *
6
+ * `typeMismatch` lands in `format` even without `validation.format`, since a
7
+ * `keyboard_type: "email"` input is `type="email"` and fails the same way.
8
+ * `stepMismatch` (a non-integer on the `numeric` keyboard) falls back to
9
+ * `format` as well.
10
+ *
11
+ * `customError` is only ever set by the component for a `pattern` the browser
12
+ * cannot compile itself, so it is reported as `pattern`.
13
+ */
14
+ export function failingRuleFromValidity(validity) {
15
+ if (validity.valid) {
16
+ return null;
17
+ }
18
+ if (validity.valueMissing) {
19
+ return "required";
20
+ }
21
+ if (validity.rangeUnderflow) {
22
+ return "minimum";
23
+ }
24
+ if (validity.rangeOverflow) {
25
+ return "maximum";
26
+ }
27
+ if (validity.tooShort) {
28
+ return "minLength";
29
+ }
30
+ if (validity.tooLong) {
31
+ return "maxLength";
32
+ }
33
+ if (validity.patternMismatch || validity.customError) {
34
+ return "pattern";
35
+ }
36
+ if (validity.typeMismatch || validity.badInput || validity.stepMismatch) {
37
+ return "format";
38
+ }
39
+ return null;
40
+ }
@@ -44,10 +44,13 @@
44
44
  if (validationContext) {
45
45
  const unsubscribe = inputChoiceContext.isSatisfied.subscribe(
46
46
  (satisfied) => {
47
- validationContext.updateSatisfaction(satisfied);
47
+ validationContext.updateSatisfaction(props.id, satisfied);
48
48
  },
49
49
  );
50
- onDestroy(unsubscribe);
50
+ onDestroy(() => {
51
+ unsubscribe();
52
+ validationContext.removeInput(props.id);
53
+ });
51
54
  }
52
55
  </script>
53
56
 
@@ -30,10 +30,13 @@
30
30
  if (validationContext) {
31
31
  const unsubscribe = inputChoiceContext.isSatisfied.subscribe(
32
32
  (satisfied) => {
33
- validationContext.updateSatisfaction(satisfied);
33
+ validationContext.updateSatisfaction(props.id, satisfied);
34
34
  },
35
35
  );
36
- onDestroy(unsubscribe);
36
+ onDestroy(() => {
37
+ unsubscribe();
38
+ validationContext.removeInput(props.id);
39
+ });
37
40
  }
38
41
  </script>
39
42
 
@@ -27,9 +27,11 @@
27
27
  import { BACKGROUND_PAYWALL } from "./fixtures/background-paywall";
28
28
  import { FIXED_HEIGHT_STACK_PAYWALL } from "./fixtures/fixed-height-stack-paywall";
29
29
  import { FILL_HEIGHT_WEBVIEW_PAYWALL } from "./fixtures/fill-height-webview-paywall";
30
+ import { createHeroMediaPaywall } from "./fixtures/hero-media-paywall";
30
31
  import { OVERRIDE_PAYWALL } from "./fixtures/override-paywall";
31
32
  import { SHEET_PAYWALL } from "./fixtures/sheet-paywall";
32
33
  import { SHEET_PAYWALL_VIDEO_STACKING } from "./fixtures/sheet-video-stacking-paywall";
34
+ import { GRADIENT_SHEET_PAYWALL } from "./fixtures/gradient-sheet-paywall";
33
35
  import { STACK_PAYWALL } from "./fixtures/stack-paywall";
34
36
  import { VARIABLES } from "./fixtures/variables";
35
37
  import { CUSTOM_VARIABLES_PAYWALL } from "./fixtures/custom-variables-paywall";
@@ -167,6 +169,19 @@
167
169
  }}
168
170
  />
169
171
 
172
+ <Story
173
+ name="Sheet — gradient background"
174
+ decorators={[viewportDecorator(500, 500, 0)]}
175
+ play={async ({ canvasElement }) => {
176
+ const button = canvasElement.querySelector("button");
177
+ button?.click();
178
+ await waitForAnimations();
179
+ }}
180
+ args={{
181
+ paywallData: GRADIENT_SHEET_PAYWALL,
182
+ }}
183
+ />
184
+
170
185
  <Story
171
186
  name="Background - Color"
172
187
  decorators={[viewportDecorator(500, 500, 0)]}
@@ -551,6 +566,31 @@
551
566
  paywallData: paywallWithTransparentHeaderAndTopImage,
552
567
  }}
553
568
  />
569
+
570
+ <Story
571
+ name="Hero media - nested image"
572
+ decorators={[viewportDecorator(375, 500, 0)]}
573
+ args={{
574
+ paywallData: createHeroMediaPaywall("image"),
575
+ }}
576
+ />
577
+
578
+ <Story
579
+ name="Hero media - nested video"
580
+ decorators={[viewportDecorator(375, 500, 0)]}
581
+ args={{
582
+ paywallData: createHeroMediaPaywall("video"),
583
+ }}
584
+ />
585
+
586
+ <Story
587
+ name="Hero media - nested web view"
588
+ decorators={[viewportDecorator(375, 500, 0)]}
589
+ args={{
590
+ paywallData: createHeroMediaPaywall("web_view"),
591
+ }}
592
+ />
593
+
554
594
  <Story
555
595
  name="Timeline"
556
596
  args={{
@@ -50,7 +50,10 @@
50
50
  import { onMount } from "svelte";
51
51
  import { derived, readable, writable } from "svelte/store";
52
52
  import Stack from "../stack/Stack.svelte";
53
- import { mapPaywallContentStackStyle } from "./paywall-utils";
53
+ import {
54
+ firstContentIsHeroMedia,
55
+ mapPaywallContentStackStyle,
56
+ } from "./paywall-utils";
54
57
  import Sheet from "./Sheet.svelte";
55
58
  import ViewportBackdrop from "./ViewportBackdrop.svelte";
56
59
  import {
@@ -425,11 +428,8 @@
425
428
 
426
429
  const { stack, sticky_footer, header } = base;
427
430
 
428
- const firstComponent = stack.components[0];
429
- const firstComponentIsFullWidthImage =
430
- firstComponent?.type === "image" &&
431
- firstComponent.size.width.type === "fill";
432
- const pullContentUnderHeader = !!header && firstComponentIsFullWidthImage;
431
+ const pullContentUnderHeader =
432
+ !!header && firstContentIsHeroMedia(stack.components);
433
433
 
434
434
  let headerHeight = $state(0);
435
435
  let footerHeight = $state(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"
@@ -495,6 +496,12 @@
495
496
  display: flex;
496
497
  flex-direction: column;
497
498
  align-items: stretch;
499
+ box-sizing: border-box;
500
+ /* Definite containing block so fill-width children (`-webkit-fill-available`)
501
+ do not shrink-wrap the host overlay and walk a Chromium zoom feedback loop. */
502
+ width: 100%;
503
+ max-width: 100%;
504
+ min-width: 0;
498
505
  height: 100%;
499
506
 
500
507
  transition-property: filter, transform;
@@ -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,3 @@
1
+ import type { PaywallData } from "../../../types/paywall";
2
+ export type HeroMediaType = "image" | "video" | "web_view";
3
+ export declare const createHeroMediaPaywall: (type: HeroMediaType) => PaywallData;
@@ -0,0 +1,182 @@
1
+ import { createStack, createTextComponent } from "./helpers";
2
+ const ZERO_SPACING = {
3
+ bottom: 0,
4
+ leading: 0,
5
+ top: 0,
6
+ trailing: 0,
7
+ };
8
+ const imageSource = (url) => ({
9
+ light: {
10
+ width: 640,
11
+ height: 360,
12
+ original: url,
13
+ heic: url,
14
+ heic_low_res: url,
15
+ webp: url,
16
+ webp_low_res: url,
17
+ },
18
+ });
19
+ const createImageHero = () => ({
20
+ type: "image",
21
+ id: "image-hero",
22
+ name: "Image hero",
23
+ source: imageSource("https://placehold.co/640x360/2563eb/ffffff.webp?text=Image+Hero"),
24
+ size: {
25
+ width: { type: "fill" },
26
+ height: { type: "fixed", value: 240 },
27
+ },
28
+ mask_shape: null,
29
+ fit_mode: "fill",
30
+ padding: { ...ZERO_SPACING },
31
+ margin: { ...ZERO_SPACING },
32
+ color_overlay: null,
33
+ border: null,
34
+ shadow: null,
35
+ });
36
+ const createVideoHero = () => ({
37
+ type: "video",
38
+ id: "video-hero",
39
+ name: "Video hero",
40
+ source: null,
41
+ fallback_source: imageSource("https://placehold.co/640x360/7c3aed/ffffff.webp?text=Video+Hero"),
42
+ size: {
43
+ width: { type: "fill" },
44
+ height: { type: "fixed", value: 240 },
45
+ },
46
+ mask_shape: null,
47
+ fit_mode: "fill",
48
+ padding: { ...ZERO_SPACING },
49
+ margin: { ...ZERO_SPACING },
50
+ color_overlay: null,
51
+ border: null,
52
+ shadow: null,
53
+ auto_play: false,
54
+ loop: false,
55
+ mute_audio: true,
56
+ show_controls: false,
57
+ });
58
+ const createWebViewHero = () => ({
59
+ type: "web_view",
60
+ id: "web-view-hero",
61
+ name: "Web view hero",
62
+ protocol_version: 1,
63
+ url: "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/2705.svg",
64
+ size: {
65
+ width: { type: "fill" },
66
+ height: { type: "fixed", value: 240 },
67
+ },
68
+ });
69
+ const createHero = (type) => {
70
+ switch (type) {
71
+ case "image":
72
+ return createImageHero();
73
+ case "video":
74
+ return createVideoHero();
75
+ case "web_view":
76
+ return createWebViewHero();
77
+ }
78
+ };
79
+ const createHeader = () => {
80
+ const stack = createStack({
81
+ id: "hero-header-stack",
82
+ name: "Hero header stack",
83
+ components: [
84
+ {
85
+ ...createTextComponent({
86
+ id: "hero-header-label",
87
+ textLid: "hero_header_label",
88
+ }),
89
+ color: {
90
+ light: {
91
+ type: "hex",
92
+ value: "#ffffff",
93
+ },
94
+ },
95
+ font_size: 18,
96
+ font_weight: "bold",
97
+ },
98
+ ],
99
+ });
100
+ stack.size.width = { type: "fill" };
101
+ stack.padding = {
102
+ bottom: 18,
103
+ leading: 20,
104
+ top: 18,
105
+ trailing: 20,
106
+ };
107
+ stack.background_color = {
108
+ light: {
109
+ type: "hex",
110
+ value: "#11182799",
111
+ },
112
+ };
113
+ return {
114
+ type: "header",
115
+ id: "hero-header",
116
+ name: "Hero header",
117
+ stack,
118
+ };
119
+ };
120
+ export const createHeroMediaPaywall = (type) => {
121
+ const heroContainer = createStack({
122
+ id: `${type}-hero-container`,
123
+ name: `${type} hero container`,
124
+ components: [createHero(type)],
125
+ });
126
+ heroContainer.size.width = { type: "fill" };
127
+ heroContainer.padding = { ...ZERO_SPACING };
128
+ heroContainer.spacing = 0;
129
+ const rootStack = createStack({
130
+ id: `${type}-hero-root`,
131
+ name: `${type} hero root`,
132
+ components: [
133
+ heroContainer,
134
+ {
135
+ ...createTextComponent({
136
+ id: `${type}-body`,
137
+ textLid: "body",
138
+ }),
139
+ font_size: 18,
140
+ horizontal_alignment: "center",
141
+ size: {
142
+ width: { type: "fill" },
143
+ height: { type: "fit" },
144
+ },
145
+ },
146
+ ],
147
+ });
148
+ rootStack.size.width = { type: "fill" };
149
+ rootStack.padding = {
150
+ bottom: 24,
151
+ leading: 0,
152
+ top: 0,
153
+ trailing: 0,
154
+ };
155
+ rootStack.spacing = 24;
156
+ return {
157
+ id: `${type}_hero_paywall`,
158
+ default_locale: "en_US",
159
+ components_localizations: {
160
+ en_US: {
161
+ hero_header_label: "Header overlays the hero",
162
+ body: `Full-width ${type.replace("_", " ")} detected through a nested stack`,
163
+ },
164
+ },
165
+ components_config: {
166
+ base: {
167
+ background: {
168
+ type: "color",
169
+ value: {
170
+ light: {
171
+ type: "hex",
172
+ value: "#ffffff",
173
+ },
174
+ },
175
+ },
176
+ header: createHeader(),
177
+ stack: rootStack,
178
+ sticky_footer: null,
179
+ },
180
+ },
181
+ };
182
+ };
@@ -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
+ };