@rocapine/react-native-onboarding-ui 1.20.0 → 1.23.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/Renderer.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/Renderer.js +8 -1
- package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +42 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js +15 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts +4 -0
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.js +57 -12
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.d.ts +11 -0
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.js +74 -0
- package/dist/UI/Pages/ComposableScreen/elements/collectDefaults.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/expression.d.ts +17 -0
- package/dist/UI/Pages/ComposableScreen/elements/expression.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/expression.js +210 -0
- package/dist/UI/Pages/ComposableScreen/elements/expression.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +6 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/types.d.ts +16 -0
- package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/types.js +16 -0
- package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
- package/package.json +2 -2
- package/src/UI/Pages/ComposableScreen/Renderer.tsx +12 -1
- package/src/UI/Pages/ComposableScreen/elements/ButtonElement.tsx +29 -1
- package/src/UI/Pages/ComposableScreen/elements/CarouselElement.tsx +45 -1
- package/src/UI/Pages/ComposableScreen/elements/collectDefaults.ts +76 -0
- package/src/UI/Pages/ComposableScreen/elements/expression.ts +199 -0
- package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +8 -0
- package/src/UI/Pages/ComposableScreen/types.ts +36 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useRef, useState } from "react";
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { View, type LayoutChangeEvent } from "react-native";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { useSharedValue } from "react-native-reanimated";
|
|
@@ -20,6 +20,8 @@ export type CarouselElementProps = BaseBoxProps & {
|
|
|
20
20
|
dotHeight?: number;
|
|
21
21
|
dotsGap?: number;
|
|
22
22
|
dotsMarginTop?: number;
|
|
23
|
+
defaultIndex?: number | null;
|
|
24
|
+
variableName?: string;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export const CarouselElementPropsSchema = BaseBoxPropsSchema.extend({
|
|
@@ -34,6 +36,8 @@ export const CarouselElementPropsSchema = BaseBoxPropsSchema.extend({
|
|
|
34
36
|
dotHeight: z.number().nonnegative().optional().default(4),
|
|
35
37
|
dotsGap: z.number().nonnegative().optional().default(8),
|
|
36
38
|
dotsMarginTop: z.number().optional().default(12),
|
|
39
|
+
defaultIndex: z.number().int().nonnegative().nullable().optional(),
|
|
40
|
+
variableName: z.string().min(1).optional(),
|
|
37
41
|
});
|
|
38
42
|
|
|
39
43
|
type CarouselUIElement = Extract<UIElement, { type: "Carousel" }>;
|
|
@@ -54,6 +58,39 @@ export function CarouselElementComponent({ element, ctx }: Props): React.ReactEl
|
|
|
54
58
|
|
|
55
59
|
const carouselType = props.carouselType ?? "normal";
|
|
56
60
|
|
|
61
|
+
const variableName = props.variableName;
|
|
62
|
+
const variableValue = variableName ? ctx.variables[variableName]?.value : undefined;
|
|
63
|
+
const childrenCount = children.length;
|
|
64
|
+
const clampIndex = (n: number) => Math.max(0, Math.min(n, Math.max(0, childrenCount - 1)));
|
|
65
|
+
|
|
66
|
+
// Frozen on first mount — RNRC `defaultIndex` only applies at mount.
|
|
67
|
+
const initialIndexRef = useRef<number | null>(null);
|
|
68
|
+
if (initialIndexRef.current === null) {
|
|
69
|
+
const parsed = variableValue !== undefined ? parseInt(variableValue, 10) : NaN;
|
|
70
|
+
const fromVar = Number.isFinite(parsed) ? parsed : null;
|
|
71
|
+
initialIndexRef.current = clampIndex(fromVar ?? props.defaultIndex ?? 0);
|
|
72
|
+
}
|
|
73
|
+
const lastSyncedIndexRef = useRef<number>(initialIndexRef.current);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!variableName) return;
|
|
77
|
+
const parsed = parseInt(variableValue ?? "", 10);
|
|
78
|
+
if (!Number.isFinite(parsed)) return;
|
|
79
|
+
const target = clampIndex(parsed);
|
|
80
|
+
if (target === lastSyncedIndexRef.current) return;
|
|
81
|
+
lastSyncedIndexRef.current = target;
|
|
82
|
+
ref.current?.scrollTo({ count: target - progress.value, animated: true });
|
|
83
|
+
}, [variableName, variableValue, childrenCount]);
|
|
84
|
+
|
|
85
|
+
// Persist the initial index into ctx.variables when no value exists yet, so the
|
|
86
|
+
// default reaches downstream renderWhen / interpolation across renders.
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!variableName) return;
|
|
89
|
+
if (variableValue !== undefined) return;
|
|
90
|
+
if (props.defaultIndex == null) return;
|
|
91
|
+
ctx.setVariable(variableName, { value: String(clampIndex(props.defaultIndex)) });
|
|
92
|
+
}, [variableName, variableValue, props.defaultIndex, childrenCount]);
|
|
93
|
+
|
|
57
94
|
const onLayout = (e: LayoutChangeEvent) => {
|
|
58
95
|
const { width, height } = e.nativeEvent.layout;
|
|
59
96
|
if (!size || size.width !== width || size.height !== height) {
|
|
@@ -132,6 +169,7 @@ export function CarouselElementComponent({ element, ctx }: Props): React.ReactEl
|
|
|
132
169
|
loop={props.loop}
|
|
133
170
|
autoPlay={props.autoPlay}
|
|
134
171
|
autoPlayInterval={props.autoPlayInterval}
|
|
172
|
+
defaultIndex={initialIndexRef.current ?? 0}
|
|
135
173
|
snapEnabled={true}
|
|
136
174
|
pagingEnabled={true}
|
|
137
175
|
data={children}
|
|
@@ -142,6 +180,12 @@ export function CarouselElementComponent({ element, ctx }: Props): React.ReactEl
|
|
|
142
180
|
onProgressChange={(_: number, absoluteProgress: number) => {
|
|
143
181
|
progress.value = absoluteProgress;
|
|
144
182
|
}}
|
|
183
|
+
onSnapToItem={(index: number) => {
|
|
184
|
+
if (!variableName) return;
|
|
185
|
+
if (index === lastSyncedIndexRef.current) return;
|
|
186
|
+
lastSyncedIndexRef.current = index;
|
|
187
|
+
ctx.setVariable(variableName, { value: String(index) });
|
|
188
|
+
}}
|
|
145
189
|
{...(modeProps as any)}
|
|
146
190
|
/>
|
|
147
191
|
)}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { UIElement } from "../types";
|
|
2
|
+
import { ComposableVariableEntry } from "../../../Provider/OnboardingProgressProvider";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Walks the element tree and returns the initial variable map declared via
|
|
6
|
+
* element-level defaults (`defaultIndex`, `defaultValue`, `defaultValues`).
|
|
7
|
+
* Used by `Renderer` to overlay defaults onto `ctx.variables` so
|
|
8
|
+
* `renderWhen` / expressions evaluate against them on first render — before
|
|
9
|
+
* per-element seeding effects run.
|
|
10
|
+
*/
|
|
11
|
+
export function collectElementDefaults(
|
|
12
|
+
elements: UIElement[]
|
|
13
|
+
): Record<string, ComposableVariableEntry> {
|
|
14
|
+
const out: Record<string, ComposableVariableEntry> = {};
|
|
15
|
+
const visit = (el: UIElement) => {
|
|
16
|
+
switch (el.type) {
|
|
17
|
+
case "Carousel": {
|
|
18
|
+
const name = el.props.variableName;
|
|
19
|
+
if (name && el.props.defaultIndex != null) {
|
|
20
|
+
// Mirror CarouselElementComponent's clampIndex so the overlaid default
|
|
21
|
+
// matches the index the carousel actually mounts at.
|
|
22
|
+
const raw = Number(el.props.defaultIndex);
|
|
23
|
+
const safe = Number.isFinite(raw) ? raw : 0;
|
|
24
|
+
const maxIdx = Math.max(0, el.children.length - 1);
|
|
25
|
+
const clamped = Math.max(0, Math.min(safe, maxIdx));
|
|
26
|
+
out[name] = { value: String(clamped) };
|
|
27
|
+
}
|
|
28
|
+
el.children.forEach(visit);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case "RadioGroup": {
|
|
32
|
+
const name = el.props.variableName;
|
|
33
|
+
const dv = el.props.defaultValue;
|
|
34
|
+
if (name && dv !== undefined) {
|
|
35
|
+
const item = el.props.items.find((i) => i.value === dv);
|
|
36
|
+
out[name] = { value: dv, label: item?.label };
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case "CheckboxGroup": {
|
|
41
|
+
const name = el.props.variableName;
|
|
42
|
+
const dvs = el.props.defaultValues;
|
|
43
|
+
if (name && dvs !== undefined) {
|
|
44
|
+
const labels = dvs.map(
|
|
45
|
+
(dv) => el.props.items.find((i) => i.value === dv)?.label ?? dv
|
|
46
|
+
);
|
|
47
|
+
out[name] = { value: JSON.stringify(dvs), label: labels.join(", ") };
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "Input": {
|
|
52
|
+
const name = el.props.variableName;
|
|
53
|
+
if (name && el.props.defaultValue !== undefined) {
|
|
54
|
+
out[name] = { value: el.props.defaultValue };
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "DatePicker": {
|
|
59
|
+
const name = el.props.variableName;
|
|
60
|
+
if (name && el.props.defaultValue !== undefined) {
|
|
61
|
+
const d = new Date(el.props.defaultValue);
|
|
62
|
+
if (!isNaN(d.getTime())) out[name] = { value: d.toISOString() };
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "YStack":
|
|
67
|
+
case "XStack":
|
|
68
|
+
case "ZStack":
|
|
69
|
+
case "SafeAreaView":
|
|
70
|
+
el.children.forEach(visit);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
elements.forEach(visit);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { ComposableVariableEntry, ComposableVariableKind } from "@rocapine/react-native-onboarding";
|
|
2
|
+
import { interpolate } from "./shared";
|
|
3
|
+
|
|
4
|
+
type Token =
|
|
5
|
+
| { kind: "num"; value: number; isInt: boolean }
|
|
6
|
+
| { kind: "var"; name: string }
|
|
7
|
+
| { kind: "op"; op: "+" | "-" | "*" | "/" }
|
|
8
|
+
| { kind: "lparen" }
|
|
9
|
+
| { kind: "rparen" }
|
|
10
|
+
| { kind: "eof" };
|
|
11
|
+
|
|
12
|
+
type Value =
|
|
13
|
+
| { kind: "number"; n: number; isInt: boolean }
|
|
14
|
+
| { kind: "string"; s: string };
|
|
15
|
+
|
|
16
|
+
const isDigit = (c: string) => c >= "0" && c <= "9";
|
|
17
|
+
const isSpace = (c: string) => c === " " || c === "\t" || c === "\n" || c === "\r";
|
|
18
|
+
|
|
19
|
+
function tokenize(input: string): Token[] | null {
|
|
20
|
+
const tokens: Token[] = [];
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < input.length) {
|
|
23
|
+
const c = input[i];
|
|
24
|
+
if (isSpace(c)) {
|
|
25
|
+
i++;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (c === "{" && input[i + 1] === "{") {
|
|
29
|
+
const end = input.indexOf("}}", i + 2);
|
|
30
|
+
if (end === -1) return null;
|
|
31
|
+
const name = input.slice(i + 2, end).trim();
|
|
32
|
+
if (!name) return null;
|
|
33
|
+
tokens.push({ kind: "var", name });
|
|
34
|
+
i = end + 2;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (c === "(") { tokens.push({ kind: "lparen" }); i++; continue; }
|
|
38
|
+
if (c === ")") { tokens.push({ kind: "rparen" }); i++; continue; }
|
|
39
|
+
if (c === "+" || c === "-" || c === "*" || c === "/") {
|
|
40
|
+
tokens.push({ kind: "op", op: c });
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (isDigit(c) || (c === "." && isDigit(input[i + 1] ?? ""))) {
|
|
45
|
+
let j = i;
|
|
46
|
+
let dot = false;
|
|
47
|
+
while (j < input.length && (isDigit(input[j]) || input[j] === ".")) {
|
|
48
|
+
if (input[j] === ".") {
|
|
49
|
+
if (dot) return null;
|
|
50
|
+
dot = true;
|
|
51
|
+
}
|
|
52
|
+
j++;
|
|
53
|
+
}
|
|
54
|
+
const num = parseFloat(input.slice(i, j));
|
|
55
|
+
if (!Number.isFinite(num)) return null;
|
|
56
|
+
tokens.push({ kind: "num", value: num, isInt: !dot });
|
|
57
|
+
i = j;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
tokens.push({ kind: "eof" });
|
|
63
|
+
return tokens;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveVar(name: string, vars: Record<string, ComposableVariableEntry>): Value {
|
|
67
|
+
const entry = vars[name];
|
|
68
|
+
// Missing variable in arithmetic context defaults to numeric 0 so increment
|
|
69
|
+
// / decrement patterns work on first click before the variable is seeded.
|
|
70
|
+
if (!entry) return { kind: "number", n: 0, isInt: true };
|
|
71
|
+
const raw = entry.value;
|
|
72
|
+
const k = entry.kind;
|
|
73
|
+
if (k === "string") return { kind: "string", s: raw };
|
|
74
|
+
if (k === "int") {
|
|
75
|
+
const n = parseInt(raw, 10);
|
|
76
|
+
return Number.isFinite(n) ? { kind: "number", n, isInt: true } : { kind: "string", s: raw };
|
|
77
|
+
}
|
|
78
|
+
if (k === "float") {
|
|
79
|
+
const n = parseFloat(raw);
|
|
80
|
+
return Number.isFinite(n) ? { kind: "number", n, isInt: false } : { kind: "string", s: raw };
|
|
81
|
+
}
|
|
82
|
+
// No kind tag — infer from string content.
|
|
83
|
+
const trimmed = raw.trim();
|
|
84
|
+
if (trimmed !== "" && /^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
85
|
+
const n = parseFloat(trimmed);
|
|
86
|
+
if (Number.isFinite(n)) return { kind: "number", n, isInt: Number.isInteger(n) && !trimmed.includes(".") };
|
|
87
|
+
}
|
|
88
|
+
return { kind: "string", s: raw };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function valueToString(v: Value): string {
|
|
92
|
+
if (v.kind === "string") return v.s;
|
|
93
|
+
if (v.isInt) return Math.trunc(v.n).toString();
|
|
94
|
+
return v.n.toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parse(tokens: Token[], vars: Record<string, ComposableVariableEntry>): Value | null {
|
|
98
|
+
let pos = 0;
|
|
99
|
+
const peek = () => tokens[pos];
|
|
100
|
+
const advance = () => tokens[pos++];
|
|
101
|
+
|
|
102
|
+
const factor = (): Value | null => {
|
|
103
|
+
const t = peek();
|
|
104
|
+
if (t.kind === "lparen") {
|
|
105
|
+
advance();
|
|
106
|
+
const v = expr();
|
|
107
|
+
if (!v) return null;
|
|
108
|
+
if (peek().kind !== "rparen") return null;
|
|
109
|
+
advance();
|
|
110
|
+
return v;
|
|
111
|
+
}
|
|
112
|
+
if (t.kind === "op" && t.op === "-") {
|
|
113
|
+
advance();
|
|
114
|
+
const v = factor();
|
|
115
|
+
if (!v || v.kind !== "number") return null;
|
|
116
|
+
return { kind: "number", n: -v.n, isInt: v.isInt };
|
|
117
|
+
}
|
|
118
|
+
if (t.kind === "num") {
|
|
119
|
+
advance();
|
|
120
|
+
return { kind: "number", n: t.value, isInt: t.isInt };
|
|
121
|
+
}
|
|
122
|
+
if (t.kind === "var") {
|
|
123
|
+
advance();
|
|
124
|
+
return resolveVar(t.name, vars);
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const term = (): Value | null => {
|
|
130
|
+
let left = factor();
|
|
131
|
+
if (!left) return null;
|
|
132
|
+
while (peek().kind === "op" && ((peek() as any).op === "*" || (peek() as any).op === "/")) {
|
|
133
|
+
const op = (advance() as any).op as "*" | "/";
|
|
134
|
+
const right = factor();
|
|
135
|
+
if (!right) return null;
|
|
136
|
+
if (left.kind !== "number" || right.kind !== "number") return null;
|
|
137
|
+
if (op === "/" && right.n === 0) return null;
|
|
138
|
+
const result: number = op === "*" ? left.n * right.n : left.n / right.n;
|
|
139
|
+
if (!Number.isFinite(result)) return null;
|
|
140
|
+
const isInt: boolean = op === "*" ? left.isInt && right.isInt : Number.isInteger(result);
|
|
141
|
+
left = { kind: "number", n: result, isInt };
|
|
142
|
+
}
|
|
143
|
+
return left;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const expr = (): Value | null => {
|
|
147
|
+
let left = term();
|
|
148
|
+
if (!left) return null;
|
|
149
|
+
while (peek().kind === "op" && ((peek() as any).op === "+" || (peek() as any).op === "-")) {
|
|
150
|
+
const op = (advance() as any).op as "+" | "-";
|
|
151
|
+
const right = term();
|
|
152
|
+
if (!right) return null;
|
|
153
|
+
if (op === "+") {
|
|
154
|
+
if (left.kind === "number" && right.kind === "number") {
|
|
155
|
+
left = { kind: "number", n: left.n + right.n, isInt: left.isInt && right.isInt };
|
|
156
|
+
} else {
|
|
157
|
+
left = { kind: "string", s: valueToString(left) + valueToString(right) };
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
if (left.kind !== "number" || right.kind !== "number") return null;
|
|
161
|
+
left = { kind: "number", n: left.n - right.n, isInt: left.isInt && right.isInt };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return left;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = expr();
|
|
168
|
+
if (!result) return null;
|
|
169
|
+
if (peek().kind !== "eof") return null;
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Evaluate a `setVariable` expression-mode value template.
|
|
175
|
+
*
|
|
176
|
+
* Accepts `{{var}}` references, numeric literals (int / float), `+ - * /` and
|
|
177
|
+
* parentheses. Variable values are coerced according to their `kind` tag
|
|
178
|
+
* (string / int / float), or inferred from their string content when no tag
|
|
179
|
+
* is present. `+` on any non-numeric operand becomes string concat.
|
|
180
|
+
*
|
|
181
|
+
* On any parse or evaluation failure, falls back to plain interpolation
|
|
182
|
+
* (existing `interpolate()` semantics, returns a string).
|
|
183
|
+
*/
|
|
184
|
+
export function evaluateSetVariableExpression(
|
|
185
|
+
template: string,
|
|
186
|
+
vars: Record<string, ComposableVariableEntry>
|
|
187
|
+
): { value: string; kind: ComposableVariableKind } {
|
|
188
|
+
const tokens = tokenize(template);
|
|
189
|
+
if (tokens) {
|
|
190
|
+
const result = parse(tokens, vars);
|
|
191
|
+
if (result) {
|
|
192
|
+
if (result.kind === "number") {
|
|
193
|
+
return { value: valueToString(result), kind: result.isInt ? "int" : "float" };
|
|
194
|
+
}
|
|
195
|
+
return { value: result.s, kind: "string" };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { value: interpolate(template, vars), kind: "string" };
|
|
199
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { evaluateCondition } from "@rocapine/react-native-onboarding";
|
|
2
3
|
import { UIElement } from "../types";
|
|
3
4
|
import { RenderContext } from "./shared";
|
|
4
5
|
import { StackElementComponent } from "./StackElement";
|
|
@@ -22,6 +23,13 @@ export const renderElement = (
|
|
|
22
23
|
ctx: RenderContext,
|
|
23
24
|
parentType?: "XStack" | "YStack" | "ZStack"
|
|
24
25
|
): React.ReactNode => {
|
|
26
|
+
if (element.renderWhen) {
|
|
27
|
+
const flatVars = Object.fromEntries(
|
|
28
|
+
Object.entries(ctx.variables).map(([k, v]) => [k, v?.value])
|
|
29
|
+
);
|
|
30
|
+
if (!evaluateCondition(element.renderWhen, flatVars)) return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
if (element.type === "YStack" || element.type === "XStack") {
|
|
26
34
|
return <StackElementComponent key={element.id} element={element} ctx={ctx} parentType={parentType} />;
|
|
27
35
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
type LeafCondition,
|
|
4
|
+
type ConditionGroup,
|
|
5
|
+
LeafConditionSchema,
|
|
6
|
+
ConditionGroupSchema,
|
|
7
|
+
} from "@rocapine/react-native-onboarding";
|
|
2
8
|
import { CustomPayloadSchema } from "../types";
|
|
3
9
|
import { type StackElementProps, StackElementPropsSchema } from "./elements/StackElement";
|
|
4
10
|
import { type TextElementProps, TextElementPropsSchema } from "./elements/TextElement";
|
|
@@ -40,6 +46,7 @@ export type UIElement =
|
|
|
40
46
|
| {
|
|
41
47
|
id: string;
|
|
42
48
|
name?: string;
|
|
49
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
43
50
|
type: "YStack" | "XStack";
|
|
44
51
|
props: StackElementProps;
|
|
45
52
|
children: UIElement[];
|
|
@@ -47,72 +54,84 @@ export type UIElement =
|
|
|
47
54
|
| {
|
|
48
55
|
id: string;
|
|
49
56
|
name?: string;
|
|
57
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
50
58
|
type: "Text";
|
|
51
59
|
props: TextElementProps;
|
|
52
60
|
}
|
|
53
61
|
| {
|
|
54
62
|
id: string;
|
|
55
63
|
name?: string;
|
|
64
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
56
65
|
type: "Image";
|
|
57
66
|
props: ImageElementProps;
|
|
58
67
|
}
|
|
59
68
|
| {
|
|
60
69
|
id: string;
|
|
61
70
|
name?: string;
|
|
71
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
62
72
|
type: "Lottie";
|
|
63
73
|
props: LottieElementProps;
|
|
64
74
|
}
|
|
65
75
|
| {
|
|
66
76
|
id: string;
|
|
67
77
|
name?: string;
|
|
78
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
68
79
|
type: "Rive";
|
|
69
80
|
props: RiveElementProps;
|
|
70
81
|
}
|
|
71
82
|
| {
|
|
72
83
|
id: string;
|
|
73
84
|
name?: string;
|
|
85
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
74
86
|
type: "Icon";
|
|
75
87
|
props: IconElementProps;
|
|
76
88
|
}
|
|
77
89
|
| {
|
|
78
90
|
id: string;
|
|
79
91
|
name?: string;
|
|
92
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
80
93
|
type: "Video";
|
|
81
94
|
props: VideoElementProps;
|
|
82
95
|
}
|
|
83
96
|
| {
|
|
84
97
|
id: string;
|
|
85
98
|
name?: string;
|
|
99
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
86
100
|
type: "Input";
|
|
87
101
|
props: InputElementProps;
|
|
88
102
|
}
|
|
89
103
|
| {
|
|
90
104
|
id: string;
|
|
91
105
|
name?: string;
|
|
106
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
92
107
|
type: "Button";
|
|
93
108
|
props: ButtonElementProps;
|
|
94
109
|
}
|
|
95
110
|
| {
|
|
96
111
|
id: string;
|
|
97
112
|
name?: string;
|
|
113
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
98
114
|
type: "RadioGroup";
|
|
99
115
|
props: RadioGroupElementProps;
|
|
100
116
|
}
|
|
101
117
|
| {
|
|
102
118
|
id: string;
|
|
103
119
|
name?: string;
|
|
120
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
104
121
|
type: "CheckboxGroup";
|
|
105
122
|
props: CheckboxGroupElementProps;
|
|
106
123
|
}
|
|
107
124
|
| {
|
|
108
125
|
id: string;
|
|
109
126
|
name?: string;
|
|
127
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
110
128
|
type: "DatePicker";
|
|
111
129
|
props: DatePickerElementProps;
|
|
112
130
|
}
|
|
113
131
|
| {
|
|
114
132
|
id: string;
|
|
115
133
|
name?: string;
|
|
134
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
116
135
|
type: "Carousel";
|
|
117
136
|
props: CarouselElementProps;
|
|
118
137
|
children: UIElement[];
|
|
@@ -120,6 +139,7 @@ export type UIElement =
|
|
|
120
139
|
| {
|
|
121
140
|
id: string;
|
|
122
141
|
name?: string;
|
|
142
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
123
143
|
type: "ZStack";
|
|
124
144
|
props: ZStackElementProps;
|
|
125
145
|
children: UIElement[];
|
|
@@ -127,6 +147,7 @@ export type UIElement =
|
|
|
127
147
|
| {
|
|
128
148
|
id: string;
|
|
129
149
|
name?: string;
|
|
150
|
+
renderWhen?: LeafCondition | ConditionGroup;
|
|
130
151
|
type: "SafeAreaView";
|
|
131
152
|
props: SafeAreaViewElementProps;
|
|
132
153
|
children: UIElement[];
|
|
@@ -137,6 +158,7 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
|
|
|
137
158
|
z.object({
|
|
138
159
|
id: z.string(),
|
|
139
160
|
name: z.string().optional(),
|
|
161
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
140
162
|
type: z.union([z.literal("YStack"), z.literal("XStack")]),
|
|
141
163
|
props: StackElementPropsSchema,
|
|
142
164
|
children: z.array(UIElementSchema),
|
|
@@ -144,72 +166,84 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
|
|
|
144
166
|
z.object({
|
|
145
167
|
id: z.string(),
|
|
146
168
|
name: z.string().optional(),
|
|
169
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
147
170
|
type: z.literal("Text"),
|
|
148
171
|
props: TextElementPropsSchema,
|
|
149
172
|
}),
|
|
150
173
|
z.object({
|
|
151
174
|
id: z.string(),
|
|
152
175
|
name: z.string().optional(),
|
|
176
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
153
177
|
type: z.literal("Image"),
|
|
154
178
|
props: ImageElementPropsSchema,
|
|
155
179
|
}),
|
|
156
180
|
z.object({
|
|
157
181
|
id: z.string(),
|
|
158
182
|
name: z.string().optional(),
|
|
183
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
159
184
|
type: z.literal("Lottie"),
|
|
160
185
|
props: LottieElementPropsSchema,
|
|
161
186
|
}),
|
|
162
187
|
z.object({
|
|
163
188
|
id: z.string(),
|
|
164
189
|
name: z.string().optional(),
|
|
190
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
165
191
|
type: z.literal("Rive"),
|
|
166
192
|
props: RiveElementPropsSchema,
|
|
167
193
|
}),
|
|
168
194
|
z.object({
|
|
169
195
|
id: z.string(),
|
|
170
196
|
name: z.string().optional(),
|
|
197
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
171
198
|
type: z.literal("Icon"),
|
|
172
199
|
props: IconElementPropsSchema,
|
|
173
200
|
}),
|
|
174
201
|
z.object({
|
|
175
202
|
id: z.string(),
|
|
176
203
|
name: z.string().optional(),
|
|
204
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
177
205
|
type: z.literal("Video"),
|
|
178
206
|
props: VideoElementPropsSchema,
|
|
179
207
|
}),
|
|
180
208
|
z.object({
|
|
181
209
|
id: z.string(),
|
|
182
210
|
name: z.string().optional(),
|
|
211
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
183
212
|
type: z.literal("Input"),
|
|
184
213
|
props: InputElementPropsSchema,
|
|
185
214
|
}),
|
|
186
215
|
z.object({
|
|
187
216
|
id: z.string(),
|
|
188
217
|
name: z.string().optional(),
|
|
218
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
189
219
|
type: z.literal("Button"),
|
|
190
220
|
props: ButtonElementPropsSchema,
|
|
191
221
|
}),
|
|
192
222
|
z.object({
|
|
193
223
|
id: z.string(),
|
|
194
224
|
name: z.string().optional(),
|
|
225
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
195
226
|
type: z.literal("RadioGroup"),
|
|
196
227
|
props: RadioGroupElementPropsSchema,
|
|
197
228
|
}),
|
|
198
229
|
z.object({
|
|
199
230
|
id: z.string(),
|
|
200
231
|
name: z.string().optional(),
|
|
232
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
201
233
|
type: z.literal("CheckboxGroup"),
|
|
202
234
|
props: CheckboxGroupElementPropsSchema,
|
|
203
235
|
}),
|
|
204
236
|
z.object({
|
|
205
237
|
id: z.string(),
|
|
206
238
|
name: z.string().optional(),
|
|
239
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
207
240
|
type: z.literal("DatePicker"),
|
|
208
241
|
props: DatePickerElementPropsSchema,
|
|
209
242
|
}),
|
|
210
243
|
z.object({
|
|
211
244
|
id: z.string(),
|
|
212
245
|
name: z.string().optional(),
|
|
246
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
213
247
|
type: z.literal("Carousel"),
|
|
214
248
|
props: CarouselElementPropsSchema,
|
|
215
249
|
children: z.array(UIElementSchema),
|
|
@@ -217,6 +251,7 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
|
|
|
217
251
|
z.object({
|
|
218
252
|
id: z.string(),
|
|
219
253
|
name: z.string().optional(),
|
|
254
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
220
255
|
type: z.literal("ZStack"),
|
|
221
256
|
props: ZStackElementPropsSchema,
|
|
222
257
|
children: z.array(UIElementSchema),
|
|
@@ -224,6 +259,7 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
|
|
|
224
259
|
z.object({
|
|
225
260
|
id: z.string(),
|
|
226
261
|
name: z.string().optional(),
|
|
262
|
+
renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
|
|
227
263
|
type: z.literal("SafeAreaView"),
|
|
228
264
|
props: SafeAreaViewElementPropsSchema,
|
|
229
265
|
children: z.array(UIElementSchema),
|