@revenuecat/purchases-ui-js 4.7.6 → 4.8.1
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/carousel/Carousel.stories.svelte +1 -1
- package/dist/components/carousel/Carousel.svelte +3 -0
- 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 +202 -16
- package/dist/components/workflows/Workflow.svelte.d.ts +7 -1
- package/dist/components/workflows/fixtures/two-page-workflow.d.ts +3 -5
- package/dist/components/workflows/fixtures/two-page-workflow.js +64 -25
- package/dist/index.d.ts +1 -1
- 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 +32 -9
- package/dist/types/workflow-nav.js +5 -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":
|
|
@@ -355,6 +355,7 @@
|
|
|
355
355
|
display: flex;
|
|
356
356
|
flex-direction: column;
|
|
357
357
|
flex-shrink: 0;
|
|
358
|
+
align-self: stretch;
|
|
358
359
|
}
|
|
359
360
|
|
|
360
361
|
.carousel-clip {
|
|
@@ -401,6 +402,8 @@
|
|
|
401
402
|
transition-delay: 2500ms;
|
|
402
403
|
transform: translateX(0);
|
|
403
404
|
|
|
405
|
+
touch-action: pan-y;
|
|
406
|
+
|
|
404
407
|
cursor: grab;
|
|
405
408
|
|
|
406
409
|
&:active {
|
|
@@ -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;
|
|
@@ -8,7 +8,25 @@
|
|
|
8
8
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
9
9
|
import type { UIConfig } from "../../types/ui-config";
|
|
10
10
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
WorkflowNavData,
|
|
13
|
+
WorkflowStepChangeEvent,
|
|
14
|
+
} from "../../types/workflow-nav";
|
|
15
|
+
import type {
|
|
16
|
+
WorkflowStepTriggerAction,
|
|
17
|
+
WorkflowStep,
|
|
18
|
+
} from "../../types/workflow";
|
|
19
|
+
|
|
20
|
+
type TriggerActionValue = WorkflowStep["trigger_actions"][string];
|
|
21
|
+
|
|
22
|
+
function isStepTriggerAction(
|
|
23
|
+
a: TriggerActionValue,
|
|
24
|
+
): a is WorkflowStepTriggerAction {
|
|
25
|
+
return (
|
|
26
|
+
a.type === "step" &&
|
|
27
|
+
typeof (a as WorkflowStepTriggerAction).step_id === "string"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
12
30
|
|
|
13
31
|
interface Props {
|
|
14
32
|
workflow: WorkflowNavData;
|
|
@@ -44,6 +62,12 @@
|
|
|
44
62
|
onComponentInteraction?: OnComponentInteraction;
|
|
45
63
|
onClose?: () => void;
|
|
46
64
|
onExitBack?: () => void;
|
|
65
|
+
/**
|
|
66
|
+
* Called whenever the current workflow step changes — on initial load,
|
|
67
|
+
* forward navigation, back navigation, and when leaving a step (close,
|
|
68
|
+
* purchase, error). Used by the host to fire workflow lifecycle events.
|
|
69
|
+
*/
|
|
70
|
+
onStepChanged?: (event: WorkflowStepChangeEvent) => void;
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
const {
|
|
@@ -68,50 +92,203 @@
|
|
|
68
92
|
onComponentInteraction,
|
|
69
93
|
onClose,
|
|
70
94
|
onExitBack,
|
|
95
|
+
onStepChanged,
|
|
71
96
|
}: Props = $props();
|
|
72
97
|
|
|
73
98
|
// ── Nav stack ─────────────────────────────────────────────────────────────
|
|
74
|
-
//
|
|
75
|
-
//
|
|
99
|
+
// Each entry pairs the screen ID (used to look up the page to render) with
|
|
100
|
+
// the step ID (used to resolve trigger_actions on button press). The current
|
|
101
|
+
// page is always the last entry; workflow actions push, navigate_back pops.
|
|
76
102
|
//
|
|
77
103
|
// We key the reset off workflow.initial_page_id (a stable string) rather
|
|
78
104
|
// than workflow object identity, so hosts that recompute the prop object on
|
|
79
105
|
// unrelated reactive updates don't accidentally snap the user back to the
|
|
80
106
|
// first page mid-flow.
|
|
81
|
-
|
|
107
|
+
interface NavEntry {
|
|
108
|
+
pageId: string;
|
|
109
|
+
stepId: string;
|
|
110
|
+
}
|
|
111
|
+
let navStack = $state<NavEntry[]>([]);
|
|
82
112
|
let trackedInitialPageId = $state<string>();
|
|
83
113
|
|
|
84
|
-
const
|
|
85
|
-
navStack[navStack.length - 1] ??
|
|
114
|
+
const current = $derived(
|
|
115
|
+
navStack[navStack.length - 1] ?? {
|
|
116
|
+
pageId: workflow.initial_page_id,
|
|
117
|
+
stepId: workflow.initial_step_id,
|
|
118
|
+
},
|
|
86
119
|
);
|
|
87
|
-
const currentScreen = $derived(workflow.pages[
|
|
120
|
+
const currentScreen = $derived(workflow.pages[current.pageId]);
|
|
88
121
|
|
|
89
122
|
$effect.pre(() => {
|
|
90
123
|
if (trackedInitialPageId !== workflow.initial_page_id) {
|
|
91
124
|
trackedInitialPageId = workflow.initial_page_id;
|
|
92
|
-
navStack = [
|
|
125
|
+
navStack = [
|
|
126
|
+
{
|
|
127
|
+
pageId: workflow.initial_page_id,
|
|
128
|
+
stepId: workflow.initial_step_id,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
emitStepChange({
|
|
132
|
+
workflowId: workflow.workflowId,
|
|
133
|
+
stepId: workflow.initial_step_id,
|
|
134
|
+
fromStepId: undefined,
|
|
135
|
+
toStepId: undefined,
|
|
136
|
+
reason: "start",
|
|
137
|
+
...stepFlags(workflow.initial_step_id),
|
|
138
|
+
});
|
|
93
139
|
}
|
|
94
140
|
});
|
|
95
141
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
142
|
+
function stepFlags(stepId: string): {
|
|
143
|
+
isFirstStep: boolean;
|
|
144
|
+
isLastStep: boolean;
|
|
145
|
+
} {
|
|
146
|
+
const step = workflow.steps[stepId];
|
|
147
|
+
return {
|
|
148
|
+
isFirstStep: stepId === workflow.initial_step_id,
|
|
149
|
+
// A step is "last" when it has no step-type trigger actions — i.e. no
|
|
150
|
+
// client-resolvable forward navigation. Condition-based actions fall back
|
|
151
|
+
// to onActionTriggered and are not counted here.
|
|
152
|
+
isLastStep:
|
|
153
|
+
step !== undefined &&
|
|
154
|
+
!Object.values(step.trigger_actions).some((a) =>
|
|
155
|
+
isStepTriggerAction(a),
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function emitStepChange(event: WorkflowStepChangeEvent) {
|
|
161
|
+
onStepChanged?.(event);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Resolves a "workflow"-type button action and navigates to the next screen.
|
|
166
|
+
*
|
|
167
|
+
* Flow: component ID → step.triggers[].action_id → trigger_actions[action_id]
|
|
168
|
+
* → step_id → step.screen_id → push onto nav stack.
|
|
169
|
+
*/
|
|
170
|
+
function handleActionTriggered(actionId: string) {
|
|
171
|
+
const currentStep = workflow.steps[current.stepId];
|
|
172
|
+
if (!currentStep) {
|
|
173
|
+
console.warn(
|
|
174
|
+
`[Workflow] onActionTriggered: no current step found for step_id "${current.stepId}"`,
|
|
175
|
+
);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Resolve the component ID to an action ID via the step's triggers array.
|
|
180
|
+
const resolvedActionId = currentStep.triggers.find(
|
|
181
|
+
(t) => t.component_id === actionId,
|
|
182
|
+
)?.action_id;
|
|
183
|
+
|
|
184
|
+
if (!resolvedActionId) {
|
|
185
|
+
console.warn(
|
|
186
|
+
`[Workflow] onActionTriggered: no trigger entry found for component "${actionId}" on step "${current.stepId}"`,
|
|
187
|
+
);
|
|
188
|
+
onActionTriggered?.(actionId);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const triggerAction = currentStep.trigger_actions[resolvedActionId];
|
|
193
|
+
if (!triggerAction) {
|
|
194
|
+
console.warn(
|
|
195
|
+
`[Workflow] onActionTriggered: no trigger_action found for action_id "${resolvedActionId}" on step "${current.stepId}"`,
|
|
196
|
+
);
|
|
197
|
+
onActionTriggered?.(actionId);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!isStepTriggerAction(triggerAction)) {
|
|
202
|
+
// Condition-based actions require a full workflow engine — fall back to
|
|
203
|
+
// the host's onActionTriggered for anything we can't resolve client-side.
|
|
204
|
+
console.warn(
|
|
205
|
+
`[Workflow] onActionTriggered: trigger_action is not a simple step action, falling back to onActionTriggered`,
|
|
206
|
+
);
|
|
207
|
+
onActionTriggered?.(actionId);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const nextStep = workflow.steps[triggerAction.step_id];
|
|
212
|
+
if (!nextStep) {
|
|
213
|
+
console.warn(
|
|
214
|
+
`[Workflow] onActionTriggered: step_id "${triggerAction.step_id}" not found in workflow steps`,
|
|
215
|
+
);
|
|
99
216
|
return;
|
|
100
217
|
}
|
|
101
|
-
|
|
218
|
+
|
|
219
|
+
const nextPageId = nextStep.screen_id;
|
|
220
|
+
if (!nextPageId) {
|
|
221
|
+
console.warn(
|
|
222
|
+
`[Workflow] onActionTriggered: step "${triggerAction.step_id}" has no screen_id (logic/experiment step?)`,
|
|
223
|
+
);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!workflow.pages[nextPageId]) {
|
|
228
|
+
console.warn(
|
|
229
|
+
`[Workflow] onActionTriggered: screen_id "${nextPageId}" not found in workflow pages`,
|
|
230
|
+
);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const fromStepId = current.stepId;
|
|
235
|
+
const toStepId = triggerAction.step_id;
|
|
236
|
+
emitStepChange({
|
|
237
|
+
workflowId: workflow.workflowId,
|
|
238
|
+
stepId: fromStepId,
|
|
239
|
+
fromStepId: undefined,
|
|
240
|
+
toStepId,
|
|
241
|
+
reason: "completed",
|
|
242
|
+
...stepFlags(fromStepId),
|
|
243
|
+
});
|
|
244
|
+
navStack = [...navStack, { pageId: nextPageId, stepId: toStepId }];
|
|
245
|
+
emitStepChange({
|
|
246
|
+
workflowId: workflow.workflowId,
|
|
247
|
+
stepId: toStepId,
|
|
248
|
+
fromStepId,
|
|
249
|
+
toStepId: undefined,
|
|
250
|
+
reason: "forward",
|
|
251
|
+
...stepFlags(toStepId),
|
|
252
|
+
});
|
|
102
253
|
}
|
|
103
254
|
|
|
104
255
|
function handleBackClicked() {
|
|
105
256
|
if (navStack.length > 1) {
|
|
257
|
+
const fromStepId = current.stepId;
|
|
258
|
+
const toStepId = navStack[navStack.length - 2].stepId;
|
|
259
|
+
emitStepChange({
|
|
260
|
+
workflowId: workflow.workflowId,
|
|
261
|
+
stepId: fromStepId,
|
|
262
|
+
fromStepId: undefined,
|
|
263
|
+
toStepId,
|
|
264
|
+
reason: "completed",
|
|
265
|
+
...stepFlags(fromStepId),
|
|
266
|
+
});
|
|
106
267
|
navStack = navStack.slice(0, -1);
|
|
268
|
+
emitStepChange({
|
|
269
|
+
workflowId: workflow.workflowId,
|
|
270
|
+
stepId: toStepId,
|
|
271
|
+
fromStepId,
|
|
272
|
+
toStepId: undefined,
|
|
273
|
+
reason: "back",
|
|
274
|
+
...stepFlags(toStepId),
|
|
275
|
+
});
|
|
107
276
|
} else {
|
|
108
277
|
// Already at root — bubble up to the host.
|
|
278
|
+
emitStepChange({
|
|
279
|
+
workflowId: workflow.workflowId,
|
|
280
|
+
stepId: current.stepId,
|
|
281
|
+
fromStepId: undefined,
|
|
282
|
+
toStepId: undefined,
|
|
283
|
+
reason: "completed",
|
|
284
|
+
...stepFlags(current.stepId),
|
|
285
|
+
});
|
|
109
286
|
onExitBack?.();
|
|
110
287
|
}
|
|
111
288
|
}
|
|
112
289
|
</script>
|
|
113
290
|
|
|
114
|
-
{#key
|
|
291
|
+
{#key current.pageId}
|
|
115
292
|
<Screen
|
|
116
293
|
paywallComponents={currentScreen}
|
|
117
294
|
{uiConfig}
|
|
@@ -124,7 +301,7 @@
|
|
|
124
301
|
{walletButtonRender}
|
|
125
302
|
{safeAreaFallbackColor}
|
|
126
303
|
{onPurchaseClicked}
|
|
127
|
-
{
|
|
304
|
+
onActionTriggered={handleActionTriggered}
|
|
128
305
|
{onInputChanged}
|
|
129
306
|
{onReservedAttributeChanged}
|
|
130
307
|
{onCompleteWorkflowNavigate}
|
|
@@ -132,8 +309,17 @@
|
|
|
132
309
|
{onRestorePurchasesClicked}
|
|
133
310
|
{onVisitCustomerCenterClicked}
|
|
134
311
|
{onComponentInteraction}
|
|
135
|
-
onNavigateToPage={handleNavigateToPage}
|
|
136
312
|
onBackClicked={handleBackClicked}
|
|
137
|
-
{
|
|
313
|
+
onClose={() => {
|
|
314
|
+
emitStepChange({
|
|
315
|
+
workflowId: workflow.workflowId,
|
|
316
|
+
stepId: current.stepId,
|
|
317
|
+
fromStepId: undefined,
|
|
318
|
+
toStepId: undefined,
|
|
319
|
+
reason: "completed",
|
|
320
|
+
...stepFlags(current.stepId),
|
|
321
|
+
});
|
|
322
|
+
onClose?.();
|
|
323
|
+
}}
|
|
138
324
|
/>
|
|
139
325
|
{/key}
|
|
@@ -6,7 +6,7 @@ import type { VariableDictionary } from "../../types/variables";
|
|
|
6
6
|
import type { WalletButtonRender } from "../../types/wallet";
|
|
7
7
|
import type { UIConfig } from "../../types/ui-config";
|
|
8
8
|
import type { ReservedAttribute } from "../../types/components/input-text";
|
|
9
|
-
import type { WorkflowNavData } from "../../types/workflow-nav";
|
|
9
|
+
import type { WorkflowNavData, WorkflowStepChangeEvent } from "../../types/workflow-nav";
|
|
10
10
|
interface Props {
|
|
11
11
|
workflow: WorkflowNavData;
|
|
12
12
|
uiConfig: UIConfig;
|
|
@@ -29,6 +29,12 @@ interface Props {
|
|
|
29
29
|
onComponentInteraction?: OnComponentInteraction;
|
|
30
30
|
onClose?: () => void;
|
|
31
31
|
onExitBack?: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Called whenever the current workflow step changes — on initial load,
|
|
34
|
+
* forward navigation, back navigation, and when leaving a step (close,
|
|
35
|
+
* purchase, error). Used by the host to fire workflow lifecycle events.
|
|
36
|
+
*/
|
|
37
|
+
onStepChanged?: (event: WorkflowStepChangeEvent) => void;
|
|
32
38
|
}
|
|
33
39
|
declare const Workflow: import("svelte").Component<Props, {}, "">;
|
|
34
40
|
type Workflow = ReturnType<typeof Workflow>;
|
|
@@ -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,49 @@
|
|
|
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 = {
|
|
10
|
+
workflowId: "wf_two_page_fixture",
|
|
12
11
|
initial_page_id: "rcpaywall_welcome",
|
|
12
|
+
initial_step_id: "step_welcome",
|
|
13
|
+
steps: {
|
|
14
|
+
step_welcome: {
|
|
15
|
+
id: "step_welcome",
|
|
16
|
+
screen_id: "rcpaywall_welcome",
|
|
17
|
+
type: "screen",
|
|
18
|
+
param_values: {},
|
|
19
|
+
trigger_actions: {
|
|
20
|
+
// "next_btn" is the action_id referenced by the triggers array entry
|
|
21
|
+
// for the "next_btn" button component. It resolves to step_details,
|
|
22
|
+
// whose screen_id is rcpaywall_details.
|
|
23
|
+
next_btn: { type: "step", step_id: "step_details" },
|
|
24
|
+
},
|
|
25
|
+
triggers: [
|
|
26
|
+
{
|
|
27
|
+
action_id: "next_btn",
|
|
28
|
+
component_id: "next_btn",
|
|
29
|
+
type: "on_press",
|
|
30
|
+
name: "Next button",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
outputs: {},
|
|
34
|
+
metadata: null,
|
|
35
|
+
},
|
|
36
|
+
step_details: {
|
|
37
|
+
id: "step_details",
|
|
38
|
+
screen_id: "rcpaywall_details",
|
|
39
|
+
type: "screen",
|
|
40
|
+
param_values: {},
|
|
41
|
+
trigger_actions: {},
|
|
42
|
+
triggers: [],
|
|
43
|
+
outputs: {},
|
|
44
|
+
metadata: null,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
13
47
|
pages: {
|
|
14
48
|
rcpaywall_welcome: {
|
|
15
49
|
id: "rcpaywall_welcome",
|
|
@@ -58,7 +92,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
58
92
|
shadow: null,
|
|
59
93
|
shape: null,
|
|
60
94
|
components: [
|
|
61
|
-
// ── Title ────────────────────────────────────────────────
|
|
62
95
|
{
|
|
63
96
|
type: "text",
|
|
64
97
|
id: "welcome_title",
|
|
@@ -79,7 +112,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
79
112
|
dark: { type: "hex", value: "transparent" },
|
|
80
113
|
},
|
|
81
114
|
},
|
|
82
|
-
// ── Body ─────────────────────────────────────────────────
|
|
83
115
|
{
|
|
84
116
|
type: "text",
|
|
85
117
|
id: "welcome_body",
|
|
@@ -100,15 +132,11 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
100
132
|
dark: { type: "hex", value: "transparent" },
|
|
101
133
|
},
|
|
102
134
|
},
|
|
103
|
-
// ── navigate_to_page button ───────────────────────────
|
|
104
135
|
{
|
|
105
136
|
type: "button",
|
|
106
137
|
id: "next_btn",
|
|
107
138
|
name: "Next",
|
|
108
|
-
action: {
|
|
109
|
-
type: "navigate_to_page",
|
|
110
|
-
page_id: "rcpaywall_details",
|
|
111
|
-
},
|
|
139
|
+
action: { type: "workflow" },
|
|
112
140
|
stack: {
|
|
113
141
|
type: "stack",
|
|
114
142
|
id: "next_btn_stack",
|
|
@@ -121,7 +149,12 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
121
149
|
},
|
|
122
150
|
spacing: 0,
|
|
123
151
|
margin: { top: 0, bottom: 0, leading: 24, trailing: 24 },
|
|
124
|
-
padding: {
|
|
152
|
+
padding: {
|
|
153
|
+
top: 16,
|
|
154
|
+
bottom: 16,
|
|
155
|
+
leading: 24,
|
|
156
|
+
trailing: 24,
|
|
157
|
+
},
|
|
125
158
|
background: {
|
|
126
159
|
type: "color",
|
|
127
160
|
value: {
|
|
@@ -187,8 +220,8 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
187
220
|
background: {
|
|
188
221
|
type: "color",
|
|
189
222
|
value: {
|
|
190
|
-
light: { type: "hex", value: "#
|
|
191
|
-
dark: { type: "hex", value: "#
|
|
223
|
+
light: { type: "hex", value: "#f0fff4ff" },
|
|
224
|
+
dark: { type: "hex", value: "#1a2e1aff" },
|
|
192
225
|
},
|
|
193
226
|
},
|
|
194
227
|
stack: {
|
|
@@ -211,7 +244,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
211
244
|
shadow: null,
|
|
212
245
|
shape: null,
|
|
213
246
|
components: [
|
|
214
|
-
// ── Title ────────────────────────────────────────────────
|
|
215
247
|
{
|
|
216
248
|
type: "text",
|
|
217
249
|
id: "details_title",
|
|
@@ -224,15 +256,14 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
224
256
|
margin: { top: 80, bottom: 16, leading: 24, trailing: 24 },
|
|
225
257
|
padding: { top: 0, bottom: 0, leading: 0, trailing: 0 },
|
|
226
258
|
color: {
|
|
227
|
-
light: { type: "hex", value: "#
|
|
228
|
-
dark: { type: "hex", value: "#
|
|
259
|
+
light: { type: "hex", value: "#1a2e1aff" },
|
|
260
|
+
dark: { type: "hex", value: "#f0fff4ff" },
|
|
229
261
|
},
|
|
230
262
|
background_color: {
|
|
231
263
|
light: { type: "hex", value: "transparent" },
|
|
232
264
|
dark: { type: "hex", value: "transparent" },
|
|
233
265
|
},
|
|
234
266
|
},
|
|
235
|
-
// ── Body ─────────────────────────────────────────────────
|
|
236
267
|
{
|
|
237
268
|
type: "text",
|
|
238
269
|
id: "details_body",
|
|
@@ -245,15 +276,14 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
245
276
|
margin: { top: 0, bottom: 48, leading: 24, trailing: 24 },
|
|
246
277
|
padding: { top: 0, bottom: 0, leading: 0, trailing: 0 },
|
|
247
278
|
color: {
|
|
248
|
-
light: { type: "hex", value: "#
|
|
249
|
-
dark: { type: "hex", value: "#
|
|
279
|
+
light: { type: "hex", value: "#337733ff" },
|
|
280
|
+
dark: { type: "hex", value: "#88cc88ff" },
|
|
250
281
|
},
|
|
251
282
|
background_color: {
|
|
252
283
|
light: { type: "hex", value: "transparent" },
|
|
253
284
|
dark: { type: "hex", value: "transparent" },
|
|
254
285
|
},
|
|
255
286
|
},
|
|
256
|
-
// ── navigate_back button ──────────────────────────────
|
|
257
287
|
{
|
|
258
288
|
type: "button",
|
|
259
289
|
id: "back_btn",
|
|
@@ -271,7 +301,12 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
271
301
|
},
|
|
272
302
|
spacing: 0,
|
|
273
303
|
margin: { top: 0, bottom: 12, leading: 24, trailing: 24 },
|
|
274
|
-
padding: {
|
|
304
|
+
padding: {
|
|
305
|
+
top: 16,
|
|
306
|
+
bottom: 16,
|
|
307
|
+
leading: 24,
|
|
308
|
+
trailing: 24,
|
|
309
|
+
},
|
|
275
310
|
background: {
|
|
276
311
|
type: "color",
|
|
277
312
|
value: {
|
|
@@ -314,7 +349,6 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
314
349
|
],
|
|
315
350
|
},
|
|
316
351
|
},
|
|
317
|
-
// ── close button ──────────────────────────────────────
|
|
318
352
|
{
|
|
319
353
|
type: "button",
|
|
320
354
|
id: "close_btn",
|
|
@@ -332,7 +366,12 @@ export const TWO_PAGE_WORKFLOW = {
|
|
|
332
366
|
},
|
|
333
367
|
spacing: 0,
|
|
334
368
|
margin: { top: 0, bottom: 40, leading: 24, trailing: 24 },
|
|
335
|
-
padding: {
|
|
369
|
+
padding: {
|
|
370
|
+
top: 16,
|
|
371
|
+
bottom: 16,
|
|
372
|
+
leading: 24,
|
|
373
|
+
trailing: 24,
|
|
374
|
+
},
|
|
336
375
|
background: {
|
|
337
376
|
type: "color",
|
|
338
377
|
value: {
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { default as Workflow } from "./components/workflows/Workflow.svelte";
|
|
|
17
17
|
export { default as Video } from "./components/video/Video.svelte";
|
|
18
18
|
export * from "./types";
|
|
19
19
|
export { type PaywallData } from "./types/paywall";
|
|
20
|
-
export { type WorkflowNavData, workflowDataToNavData, } from "./types/workflow-nav";
|
|
20
|
+
export { type WorkflowNavData, type WorkflowStepChangeEvent, workflowDataToNavData, } from "./types/workflow-nav";
|
|
21
21
|
export { type WorkflowData, type WorkflowStep } from "./types/workflow";
|
|
22
22
|
export { type InitialInputSelections } from "./stores/inputValidation";
|
|
23
23
|
export { type UIConfig } from "./types/ui-config";
|
|
@@ -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,50 @@
|
|
|
1
|
-
import type { WorkflowData, WorkflowScreen } from "./workflow";
|
|
1
|
+
import type { WorkflowData, WorkflowScreen, WorkflowStep } from "./workflow";
|
|
2
|
+
export interface WorkflowStepChangeEvent {
|
|
3
|
+
workflowId: string;
|
|
4
|
+
stepId: string;
|
|
5
|
+
fromStepId: string | undefined;
|
|
6
|
+
toStepId: string | undefined;
|
|
7
|
+
isFirstStep: boolean;
|
|
8
|
+
isLastStep: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Why this event was emitted:
|
|
11
|
+
* - `"start"` / `"forward"` / `"back"` — step is being entered (fire step_started)
|
|
12
|
+
* - `"completed"` — step is being left (fire step_completed); toStepId is set
|
|
13
|
+
* for forward nav, undefined when the workflow is dismissed/closed.
|
|
14
|
+
*/
|
|
15
|
+
reason: "start" | "forward" | "back" | "completed";
|
|
16
|
+
}
|
|
2
17
|
/**
|
|
3
18
|
* A self-contained multi-page workflow payload for use with the Workflow
|
|
4
19
|
* 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.
|
|
20
|
+
* includes the fields needed for client-side page navigation.
|
|
8
21
|
*/
|
|
9
22
|
export interface WorkflowNavData {
|
|
23
|
+
/** The workflow's unique identifier. */
|
|
24
|
+
workflowId: string;
|
|
10
25
|
/** ID of the first page to display. Must be a key in `pages`. */
|
|
11
26
|
initial_page_id: string;
|
|
12
27
|
/**
|
|
13
|
-
* All pages in this workflow keyed by
|
|
14
|
-
*
|
|
28
|
+
* All pages in this workflow keyed by screen ID.
|
|
29
|
+
* Workflow step actions resolve to these IDs via `steps[stepId].screen_id`.
|
|
15
30
|
*/
|
|
16
31
|
pages: Record<string, WorkflowScreen>;
|
|
32
|
+
/**
|
|
33
|
+
* All steps in this workflow keyed by step ID.
|
|
34
|
+
* Used to resolve `workflow`-type button actions: the button's component ID
|
|
35
|
+
* is looked up in the current step's `triggers` array to get an action_id,
|
|
36
|
+
* which maps to a `trigger_actions` entry pointing to the next step_id,
|
|
37
|
+
* whose screen_id is then used to navigate to the correct page.
|
|
38
|
+
*/
|
|
39
|
+
steps: Record<string, WorkflowStep>;
|
|
40
|
+
/** The step ID of the first step, corresponding to `initial_page_id`. */
|
|
41
|
+
initial_step_id: string;
|
|
17
42
|
}
|
|
18
43
|
/**
|
|
19
44
|
* Map a full `WorkflowData` SDK response to the `WorkflowNavData` shape
|
|
20
45
|
* consumed by the `Workflow` component.
|
|
21
46
|
*
|
|
22
|
-
* - `pages` is taken directly from `WorkflowData.screens
|
|
23
|
-
* screen/paywall rc_public_id, which is what `navigate_to_page` page_ids
|
|
24
|
-
* reference).
|
|
47
|
+
* - `pages` is taken directly from `WorkflowData.screens`, keyed by screen ID.
|
|
25
48
|
* - `initial_page_id` is resolved by looking up the screen_id of the initial
|
|
26
49
|
* step, since `initial_step_id` is a step ID, not a screen ID.
|
|
27
50
|
*
|
|
@@ -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
|
*
|
|
@@ -26,9 +24,12 @@ export function workflowDataToNavData(data) {
|
|
|
26
24
|
return null;
|
|
27
25
|
}
|
|
28
26
|
return {
|
|
27
|
+
workflowId: data.id,
|
|
29
28
|
initial_page_id: initialPageId,
|
|
30
|
-
|
|
29
|
+
initial_step_id: data.initial_step_id,
|
|
30
|
+
// Shared by reference intentionally — screens/steps are never mutated after
|
|
31
31
|
// WorkflowData is received from the SDK, so a copy would be wasteful.
|
|
32
32
|
pages: data.screens,
|
|
33
|
+
steps: data.steps,
|
|
33
34
|
};
|
|
34
35
|
}
|
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
|
}
|