@revenuecat/purchases-ui-js 4.7.6 → 4.8.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/button/ButtonNode.stories.svelte +0 -3
- package/dist/components/button/ButtonNode.svelte +3 -9
- package/dist/components/paywall/Paywall.stories.svelte +15 -0
- package/dist/components/paywall/Paywall.svelte +0 -9
- package/dist/components/paywall/Paywall.svelte.d.ts +0 -5
- package/dist/components/paywall/fixtures/fixed-height-stack-paywall.d.ts +2 -0
- package/dist/components/paywall/fixtures/fixed-height-stack-paywall.js +87 -0
- package/dist/components/stack/Stack.svelte +9 -1
- package/dist/components/stack/stack-utils.d.ts +3 -0
- package/dist/components/stack/stack-utils.js +14 -0
- package/dist/components/workflows/Screen.svelte +0 -3
- package/dist/components/workflows/Screen.svelte.d.ts +0 -1
- package/dist/components/workflows/Workflow.svelte +109 -14
- package/dist/components/workflows/fixtures/two-page-workflow.d.ts +3 -5
- package/dist/components/workflows/fixtures/two-page-workflow.js +63 -25
- package/dist/types/components/button.d.ts +1 -8
- package/dist/types/paywall-component-interaction.d.ts +1 -1
- package/dist/types/workflow-nav.d.ts +15 -9
- package/dist/types/workflow-nav.js +4 -4
- package/dist/types/workflow.d.ts +34 -2
- package/package.json +1 -1
|
@@ -67,12 +67,6 @@
|
|
|
67
67
|
componentName: props.name,
|
|
68
68
|
componentValue: "navigate_back",
|
|
69
69
|
};
|
|
70
|
-
case "navigate_to_page":
|
|
71
|
-
return {
|
|
72
|
-
componentType: "button",
|
|
73
|
-
componentName: props.name,
|
|
74
|
-
componentValue: "navigate_to_page",
|
|
75
|
-
};
|
|
76
70
|
case "close_workflow":
|
|
77
71
|
return {
|
|
78
72
|
componentType: "button",
|
|
@@ -149,7 +143,9 @@
|
|
|
149
143
|
|
|
150
144
|
const onclick = () => {
|
|
151
145
|
if (isDisabled) return;
|
|
152
|
-
|
|
146
|
+
// For "workflow" actions, pass the component's own id so Workflow.svelte
|
|
147
|
+
// can look it up in the step's triggers array (component ID → action ID).
|
|
148
|
+
const actionId = action.type === "workflow" ? props.id : undefined;
|
|
153
149
|
if (action.type === "navigate_to" && action.destination === "sheet") {
|
|
154
150
|
if (action.sheet != null) {
|
|
155
151
|
emitComponentInteraction(getSheetOpenInteractionData(action));
|
|
@@ -173,8 +169,6 @@
|
|
|
173
169
|
return false;
|
|
174
170
|
case "navigate_back":
|
|
175
171
|
return !hideBackButtons;
|
|
176
|
-
case "navigate_to_page":
|
|
177
|
-
return true;
|
|
178
172
|
case "close_workflow":
|
|
179
173
|
return true;
|
|
180
174
|
case "navigate_to":
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { waitForAnimations } from "storybook/internal/preview-api";
|
|
26
26
|
import { viewportDecorator } from "../../stories/viewport-decorator";
|
|
27
27
|
import { BACKGROUND_PAYWALL } from "./fixtures/background-paywall";
|
|
28
|
+
import { FIXED_HEIGHT_STACK_PAYWALL } from "./fixtures/fixed-height-stack-paywall";
|
|
28
29
|
import { OVERRIDE_PAYWALL } from "./fixtures/override-paywall";
|
|
29
30
|
import { SHEET_PAYWALL } from "./fixtures/sheet-paywall";
|
|
30
31
|
import { SHEET_PAYWALL_VIDEO_STACKING } from "./fixtures/sheet-video-stacking-paywall";
|
|
@@ -74,6 +75,20 @@
|
|
|
74
75
|
}}
|
|
75
76
|
/>
|
|
76
77
|
|
|
78
|
+
<!--
|
|
79
|
+
Regression story for fixed-height stacks being squeezed by the flex algorithm.
|
|
80
|
+
The root stack uses `space_between` and children's fixed heights sum to more than
|
|
81
|
+
the 400px viewport, so the empty spacer would collapse without `flex-shrink: 0`.
|
|
82
|
+
Chromatic diffs this story to block regressions of Stack.svelte's flex-shrink rule.
|
|
83
|
+
-->
|
|
84
|
+
<Story
|
|
85
|
+
name="Fixed-height stack (regression)"
|
|
86
|
+
decorators={[viewportDecorator(320, 400, 0)]}
|
|
87
|
+
args={{
|
|
88
|
+
paywallData: FIXED_HEIGHT_STACK_PAYWALL,
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
|
|
77
92
|
<Story
|
|
78
93
|
name="Override paywall"
|
|
79
94
|
args={{
|
|
@@ -71,11 +71,6 @@
|
|
|
71
71
|
preferredColorMode?: ColorMode;
|
|
72
72
|
onPurchaseClicked?: (selectedPackageId: string, actionId: string) => void;
|
|
73
73
|
onBackClicked?: () => void;
|
|
74
|
-
/**
|
|
75
|
-
* Called when a `navigate_to_page` button is pressed and there is no
|
|
76
|
-
* active nav_host context to handle it internally.
|
|
77
|
-
*/
|
|
78
|
-
onNavigateToPage?: (pageId: string) => void;
|
|
79
74
|
/** Called when a `close_workflow` button is pressed to dismiss the paywall/workflow. */
|
|
80
75
|
onClose?: () => void;
|
|
81
76
|
onVisitCustomerCenterClicked?: () => void;
|
|
@@ -135,7 +130,6 @@
|
|
|
135
130
|
preferredColorMode,
|
|
136
131
|
onPurchaseClicked,
|
|
137
132
|
onBackClicked,
|
|
138
|
-
onNavigateToPage,
|
|
139
133
|
onClose,
|
|
140
134
|
onVisitCustomerCenterClicked,
|
|
141
135
|
onRestorePurchasesClicked,
|
|
@@ -248,9 +242,6 @@
|
|
|
248
242
|
case "navigate_back":
|
|
249
243
|
onBackClicked?.();
|
|
250
244
|
return;
|
|
251
|
-
case "navigate_to_page":
|
|
252
|
-
onNavigateToPage?.(action.page_id);
|
|
253
|
-
return;
|
|
254
245
|
case "close_workflow":
|
|
255
246
|
onClose?.();
|
|
256
247
|
return;
|
|
@@ -28,11 +28,6 @@ interface Props {
|
|
|
28
28
|
preferredColorMode?: ColorMode;
|
|
29
29
|
onPurchaseClicked?: (selectedPackageId: string, actionId: string) => void;
|
|
30
30
|
onBackClicked?: () => void;
|
|
31
|
-
/**
|
|
32
|
-
* Called when a `navigate_to_page` button is pressed and there is no
|
|
33
|
-
* active nav_host context to handle it internally.
|
|
34
|
-
*/
|
|
35
|
-
onNavigateToPage?: (pageId: string) => void;
|
|
36
31
|
/** Called when a `close_workflow` button is pressed to dismiss the paywall/workflow. */
|
|
37
32
|
onClose?: () => void;
|
|
38
33
|
onVisitCustomerCenterClicked?: () => void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Regression fixture: fixed-height empty stack collapses under flex pressure.
|
|
2
|
+
// Root `space_between` with children summing above viewport height. Pair with
|
|
3
|
+
// `viewportDecorator` in the story so the parent is bounded; otherwise the
|
|
4
|
+
// paywall grows to content height and no shrinkage happens.
|
|
5
|
+
const solidStack = (id, height, hex) => ({
|
|
6
|
+
type: "stack",
|
|
7
|
+
id,
|
|
8
|
+
name: id,
|
|
9
|
+
components: [],
|
|
10
|
+
dimension: {
|
|
11
|
+
type: "vertical",
|
|
12
|
+
alignment: "center",
|
|
13
|
+
distribution: "center",
|
|
14
|
+
},
|
|
15
|
+
size: {
|
|
16
|
+
width: { type: "fill" },
|
|
17
|
+
height: { type: "fixed", value: height },
|
|
18
|
+
},
|
|
19
|
+
background: null,
|
|
20
|
+
background_color: { light: { type: "hex", value: hex } },
|
|
21
|
+
badge: null,
|
|
22
|
+
border: null,
|
|
23
|
+
margin: { bottom: 0, leading: 0, top: 0, trailing: 0 },
|
|
24
|
+
padding: { bottom: 0, leading: 0, top: 0, trailing: 0 },
|
|
25
|
+
shadow: null,
|
|
26
|
+
shape: {
|
|
27
|
+
type: "rectangle",
|
|
28
|
+
corners: {
|
|
29
|
+
bottom_leading: 0,
|
|
30
|
+
bottom_trailing: 0,
|
|
31
|
+
top_leading: 0,
|
|
32
|
+
top_trailing: 0,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
spacing: 0,
|
|
36
|
+
});
|
|
37
|
+
export const FIXED_HEIGHT_STACK_PAYWALL = {
|
|
38
|
+
id: "fixed_height_stack_paywall",
|
|
39
|
+
default_locale: "en_US",
|
|
40
|
+
components_localizations: { en_US: {} },
|
|
41
|
+
components_config: {
|
|
42
|
+
base: {
|
|
43
|
+
background: {
|
|
44
|
+
type: "color",
|
|
45
|
+
value: { light: { type: "hex", value: "#ffffff" } },
|
|
46
|
+
},
|
|
47
|
+
stack: {
|
|
48
|
+
type: "stack",
|
|
49
|
+
id: "root",
|
|
50
|
+
name: "Root",
|
|
51
|
+
components: [
|
|
52
|
+
solidStack("hero", 180, "#ff5f5f"),
|
|
53
|
+
solidStack("empty_spacer", 50, "#11d483"),
|
|
54
|
+
solidStack("card", 200, "#6366f1"),
|
|
55
|
+
solidStack("cta", 48, "#000000"),
|
|
56
|
+
],
|
|
57
|
+
dimension: {
|
|
58
|
+
type: "vertical",
|
|
59
|
+
alignment: "center",
|
|
60
|
+
distribution: "space_between",
|
|
61
|
+
},
|
|
62
|
+
size: {
|
|
63
|
+
width: { type: "fill" },
|
|
64
|
+
height: { type: "fill" },
|
|
65
|
+
},
|
|
66
|
+
background: null,
|
|
67
|
+
background_color: null,
|
|
68
|
+
badge: null,
|
|
69
|
+
border: null,
|
|
70
|
+
margin: { bottom: 0, leading: 0, top: 0, trailing: 0 },
|
|
71
|
+
padding: { bottom: 16, leading: 16, top: 16, trailing: 16 },
|
|
72
|
+
shadow: null,
|
|
73
|
+
shape: {
|
|
74
|
+
type: "rectangle",
|
|
75
|
+
corners: {
|
|
76
|
+
bottom_leading: 0,
|
|
77
|
+
bottom_trailing: 0,
|
|
78
|
+
top_leading: 0,
|
|
79
|
+
top_trailing: 0,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
spacing: 12,
|
|
83
|
+
},
|
|
84
|
+
sticky_footer: null,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -32,7 +32,13 @@
|
|
|
32
32
|
getHoverStateProps,
|
|
33
33
|
} from "../../utils/style-utils";
|
|
34
34
|
import type { Snippet } from "svelte";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
mapBadge,
|
|
37
|
+
mapDimension,
|
|
38
|
+
mapFixedHeightBounds,
|
|
39
|
+
mapFixedWidthBounds,
|
|
40
|
+
mapLayerAlignment,
|
|
41
|
+
} from "./stack-utils";
|
|
36
42
|
|
|
37
43
|
interface MiscProps {
|
|
38
44
|
onclick?: () => void;
|
|
@@ -89,6 +95,8 @@
|
|
|
89
95
|
position: "relative",
|
|
90
96
|
width: mapSize(size.width),
|
|
91
97
|
height: mapSize(size.height),
|
|
98
|
+
...mapFixedWidthBounds(size.width),
|
|
99
|
+
...mapFixedHeightBounds(size.height),
|
|
92
100
|
...mapDimension(dimension),
|
|
93
101
|
gap: px(spacing),
|
|
94
102
|
margin: mapSpacing(margin),
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { Size } from "../../types";
|
|
1
2
|
import type { Dimension, ZAlignment } from "../../types/alignment";
|
|
2
3
|
import type { Badge } from "../../types/components/stack";
|
|
3
4
|
import { type CSS } from "../../utils/base-utils";
|
|
5
|
+
export declare function mapFixedWidthBounds(width: Size): CSS;
|
|
6
|
+
export declare function mapFixedHeightBounds(height: Size): CSS;
|
|
4
7
|
export declare function mapDimension(dimension: Dimension): Record<string, string>;
|
|
5
8
|
type JustifyAlign = {
|
|
6
9
|
justifyContent: string;
|
|
@@ -14,6 +14,20 @@ const DISTRIBUTION_MAP = Object.freeze({
|
|
|
14
14
|
center: "center",
|
|
15
15
|
end: "flex-end",
|
|
16
16
|
});
|
|
17
|
+
export function mapFixedWidthBounds(width) {
|
|
18
|
+
if (width.type !== "fixed") {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
const value = px(width.value);
|
|
22
|
+
return { "min-width": value, "max-width": value };
|
|
23
|
+
}
|
|
24
|
+
export function mapFixedHeightBounds(height) {
|
|
25
|
+
if (height.type !== "fixed") {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
const value = px(height.value);
|
|
29
|
+
return { "min-height": value, "max-height": value };
|
|
30
|
+
}
|
|
17
31
|
export function mapDimension(dimension) {
|
|
18
32
|
if (dimension.type === "zlayer") {
|
|
19
33
|
return {};
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
actionId: string,
|
|
22
22
|
) => void | Promise<void>;
|
|
23
23
|
onBackClicked?: () => void;
|
|
24
|
-
onNavigateToPage?: (pageId: string) => void;
|
|
25
24
|
onClose?: () => void;
|
|
26
25
|
containerId?: string;
|
|
27
26
|
maxContentWidth?: string;
|
|
@@ -55,7 +54,6 @@
|
|
|
55
54
|
onComponentInteraction,
|
|
56
55
|
onPurchaseClicked,
|
|
57
56
|
onBackClicked,
|
|
58
|
-
onNavigateToPage,
|
|
59
57
|
onClose,
|
|
60
58
|
containerId = "screen-container",
|
|
61
59
|
maxContentWidth,
|
|
@@ -94,7 +92,6 @@
|
|
|
94
92
|
{onCompleteWorkflowNavigate}
|
|
95
93
|
onVisitCustomerCenterClicked={() => onVisitCustomerCenterClicked?.()}
|
|
96
94
|
{onBackClicked}
|
|
97
|
-
{onNavigateToPage}
|
|
98
95
|
{onClose}
|
|
99
96
|
onRestorePurchasesClicked={() => onRestorePurchasesClicked?.()}
|
|
100
97
|
onActionTriggered={(actionId: string) => {
|
|
@@ -16,7 +16,6 @@ interface Props {
|
|
|
16
16
|
onComponentInteraction?: OnComponentInteraction;
|
|
17
17
|
onPurchaseClicked?: (packageId: string, actionId: string) => void | Promise<void>;
|
|
18
18
|
onBackClicked?: () => void;
|
|
19
|
-
onNavigateToPage?: (pageId: string) => void;
|
|
20
19
|
onClose?: () => void;
|
|
21
20
|
containerId?: string;
|
|
22
21
|
maxContentWidth?: string;
|
|
@@ -9,6 +9,21 @@
|
|
|
9
9
|
import type { UIConfig } from "../../types/ui-config";
|
|
10
10
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
11
11
|
import type { WorkflowNavData } from "../../types/workflow-nav";
|
|
12
|
+
import type {
|
|
13
|
+
WorkflowStepTriggerAction,
|
|
14
|
+
WorkflowStep,
|
|
15
|
+
} from "../../types/workflow";
|
|
16
|
+
|
|
17
|
+
type TriggerActionValue = WorkflowStep["trigger_actions"][string];
|
|
18
|
+
|
|
19
|
+
function isStepTriggerAction(
|
|
20
|
+
a: TriggerActionValue,
|
|
21
|
+
): a is WorkflowStepTriggerAction {
|
|
22
|
+
return (
|
|
23
|
+
a.type === "step" &&
|
|
24
|
+
typeof (a as WorkflowStepTriggerAction).step_id === "string"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
12
27
|
|
|
13
28
|
interface Props {
|
|
14
29
|
workflow: WorkflowNavData;
|
|
@@ -71,34 +86,115 @@
|
|
|
71
86
|
}: Props = $props();
|
|
72
87
|
|
|
73
88
|
// ── Nav stack ─────────────────────────────────────────────────────────────
|
|
74
|
-
//
|
|
75
|
-
//
|
|
89
|
+
// Each entry pairs the screen ID (used to look up the page to render) with
|
|
90
|
+
// the step ID (used to resolve trigger_actions on button press). The current
|
|
91
|
+
// page is always the last entry; workflow actions push, navigate_back pops.
|
|
76
92
|
//
|
|
77
93
|
// We key the reset off workflow.initial_page_id (a stable string) rather
|
|
78
94
|
// than workflow object identity, so hosts that recompute the prop object on
|
|
79
95
|
// unrelated reactive updates don't accidentally snap the user back to the
|
|
80
96
|
// first page mid-flow.
|
|
81
|
-
|
|
97
|
+
interface NavEntry {
|
|
98
|
+
pageId: string;
|
|
99
|
+
stepId: string;
|
|
100
|
+
}
|
|
101
|
+
let navStack = $state<NavEntry[]>([]);
|
|
82
102
|
let trackedInitialPageId = $state<string>();
|
|
83
103
|
|
|
84
|
-
const
|
|
85
|
-
navStack[navStack.length - 1] ??
|
|
104
|
+
const current = $derived(
|
|
105
|
+
navStack[navStack.length - 1] ?? {
|
|
106
|
+
pageId: workflow.initial_page_id,
|
|
107
|
+
stepId: workflow.initial_step_id,
|
|
108
|
+
},
|
|
86
109
|
);
|
|
87
|
-
const currentScreen = $derived(workflow.pages[
|
|
110
|
+
const currentScreen = $derived(workflow.pages[current.pageId]);
|
|
88
111
|
|
|
89
112
|
$effect.pre(() => {
|
|
90
113
|
if (trackedInitialPageId !== workflow.initial_page_id) {
|
|
91
114
|
trackedInitialPageId = workflow.initial_page_id;
|
|
92
|
-
navStack = [
|
|
115
|
+
navStack = [
|
|
116
|
+
{
|
|
117
|
+
pageId: workflow.initial_page_id,
|
|
118
|
+
stepId: workflow.initial_step_id,
|
|
119
|
+
},
|
|
120
|
+
];
|
|
93
121
|
}
|
|
94
122
|
});
|
|
95
123
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Resolves a "workflow"-type button action and navigates to the next screen.
|
|
126
|
+
*
|
|
127
|
+
* Flow: component ID → step.triggers[].action_id → trigger_actions[action_id]
|
|
128
|
+
* → step_id → step.screen_id → push onto nav stack.
|
|
129
|
+
*/
|
|
130
|
+
function handleActionTriggered(actionId: string) {
|
|
131
|
+
const currentStep = workflow.steps[current.stepId];
|
|
132
|
+
if (!currentStep) {
|
|
133
|
+
console.warn(
|
|
134
|
+
`[Workflow] onActionTriggered: no current step found for step_id "${current.stepId}"`,
|
|
135
|
+
);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Resolve the component ID to an action ID via the step's triggers array.
|
|
140
|
+
const resolvedActionId = currentStep.triggers.find(
|
|
141
|
+
(t) => t.component_id === actionId,
|
|
142
|
+
)?.action_id;
|
|
143
|
+
|
|
144
|
+
if (!resolvedActionId) {
|
|
145
|
+
console.warn(
|
|
146
|
+
`[Workflow] onActionTriggered: no trigger entry found for component "${actionId}" on step "${current.stepId}"`,
|
|
147
|
+
);
|
|
148
|
+
onActionTriggered?.(actionId);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const triggerAction = currentStep.trigger_actions[resolvedActionId];
|
|
153
|
+
if (!triggerAction) {
|
|
154
|
+
console.warn(
|
|
155
|
+
`[Workflow] onActionTriggered: no trigger_action found for action_id "${resolvedActionId}" on step "${current.stepId}"`,
|
|
156
|
+
);
|
|
157
|
+
onActionTriggered?.(actionId);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!isStepTriggerAction(triggerAction)) {
|
|
162
|
+
// Condition-based actions require a full workflow engine — fall back to
|
|
163
|
+
// the host's onActionTriggered for anything we can't resolve client-side.
|
|
164
|
+
console.warn(
|
|
165
|
+
`[Workflow] onActionTriggered: trigger_action is not a simple step action, falling back to onActionTriggered`,
|
|
166
|
+
);
|
|
167
|
+
onActionTriggered?.(actionId);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const nextStep = workflow.steps[triggerAction.step_id];
|
|
172
|
+
if (!nextStep) {
|
|
173
|
+
console.warn(
|
|
174
|
+
`[Workflow] onActionTriggered: step_id "${triggerAction.step_id}" not found in workflow steps`,
|
|
175
|
+
);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const nextPageId = nextStep.screen_id;
|
|
180
|
+
if (!nextPageId) {
|
|
181
|
+
console.warn(
|
|
182
|
+
`[Workflow] onActionTriggered: step "${triggerAction.step_id}" has no screen_id (logic/experiment step?)`,
|
|
183
|
+
);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!workflow.pages[nextPageId]) {
|
|
188
|
+
console.warn(
|
|
189
|
+
`[Workflow] onActionTriggered: screen_id "${nextPageId}" not found in workflow pages`,
|
|
190
|
+
);
|
|
99
191
|
return;
|
|
100
192
|
}
|
|
101
|
-
|
|
193
|
+
|
|
194
|
+
navStack = [
|
|
195
|
+
...navStack,
|
|
196
|
+
{ pageId: nextPageId, stepId: triggerAction.step_id },
|
|
197
|
+
];
|
|
102
198
|
}
|
|
103
199
|
|
|
104
200
|
function handleBackClicked() {
|
|
@@ -111,7 +207,7 @@
|
|
|
111
207
|
}
|
|
112
208
|
</script>
|
|
113
209
|
|
|
114
|
-
{#key
|
|
210
|
+
{#key current.pageId}
|
|
115
211
|
<Screen
|
|
116
212
|
paywallComponents={currentScreen}
|
|
117
213
|
{uiConfig}
|
|
@@ -124,7 +220,7 @@
|
|
|
124
220
|
{walletButtonRender}
|
|
125
221
|
{safeAreaFallbackColor}
|
|
126
222
|
{onPurchaseClicked}
|
|
127
|
-
{
|
|
223
|
+
onActionTriggered={handleActionTriggered}
|
|
128
224
|
{onInputChanged}
|
|
129
225
|
{onReservedAttributeChanged}
|
|
130
226
|
{onCompleteWorkflowNavigate}
|
|
@@ -132,7 +228,6 @@
|
|
|
132
228
|
{onRestorePurchasesClicked}
|
|
133
229
|
{onVisitCustomerCenterClicked}
|
|
134
230
|
{onComponentInteraction}
|
|
135
|
-
onNavigateToPage={handleNavigateToPage}
|
|
136
231
|
onBackClicked={handleBackClicked}
|
|
137
232
|
{onClose}
|
|
138
233
|
/>
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { WorkflowNavData } from "../../../types/workflow-nav";
|
|
2
2
|
/**
|
|
3
|
-
* Two-page workflow fixture
|
|
4
|
-
*
|
|
3
|
+
* Two-page workflow fixture. The "Next" button uses a "workflow" action, whose
|
|
4
|
+
* destination is resolved client-side via the step's trigger_actions map.
|
|
5
5
|
*
|
|
6
6
|
* Page layout:
|
|
7
|
-
* "rcpaywall_welcome" — welcome screen with a "Next →"
|
|
7
|
+
* "rcpaywall_welcome" — welcome screen with a "Next →" button
|
|
8
8
|
* "rcpaywall_details" — details screen with "← Back" and "✕ Close" buttons
|
|
9
|
-
*
|
|
10
|
-
* Page IDs match a paywall rc_public_id as they would in a real SDK response.
|
|
11
9
|
*/
|
|
12
10
|
export declare const TWO_PAGE_WORKFLOW: WorkflowNavData;
|
|
@@ -1,15 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Two-page workflow fixture
|
|
3
|
-
*
|
|
2
|
+
* Two-page workflow fixture. The "Next" button uses a "workflow" action, whose
|
|
3
|
+
* destination is resolved client-side via the step's trigger_actions map.
|
|
4
4
|
*
|
|
5
5
|
* Page layout:
|
|
6
|
-
* "rcpaywall_welcome" — welcome screen with a "Next →"
|
|
6
|
+
* "rcpaywall_welcome" — welcome screen with a "Next →" button
|
|
7
7
|
* "rcpaywall_details" — details screen with "← Back" and "✕ Close" buttons
|
|
8
|
-
*
|
|
9
|
-
* Page IDs match a paywall rc_public_id as they would in a real SDK response.
|
|
10
8
|
*/
|
|
11
9
|
export const TWO_PAGE_WORKFLOW = {
|
|
12
10
|
initial_page_id: "rcpaywall_welcome",
|
|
11
|
+
initial_step_id: "step_welcome",
|
|
12
|
+
steps: {
|
|
13
|
+
step_welcome: {
|
|
14
|
+
id: "step_welcome",
|
|
15
|
+
screen_id: "rcpaywall_welcome",
|
|
16
|
+
type: "screen",
|
|
17
|
+
param_values: {},
|
|
18
|
+
trigger_actions: {
|
|
19
|
+
// "next_btn" is the action_id referenced by the triggers array entry
|
|
20
|
+
// for the "next_btn" button component. It resolves to step_details,
|
|
21
|
+
// whose screen_id is rcpaywall_details.
|
|
22
|
+
next_btn: { type: "step", step_id: "step_details" },
|
|
23
|
+
},
|
|
24
|
+
triggers: [
|
|
25
|
+
{
|
|
26
|
+
action_id: "next_btn",
|
|
27
|
+
component_id: "next_btn",
|
|
28
|
+
type: "on_press",
|
|
29
|
+
name: "Next button",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
outputs: {},
|
|
33
|
+
metadata: null,
|
|
34
|
+
},
|
|
35
|
+
step_details: {
|
|
36
|
+
id: "step_details",
|
|
37
|
+
screen_id: "rcpaywall_details",
|
|
38
|
+
type: "screen",
|
|
39
|
+
param_values: {},
|
|
40
|
+
trigger_actions: {},
|
|
41
|
+
triggers: [],
|
|
42
|
+
outputs: {},
|
|
43
|
+
metadata: null,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
13
46
|
pages: {
|
|
14
47
|
rcpaywall_welcome: {
|
|
15
48
|
id: "rcpaywall_welcome",
|
|
@@ -58,7 +91,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
58
91
|
shadow: null,
|
|
59
92
|
shape: null,
|
|
60
93
|
components: [
|
|
61
|
-
// ── Title ────────────────────────────────────────────────
|
|
62
94
|
{
|
|
63
95
|
type: "text",
|
|
64
96
|
id: "welcome_title",
|
|
@@ -79,7 +111,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
79
111
|
dark: { type: "hex", value: "transparent" },
|
|
80
112
|
},
|
|
81
113
|
},
|
|
82
|
-
// ── Body ─────────────────────────────────────────────────
|
|
83
114
|
{
|
|
84
115
|
type: "text",
|
|
85
116
|
id: "welcome_body",
|
|
@@ -100,15 +131,11 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
100
131
|
dark: { type: "hex", value: "transparent" },
|
|
101
132
|
},
|
|
102
133
|
},
|
|
103
|
-
// ── navigate_to_page button ───────────────────────────
|
|
104
134
|
{
|
|
105
135
|
type: "button",
|
|
106
136
|
id: "next_btn",
|
|
107
137
|
name: "Next",
|
|
108
|
-
action: {
|
|
109
|
-
type: "navigate_to_page",
|
|
110
|
-
page_id: "rcpaywall_details",
|
|
111
|
-
},
|
|
138
|
+
action: { type: "workflow" },
|
|
112
139
|
stack: {
|
|
113
140
|
type: "stack",
|
|
114
141
|
id: "next_btn_stack",
|
|
@@ -121,7 +148,12 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
121
148
|
},
|
|
122
149
|
spacing: 0,
|
|
123
150
|
margin: { top: 0, bottom: 0, leading: 24, trailing: 24 },
|
|
124
|
-
padding: {
|
|
151
|
+
padding: {
|
|
152
|
+
top: 16,
|
|
153
|
+
bottom: 16,
|
|
154
|
+
leading: 24,
|
|
155
|
+
trailing: 24,
|
|
156
|
+
},
|
|
125
157
|
background: {
|
|
126
158
|
type: "color",
|
|
127
159
|
value: {
|
|
@@ -187,8 +219,8 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
187
219
|
background: {
|
|
188
220
|
type: "color",
|
|
189
221
|
value: {
|
|
190
|
-
light: { type: "hex", value: "#
|
|
191
|
-
dark: { type: "hex", value: "#
|
|
222
|
+
light: { type: "hex", value: "#f0fff4ff" },
|
|
223
|
+
dark: { type: "hex", value: "#1a2e1aff" },
|
|
192
224
|
},
|
|
193
225
|
},
|
|
194
226
|
stack: {
|
|
@@ -211,7 +243,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
211
243
|
shadow: null,
|
|
212
244
|
shape: null,
|
|
213
245
|
components: [
|
|
214
|
-
// ── Title ────────────────────────────────────────────────
|
|
215
246
|
{
|
|
216
247
|
type: "text",
|
|
217
248
|
id: "details_title",
|
|
@@ -224,15 +255,14 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
224
255
|
margin: { top: 80, bottom: 16, leading: 24, trailing: 24 },
|
|
225
256
|
padding: { top: 0, bottom: 0, leading: 0, trailing: 0 },
|
|
226
257
|
color: {
|
|
227
|
-
light: { type: "hex", value: "#
|
|
228
|
-
dark: { type: "hex", value: "#
|
|
258
|
+
light: { type: "hex", value: "#1a2e1aff" },
|
|
259
|
+
dark: { type: "hex", value: "#f0fff4ff" },
|
|
229
260
|
},
|
|
230
261
|
background_color: {
|
|
231
262
|
light: { type: "hex", value: "transparent" },
|
|
232
263
|
dark: { type: "hex", value: "transparent" },
|
|
233
264
|
},
|
|
234
265
|
},
|
|
235
|
-
// ── Body ─────────────────────────────────────────────────
|
|
236
266
|
{
|
|
237
267
|
type: "text",
|
|
238
268
|
id: "details_body",
|
|
@@ -245,15 +275,14 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
245
275
|
margin: { top: 0, bottom: 48, leading: 24, trailing: 24 },
|
|
246
276
|
padding: { top: 0, bottom: 0, leading: 0, trailing: 0 },
|
|
247
277
|
color: {
|
|
248
|
-
light: { type: "hex", value: "#
|
|
249
|
-
dark: { type: "hex", value: "#
|
|
278
|
+
light: { type: "hex", value: "#337733ff" },
|
|
279
|
+
dark: { type: "hex", value: "#88cc88ff" },
|
|
250
280
|
},
|
|
251
281
|
background_color: {
|
|
252
282
|
light: { type: "hex", value: "transparent" },
|
|
253
283
|
dark: { type: "hex", value: "transparent" },
|
|
254
284
|
},
|
|
255
285
|
},
|
|
256
|
-
// ── navigate_back button ──────────────────────────────
|
|
257
286
|
{
|
|
258
287
|
type: "button",
|
|
259
288
|
id: "back_btn",
|
|
@@ -271,7 +300,12 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
271
300
|
},
|
|
272
301
|
spacing: 0,
|
|
273
302
|
margin: { top: 0, bottom: 12, leading: 24, trailing: 24 },
|
|
274
|
-
padding: {
|
|
303
|
+
padding: {
|
|
304
|
+
top: 16,
|
|
305
|
+
bottom: 16,
|
|
306
|
+
leading: 24,
|
|
307
|
+
trailing: 24,
|
|
308
|
+
},
|
|
275
309
|
background: {
|
|
276
310
|
type: "color",
|
|
277
311
|
value: {
|
|
@@ -314,7 +348,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
314
348
|
],
|
|
315
349
|
},
|
|
316
350
|
},
|
|
317
|
-
// ── close button ──────────────────────────────────────
|
|
318
351
|
{
|
|
319
352
|
type: "button",
|
|
320
353
|
id: "close_btn",
|
|
@@ -332,7 +365,12 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
332
365
|
},
|
|
333
366
|
spacing: 0,
|
|
334
367
|
margin: { top: 0, bottom: 40, leading: 24, trailing: 24 },
|
|
335
|
-
padding: {
|
|
368
|
+
padding: {
|
|
369
|
+
top: 16,
|
|
370
|
+
bottom: 16,
|
|
371
|
+
leading: 24,
|
|
372
|
+
trailing: 24,
|
|
373
|
+
},
|
|
336
374
|
background: {
|
|
337
375
|
type: "color",
|
|
338
376
|
value: {
|
|
@@ -11,10 +11,6 @@ interface RestorePurchasesAction {
|
|
|
11
11
|
interface NavigateBackAction {
|
|
12
12
|
type: "navigate_back";
|
|
13
13
|
}
|
|
14
|
-
interface NavigateToPageAction {
|
|
15
|
-
type: "navigate_to_page";
|
|
16
|
-
page_id: string;
|
|
17
|
-
}
|
|
18
14
|
interface CloseAction {
|
|
19
15
|
type: "close_workflow";
|
|
20
16
|
}
|
|
@@ -60,15 +56,12 @@ interface CompleteWorkflowAction {
|
|
|
60
56
|
};
|
|
61
57
|
url_query_params?: CompleteWorkflowUrlQueryParams;
|
|
62
58
|
}
|
|
63
|
-
export type Action = WorkflowAction | RestorePurchasesAction | NavigateBackAction |
|
|
59
|
+
export type Action = WorkflowAction | RestorePurchasesAction | NavigateBackAction | CloseAction | NavigateToAction | NavigateToSheetAction | NavigateToWebPurchase | NavigateToUrlAction | CompleteWorkflowAction;
|
|
64
60
|
export interface ButtonProps extends BaseComponent {
|
|
65
61
|
type: "button";
|
|
66
62
|
action: Action;
|
|
67
63
|
stack: StackProps;
|
|
68
64
|
transition?: null;
|
|
69
|
-
triggers?: {
|
|
70
|
-
on_press?: string;
|
|
71
|
-
};
|
|
72
65
|
overrides?: Overrides<ButtonProps>;
|
|
73
66
|
}
|
|
74
67
|
export {};
|
|
@@ -16,7 +16,7 @@ interface PackageTransition {
|
|
|
16
16
|
destinationPackageId: string;
|
|
17
17
|
defaultPackageId?: string;
|
|
18
18
|
}
|
|
19
|
-
type ButtonInteractionValue = "workflow" | "navigate_to_url" | "navigate_back" | "
|
|
19
|
+
type ButtonInteractionValue = "workflow" | "navigate_to_url" | "navigate_back" | "close_workflow" | "restore_purchases" | "navigate_to_customer_center" | "screen_redirect" | "navigate_to_privacy_policy" | "navigate_to_terms" | "navigate_to_sheet" | "navigate_to_offer_code" | "navigate_to_web_paywall_link";
|
|
20
20
|
export type TabInteractionData = ComponentInteractionBase<"tab"> & IndexedTransition;
|
|
21
21
|
export type SwitchInteractionData = ComponentInteractionBase<"switch", "on" | "off">;
|
|
22
22
|
export type CarouselInteractionData = ComponentInteractionBase<"carousel"> & IndexedTransition;
|
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
import type { WorkflowData, WorkflowScreen } from "./workflow";
|
|
1
|
+
import type { WorkflowData, WorkflowScreen, WorkflowStep } from "./workflow";
|
|
2
2
|
/**
|
|
3
3
|
* A self-contained multi-page workflow payload for use with the Workflow
|
|
4
4
|
* component. This mirrors the shape of rc-workflows' WorkflowData but only
|
|
5
|
-
* includes the fields needed for client-side page navigation
|
|
6
|
-
* include step trigger_actions or experiment logic, which remain rc-workflows
|
|
7
|
-
* concerns.
|
|
5
|
+
* includes the fields needed for client-side page navigation.
|
|
8
6
|
*/
|
|
9
7
|
export interface WorkflowNavData {
|
|
10
8
|
/** ID of the first page to display. Must be a key in `pages`. */
|
|
11
9
|
initial_page_id: string;
|
|
12
10
|
/**
|
|
13
|
-
* All pages in this workflow keyed by
|
|
14
|
-
*
|
|
11
|
+
* All pages in this workflow keyed by screen ID.
|
|
12
|
+
* Workflow step actions resolve to these IDs via `steps[stepId].screen_id`.
|
|
15
13
|
*/
|
|
16
14
|
pages: Record<string, WorkflowScreen>;
|
|
15
|
+
/**
|
|
16
|
+
* All steps in this workflow keyed by step ID.
|
|
17
|
+
* Used to resolve `workflow`-type button actions: the button's component ID
|
|
18
|
+
* is looked up in the current step's `triggers` array to get an action_id,
|
|
19
|
+
* which maps to a `trigger_actions` entry pointing to the next step_id,
|
|
20
|
+
* whose screen_id is then used to navigate to the correct page.
|
|
21
|
+
*/
|
|
22
|
+
steps: Record<string, WorkflowStep>;
|
|
23
|
+
/** The step ID of the first step, corresponding to `initial_page_id`. */
|
|
24
|
+
initial_step_id: string;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Map a full `WorkflowData` SDK response to the `WorkflowNavData` shape
|
|
20
28
|
* consumed by the `Workflow` component.
|
|
21
29
|
*
|
|
22
|
-
* - `pages` is taken directly from `WorkflowData.screens
|
|
23
|
-
* screen/paywall rc_public_id, which is what `navigate_to_page` page_ids
|
|
24
|
-
* reference).
|
|
30
|
+
* - `pages` is taken directly from `WorkflowData.screens`, keyed by screen ID.
|
|
25
31
|
* - `initial_page_id` is resolved by looking up the screen_id of the initial
|
|
26
32
|
* step, since `initial_step_id` is a step ID, not a screen ID.
|
|
27
33
|
*
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* Map a full `WorkflowData` SDK response to the `WorkflowNavData` shape
|
|
3
3
|
* consumed by the `Workflow` component.
|
|
4
4
|
*
|
|
5
|
-
* - `pages` is taken directly from `WorkflowData.screens
|
|
6
|
-
* screen/paywall rc_public_id, which is what `navigate_to_page` page_ids
|
|
7
|
-
* reference).
|
|
5
|
+
* - `pages` is taken directly from `WorkflowData.screens`, keyed by screen ID.
|
|
8
6
|
* - `initial_page_id` is resolved by looking up the screen_id of the initial
|
|
9
7
|
* step, since `initial_step_id` is a step ID, not a screen ID.
|
|
10
8
|
*
|
|
@@ -27,8 +25,10 @@ export function workflowDataToNavData(data) {
|
|
|
27
25
|
}
|
|
28
26
|
return {
|
|
29
27
|
initial_page_id: initialPageId,
|
|
30
|
-
|
|
28
|
+
initial_step_id: data.initial_step_id,
|
|
29
|
+
// Shared by reference intentionally — screens/steps are never mutated after
|
|
31
30
|
// WorkflowData is received from the SDK, so a copy would be wasteful.
|
|
32
31
|
pages: data.screens,
|
|
32
|
+
steps: data.steps,
|
|
33
33
|
};
|
|
34
34
|
}
|
package/dist/types/workflow.d.ts
CHANGED
|
@@ -13,6 +13,27 @@ export interface WorkflowScreen extends PaywallData {
|
|
|
13
13
|
revision: number;
|
|
14
14
|
template_name: string;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* A trigger action that navigates directly to another step.
|
|
18
|
+
*/
|
|
19
|
+
export interface WorkflowStepTriggerAction {
|
|
20
|
+
type: "step";
|
|
21
|
+
step_id: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* An entry in a step's `triggers` array that maps a component interaction
|
|
25
|
+
* (e.g. button press) to a named action_id, which in turn maps to a
|
|
26
|
+
* `trigger_actions` entry.
|
|
27
|
+
*/
|
|
28
|
+
export interface WorkflowStepTrigger {
|
|
29
|
+
/** The action ID to look up in `trigger_actions`. */
|
|
30
|
+
action_id: string;
|
|
31
|
+
/** The component (e.g. button) id that fires this trigger. */
|
|
32
|
+
component_id: string;
|
|
33
|
+
/** The interaction type, e.g. "on_press". */
|
|
34
|
+
type: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
}
|
|
16
37
|
/**
|
|
17
38
|
* A single step in a workflow, referencing a screen by ID.
|
|
18
39
|
* Aligns with the SDK response shape from khepri.
|
|
@@ -22,8 +43,19 @@ export interface WorkflowStep {
|
|
|
22
43
|
screen_id?: string;
|
|
23
44
|
type: string;
|
|
24
45
|
param_values: Record<string, unknown>;
|
|
25
|
-
|
|
26
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Maps action IDs (from `triggers[].action_id`) to the next step to navigate
|
|
48
|
+
* to. Currently only `type: "step"` is supported for client-side resolution;
|
|
49
|
+
* condition-based actions are ignored.
|
|
50
|
+
*/
|
|
51
|
+
trigger_actions: Record<string, WorkflowStepTriggerAction | Record<string, unknown>>;
|
|
52
|
+
/**
|
|
53
|
+
* Array of trigger entries mapping component interactions to action IDs.
|
|
54
|
+
* Resolve a button press by matching its component `id` against
|
|
55
|
+
* `component_id`, then use the entry's `action_id` to look up
|
|
56
|
+
* `trigger_actions`.
|
|
57
|
+
*/
|
|
58
|
+
triggers: WorkflowStepTrigger[];
|
|
27
59
|
outputs: Record<string, unknown>;
|
|
28
60
|
metadata: Record<string, unknown> | null;
|
|
29
61
|
}
|