@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.
- package/README.md +6 -0
- package/dist/components/carousel/Carousel.svelte +3 -6
- package/dist/components/carousel/carousel-utils.d.ts +6 -0
- package/dist/components/carousel/carousel-utils.js +8 -0
- package/dist/components/input-text/InputText.stories.svelte +92 -0
- package/dist/components/input-text/InputText.stories.svelte.d.ts +19 -0
- package/dist/components/input-text/InputText.svelte +281 -32
- package/dist/components/input-text/InputTextTestWrapper.svelte +14 -4
- package/dist/components/input-text/InputTextTestWrapper.svelte.d.ts +6 -0
- package/dist/components/input-text/pattern.d.ts +16 -0
- package/dist/components/input-text/pattern.js +24 -0
- package/dist/components/input-text/validity-rule.d.ts +15 -0
- package/dist/components/input-text/validity-rule.js +40 -0
- package/dist/components/options/InputMultipleChoice.svelte +5 -2
- package/dist/components/options/InputSingleChoice.svelte +5 -2
- package/dist/components/paywall/Paywall.stories.svelte +40 -0
- package/dist/components/paywall/Paywall.svelte +14 -7
- package/dist/components/paywall/Sheet.svelte +19 -6
- package/dist/components/paywall/ViewportBackdrop.svelte +13 -1
- package/dist/components/paywall/ViewportBackdrop.svelte.d.ts +1 -0
- package/dist/components/paywall/fixtures/gradient-sheet-paywall.d.ts +2 -0
- package/dist/components/paywall/fixtures/gradient-sheet-paywall.js +68 -0
- package/dist/components/paywall/fixtures/hero-media-paywall.d.ts +3 -0
- package/dist/components/paywall/fixtures/hero-media-paywall.js +182 -0
- package/dist/components/paywall/fixtures/sheet-close-button-paywall.d.ts +2 -0
- package/dist/components/paywall/fixtures/sheet-close-button-paywall.js +62 -0
- package/dist/components/paywall/paywall-utils.d.ts +2 -0
- package/dist/components/paywall/paywall-utils.js +22 -0
- package/dist/components/text/TextNode.svelte +2 -22
- package/dist/components/text/text-utils.d.ts +8 -0
- package/dist/components/text/text-utils.js +26 -0
- package/dist/components/workflows/Screen.svelte +5 -1
- package/dist/components/workflows/Workflow.svelte +2 -1
- package/dist/stores/inputValidation.d.ts +6 -3
- package/dist/stores/inputValidation.js +27 -4
- package/dist/stores/state.js +2 -2
- package/dist/types/components/input-text.d.ts +19 -0
- package/dist/types/overrides.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils/safe-area-background.js +1 -1
- package/dist/utils/style-utils.js +2 -2
- 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(
|
|
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(
|
|
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 {
|
|
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
|
|
429
|
-
|
|
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
|
|
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 {
|
|
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,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,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,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
|
+
};
|