@revenuecat/purchases-ui-js 4.3.0 → 4.4.0
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/paywall/Paywall.svelte +13 -1
- package/dist/components/paywall/Paywall.svelte.d.ts +7 -0
- package/dist/components/workflows/Screen.svelte +4 -0
- package/dist/components/workflows/Screen.svelte.d.ts +2 -0
- package/dist/types/colors.d.ts +1 -1
- package/dist/types/paywall.d.ts +2 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/document-background.d.ts +7 -1
- package/dist/utils/document-background.js +18 -36
- package/dist/utils/safe-area-background.d.ts +21 -0
- package/dist/utils/safe-area-background.js +211 -0
- package/dist/utils/safe-area.d.ts +1 -0
- package/dist/utils/safe-area.js +4 -0
- package/package.json +5 -1
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
type VariablesStore,
|
|
16
16
|
} from "../../stores/variables";
|
|
17
17
|
import type { ColorMode } from "../../types";
|
|
18
|
+
import type { ColorScheme } from "../../types/colors";
|
|
18
19
|
import type {
|
|
19
20
|
Action,
|
|
20
21
|
CompleteWorkflowNavigateArgs,
|
|
@@ -105,6 +106,12 @@
|
|
|
105
106
|
* ```
|
|
106
107
|
*/
|
|
107
108
|
customVariables?: CustomVariables;
|
|
109
|
+
/**
|
|
110
|
+
* Optional baseline safe-area colour applied when the paywall background
|
|
111
|
+
* can't derive one and `background.safe_area_fallback_color` is unset.
|
|
112
|
+
* Hosts (e.g. workflow runtimes) pass their workflow-level fallback here.
|
|
113
|
+
*/
|
|
114
|
+
safeAreaFallbackColor?: ColorScheme | null;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
const {
|
|
@@ -130,6 +137,7 @@
|
|
|
130
137
|
maxContentWidth,
|
|
131
138
|
initialInputSelections = {},
|
|
132
139
|
customVariables = {},
|
|
140
|
+
safeAreaFallbackColor,
|
|
133
141
|
}: Props = $props();
|
|
134
142
|
|
|
135
143
|
const getColorMode = setColorModeContext(() => preferredColorMode);
|
|
@@ -140,7 +148,11 @@
|
|
|
140
148
|
|
|
141
149
|
const instanceId: symbol = Symbol();
|
|
142
150
|
$effect(() =>
|
|
143
|
-
applyDocumentBackground(instanceId, viewportBackdropModel,
|
|
151
|
+
applyDocumentBackground(instanceId, viewportBackdropModel, {
|
|
152
|
+
paywallData,
|
|
153
|
+
colorMode: getColorMode(),
|
|
154
|
+
hostFallbackColor: safeAreaFallbackColor,
|
|
155
|
+
}),
|
|
144
156
|
);
|
|
145
157
|
|
|
146
158
|
const { default_locale, components_config, components_localizations } =
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type InitialInputSelections } from "../../stores/inputValidation";
|
|
2
2
|
import type { ColorMode } from "../../types";
|
|
3
|
+
import type { ColorScheme } from "../../types/colors";
|
|
3
4
|
import type { CompleteWorkflowNavigateArgs } from "../../types/components/button";
|
|
4
5
|
import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
|
|
5
6
|
import type { PaywallData } from "../../types/paywall";
|
|
@@ -56,6 +57,12 @@ interface Props {
|
|
|
56
57
|
* ```
|
|
57
58
|
*/
|
|
58
59
|
customVariables?: CustomVariables;
|
|
60
|
+
/**
|
|
61
|
+
* Optional baseline safe-area colour applied when the paywall background
|
|
62
|
+
* can't derive one and `background.safe_area_fallback_color` is unset.
|
|
63
|
+
* Hosts (e.g. workflow runtimes) pass their workflow-level fallback here.
|
|
64
|
+
*/
|
|
65
|
+
safeAreaFallbackColor?: ColorScheme | null;
|
|
59
66
|
}
|
|
60
67
|
declare const Paywall: import("svelte").Component<Props, {}, "">;
|
|
61
68
|
type Paywall = ReturnType<typeof Paywall>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Paywall from "../paywall/Paywall.svelte";
|
|
3
3
|
import type { CompleteWorkflowNavigateArgs } from "../../types/components/button";
|
|
4
|
+
import type { ColorScheme } from "../../types/colors";
|
|
4
5
|
import type { InitialInputSelections } from "../../stores/inputValidation";
|
|
5
6
|
import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
|
|
6
7
|
import type { WorkflowScreen } from "../../types/workflow";
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
args: CompleteWorkflowNavigateArgs,
|
|
33
34
|
) => void | Promise<void>;
|
|
34
35
|
walletButtonRender?: WalletButtonRender;
|
|
36
|
+
safeAreaFallbackColor?: ColorScheme | null;
|
|
35
37
|
}
|
|
36
38
|
const {
|
|
37
39
|
paywallComponents,
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
onInputChanged,
|
|
50
52
|
onCompleteWorkflowNavigate,
|
|
51
53
|
walletButtonRender,
|
|
54
|
+
safeAreaFallbackColor,
|
|
52
55
|
}: Props = $props();
|
|
53
56
|
</script>
|
|
54
57
|
|
|
@@ -76,6 +79,7 @@
|
|
|
76
79
|
{onPurchaseClicked}
|
|
77
80
|
{onInputChanged}
|
|
78
81
|
{walletButtonRender}
|
|
82
|
+
{safeAreaFallbackColor}
|
|
79
83
|
onError={(error) => {
|
|
80
84
|
console.error("Paywall error:", error);
|
|
81
85
|
}}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CompleteWorkflowNavigateArgs } from "../../types/components/button";
|
|
2
|
+
import type { ColorScheme } from "../../types/colors";
|
|
2
3
|
import type { InitialInputSelections } from "../../stores/inputValidation";
|
|
3
4
|
import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
|
|
4
5
|
import type { WorkflowScreen } from "../../types/workflow";
|
|
@@ -21,6 +22,7 @@ interface Props {
|
|
|
21
22
|
onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
|
|
22
23
|
onCompleteWorkflowNavigate?: (args: CompleteWorkflowNavigateArgs) => void | Promise<void>;
|
|
23
24
|
walletButtonRender?: WalletButtonRender;
|
|
25
|
+
safeAreaFallbackColor?: ColorScheme | null;
|
|
24
26
|
}
|
|
25
27
|
declare const Screen: import("svelte").Component<Props, {}, "">;
|
|
26
28
|
type Screen = ReturnType<typeof Screen>;
|
package/dist/types/colors.d.ts
CHANGED
package/dist/types/paywall.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Background } from "./background";
|
|
2
|
+
import type { ColorScheme } from "./colors";
|
|
2
3
|
import type { FooterProps } from "./components/footer";
|
|
3
4
|
import type { HeaderProps } from "./components/header";
|
|
4
5
|
import type { StackProps } from "./components/stack";
|
|
@@ -8,6 +9,7 @@ export interface RootPaywall {
|
|
|
8
9
|
stack: StackProps;
|
|
9
10
|
sticky_footer?: FooterProps | null;
|
|
10
11
|
header?: HeaderProps | null;
|
|
12
|
+
safe_area_fallback_color?: ColorScheme | null;
|
|
11
13
|
}
|
|
12
14
|
export interface ComponentConfig {
|
|
13
15
|
colors?: Record<string, string>;
|
package/dist/types.d.ts
CHANGED
|
@@ -120,4 +120,5 @@ export declare enum StackDistribution {
|
|
|
120
120
|
end = "flex-end"
|
|
121
121
|
}
|
|
122
122
|
export type { WorkflowScreen } from "./types/workflow";
|
|
123
|
+
export type { ColorScheme } from "./types/colors";
|
|
123
124
|
export type { ComponentInteractionData, ComponentInteractionType, OnComponentInteraction, } from "./types/paywall-component-interaction";
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import type { PaywallData } from "../types/paywall";
|
|
2
2
|
import type { PaywallRootBackgroundModel } from "./background-utils";
|
|
3
|
-
|
|
3
|
+
import type { ColorMode } from "../types";
|
|
4
|
+
import type { ColorScheme } from "../types/colors";
|
|
5
|
+
export declare function applyDocumentBackground(instanceId: symbol, model: PaywallRootBackgroundModel, options: {
|
|
6
|
+
paywallData: PaywallData | null | undefined;
|
|
7
|
+
colorMode: ColorMode;
|
|
8
|
+
hostFallbackColor?: ColorScheme | null;
|
|
9
|
+
}): () => void;
|
|
@@ -1,40 +1,14 @@
|
|
|
1
|
+
import { getBackgroundSafeAreaColors, resolveSafeAreaFallbackCss, SAFE_AREA_FALLBACK_COLOR_CSS_VAR, } from "./safe-area-background";
|
|
1
2
|
// Last writer wins, but cleanup only fires for the last writer — otherwise an
|
|
2
|
-
// unmounting paywall would clear a variable set by another paywall that
|
|
3
|
-
// it during a transition.
|
|
3
|
+
// unmounting paywall would clear a variable set by another paywall that
|
|
4
|
+
// overlapped it during a transition.
|
|
4
5
|
let lastBgWriter = null;
|
|
5
|
-
|
|
6
|
-
// safe-area painting for the opposite strip. A vertical gradient's edge stop
|
|
7
|
-
// can stand in as a solid fallback for that strip; any other angle would smear
|
|
8
|
-
// a horizontal colour range across the strip that no single colour represents.
|
|
9
|
-
function gradientSafeAreaFallbackColour(gradient, hasHeader, hasFooter) {
|
|
10
|
-
if (!hasHeader && !hasFooter)
|
|
11
|
-
return null;
|
|
12
|
-
if (hasHeader && hasFooter)
|
|
13
|
-
return null;
|
|
14
|
-
const angleMatch = gradient.match(/linear-gradient\((\d+)deg/);
|
|
15
|
-
if (!angleMatch)
|
|
16
|
-
return null;
|
|
17
|
-
if (parseInt(angleMatch[1], 10) % 180 !== 0)
|
|
18
|
-
return null;
|
|
19
|
-
const stops = gradient.match(/#[0-9a-fA-F]{6,8}/g);
|
|
20
|
-
if (!stops || stops.length < 2)
|
|
21
|
-
return null;
|
|
22
|
-
const candidate = hasHeader ? stops[stops.length - 1] : stops[0];
|
|
23
|
-
if (candidate.length === 9 && candidate.slice(-2).toLowerCase() !== "ff") {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
return candidate;
|
|
27
|
-
}
|
|
28
|
-
// Published as a CSS variable so consumer surfaces can opt in to safe-area
|
|
29
|
-
// painting (`background: var(--rc-purchases-ui-bg-color, Canvas)` on any
|
|
30
|
-
// element that extends into the safe-area canvas — typically html, body, or
|
|
31
|
-
// a position:fixed root). Writing to documentElement.backgroundColor directly
|
|
32
|
-
// would bleed the paywall colour onto host page chrome that shouldn't take
|
|
33
|
-
// it on, so we expose the value rather than apply it.
|
|
34
|
-
export function applyDocumentBackground(instanceId, model, paywallData) {
|
|
6
|
+
export function applyDocumentBackground(instanceId, model, options) {
|
|
35
7
|
if (typeof document === "undefined")
|
|
36
8
|
return () => { };
|
|
9
|
+
const { paywallData, colorMode, hostFallbackColor } = options;
|
|
37
10
|
const root = document.documentElement;
|
|
11
|
+
const base = paywallData?.components_config?.base;
|
|
38
12
|
let value = null;
|
|
39
13
|
if (model.kind === "style" && model.style.background) {
|
|
40
14
|
const bg = model.style.background;
|
|
@@ -42,18 +16,26 @@ export function applyDocumentBackground(instanceId, model, paywallData) {
|
|
|
42
16
|
value = bg;
|
|
43
17
|
}
|
|
44
18
|
else {
|
|
45
|
-
const
|
|
46
|
-
|
|
19
|
+
const edges = getBackgroundSafeAreaColors(base?.background, colorMode, { width: window.innerWidth, height: window.innerHeight }, {
|
|
20
|
+
stickyComponents: {
|
|
21
|
+
hasHeader: base?.header != null,
|
|
22
|
+
hasFooter: base?.sticky_footer != null,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
value = edges.top ?? edges.bottom;
|
|
47
26
|
}
|
|
48
27
|
}
|
|
28
|
+
if (value === null) {
|
|
29
|
+
value = resolveSafeAreaFallbackCss(base?.safe_area_fallback_color, hostFallbackColor, colorMode);
|
|
30
|
+
}
|
|
49
31
|
if (value !== null) {
|
|
50
32
|
lastBgWriter = instanceId;
|
|
51
|
-
root.style.setProperty(
|
|
33
|
+
root.style.setProperty(SAFE_AREA_FALLBACK_COLOR_CSS_VAR, value);
|
|
52
34
|
}
|
|
53
35
|
return () => {
|
|
54
36
|
if (lastBgWriter === instanceId) {
|
|
55
37
|
lastBgWriter = null;
|
|
56
|
-
root.style.removeProperty(
|
|
38
|
+
root.style.removeProperty(SAFE_AREA_FALLBACK_COLOR_CSS_VAR);
|
|
57
39
|
}
|
|
58
40
|
};
|
|
59
41
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ColorMode } from "../types";
|
|
2
|
+
import type { Background } from "../types/background";
|
|
3
|
+
import type { ColorScheme } from "../types/colors";
|
|
4
|
+
import type { PaywallData } from "../types/paywall";
|
|
5
|
+
type Viewport = {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
};
|
|
9
|
+
type SafeAreaEdge = "top" | "bottom";
|
|
10
|
+
export declare const SAFE_AREA_FALLBACK_COLOR_CSS_VAR = "--rc-purchases-ui-bg-color";
|
|
11
|
+
export declare function resolveSafeAreaFallbackCss(paywallOverride: ColorScheme | null | undefined, hostFallback: ColorScheme | null | undefined, colorMode: ColorMode): string | null;
|
|
12
|
+
export declare function getSafeAreaFallbackColorHeadStyles(paywallData: PaywallData | null | undefined, hostFallbackColor?: ColorScheme | null): string;
|
|
13
|
+
export declare function getBackgroundSafeAreaColors(background: Background | null | undefined, colorMode: ColorMode, viewport: Viewport, options?: {
|
|
14
|
+
stickyComponents?: {
|
|
15
|
+
hasHeader?: boolean;
|
|
16
|
+
hasFooter?: boolean;
|
|
17
|
+
};
|
|
18
|
+
paywallFallbackColor?: ColorScheme | null;
|
|
19
|
+
hostFallbackColor?: ColorScheme | null;
|
|
20
|
+
}): Record<SafeAreaEdge, string | null>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { mapColorInfo, mapColorMode } from "./base-utils";
|
|
2
|
+
export const SAFE_AREA_FALLBACK_COLOR_CSS_VAR = "--rc-purchases-ui-bg-color";
|
|
3
|
+
// Light-only schemes must not bleed into the dark media query — a dark rule
|
|
4
|
+
// reading from `light` would override the light value.
|
|
5
|
+
function getSafeAreaFallbackColorInfoForMode(paywallOverride, hostFallback, colorMode) {
|
|
6
|
+
const colorAtMode = (scheme) => colorMode === "dark" ? scheme?.dark : scheme?.light;
|
|
7
|
+
return colorAtMode(paywallOverride) ?? colorAtMode(hostFallback) ?? null;
|
|
8
|
+
}
|
|
9
|
+
const HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/;
|
|
10
|
+
function isHexColor(value) {
|
|
11
|
+
return HEX_COLOR_REGEX.test(value);
|
|
12
|
+
}
|
|
13
|
+
function isOpaqueHexColor(value) {
|
|
14
|
+
const match = HEX_COLOR_REGEX.exec(value);
|
|
15
|
+
return match != null && (match[1] == null || match[1].toLowerCase() === "ff");
|
|
16
|
+
}
|
|
17
|
+
function normalizeHexColor(color) {
|
|
18
|
+
return color.length === 7 ? `${color.toLowerCase()}ff` : color.toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
function getCssHexColor(value) {
|
|
21
|
+
return value?.type === "hex" && isHexColor(value.value) ? value.value : null;
|
|
22
|
+
}
|
|
23
|
+
function isAxisAlignedVerticalGradient(degrees) {
|
|
24
|
+
return Math.abs(((degrees % 180) + 180) % 180) < 1e-10;
|
|
25
|
+
}
|
|
26
|
+
// Alias-aware counterpart for the SDK runtime; mapColorInfo resolves aliases
|
|
27
|
+
// that the hex-only path can't.
|
|
28
|
+
export function resolveSafeAreaFallbackCss(paywallOverride, hostFallback, colorMode) {
|
|
29
|
+
const info = getSafeAreaFallbackColorInfoForMode(paywallOverride, hostFallback, colorMode);
|
|
30
|
+
return info ? mapColorInfo(info) : null;
|
|
31
|
+
}
|
|
32
|
+
// SSR has no real viewport (no off-axis/radial sampling). Hex and alias
|
|
33
|
+
// backgrounds short-circuit immediately; axis-aligned vertical gradients go
|
|
34
|
+
// through the sticky-component disambiguation since edges may differ. Alias
|
|
35
|
+
// values are emitted as var() references — valid CSS that resolves once the
|
|
36
|
+
// SDK stylesheet is present. Invalid hex is skipped rather than emitted.
|
|
37
|
+
function getSsrSafeAreaCssForMode(paywallData, hostFallback, colorMode) {
|
|
38
|
+
const base = paywallData?.components_config?.base;
|
|
39
|
+
const background = base?.background;
|
|
40
|
+
if (background?.type === "color") {
|
|
41
|
+
const color = mapColorMode(colorMode, background.value);
|
|
42
|
+
if (color.type === "hex") {
|
|
43
|
+
const hex = getCssHexColor(color);
|
|
44
|
+
if (hex)
|
|
45
|
+
return hex;
|
|
46
|
+
}
|
|
47
|
+
else if (color.type === "alias") {
|
|
48
|
+
return mapColorInfo(color);
|
|
49
|
+
}
|
|
50
|
+
else if (color.type === "linear" &&
|
|
51
|
+
isAxisAlignedVerticalGradient(color.degrees)) {
|
|
52
|
+
const edges = getBackgroundSafeAreaColors(background, colorMode, { width: 1, height: 1 }, {
|
|
53
|
+
stickyComponents: {
|
|
54
|
+
hasHeader: base?.header != null,
|
|
55
|
+
hasFooter: base?.sticky_footer != null,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const sampled = edges.top ?? edges.bottom;
|
|
59
|
+
if (sampled && isHexColor(sampled))
|
|
60
|
+
return sampled;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const fallbackInfo = getSafeAreaFallbackColorInfoForMode(base?.safe_area_fallback_color, hostFallback, colorMode);
|
|
64
|
+
if (fallbackInfo == null)
|
|
65
|
+
return null;
|
|
66
|
+
return fallbackInfo.type === "alias"
|
|
67
|
+
? mapColorInfo(fallbackInfo)
|
|
68
|
+
: getCssHexColor(fallbackInfo);
|
|
69
|
+
}
|
|
70
|
+
export function getSafeAreaFallbackColorHeadStyles(paywallData, hostFallbackColor) {
|
|
71
|
+
return ["light", "dark"]
|
|
72
|
+
.map((mode) => {
|
|
73
|
+
const value = getSsrSafeAreaCssForMode(paywallData, hostFallbackColor, mode);
|
|
74
|
+
return value
|
|
75
|
+
? `@media (prefers-color-scheme: ${mode}) { html, body { ${SAFE_AREA_FALLBACK_COLOR_CSS_VAR}: ${value}; } }`
|
|
76
|
+
: null;
|
|
77
|
+
})
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join("\n");
|
|
80
|
+
}
|
|
81
|
+
function colorAtPercent(sortedPoints, percent) {
|
|
82
|
+
const firstPoint = sortedPoints[0];
|
|
83
|
+
const lastPoint = sortedPoints.at(-1);
|
|
84
|
+
if (firstPoint == null || lastPoint == null) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (percent <= firstPoint.percent) {
|
|
88
|
+
return normalizeHexColor(firstPoint.color);
|
|
89
|
+
}
|
|
90
|
+
if (percent >= lastPoint.percent) {
|
|
91
|
+
return normalizeHexColor(lastPoint.color);
|
|
92
|
+
}
|
|
93
|
+
const nextPointIndex = sortedPoints.findIndex((point) => point.percent >= percent);
|
|
94
|
+
const previousPoint = sortedPoints[nextPointIndex - 1];
|
|
95
|
+
const nextPoint = sortedPoints[nextPointIndex];
|
|
96
|
+
if (previousPoint == null || nextPoint == null) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (previousPoint.color.toLowerCase() === nextPoint.color.toLowerCase()) {
|
|
100
|
+
return normalizeHexColor(previousPoint.color);
|
|
101
|
+
}
|
|
102
|
+
if (previousPoint.percent === nextPoint.percent) {
|
|
103
|
+
return normalizeHexColor(nextPoint.color);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function getSortedGradientPoints(points) {
|
|
108
|
+
return points
|
|
109
|
+
.filter((point) => Number.isFinite(point.percent) && isHexColor(point.color))
|
|
110
|
+
.slice()
|
|
111
|
+
.sort((a, b) => a.percent - b.percent);
|
|
112
|
+
}
|
|
113
|
+
function solidColorForRange(sortedPoints, [rangeStart, rangeEnd]) {
|
|
114
|
+
const expectedColor = colorAtPercent(sortedPoints, rangeStart);
|
|
115
|
+
if (expectedColor == null || !isOpaqueHexColor(expectedColor)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const breakpoints = [
|
|
119
|
+
rangeStart,
|
|
120
|
+
...sortedPoints
|
|
121
|
+
.map((point) => point.percent)
|
|
122
|
+
.filter((percent) => percent > rangeStart && percent < rangeEnd),
|
|
123
|
+
rangeEnd,
|
|
124
|
+
];
|
|
125
|
+
const isSolid = breakpoints.every((breakpoint, index) => {
|
|
126
|
+
if (colorAtPercent(sortedPoints, breakpoint) !== expectedColor) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const nextBreakpoint = breakpoints[index + 1];
|
|
130
|
+
if (nextBreakpoint == null || nextBreakpoint === breakpoint) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return (colorAtPercent(sortedPoints, (breakpoint + nextBreakpoint) / 2) ===
|
|
134
|
+
expectedColor);
|
|
135
|
+
});
|
|
136
|
+
return isSolid ? expectedColor : null;
|
|
137
|
+
}
|
|
138
|
+
function snapToZero(value) {
|
|
139
|
+
return Math.abs(value) < 1e-10 ? 0 : value;
|
|
140
|
+
}
|
|
141
|
+
function linearGradientPositionPercent(degrees, x, y, viewport) {
|
|
142
|
+
const radians = (degrees * Math.PI) / 180;
|
|
143
|
+
// Math.sin(Math.PI) is ~1.22e-16, not 0. Snap so a 180° gradient's edge
|
|
144
|
+
// sits exactly on a stop instead of just barely missing it.
|
|
145
|
+
const dx = snapToZero(Math.sin(radians));
|
|
146
|
+
const dy = snapToZero(-Math.cos(radians));
|
|
147
|
+
const gradientLength = Math.abs(dx) * viewport.width + Math.abs(dy) * viewport.height;
|
|
148
|
+
const offset = (x - viewport.width / 2) * dx + (y - viewport.height / 2) * dy;
|
|
149
|
+
return ((offset + gradientLength / 2) / gradientLength) * 100;
|
|
150
|
+
}
|
|
151
|
+
function linearEdgeRangePercent(degrees, edge, viewport) {
|
|
152
|
+
const y = edge === "top" ? 0 : viewport.height;
|
|
153
|
+
const start = linearGradientPositionPercent(degrees, 0, y, viewport);
|
|
154
|
+
const end = linearGradientPositionPercent(degrees, viewport.width, y, viewport);
|
|
155
|
+
return [Math.min(start, end), Math.max(start, end)];
|
|
156
|
+
}
|
|
157
|
+
function radialGradientPositionPercent(x, y, viewport) {
|
|
158
|
+
const radius = Math.hypot(viewport.width / 2, viewport.height / 2);
|
|
159
|
+
const distance = Math.hypot(x - viewport.width / 2, y - viewport.height / 2);
|
|
160
|
+
return (distance / radius) * 100;
|
|
161
|
+
}
|
|
162
|
+
function radialEdgeRangePercent(edge, viewport) {
|
|
163
|
+
const y = edge === "top" ? 0 : viewport.height;
|
|
164
|
+
const center = radialGradientPositionPercent(viewport.width / 2, y, viewport);
|
|
165
|
+
const corner = radialGradientPositionPercent(0, y, viewport);
|
|
166
|
+
return [Math.min(center, corner), Math.max(center, corner)];
|
|
167
|
+
}
|
|
168
|
+
function getSolidEdgeColor(color, edge, viewport, sortedPoints) {
|
|
169
|
+
switch (color.type) {
|
|
170
|
+
case "hex":
|
|
171
|
+
case "alias":
|
|
172
|
+
return mapColorInfo(color);
|
|
173
|
+
case "linear":
|
|
174
|
+
return solidColorForRange(sortedPoints ?? [], linearEdgeRangePercent(color.degrees, edge, viewport));
|
|
175
|
+
case "radial":
|
|
176
|
+
return solidColorForRange(sortedPoints ?? [], radialEdgeRangePercent(edge, viewport));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// `position: sticky` promotes a header/footer to its own compositor layer
|
|
180
|
+
// regardless of fill, so even transparent sticky elements break the opposite
|
|
181
|
+
// safe-area strip. Pass `stickyComponents` when painting into a single CSS
|
|
182
|
+
// variable (SDK runtime + SSR head) so the covered edge is nulled and the
|
|
183
|
+
// caller can fall through to the override/host chain; editor previews leave
|
|
184
|
+
// it absent.
|
|
185
|
+
export function getBackgroundSafeAreaColors(background, colorMode, viewport, options) {
|
|
186
|
+
let top = null;
|
|
187
|
+
let bottom = null;
|
|
188
|
+
if (background?.type === "color") {
|
|
189
|
+
const color = mapColorMode(colorMode, background.value);
|
|
190
|
+
const sortedPoints = color.type === "linear" || color.type === "radial"
|
|
191
|
+
? getSortedGradientPoints(color.points)
|
|
192
|
+
: null;
|
|
193
|
+
top = getSolidEdgeColor(color, "top", viewport, sortedPoints);
|
|
194
|
+
bottom = getSolidEdgeColor(color, "bottom", viewport, sortedPoints);
|
|
195
|
+
}
|
|
196
|
+
if (options?.stickyComponents) {
|
|
197
|
+
const hasHeader = options.stickyComponents.hasHeader === true;
|
|
198
|
+
const hasFooter = options.stickyComponents.hasFooter === true;
|
|
199
|
+
// A single CSS variable can only paint one strip; emit only when exactly
|
|
200
|
+
// one edge is exposed (the opposite edge is occluded by a sticky element).
|
|
201
|
+
top = hasFooter && !hasHeader ? top : null;
|
|
202
|
+
bottom = hasHeader && !hasFooter ? bottom : null;
|
|
203
|
+
}
|
|
204
|
+
if (options?.paywallFallbackColor != null ||
|
|
205
|
+
options?.hostFallbackColor != null) {
|
|
206
|
+
const fallback = resolveSafeAreaFallbackCss(options.paywallFallbackColor, options.hostFallbackColor, colorMode);
|
|
207
|
+
top = top ?? fallback;
|
|
208
|
+
bottom = bottom ?? fallback;
|
|
209
|
+
}
|
|
210
|
+
return { top, bottom };
|
|
211
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getBackgroundSafeAreaColors, getSafeAreaFallbackColorHeadStyles, } from "./safe-area-background";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Public surface for `@revenuecat/purchases-ui-js/safe-area`. The CSS var
|
|
2
|
+
// name and helpers used internally by `applyDocumentBackground` are
|
|
3
|
+
// deliberately not re-exported.
|
|
4
|
+
export { getBackgroundSafeAreaColors, getSafeAreaFallbackColorHeadStyles, } from "./safe-area-background";
|
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.
|
|
5
|
+
"version": "4.4.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "RevenueCat, Inc."
|
|
8
8
|
},
|
|
@@ -65,6 +65,10 @@
|
|
|
65
65
|
},
|
|
66
66
|
"./web-components": {
|
|
67
67
|
"default": "./dist/web-components/index.js"
|
|
68
|
+
},
|
|
69
|
+
"./safe-area": {
|
|
70
|
+
"types": "./dist/utils/safe-area.d.ts",
|
|
71
|
+
"default": "./dist/utils/safe-area.js"
|
|
68
72
|
}
|
|
69
73
|
},
|
|
70
74
|
"engines": {
|