@rocapine/react-native-onboarding-ui 1.36.1 → 1.37.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/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts +26 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js +2 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +106 -67
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js +11 -59
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.js +8 -2
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.js +4 -0
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/KeyboardAvoidingViewElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/KeyboardAvoidingViewElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ProgressiveBlurImageElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/ProgressiveBlurImageElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.js +8 -2
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ScrollViewElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/ScrollViewElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts +25 -1
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/WheelPickerElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/WheelPickerElement.js +3 -1
- package/dist/UI/Pages/ComposableScreen/elements/WheelPickerElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/actions.d.ts +72 -0
- package/dist/UI/Pages/ComposableScreen/elements/actions.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/actions.js +24 -0
- package/dist/UI/Pages/ComposableScreen/elements/actions.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +47 -4
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/runActions.d.ts +4 -0
- package/dist/UI/Pages/ComposableScreen/elements/runActions.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/runActions.js +98 -0
- package/dist/UI/Pages/ComposableScreen/elements/runActions.js.map +1 -0
- package/package.json +1 -1
- package/src/UI/Pages/ComposableScreen/elements/BaseBoxProps.ts +3 -0
- package/src/UI/Pages/ComposableScreen/elements/ButtonElement.tsx +15 -91
- package/src/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.tsx +13 -3
- package/src/UI/Pages/ComposableScreen/elements/DatePickerElement.tsx +4 -0
- package/src/UI/Pages/ComposableScreen/elements/RadioGroupElement.tsx +13 -3
- package/src/UI/Pages/ComposableScreen/elements/WheelPickerElement.tsx +8 -3
- package/src/UI/Pages/ComposableScreen/elements/actions.ts +53 -0
- package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +59 -4
- package/src/UI/Pages/ComposableScreen/elements/runActions.ts +109 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runActions = runActions;
|
|
4
|
+
const expression_1 = require("./expression");
|
|
5
|
+
// Decode a multi-select variable's stored value (JSON-encoded string[], as
|
|
6
|
+
// written by CheckboxGroup) into a string array. Tolerates undefined / non-array
|
|
7
|
+
// / unparseable input by returning [].
|
|
8
|
+
function decodeArrayValue(raw) {
|
|
9
|
+
if (!raw)
|
|
10
|
+
return [];
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
return Array.isArray(parsed) ? parsed.map(String) : [];
|
|
14
|
+
}
|
|
15
|
+
catch (_a) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Sequentially runs a list of press actions against the render context. Shared
|
|
20
|
+
// by `Button` (its `actions`) and the generic `onPress` on every UIElement
|
|
21
|
+
// (wired centrally in renderElement.tsx). Semantics:
|
|
22
|
+
// - "continue" → advance the onboarding; terminal (stops the loop).
|
|
23
|
+
// - {setVariable} → write a variable (expression-evaluated when valueMode === "expression").
|
|
24
|
+
// - {custom} → invoke the host-registered customAction with the requested
|
|
25
|
+
// variables; warns if unregistered, aborts the loop on throw.
|
|
26
|
+
async function runActions(actions, ctx) {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const { onContinue, setVariable, customActions, variables } = ctx;
|
|
29
|
+
for (const act of actions) {
|
|
30
|
+
if (act === "continue") {
|
|
31
|
+
onContinue();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (act.type === "setVariable") {
|
|
35
|
+
let value;
|
|
36
|
+
let kind;
|
|
37
|
+
if (act.valueMode === "expression") {
|
|
38
|
+
const computed = (0, expression_1.evaluateSetVariableExpression)(act.value, variables);
|
|
39
|
+
value = computed.value;
|
|
40
|
+
kind = computed.kind;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
value = act.value;
|
|
44
|
+
kind = act.kind;
|
|
45
|
+
}
|
|
46
|
+
if (act.arrayOp) {
|
|
47
|
+
// Multi-select set operation on the JSON-encoded string[] used by
|
|
48
|
+
// CheckboxGroup. value/label are the single member being added/removed.
|
|
49
|
+
const entry = variables[act.name];
|
|
50
|
+
const curValues = decodeArrayValue(entry === null || entry === void 0 ? void 0 : entry.value);
|
|
51
|
+
const curLabels = (entry === null || entry === void 0 ? void 0 : entry.label) ? entry.label.split(", ") : [];
|
|
52
|
+
const memberLabel = (_a = act.label) !== null && _a !== void 0 ? _a : value;
|
|
53
|
+
const idx = curValues.indexOf(value);
|
|
54
|
+
const present = idx !== -1;
|
|
55
|
+
const add = act.arrayOp === "append" || (act.arrayOp === "toggle" && !present);
|
|
56
|
+
let nextValues;
|
|
57
|
+
let nextLabels;
|
|
58
|
+
if (add) {
|
|
59
|
+
// Dedup: appending an already-present member is a no-op.
|
|
60
|
+
nextValues = present ? curValues : [...curValues, value];
|
|
61
|
+
nextLabels = present ? curLabels : [...curLabels, memberLabel];
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// remove, or toggle-when-present
|
|
65
|
+
nextValues = curValues.filter((_, i) => i !== idx);
|
|
66
|
+
nextLabels =
|
|
67
|
+
present && idx < curLabels.length
|
|
68
|
+
? curLabels.filter((_, i) => i !== idx)
|
|
69
|
+
: curLabels.filter((l) => l !== memberLabel);
|
|
70
|
+
}
|
|
71
|
+
setVariable(act.name, {
|
|
72
|
+
value: JSON.stringify(nextValues),
|
|
73
|
+
label: nextLabels.join(", "),
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
setVariable(act.name, { value, label: act.label, kind });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const handler = customActions[act.function];
|
|
81
|
+
if (!handler) {
|
|
82
|
+
console.warn(`[ComposableScreen] No customAction registered for "${act.function}"`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const requested = (_b = act.variables) !== null && _b !== void 0 ? _b : [];
|
|
86
|
+
const vars = {};
|
|
87
|
+
for (const name of requested)
|
|
88
|
+
vars[name] = variables[name];
|
|
89
|
+
try {
|
|
90
|
+
await handler({ variables: vars });
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.error(`[ComposableScreen] customAction "${act.function}" threw:`, err);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=runActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runActions.js","sourceRoot":"","sources":["../../../../../src/UI/Pages/ComposableScreen/elements/runActions.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rocapine/react-native-onboarding-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"description": "UI components and renderers for Rocapine Onboarding Studio - Built on top of the headless SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { type ButtonAction, ButtonActionSchema } from "./actions";
|
|
2
3
|
|
|
3
4
|
export type GradientStop = {
|
|
4
5
|
color: string;
|
|
@@ -289,6 +290,7 @@ export type BaseBoxProps = {
|
|
|
289
290
|
elevation?: number;
|
|
290
291
|
transform?: ElementTransform;
|
|
291
292
|
animation?: ElementAnimation;
|
|
293
|
+
onPress?: ButtonAction[];
|
|
292
294
|
};
|
|
293
295
|
|
|
294
296
|
export const BaseBoxPropsSchema = z.object({
|
|
@@ -323,4 +325,5 @@ export const BaseBoxPropsSchema = z.object({
|
|
|
323
325
|
elevation: z.number().min(0).optional(),
|
|
324
326
|
transform: TransformSchema.optional(),
|
|
325
327
|
animation: ElementAnimationSchema.optional(),
|
|
328
|
+
onPress: z.array(ButtonActionSchema).optional(),
|
|
326
329
|
});
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
evaluateCondition,
|
|
7
7
|
type LeafCondition,
|
|
8
8
|
type ConditionGroup,
|
|
9
|
-
type ComposableVariableKind,
|
|
10
9
|
LeafConditionSchema,
|
|
11
10
|
ConditionGroupSchema,
|
|
12
11
|
} from "@rocapine/react-native-onboarding";
|
|
@@ -14,59 +13,23 @@ import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
|
|
|
14
13
|
import { UIElement } from "../types";
|
|
15
14
|
import { RenderContext, buildShadowStyle, dim, resolveInheritedFontFamily } from "./shared";
|
|
16
15
|
import { GradientBox } from "./GradientBox";
|
|
17
|
-
import { ComposableVariableEntry } from "../../../Provider/OnboardingProgressProvider";
|
|
18
|
-
import { evaluateSetVariableExpression } from "./expression";
|
|
19
16
|
import { triggerHaptic, type HapticStyle } from "./haptics";
|
|
17
|
+
import {
|
|
18
|
+
type ButtonAction,
|
|
19
|
+
ButtonActionSchema,
|
|
20
|
+
} from "./actions";
|
|
21
|
+
import { runActions } from "./runActions";
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const CustomButtonActionSchema = z.object({
|
|
28
|
-
type: z.literal("custom"),
|
|
29
|
-
function: z.string().min(1, "function must not be empty"),
|
|
30
|
-
variables: z.array(z.string()).optional(),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export type SetVariableButtonAction = {
|
|
34
|
-
type: "setVariable";
|
|
35
|
-
name: string;
|
|
36
|
-
value: string;
|
|
37
|
-
label?: string;
|
|
38
|
-
/**
|
|
39
|
-
* When `"expression"`, `value` is parsed as an arithmetic expression with
|
|
40
|
-
* `{{var}}` references, numeric literals, and `+ - * /` (parens supported).
|
|
41
|
-
* On parse failure, falls back to plain interpolation (string).
|
|
42
|
-
* Defaults to `"literal"` — `value` stored verbatim.
|
|
43
|
-
*/
|
|
44
|
-
valueMode?: "literal" | "expression";
|
|
45
|
-
/**
|
|
46
|
-
* Tags the stored variable's underlying type. In `"literal"` mode this is
|
|
47
|
-
* used as-is. In `"expression"` mode the inferred result kind is used
|
|
48
|
-
* unless `kind` is explicitly set (ignored — expression mode derives kind
|
|
49
|
-
* from evaluation).
|
|
50
|
-
*/
|
|
51
|
-
kind?: ComposableVariableKind;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const SetVariableButtonActionSchema = z.object({
|
|
55
|
-
type: z.literal("setVariable"),
|
|
56
|
-
name: z.string().min(1, "name must not be empty"),
|
|
57
|
-
value: z.string(),
|
|
58
|
-
label: z.string().optional(),
|
|
59
|
-
valueMode: z.enum(["literal", "expression"]).optional(),
|
|
60
|
-
kind: z.enum(["int", "float", "string"]).optional(),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export type ButtonAction = "continue" | CustomButtonAction | SetVariableButtonAction;
|
|
64
|
-
|
|
65
|
-
export const ButtonActionSchema = z.union([
|
|
66
|
-
z.literal("continue"),
|
|
23
|
+
// `ButtonAction` and its variants live in `./actions` (shared with the generic
|
|
24
|
+
// `onPress` on BaseBoxProps). Re-exported for back-compat.
|
|
25
|
+
export {
|
|
26
|
+
type CustomButtonAction,
|
|
67
27
|
CustomButtonActionSchema,
|
|
28
|
+
type SetVariableButtonAction,
|
|
68
29
|
SetVariableButtonActionSchema,
|
|
69
|
-
|
|
30
|
+
} from "./actions";
|
|
31
|
+
export type { ButtonAction };
|
|
32
|
+
export { ButtonActionSchema };
|
|
70
33
|
|
|
71
34
|
type ButtonOverridableProps = BaseBoxProps & {
|
|
72
35
|
variant?: "filled" | "outlined" | "ghost";
|
|
@@ -145,7 +108,7 @@ type Props = {
|
|
|
145
108
|
};
|
|
146
109
|
|
|
147
110
|
export const ButtonElementComponent = ({ element, ctx }: Props): React.ReactElement => {
|
|
148
|
-
const { theme,
|
|
111
|
+
const { theme, variables } = ctx;
|
|
149
112
|
const flatVariables = useMemo(
|
|
150
113
|
() =>
|
|
151
114
|
Object.fromEntries(
|
|
@@ -164,46 +127,7 @@ export const ButtonElementComponent = ({ element, ctx }: Props): React.ReactElem
|
|
|
164
127
|
const { actions, action } = element.props;
|
|
165
128
|
const effective: ButtonAction[] =
|
|
166
129
|
actions ?? (action === "continue" ? ["continue"] : []);
|
|
167
|
-
|
|
168
|
-
for (const act of effective) {
|
|
169
|
-
if (act === "continue") {
|
|
170
|
-
onContinue();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (act.type === "setVariable") {
|
|
174
|
-
let value: string;
|
|
175
|
-
let kind: ComposableVariableKind | undefined;
|
|
176
|
-
if (act.valueMode === "expression") {
|
|
177
|
-
const computed = evaluateSetVariableExpression(act.value, variables);
|
|
178
|
-
value = computed.value;
|
|
179
|
-
kind = computed.kind;
|
|
180
|
-
} else {
|
|
181
|
-
value = act.value;
|
|
182
|
-
kind = act.kind;
|
|
183
|
-
}
|
|
184
|
-
setVariable(act.name, { value, label: act.label, kind });
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const handler = customActions[act.function];
|
|
188
|
-
if (!handler) {
|
|
189
|
-
console.warn(
|
|
190
|
-
`[ComposableScreen] No customAction registered for "${act.function}"`
|
|
191
|
-
);
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
const requested = act.variables ?? [];
|
|
195
|
-
const vars: Record<string, ComposableVariableEntry | undefined> = {};
|
|
196
|
-
for (const name of requested) vars[name] = variables[name];
|
|
197
|
-
try {
|
|
198
|
-
await handler({ variables: vars });
|
|
199
|
-
} catch (err) {
|
|
200
|
-
console.error(
|
|
201
|
-
`[ComposableScreen] customAction "${act.function}" threw:`,
|
|
202
|
-
err
|
|
203
|
-
);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
130
|
+
await runActions(effective, ctx);
|
|
207
131
|
};
|
|
208
132
|
|
|
209
133
|
// State overrides are merged over base props. disabledStyle wins over the
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { View, Text, TouchableOpacity } from "react-native";
|
|
4
|
+
import { useResolvedFontStyle } from "@rocapine/react-native-onboarding";
|
|
4
5
|
import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
|
|
5
6
|
import type { UIElement } from "../types";
|
|
6
|
-
import { dim, type RenderContext } from "./shared";
|
|
7
|
+
import { dim, resolveInheritedFontFamily, type RenderContext } from "./shared";
|
|
7
8
|
import { GradientBox } from "./GradientBox";
|
|
8
9
|
import { triggerHaptic, type HapticStyle } from "./haptics";
|
|
9
10
|
|
|
@@ -86,6 +87,13 @@ export const CheckboxGroupComponent = ({ element, ctx }: Props): React.ReactElem
|
|
|
86
87
|
try { return JSON.parse(rawValue) as string[]; } catch { return undefined; }
|
|
87
88
|
})();
|
|
88
89
|
|
|
90
|
+
// Resolve item typography once (group-level props apply to every item). Falls
|
|
91
|
+
// back to theme.typography.defaultFontFamily so labels honor the theme font.
|
|
92
|
+
const resolvedFont = useResolvedFontStyle(
|
|
93
|
+
resolveInheritedFontFamily(element.props.itemFontFamily, theme.typography.defaultFontFamily),
|
|
94
|
+
element.props.itemFontWeight
|
|
95
|
+
);
|
|
96
|
+
|
|
89
97
|
useEffect(() => {
|
|
90
98
|
if (element.props.variableName && element.props.defaultValues && selectedValues === undefined) {
|
|
91
99
|
const defaultLabels = element.props.defaultValues.map((dv) => element.props.items.find((i) => i.value === dv)?.label ?? dv);
|
|
@@ -198,8 +206,10 @@ export const CheckboxGroupComponent = ({ element, ctx }: Props): React.ReactElem
|
|
|
198
206
|
flexShrink: 1,
|
|
199
207
|
color: textColor,
|
|
200
208
|
fontSize: element.props.itemFontSize ?? theme.typography.textStyles.body.fontSize,
|
|
201
|
-
fontWeight:
|
|
202
|
-
|
|
209
|
+
fontWeight: resolvedFont.resolvedToVariant
|
|
210
|
+
? undefined
|
|
211
|
+
: ((element.props.itemFontWeight as any) ?? theme.typography.textStyles.body.fontWeight),
|
|
212
|
+
fontFamily: resolvedFont.fontFamily,
|
|
203
213
|
fontStyle: element.props.itemFontStyle,
|
|
204
214
|
}}
|
|
205
215
|
>
|
|
@@ -4,6 +4,7 @@ import DateTimePicker, { DateTimePickerEvent } from "@react-native-community/dat
|
|
|
4
4
|
import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import type { UIElement } from "../types";
|
|
7
|
+
import { useResolvedFontFamily } from "@rocapine/react-native-onboarding";
|
|
7
8
|
import { dim, type RenderContext } from "./shared";
|
|
8
9
|
import { GradientBox } from "./GradientBox";
|
|
9
10
|
|
|
@@ -61,6 +62,8 @@ function formatDate(date: Date, mode: "date" | "time" | "datetime"): string {
|
|
|
61
62
|
export const DatePickerElementComponent = ({ element, ctx }: Props): React.ReactElement => {
|
|
62
63
|
const { theme, variables, setVariable } = ctx;
|
|
63
64
|
const { props } = element;
|
|
65
|
+
// Trigger label honors the theme default font (Android shows a custom Text trigger).
|
|
66
|
+
const labelFontFamily = useResolvedFontFamily(theme.typography.defaultFontFamily, undefined);
|
|
64
67
|
|
|
65
68
|
const persistedValue = props.variableName ? variables[props.variableName]?.value : undefined;
|
|
66
69
|
const initialDate = persistedValue
|
|
@@ -151,6 +154,7 @@ export const DatePickerElementComponent = ({ element, ctx }: Props): React.React
|
|
|
151
154
|
style={{
|
|
152
155
|
color: props.textColor ?? theme.colors.text.primary,
|
|
153
156
|
fontSize: theme.typography.textStyles.body.fontSize,
|
|
157
|
+
fontFamily: labelFontFamily,
|
|
154
158
|
}}
|
|
155
159
|
>
|
|
156
160
|
{formatDate(date, mode)}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { View, Text, TouchableOpacity } from "react-native";
|
|
4
|
+
import { useResolvedFontStyle } from "@rocapine/react-native-onboarding";
|
|
4
5
|
import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
|
|
5
6
|
import { UIElement } from "../types";
|
|
6
|
-
import { RenderContext, dim } from "./shared";
|
|
7
|
+
import { RenderContext, dim, resolveInheritedFontFamily } from "./shared";
|
|
7
8
|
import { GradientBox } from "./GradientBox";
|
|
8
9
|
import { triggerHaptic, type HapticStyle } from "./haptics";
|
|
9
10
|
|
|
@@ -77,6 +78,13 @@ export const RadioGroupComponent = ({ element, ctx }: Props): React.ReactElement
|
|
|
77
78
|
const { theme, variables, setVariable } = ctx;
|
|
78
79
|
const selectedValue = element.props.variableName ? variables[element.props.variableName]?.value : undefined;
|
|
79
80
|
|
|
81
|
+
// Resolve item typography once (group-level props apply to every item). Falls
|
|
82
|
+
// back to theme.typography.defaultFontFamily so labels honor the theme font.
|
|
83
|
+
const resolvedFont = useResolvedFontStyle(
|
|
84
|
+
resolveInheritedFontFamily(element.props.itemFontFamily, theme.typography.defaultFontFamily),
|
|
85
|
+
element.props.itemFontWeight
|
|
86
|
+
);
|
|
87
|
+
|
|
80
88
|
useEffect(() => {
|
|
81
89
|
if (element.props.variableName && element.props.defaultValue && selectedValue === undefined) {
|
|
82
90
|
const defaultItem = element.props.items.find((i) => i.value === element.props.defaultValue);
|
|
@@ -180,8 +188,10 @@ export const RadioGroupComponent = ({ element, ctx }: Props): React.ReactElement
|
|
|
180
188
|
flexShrink: 1,
|
|
181
189
|
color: textColor,
|
|
182
190
|
fontSize: element.props.itemFontSize ?? theme.typography.textStyles.body.fontSize,
|
|
183
|
-
fontWeight:
|
|
184
|
-
|
|
191
|
+
fontWeight: resolvedFont.resolvedToVariant
|
|
192
|
+
? undefined
|
|
193
|
+
: ((element.props.itemFontWeight as any) ?? theme.typography.textStyles.body.fontWeight),
|
|
194
|
+
fontFamily: resolvedFont.fontFamily,
|
|
185
195
|
fontStyle: element.props.itemFontStyle,
|
|
186
196
|
}}
|
|
187
197
|
>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useMemo } from "react";
|
|
2
2
|
import { View, Text } from "react-native";
|
|
3
|
-
import { resolveWheelPickerItems } from "@rocapine/react-native-onboarding";
|
|
3
|
+
import { resolveWheelPickerItems, useResolvedFontFamily } from "@rocapine/react-native-onboarding";
|
|
4
4
|
import { UIElement } from "../types";
|
|
5
|
-
import { RenderContext, dim } from "./shared";
|
|
5
|
+
import { RenderContext, dim, resolveInheritedFontFamily } from "./shared";
|
|
6
6
|
|
|
7
7
|
// Lazy load Picker - only needed for WheelPicker elements, peer dep is optional.
|
|
8
8
|
let PickerComponent: any;
|
|
@@ -72,6 +72,11 @@ export const WheelPickerElementComponent = ({ element, ctx }: Props): React.Reac
|
|
|
72
72
|
} as const;
|
|
73
73
|
|
|
74
74
|
const itemColor = props.itemColor ?? theme.colors.text.primary;
|
|
75
|
+
// Fall back to the theme default font so wheel items honor theme typography.
|
|
76
|
+
const itemFontFamily = useResolvedFontFamily(
|
|
77
|
+
resolveInheritedFontFamily(props.itemFontFamily, theme.typography.defaultFontFamily),
|
|
78
|
+
undefined
|
|
79
|
+
);
|
|
75
80
|
|
|
76
81
|
if (!PickerComponent) {
|
|
77
82
|
// Peer dep not installed — surface a clear placeholder instead of crashing.
|
|
@@ -92,7 +97,7 @@ export const WheelPickerElementComponent = ({ element, ctx }: Props): React.Reac
|
|
|
92
97
|
itemStyle={{
|
|
93
98
|
color: itemColor,
|
|
94
99
|
fontSize: props.itemFontSize,
|
|
95
|
-
fontFamily:
|
|
100
|
+
fontFamily: itemFontFamily,
|
|
96
101
|
}}
|
|
97
102
|
>
|
|
98
103
|
{items.map((item) => (
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Self-contained UI mirror of the headless `ButtonAction` (packages/onboarding
|
|
4
|
+
// src/steps/common.types.ts). Kept here as a leaf module (imports only zod) so
|
|
5
|
+
// both BaseBoxProps.ts (schema) and shared.ts (runActions dispatch) can depend
|
|
6
|
+
// on it without an import cycle. Shared by `Button.actions` and the generic
|
|
7
|
+
// `onPress` on every UIElement.
|
|
8
|
+
|
|
9
|
+
export type CustomButtonAction = {
|
|
10
|
+
type: "custom";
|
|
11
|
+
function: string;
|
|
12
|
+
variables?: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const CustomButtonActionSchema = z.object({
|
|
16
|
+
type: z.literal("custom"),
|
|
17
|
+
function: z.string().min(1, "function must not be empty"),
|
|
18
|
+
variables: z.array(z.string()).optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type SetVariableButtonAction = {
|
|
22
|
+
type: "setVariable";
|
|
23
|
+
name: string;
|
|
24
|
+
value: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
valueMode?: "literal" | "expression";
|
|
27
|
+
kind?: "int" | "float" | "string";
|
|
28
|
+
/**
|
|
29
|
+
* Treat the target variable as the JSON-encoded `string[]` multi-select
|
|
30
|
+
* collection used by `CheckboxGroup` and apply `value` as a set operation
|
|
31
|
+
* (`"append"` / `"remove"` / `"toggle"`) instead of overwriting. `kind` is
|
|
32
|
+
* ignored in this mode. Omit for the default overwrite behavior.
|
|
33
|
+
*/
|
|
34
|
+
arrayOp?: "append" | "remove" | "toggle";
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const SetVariableButtonActionSchema = z.object({
|
|
38
|
+
type: z.literal("setVariable"),
|
|
39
|
+
name: z.string().min(1, "name must not be empty"),
|
|
40
|
+
value: z.string(),
|
|
41
|
+
label: z.string().optional(),
|
|
42
|
+
valueMode: z.enum(["literal", "expression"]).optional(),
|
|
43
|
+
kind: z.enum(["int", "float", "string"]).optional(),
|
|
44
|
+
arrayOp: z.enum(["append", "remove", "toggle"]).optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export type ButtonAction = "continue" | CustomButtonAction | SetVariableButtonAction;
|
|
48
|
+
|
|
49
|
+
export const ButtonActionSchema = z.union([
|
|
50
|
+
z.literal("continue"),
|
|
51
|
+
CustomButtonActionSchema,
|
|
52
|
+
SetVariableButtonActionSchema,
|
|
53
|
+
]);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { Pressable } from "react-native";
|
|
2
3
|
import { evaluateCondition } from "@rocapine/react-native-onboarding";
|
|
3
4
|
import { UIElement } from "../types";
|
|
4
5
|
import { BaseBoxProps } from "./BaseBoxProps";
|
|
5
6
|
import { RenderContext } from "./shared";
|
|
7
|
+
import { runActions } from "./runActions";
|
|
6
8
|
import { StackElementComponent } from "./StackElement";
|
|
7
9
|
import { TextElementComponent } from "./TextElement";
|
|
8
10
|
import { RichTextElementComponent } from "./RichTextElement";
|
|
@@ -26,6 +28,19 @@ import { KeyboardAvoidingViewElementComponent } from "./KeyboardAvoidingViewElem
|
|
|
26
28
|
import { ProgressIndicatorElementComponent } from "./ProgressIndicatorElement";
|
|
27
29
|
import { AnimatedBox } from "./AnimatedBox";
|
|
28
30
|
|
|
31
|
+
// Element types that own their own press / focus / scroll handling. The generic
|
|
32
|
+
// `onPress` (BaseBoxProps) is NOT wired for these — Button/RadioGroup/Checkbox/
|
|
33
|
+
// DatePicker already dispatch actions or selections; Input/WheelPicker would have
|
|
34
|
+
// their focus / scroll-selection intercepted by an outer Pressable.
|
|
35
|
+
const PRESS_HANDLED_TYPES = new Set<UIElement["type"]>([
|
|
36
|
+
"Button",
|
|
37
|
+
"RadioGroup",
|
|
38
|
+
"CheckboxGroup",
|
|
39
|
+
"DatePicker",
|
|
40
|
+
"Input",
|
|
41
|
+
"WheelPicker",
|
|
42
|
+
]);
|
|
43
|
+
|
|
29
44
|
export const renderElement = (
|
|
30
45
|
element: UIElement,
|
|
31
46
|
ctx: RenderContext,
|
|
@@ -128,12 +143,52 @@ export const renderElement = (
|
|
|
128
143
|
return null;
|
|
129
144
|
})();
|
|
130
145
|
|
|
146
|
+
// Cast to BaseBoxProps: not every element's props type extends it (e.g.
|
|
147
|
+
// WheelPicker), but the onPress/animation/transform/flex/alignSelf fields are
|
|
148
|
+
// all optional BaseBoxProps members and simply read as undefined when absent.
|
|
149
|
+
const p = element.props as BaseBoxProps;
|
|
150
|
+
|
|
151
|
+
// Generic onPress: make any non-pressable element tappable, dispatching the
|
|
152
|
+
// same action list as Button via runActions. Skipped for PRESS_HANDLED_TYPES.
|
|
153
|
+
// A Pressable around a scroll/carousel keeps inner scrolling working — RN's
|
|
154
|
+
// gesture responder gives the scroll the touch when it pans.
|
|
155
|
+
let content: React.ReactNode = node;
|
|
156
|
+
if (
|
|
157
|
+
content !== null &&
|
|
158
|
+
p.onPress &&
|
|
159
|
+
p.onPress.length > 0 &&
|
|
160
|
+
!PRESS_HANDLED_TYPES.has(element.type)
|
|
161
|
+
) {
|
|
162
|
+
const onPress = p.onPress;
|
|
163
|
+
// The Pressable must be layout-transparent (like AnimatedBox): forward the
|
|
164
|
+
// element's flex sizing so the wrapper participates in its parent's flex
|
|
165
|
+
// context exactly as the element would. Without this, the style-less
|
|
166
|
+
// Pressable sizes to content while the element's `flex`/`flexShrink` sit on
|
|
167
|
+
// the inner node — breaking row splits (cards overflow) and column flow.
|
|
168
|
+
// Mirror StackElement's `parentType === "XStack"` flexShrink:1 default.
|
|
169
|
+
content = (
|
|
170
|
+
<Pressable
|
|
171
|
+
key={element.id}
|
|
172
|
+
onPress={() => {
|
|
173
|
+
void runActions(onPress, ctx);
|
|
174
|
+
}}
|
|
175
|
+
style={{
|
|
176
|
+
flex: p.flex,
|
|
177
|
+
flexGrow: p.flexGrow,
|
|
178
|
+
flexShrink: p.flexShrink ?? (parentType === "XStack" ? 1 : undefined),
|
|
179
|
+
alignSelf: p.alignSelf,
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
{content}
|
|
183
|
+
</Pressable>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
131
187
|
// Wrap only when motion is requested — zero overhead (no extra view) otherwise.
|
|
132
188
|
// Cast to BaseBoxProps: not every element's props type extends it (e.g.
|
|
133
189
|
// WheelPicker), but the animation/transform/flex/alignSelf fields are all
|
|
134
190
|
// optional BaseBoxProps members and simply read as undefined when absent.
|
|
135
|
-
|
|
136
|
-
if (node !== null && (p.animation || p.transform)) {
|
|
191
|
+
if (content !== null && (p.animation || p.transform)) {
|
|
137
192
|
return (
|
|
138
193
|
<AnimatedBox
|
|
139
194
|
key={element.id}
|
|
@@ -142,10 +197,10 @@ export const renderElement = (
|
|
|
142
197
|
flex={p.flex}
|
|
143
198
|
alignSelf={p.alignSelf}
|
|
144
199
|
>
|
|
145
|
-
{
|
|
200
|
+
{content}
|
|
146
201
|
</AnimatedBox>
|
|
147
202
|
);
|
|
148
203
|
}
|
|
149
204
|
|
|
150
|
-
return
|
|
205
|
+
return content;
|
|
151
206
|
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ComposableVariableEntry,
|
|
3
|
+
ComposableVariableKind,
|
|
4
|
+
} from "@rocapine/react-native-onboarding";
|
|
5
|
+
import type { ButtonAction } from "./actions";
|
|
6
|
+
import type { RenderContext } from "./shared";
|
|
7
|
+
import { evaluateSetVariableExpression } from "./expression";
|
|
8
|
+
|
|
9
|
+
// Decode a multi-select variable's stored value (JSON-encoded string[], as
|
|
10
|
+
// written by CheckboxGroup) into a string array. Tolerates undefined / non-array
|
|
11
|
+
// / unparseable input by returning [].
|
|
12
|
+
function decodeArrayValue(raw: string | undefined): string[] {
|
|
13
|
+
if (!raw) return [];
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(raw);
|
|
16
|
+
return Array.isArray(parsed) ? parsed.map(String) : [];
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Sequentially runs a list of press actions against the render context. Shared
|
|
23
|
+
// by `Button` (its `actions`) and the generic `onPress` on every UIElement
|
|
24
|
+
// (wired centrally in renderElement.tsx). Semantics:
|
|
25
|
+
// - "continue" → advance the onboarding; terminal (stops the loop).
|
|
26
|
+
// - {setVariable} → write a variable (expression-evaluated when valueMode === "expression").
|
|
27
|
+
// - {custom} → invoke the host-registered customAction with the requested
|
|
28
|
+
// variables; warns if unregistered, aborts the loop on throw.
|
|
29
|
+
export async function runActions(
|
|
30
|
+
actions: ButtonAction[],
|
|
31
|
+
ctx: RenderContext
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const { onContinue, setVariable, customActions, variables } = ctx;
|
|
34
|
+
|
|
35
|
+
for (const act of actions) {
|
|
36
|
+
if (act === "continue") {
|
|
37
|
+
onContinue();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (act.type === "setVariable") {
|
|
41
|
+
let value: string;
|
|
42
|
+
let kind: ComposableVariableKind | undefined;
|
|
43
|
+
if (act.valueMode === "expression") {
|
|
44
|
+
const computed = evaluateSetVariableExpression(act.value, variables);
|
|
45
|
+
value = computed.value;
|
|
46
|
+
kind = computed.kind;
|
|
47
|
+
} else {
|
|
48
|
+
value = act.value;
|
|
49
|
+
kind = act.kind;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (act.arrayOp) {
|
|
53
|
+
// Multi-select set operation on the JSON-encoded string[] used by
|
|
54
|
+
// CheckboxGroup. value/label are the single member being added/removed.
|
|
55
|
+
const entry = variables[act.name];
|
|
56
|
+
const curValues = decodeArrayValue(entry?.value);
|
|
57
|
+
const curLabels = entry?.label ? entry.label.split(", ") : [];
|
|
58
|
+
const memberLabel = act.label ?? value;
|
|
59
|
+
const idx = curValues.indexOf(value);
|
|
60
|
+
const present = idx !== -1;
|
|
61
|
+
const add =
|
|
62
|
+
act.arrayOp === "append" || (act.arrayOp === "toggle" && !present);
|
|
63
|
+
|
|
64
|
+
let nextValues: string[];
|
|
65
|
+
let nextLabels: string[];
|
|
66
|
+
if (add) {
|
|
67
|
+
// Dedup: appending an already-present member is a no-op.
|
|
68
|
+
nextValues = present ? curValues : [...curValues, value];
|
|
69
|
+
nextLabels = present ? curLabels : [...curLabels, memberLabel];
|
|
70
|
+
} else {
|
|
71
|
+
// remove, or toggle-when-present
|
|
72
|
+
nextValues = curValues.filter((_, i) => i !== idx);
|
|
73
|
+
nextLabels =
|
|
74
|
+
present && idx < curLabels.length
|
|
75
|
+
? curLabels.filter((_, i) => i !== idx)
|
|
76
|
+
: curLabels.filter((l) => l !== memberLabel);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setVariable(act.name, {
|
|
80
|
+
value: JSON.stringify(nextValues),
|
|
81
|
+
label: nextLabels.join(", "),
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setVariable(act.name, { value, label: act.label, kind });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const handler = customActions[act.function];
|
|
90
|
+
if (!handler) {
|
|
91
|
+
console.warn(
|
|
92
|
+
`[ComposableScreen] No customAction registered for "${act.function}"`
|
|
93
|
+
);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const requested = act.variables ?? [];
|
|
97
|
+
const vars: Record<string, ComposableVariableEntry | undefined> = {};
|
|
98
|
+
for (const name of requested) vars[name] = variables[name];
|
|
99
|
+
try {
|
|
100
|
+
await handler({ variables: vars });
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(
|
|
103
|
+
`[ComposableScreen] customAction "${act.function}" threw:`,
|
|
104
|
+
err
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|