@revenuecat/purchases-ui-js 4.8.6 → 4.8.7
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/dist/components/footer/Footer.svelte +1 -4
- package/dist/components/paywall/Paywall.stories.svelte +27 -0
- package/dist/components/paywall/Paywall.svelte +33 -13
- package/dist/components/paywall/fixtures/transparent-footer-paywall.d.ts +7 -0
- package/dist/components/paywall/fixtures/transparent-footer-paywall.js +59 -0
- package/dist/components/web-view/WebView.svelte +8 -2
- package/dist/types.d.ts +9 -3
- package/package.json +1 -1
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Stack from "../stack/Stack.svelte";
|
|
3
3
|
import type { FooterProps } from "../../types/components/footer";
|
|
4
|
-
import { STICKY_OVERLAY_Z_INDEX } from "../../utils/constants";
|
|
5
4
|
|
|
6
5
|
const { stack }: FooterProps = $props();
|
|
7
6
|
</script>
|
|
8
7
|
|
|
9
|
-
<div
|
|
8
|
+
<div>
|
|
10
9
|
<Stack {...stack} />
|
|
11
10
|
</div>
|
|
12
11
|
|
|
13
12
|
<style>
|
|
14
13
|
div {
|
|
15
|
-
position: sticky;
|
|
16
|
-
bottom: 0;
|
|
17
14
|
width: 100%;
|
|
18
15
|
|
|
19
16
|
display: flex;
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
DUELINGUE_PAYWALL,
|
|
40
40
|
} from "./fixtures/express-purchase-button-paywall";
|
|
41
41
|
import { CustomVariableValue } from "../../types/variables";
|
|
42
|
+
import {
|
|
43
|
+
paywallWithTransparentFooter,
|
|
44
|
+
paywallWithCenteredBodyAndFooter,
|
|
45
|
+
paywallWithHeaderAndFooter,
|
|
46
|
+
} from "./fixtures/transparent-footer-paywall";
|
|
42
47
|
|
|
43
48
|
const { Story } = defineMeta({
|
|
44
49
|
title: "Example/Paywall",
|
|
@@ -480,6 +485,28 @@
|
|
|
480
485
|
}}
|
|
481
486
|
/>
|
|
482
487
|
|
|
488
|
+
<Story
|
|
489
|
+
name="Sticky Footer - transparent (overlaps content)"
|
|
490
|
+
args={{
|
|
491
|
+
paywallData: paywallWithTransparentFooter,
|
|
492
|
+
}}
|
|
493
|
+
/>
|
|
494
|
+
|
|
495
|
+
<Story
|
|
496
|
+
name="Sticky Footer - centered body above footer"
|
|
497
|
+
decorators={[viewportDecorator(500, 500, 0)]}
|
|
498
|
+
args={{
|
|
499
|
+
paywallData: paywallWithCenteredBodyAndFooter,
|
|
500
|
+
}}
|
|
501
|
+
/>
|
|
502
|
+
|
|
503
|
+
<Story
|
|
504
|
+
name="Sticky Footer - with header"
|
|
505
|
+
args={{
|
|
506
|
+
paywallData: paywallWithHeaderAndFooter,
|
|
507
|
+
}}
|
|
508
|
+
/>
|
|
509
|
+
|
|
483
510
|
<Story
|
|
484
511
|
name="Header"
|
|
485
512
|
args={{
|
|
@@ -352,6 +352,13 @@
|
|
|
352
352
|
const pullContentUnderHeader = !!header && firstComponentIsFullWidthImage;
|
|
353
353
|
|
|
354
354
|
let headerHeight = $state(0);
|
|
355
|
+
let footerHeight = $state(0);
|
|
356
|
+
|
|
357
|
+
const contentColumnStyle = $derived(
|
|
358
|
+
maxContentWidth
|
|
359
|
+
? `max-width: ${maxContentWidth}; margin-inline: auto;`
|
|
360
|
+
: "",
|
|
361
|
+
);
|
|
355
362
|
</script>
|
|
356
363
|
|
|
357
364
|
<svelte:boundary onerror={onError}>
|
|
@@ -366,19 +373,16 @@
|
|
|
366
373
|
<Header {...header} />
|
|
367
374
|
</div>
|
|
368
375
|
{/if}
|
|
369
|
-
<div
|
|
370
|
-
class="paywall-content"
|
|
371
|
-
style={[
|
|
372
|
-
maxContentWidth
|
|
373
|
-
? `max-width: ${maxContentWidth}; margin-inline: auto;`
|
|
374
|
-
: "",
|
|
375
|
-
]
|
|
376
|
-
.filter(Boolean)
|
|
377
|
-
.join(" ")}
|
|
378
|
-
>
|
|
376
|
+
<div class="paywall-content" style={contentColumnStyle}>
|
|
379
377
|
<Stack
|
|
380
378
|
{...stack}
|
|
381
379
|
class="paywall-content-scroll"
|
|
380
|
+
padding={sticky_footer
|
|
381
|
+
? {
|
|
382
|
+
...stack.padding,
|
|
383
|
+
bottom: (stack.padding?.bottom ?? 0) + footerHeight,
|
|
384
|
+
}
|
|
385
|
+
: stack.padding}
|
|
382
386
|
style={{
|
|
383
387
|
height: "auto",
|
|
384
388
|
...(pullContentUnderHeader
|
|
@@ -386,10 +390,19 @@
|
|
|
386
390
|
: {}),
|
|
387
391
|
}}
|
|
388
392
|
/>
|
|
389
|
-
{#if sticky_footer}
|
|
390
|
-
<Footer {...sticky_footer} />
|
|
391
|
-
{/if}
|
|
392
393
|
</div>
|
|
394
|
+
{#if sticky_footer}
|
|
395
|
+
<!-- Overlaps the content so translucent footers show it; sticky rather than
|
|
396
|
+
absolute so it stays visible when an ancestor scrollport is shorter than
|
|
397
|
+
the paywall. -->
|
|
398
|
+
<div
|
|
399
|
+
class="footer-wrapper"
|
|
400
|
+
style="z-index: {STICKY_OVERLAY_Z_INDEX}; margin-top: -{footerHeight}px; {contentColumnStyle}"
|
|
401
|
+
bind:clientHeight={footerHeight}
|
|
402
|
+
>
|
|
403
|
+
<Footer {...sticky_footer} />
|
|
404
|
+
</div>
|
|
405
|
+
{/if}
|
|
393
406
|
</div>
|
|
394
407
|
|
|
395
408
|
{#if sheet}
|
|
@@ -422,6 +435,13 @@
|
|
|
422
435
|
width: 100%;
|
|
423
436
|
}
|
|
424
437
|
|
|
438
|
+
.footer-wrapper {
|
|
439
|
+
position: sticky;
|
|
440
|
+
bottom: 0;
|
|
441
|
+
width: 100%;
|
|
442
|
+
flex-shrink: 0;
|
|
443
|
+
}
|
|
444
|
+
|
|
425
445
|
.paywall-content {
|
|
426
446
|
z-index: 2;
|
|
427
447
|
width: 100%;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PaywallData } from "../../../types/paywall";
|
|
2
|
+
/** Translucent sticky footer over long scrollable content. */
|
|
3
|
+
export declare const paywallWithTransparentFooter: PaywallData;
|
|
4
|
+
/** Small body that must center in the space above the footer, not the whole screen. */
|
|
5
|
+
export declare const paywallWithCenteredBodyAndFooter: PaywallData;
|
|
6
|
+
/** Header and sticky footer together: the footer overlay must not affect the header. */
|
|
7
|
+
export declare const paywallWithHeaderAndFooter: PaywallData;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { paywallWithFooter, paywallWithHeader } from "../../../stories/fixtures";
|
|
2
|
+
function footerStack(paywall) {
|
|
3
|
+
const footer = paywall.components_config.base.sticky_footer;
|
|
4
|
+
if (!footer) {
|
|
5
|
+
throw new Error("Fixture has no sticky footer");
|
|
6
|
+
}
|
|
7
|
+
return footer.stack;
|
|
8
|
+
}
|
|
9
|
+
function lastTextComponent(components) {
|
|
10
|
+
return components.findLast((component) => component.type === "text");
|
|
11
|
+
}
|
|
12
|
+
/** Duplicates the last text component so the content is tall enough to scroll. */
|
|
13
|
+
function appendScrollableContent(paywall, count) {
|
|
14
|
+
const components = paywall.components_config.base.stack.components;
|
|
15
|
+
const lastText = lastTextComponent(components);
|
|
16
|
+
if (!lastText) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (let index = 0; index < count; index++) {
|
|
20
|
+
const copy = structuredClone(lastText);
|
|
21
|
+
copy.id = `scroll-filler-${index}`;
|
|
22
|
+
components.push(copy);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function setFooterBackground(paywall, hex) {
|
|
26
|
+
footerStack(paywall).background_color = {
|
|
27
|
+
light: { type: "hex", value: hex },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Translucent sticky footer over long scrollable content. */
|
|
31
|
+
export const paywallWithTransparentFooter = (() => {
|
|
32
|
+
const paywall = structuredClone(paywallWithFooter);
|
|
33
|
+
appendScrollableContent(paywall, 25);
|
|
34
|
+
setFooterBackground(paywall, "#057C5BAA");
|
|
35
|
+
return paywall;
|
|
36
|
+
})();
|
|
37
|
+
/** Small body that must center in the space above the footer, not the whole screen. */
|
|
38
|
+
export const paywallWithCenteredBodyAndFooter = (() => {
|
|
39
|
+
const paywall = structuredClone(paywallWithFooter);
|
|
40
|
+
const stack = paywall.components_config.base.stack;
|
|
41
|
+
const lastText = lastTextComponent(stack.components);
|
|
42
|
+
stack.components = lastText ? [lastText] : [];
|
|
43
|
+
stack.dimension.distribution = "center";
|
|
44
|
+
setFooterBackground(paywall, "#057C5BAA");
|
|
45
|
+
return paywall;
|
|
46
|
+
})();
|
|
47
|
+
/** Header and sticky footer together: the footer overlay must not affect the header. */
|
|
48
|
+
export const paywallWithHeaderAndFooter = (() => {
|
|
49
|
+
const paywall = structuredClone(paywallWithFooter);
|
|
50
|
+
appendScrollableContent(paywall, 15);
|
|
51
|
+
const headerDonor = structuredClone(paywallWithHeader);
|
|
52
|
+
paywall.components_config.base.header =
|
|
53
|
+
headerDonor.components_config.base.header;
|
|
54
|
+
paywall.components_localizations.en_US = {
|
|
55
|
+
...paywall.components_localizations.en_US,
|
|
56
|
+
...headerDonor.components_localizations.en_US,
|
|
57
|
+
};
|
|
58
|
+
return paywall;
|
|
59
|
+
})();
|
|
@@ -56,17 +56,23 @@
|
|
|
56
56
|
let fitWidth = $state<number | undefined>(undefined);
|
|
57
57
|
let fitHeight = $state<number | undefined>(undefined);
|
|
58
58
|
|
|
59
|
+
const fitAxisFallback = (axis: typeof props.size.width): number =>
|
|
60
|
+
axis.type === "fit" && typeof axis.default === "number"
|
|
61
|
+
? axis.default
|
|
62
|
+
: FIT_FALLBACK_SIZE_PX;
|
|
63
|
+
|
|
59
64
|
const style = $derived.by(() => {
|
|
60
65
|
const { width, height } = props.size;
|
|
61
66
|
const widthFit = width.type === "fit";
|
|
62
67
|
const heightFit = height.type === "fit";
|
|
63
68
|
// Pin a `fit` axis with an explicit size. `flex-shrink: 0` keeps flex parents
|
|
64
69
|
// from collapsing the box; avoid mirroring the size in `min-*` or it beats `max-*`.
|
|
70
|
+
// Prefer author-declared `default` when present; else the hard 300px fallback.
|
|
65
71
|
const fitWidthPx = widthFit
|
|
66
|
-
? clampFitSize(fitWidth ??
|
|
72
|
+
? clampFitSize(fitWidth ?? fitAxisFallback(width))
|
|
67
73
|
: null;
|
|
68
74
|
const fitHeightPx = heightFit
|
|
69
|
-
? clampFitSize(fitHeight ??
|
|
75
|
+
? clampFitSize(fitHeight ?? fitAxisFallback(height))
|
|
70
76
|
: null;
|
|
71
77
|
return css({
|
|
72
78
|
width: fitWidthPx != null ? px(fitWidthPx) : mapSize(width),
|
package/dist/types.d.ts
CHANGED
|
@@ -48,11 +48,17 @@ type RelativeSize = {
|
|
|
48
48
|
type: "relative";
|
|
49
49
|
value: number;
|
|
50
50
|
};
|
|
51
|
-
type
|
|
52
|
-
type: "fit"
|
|
51
|
+
type FitSize = {
|
|
52
|
+
type: "fit";
|
|
53
53
|
value?: null;
|
|
54
|
+
/** Placeholder px until the content reports its measured size. */
|
|
55
|
+
default?: number;
|
|
54
56
|
};
|
|
55
|
-
|
|
57
|
+
type FillSize = {
|
|
58
|
+
type: "fill";
|
|
59
|
+
value?: null;
|
|
60
|
+
};
|
|
61
|
+
export type Size = FixedSize | RelativeSize | FitSize | FillSize;
|
|
56
62
|
export type SizeType = {
|
|
57
63
|
width: Size;
|
|
58
64
|
height: Size;
|