@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,3 +1,5 @@
|
|
|
1
1
|
import type { Size } from "../../types";
|
|
2
|
+
import type { Component } from "../../types/component";
|
|
2
3
|
import type { CSS } from "../../utils/base-utils";
|
|
4
|
+
export declare function firstContentIsHeroMedia(components: Component[]): boolean;
|
|
3
5
|
export declare function mapPaywallContentStackStyle(height: Size, pullContentUnderHeader: boolean, headerHeight: number): CSS;
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
function findFirstContent(components) {
|
|
2
|
+
for (const component of components) {
|
|
3
|
+
if (component.type === "fallback_header") {
|
|
4
|
+
continue;
|
|
5
|
+
}
|
|
6
|
+
if (component.type === "stack") {
|
|
7
|
+
const nestedContent = findFirstContent(component.components);
|
|
8
|
+
if (nestedContent) {
|
|
9
|
+
return nestedContent;
|
|
10
|
+
}
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
return component;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function firstContentIsHeroMedia(components) {
|
|
17
|
+
const firstContent = findFirstContent(components);
|
|
18
|
+
return ((firstContent?.type === "image" ||
|
|
19
|
+
firstContent?.type === "video" ||
|
|
20
|
+
firstContent?.type === "web_view") &&
|
|
21
|
+
firstContent.size.width.type === "fill");
|
|
22
|
+
}
|
|
1
23
|
export function mapPaywallContentStackStyle(height, pullContentUnderHeader, headerHeight) {
|
|
2
24
|
return {
|
|
3
25
|
// Nested fill children need a definite root height; other roots keep
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
getHtmlFromMarkdown,
|
|
4
4
|
getTextComponentStyles,
|
|
5
5
|
getTextWrapperInlineStyles,
|
|
6
|
+
markdownLinkUrlFromClick,
|
|
6
7
|
} from "./text-utils";
|
|
7
8
|
import Text from "./Text.svelte";
|
|
8
9
|
import { getColorModeContext } from "../../stores/color-mode";
|
|
@@ -53,28 +54,7 @@
|
|
|
53
54
|
const markdownParsed = $derived(getHtmlFromMarkdown(label));
|
|
54
55
|
|
|
55
56
|
const onclick = (event: MouseEvent) => {
|
|
56
|
-
|
|
57
|
-
event.defaultPrevented ||
|
|
58
|
-
event.button !== 0 ||
|
|
59
|
-
event.metaKey ||
|
|
60
|
-
event.ctrlKey ||
|
|
61
|
-
event.shiftKey ||
|
|
62
|
-
event.altKey
|
|
63
|
-
) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const target = event.target;
|
|
68
|
-
if (!(target instanceof Element)) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const anchor = target.closest("a[href]");
|
|
73
|
-
if (!(anchor instanceof HTMLAnchorElement)) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const url = anchor.getAttribute("href") ?? anchor.href;
|
|
57
|
+
const url = markdownLinkUrlFromClick(event);
|
|
78
58
|
if (!url) {
|
|
79
59
|
return;
|
|
80
60
|
}
|
|
@@ -4,6 +4,14 @@ import type { TextNodeProps } from "../../types/components/text";
|
|
|
4
4
|
import type { AppFontsConfig } from "../../types/ui-config";
|
|
5
5
|
import { type CSS } from "../../utils/base-utils";
|
|
6
6
|
export declare const defaultColor: ColorScheme;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the `href` of the markdown link a plain left click landed on, or
|
|
9
|
+
* `null` when the click should be left alone (modifier keys, non-primary
|
|
10
|
+
* button, already handled, not on a link). Hosts route these through
|
|
11
|
+
* `onNavigateToUrl`, since a bare `target="_blank"` anchor is blocked in some
|
|
12
|
+
* of them (embedded webviews, sandboxed frames).
|
|
13
|
+
*/
|
|
14
|
+
export declare function markdownLinkUrlFromClick(event: MouseEvent): string | null;
|
|
7
15
|
export declare function mapTextColor(colorMode: ColorMode, scheme: ColorGradientScheme): CSS;
|
|
8
16
|
/**
|
|
9
17
|
* Generates comprehensive styles for text components by combining text, component and size styles
|
|
@@ -6,6 +6,32 @@ import { getScopedFontFamily, isFontRCFMManaged } from "../../utils/font-utils";
|
|
|
6
6
|
export const defaultColor = {
|
|
7
7
|
light: { type: "hex", value: DEFAULT_TEXT_COLOR },
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Returns the `href` of the markdown link a plain left click landed on, or
|
|
11
|
+
* `null` when the click should be left alone (modifier keys, non-primary
|
|
12
|
+
* button, already handled, not on a link). Hosts route these through
|
|
13
|
+
* `onNavigateToUrl`, since a bare `target="_blank"` anchor is blocked in some
|
|
14
|
+
* of them (embedded webviews, sandboxed frames).
|
|
15
|
+
*/
|
|
16
|
+
export function markdownLinkUrlFromClick(event) {
|
|
17
|
+
if (event.defaultPrevented ||
|
|
18
|
+
event.button !== 0 ||
|
|
19
|
+
event.metaKey ||
|
|
20
|
+
event.ctrlKey ||
|
|
21
|
+
event.shiftKey ||
|
|
22
|
+
event.altKey) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const target = event.target;
|
|
26
|
+
if (!(target instanceof Element)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const anchor = target.closest("a[href]");
|
|
30
|
+
if (!(anchor instanceof HTMLAnchorElement)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return anchor.getAttribute("href") || anchor.href || null;
|
|
34
|
+
}
|
|
9
35
|
export function mapTextColor(colorMode, scheme) {
|
|
10
36
|
const info = mapColorMode(colorMode, scheme);
|
|
11
37
|
const color = mapColorInfo(info);
|
|
@@ -101,7 +101,11 @@
|
|
|
101
101
|
}: Props = $props();
|
|
102
102
|
</script>
|
|
103
103
|
|
|
104
|
-
<div
|
|
104
|
+
<div
|
|
105
|
+
id={containerId}
|
|
106
|
+
class="paywall-container"
|
|
107
|
+
style="width: 100%; min-width: 0; height: 100%;"
|
|
108
|
+
>
|
|
105
109
|
{#if paywallComponents}
|
|
106
110
|
<Paywall
|
|
107
111
|
paywallData={paywallComponents}
|
|
@@ -142,7 +142,8 @@
|
|
|
142
142
|
for (const [key, declaration] of Object.entries(
|
|
143
143
|
screen.state_declarations ?? {},
|
|
144
144
|
)) {
|
|
145
|
-
if (!Object.
|
|
145
|
+
if (!Object.prototype.hasOwnProperty.call(merged, key))
|
|
146
|
+
merged[key] = declaration;
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
return merged;
|
|
@@ -4,13 +4,16 @@ import { type Readable } from "svelte/store";
|
|
|
4
4
|
* Used to restore selection state when navigating back to a step.
|
|
5
5
|
*/
|
|
6
6
|
export type InitialInputSelections = Record<string, string[]>;
|
|
7
|
-
interface InputValidationContext {
|
|
7
|
+
export interface InputValidationContext {
|
|
8
|
+
/** True only when every registered input is satisfied. */
|
|
8
9
|
isSatisfied: Readable<boolean>;
|
|
9
|
-
|
|
10
|
+
/** Registers or updates the satisfaction of a single input, keyed by component id. */
|
|
11
|
+
updateSatisfaction: (id: string, satisfied: boolean) => void;
|
|
12
|
+
/** Stops an unmounted input from gating the screen. */
|
|
13
|
+
removeInput: (id: string) => void;
|
|
10
14
|
}
|
|
11
15
|
export declare function createInputValidationContext(initiallySatisfied?: boolean): InputValidationContext;
|
|
12
16
|
export declare function setInputValidationContext(context: InputValidationContext): void;
|
|
13
17
|
export declare function getInputValidationContext(): InputValidationContext | undefined;
|
|
14
18
|
export declare function setInitialInputSelectionsContext(selections: InitialInputSelections): void;
|
|
15
19
|
export declare function getInitialInputSelectionsContext(): InitialInputSelections;
|
|
16
|
-
export {};
|
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
import { getContext, setContext } from "svelte";
|
|
2
|
-
import { writable } from "svelte/store";
|
|
2
|
+
import { derived, writable } from "svelte/store";
|
|
3
3
|
const key = Symbol("inputValidation");
|
|
4
4
|
const initialSelectionsKey = Symbol("initialInputSelections");
|
|
5
5
|
export function createInputValidationContext(initiallySatisfied = true) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const satisfactionById = writable(new Map());
|
|
7
|
+
// `initiallySatisfied` only matters while no input has registered yet; once
|
|
8
|
+
// any input reports, the map is the single source of truth.
|
|
9
|
+
const isSatisfied = derived(satisfactionById, (byId) => byId.size === 0
|
|
10
|
+
? initiallySatisfied
|
|
11
|
+
: Array.from(byId.values()).every(Boolean));
|
|
12
|
+
const updateSatisfaction = (id, satisfied) => {
|
|
13
|
+
satisfactionById.update((byId) => {
|
|
14
|
+
if (byId.get(id) === satisfied) {
|
|
15
|
+
return byId;
|
|
16
|
+
}
|
|
17
|
+
const next = new Map(byId);
|
|
18
|
+
next.set(id, satisfied);
|
|
19
|
+
return next;
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const removeInput = (id) => {
|
|
23
|
+
satisfactionById.update((byId) => {
|
|
24
|
+
if (!byId.has(id)) {
|
|
25
|
+
return byId;
|
|
26
|
+
}
|
|
27
|
+
const next = new Map(byId);
|
|
28
|
+
next.delete(id);
|
|
29
|
+
return next;
|
|
30
|
+
});
|
|
9
31
|
};
|
|
10
32
|
return {
|
|
11
33
|
isSatisfied,
|
|
12
34
|
updateSatisfaction,
|
|
35
|
+
removeInput,
|
|
13
36
|
};
|
|
14
37
|
}
|
|
15
38
|
export function setInputValidationContext(context) {
|
package/dist/stores/state.js
CHANGED
|
@@ -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.
|
|
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.
|
|
44
|
+
if (!Object.prototype.hasOwnProperty.call(this.#currentValues, key)) {
|
|
45
45
|
this.#currentValues[key] = normalized;
|
|
46
46
|
changed = true;
|
|
47
47
|
}
|
|
@@ -6,6 +6,22 @@ import type { Overrides } from "../overrides";
|
|
|
6
6
|
export type InputTextCapitalizeType = "none" | "sentences" | "words" | "characters";
|
|
7
7
|
export type InputTextKeyboardType = "decimal" | "email" | "numeric" | "password" | "tel" | "text" | "url";
|
|
8
8
|
export type ReservedAttribute = "$email" | "$displayName" | "$phoneNumber";
|
|
9
|
+
export type InputTextValidationFormat = "email" | "uri";
|
|
10
|
+
/**
|
|
11
|
+
* JSON Schema subset mapped to native HTML constraint attributes.
|
|
12
|
+
* Keys keep the JSON Schema camelCase vocabulary.
|
|
13
|
+
*/
|
|
14
|
+
export interface InputTextValidation {
|
|
15
|
+
minimum?: number | null;
|
|
16
|
+
maximum?: number | null;
|
|
17
|
+
minLength?: number | null;
|
|
18
|
+
maxLength?: number | null;
|
|
19
|
+
pattern?: string | null;
|
|
20
|
+
format?: InputTextValidationFormat | null;
|
|
21
|
+
}
|
|
22
|
+
export type InputTextValidationRule = "required" | "minimum" | "maximum" | "minLength" | "maxLength" | "pattern" | "format";
|
|
23
|
+
/** Per-rule error copy; `validation_error_lid` is the fallback for missing keys. */
|
|
24
|
+
export type InputTextValidationErrorLids = Partial<Record<InputTextValidationRule, string | null>>;
|
|
9
25
|
export interface InputTextProps extends BaseComponent {
|
|
10
26
|
type: "input_text";
|
|
11
27
|
visible?: boolean | null;
|
|
@@ -15,6 +31,9 @@ export interface InputTextProps extends BaseComponent {
|
|
|
15
31
|
field_id: string;
|
|
16
32
|
reserved_attribute?: ReservedAttribute | null;
|
|
17
33
|
required: boolean;
|
|
34
|
+
validation?: InputTextValidation | null;
|
|
35
|
+
validation_error_lid?: string | null;
|
|
36
|
+
validation_error_lids?: InputTextValidationErrorLids | null;
|
|
18
37
|
size: SizeType;
|
|
19
38
|
padding: Spacing;
|
|
20
39
|
margin: Spacing;
|
|
@@ -61,7 +61,7 @@ export type OverrideCondition = IntroOfferCondition | MultipleIntroOffersConditi
|
|
|
61
61
|
/**
|
|
62
62
|
* The reserved properties of an override that should not be included in the override properties
|
|
63
63
|
*/
|
|
64
|
-
type ReservedProperty = "id" | "type" | "overrides" | "stack" | "components" | "items" | "pages" | "tabs" | "field_id" | "option_id" | "keyboard_type" | "capitalize";
|
|
64
|
+
type ReservedProperty = "id" | "type" | "overrides" | "stack" | "components" | "items" | "pages" | "tabs" | "field_id" | "option_id" | "keyboard_type" | "capitalize" | "validation" | "validation_error_lid" | "validation_error_lids";
|
|
65
65
|
type OverrideProperties<C> = Partial<Omit<C, ReservedProperty>>;
|
|
66
66
|
type Override<C> = {
|
|
67
67
|
conditions: OverrideCondition[];
|
package/dist/types.d.ts
CHANGED
|
@@ -128,4 +128,4 @@ export declare enum StackDistribution {
|
|
|
128
128
|
export type { WorkflowScreen } from "./types/workflow";
|
|
129
129
|
export type { ColorScheme } from "./types/colors";
|
|
130
130
|
export type { ComponentInteractionData, ComponentInteractionType, OnComponentInteraction, } from "./types/paywall-component-interaction";
|
|
131
|
-
export type { ReservedAttribute } from "./types/components/input-text";
|
|
131
|
+
export type { InputTextValidation, InputTextValidationErrorLids, InputTextValidationFormat, InputTextValidationRule, ReservedAttribute, } from "./types/components/input-text";
|
|
@@ -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.
|
|
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.
|
|
210
|
-
const hasDeclaredDefault = Object.
|
|
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,
|