@rocapine/react-native-onboarding-ui 1.31.0 → 1.33.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.
Files changed (81) hide show
  1. package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -1
  2. package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.d.ts +21 -0
  3. package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.d.ts.map +1 -0
  4. package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.js +140 -0
  5. package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.js.map +1 -0
  6. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts +220 -0
  7. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts.map +1 -1
  8. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js +83 -0
  9. package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js.map +1 -1
  10. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +640 -0
  11. package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -1
  12. package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts +160 -0
  13. package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts.map +1 -1
  14. package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts +160 -0
  15. package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts.map +1 -1
  16. package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts +160 -0
  17. package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts.map +1 -1
  18. package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts +160 -0
  19. package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts.map +1 -1
  20. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts +160 -0
  21. package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts.map +1 -1
  22. package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts +160 -0
  23. package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts.map +1 -1
  24. package/dist/UI/Pages/ComposableScreen/elements/KeyboardAvoidingViewElement.d.ts +160 -0
  25. package/dist/UI/Pages/ComposableScreen/elements/KeyboardAvoidingViewElement.d.ts.map +1 -1
  26. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts +160 -0
  27. package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts.map +1 -1
  28. package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.d.ts +160 -0
  29. package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.d.ts.map +1 -1
  30. package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.js +2 -8
  31. package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.js.map +1 -1
  32. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts +160 -0
  33. package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts.map +1 -1
  34. package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.d.ts +305 -0
  35. package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.d.ts.map +1 -0
  36. package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.js +133 -0
  37. package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.js.map +1 -0
  38. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts +160 -0
  39. package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts.map +1 -1
  40. package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts +160 -0
  41. package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts.map +1 -1
  42. package/dist/UI/Pages/ComposableScreen/elements/ScrollViewElement.d.ts +160 -0
  43. package/dist/UI/Pages/ComposableScreen/elements/ScrollViewElement.d.ts.map +1 -1
  44. package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts +161 -1
  45. package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts.map +1 -1
  46. package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts +199 -1
  47. package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts.map +1 -1
  48. package/dist/UI/Pages/ComposableScreen/elements/TextElement.js +38 -12
  49. package/dist/UI/Pages/ComposableScreen/elements/TextElement.js.map +1 -1
  50. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts +160 -0
  51. package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts.map +1 -1
  52. package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts +160 -0
  53. package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts.map +1 -1
  54. package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.d.ts +9 -0
  55. package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.d.ts.map +1 -0
  56. package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.js +106 -0
  57. package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.js.map +1 -0
  58. package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts +1 -1
  59. package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -1
  60. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +76 -58
  61. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -1
  62. package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts +12 -1
  63. package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts.map +1 -1
  64. package/dist/UI/Pages/ComposableScreen/elements/shared.js +6 -1
  65. package/dist/UI/Pages/ComposableScreen/elements/shared.js.map +1 -1
  66. package/dist/UI/Pages/ComposableScreen/types.d.ts +11 -0
  67. package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
  68. package/dist/UI/Pages/ComposableScreen/types.js +15 -2
  69. package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
  70. package/package.json +1 -1
  71. package/src/UI/Pages/ComposableScreen/Renderer.tsx +1 -1
  72. package/src/UI/Pages/ComposableScreen/elements/AnimatedBox.tsx +133 -0
  73. package/src/UI/Pages/ComposableScreen/elements/BaseBoxProps.ts +211 -0
  74. package/src/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.tsx +1 -9
  75. package/src/UI/Pages/ComposableScreen/elements/RichTextElement.tsx +177 -0
  76. package/src/UI/Pages/ComposableScreen/elements/StackElement.tsx +1 -1
  77. package/src/UI/Pages/ComposableScreen/elements/TextElement.tsx +39 -11
  78. package/src/UI/Pages/ComposableScreen/elements/buildAnimation.ts +83 -0
  79. package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +34 -2
  80. package/src/UI/Pages/ComposableScreen/elements/shared.ts +19 -1
  81. package/src/UI/Pages/ComposableScreen/types.ts +25 -2
@@ -50,6 +50,213 @@ export const ShadowOffsetSchema = z.object({
50
50
  height: z.number(),
51
51
  });
52
52
 
53
+ // ---------------------------------------------------------------------------
54
+ // Animation / Transform surface
55
+ //
56
+ // Schema stays intentionally close to react-native-reanimated: `preset` values
57
+ // are the *exact* reanimated builder names (e.g. `FadeInDown`, `SlideOutLeft`,
58
+ // `LinearTransition`), so the UI renderer resolves them by direct namespace
59
+ // lookup (`Reanimated[preset]`) rather than a translation table. Modifier fields
60
+ // (`duration`/`delay`/`spring`/`easing`) map to reanimated builder methods
61
+ // (`.duration().delay().springify().easing()`).
62
+ // ---------------------------------------------------------------------------
63
+
64
+ // Reuses the easing-name convention from ProgressIndicatorElement.
65
+ export type AnimationEasing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
66
+
67
+ const AnimationEasingSchema = z.enum(["linear", "ease-in", "ease-out", "ease-in-out"]);
68
+
69
+ // Mirrors reanimated's `.springify(config)` — only the fields it accepts.
70
+ // When `spring` is present it wins over `easing` (matches reanimated semantics).
71
+ export type SpringConfig = {
72
+ damping?: number;
73
+ stiffness?: number;
74
+ mass?: number;
75
+ };
76
+
77
+ const SpringConfigSchema = z.object({
78
+ damping: z.number().positive().optional(),
79
+ stiffness: z.number().positive().optional(),
80
+ mass: z.number().positive().optional(),
81
+ });
82
+
83
+ // Exact reanimated entering builder names.
84
+ export type EnteringPreset =
85
+ | "FadeIn" | "FadeInUp" | "FadeInDown" | "FadeInLeft" | "FadeInRight"
86
+ | "SlideInUp" | "SlideInDown" | "SlideInLeft" | "SlideInRight"
87
+ | "ZoomIn" | "ZoomInRotate" | "ZoomInUp" | "ZoomInDown" | "ZoomInLeft" | "ZoomInRight"
88
+ | "ZoomInEasyUp" | "ZoomInEasyDown"
89
+ | "BounceIn" | "BounceInUp" | "BounceInDown" | "BounceInLeft" | "BounceInRight"
90
+ | "FlipInXUp" | "FlipInYLeft" | "FlipInXDown" | "FlipInYRight" | "FlipInEasyX" | "FlipInEasyY"
91
+ | "StretchInX" | "StretchInY"
92
+ | "RotateInDownLeft" | "RotateInDownRight" | "RotateInUpLeft" | "RotateInUpRight"
93
+ | "RollInLeft" | "RollInRight"
94
+ | "PinwheelIn"
95
+ | "LightSpeedInLeft" | "LightSpeedInRight";
96
+
97
+ const EnteringPresetSchema = z.enum([
98
+ "FadeIn", "FadeInUp", "FadeInDown", "FadeInLeft", "FadeInRight",
99
+ "SlideInUp", "SlideInDown", "SlideInLeft", "SlideInRight",
100
+ "ZoomIn", "ZoomInRotate", "ZoomInUp", "ZoomInDown", "ZoomInLeft", "ZoomInRight",
101
+ "ZoomInEasyUp", "ZoomInEasyDown",
102
+ "BounceIn", "BounceInUp", "BounceInDown", "BounceInLeft", "BounceInRight",
103
+ "FlipInXUp", "FlipInYLeft", "FlipInXDown", "FlipInYRight", "FlipInEasyX", "FlipInEasyY",
104
+ "StretchInX", "StretchInY",
105
+ "RotateInDownLeft", "RotateInDownRight", "RotateInUpLeft", "RotateInUpRight",
106
+ "RollInLeft", "RollInRight",
107
+ "PinwheelIn",
108
+ "LightSpeedInLeft", "LightSpeedInRight",
109
+ ]);
110
+
111
+ // Exact reanimated exiting builder names.
112
+ export type ExitingPreset =
113
+ | "FadeOut" | "FadeOutUp" | "FadeOutDown" | "FadeOutLeft" | "FadeOutRight"
114
+ | "SlideOutUp" | "SlideOutDown" | "SlideOutLeft" | "SlideOutRight"
115
+ | "ZoomOut" | "ZoomOutRotate" | "ZoomOutUp" | "ZoomOutDown" | "ZoomOutLeft" | "ZoomOutRight"
116
+ | "ZoomOutEasyUp" | "ZoomOutEasyDown"
117
+ | "BounceOut" | "BounceOutUp" | "BounceOutDown" | "BounceOutLeft" | "BounceOutRight"
118
+ | "FlipOutXUp" | "FlipOutYLeft" | "FlipOutXDown" | "FlipOutYRight" | "FlipOutEasyX" | "FlipOutEasyY"
119
+ | "StretchOutX" | "StretchOutY"
120
+ | "RotateOutDownLeft" | "RotateOutDownRight" | "RotateOutUpLeft" | "RotateOutUpRight"
121
+ | "RollOutLeft" | "RollOutRight"
122
+ | "PinwheelOut"
123
+ | "LightSpeedOutLeft" | "LightSpeedOutRight";
124
+
125
+ const ExitingPresetSchema = z.enum([
126
+ "FadeOut", "FadeOutUp", "FadeOutDown", "FadeOutLeft", "FadeOutRight",
127
+ "SlideOutUp", "SlideOutDown", "SlideOutLeft", "SlideOutRight",
128
+ "ZoomOut", "ZoomOutRotate", "ZoomOutUp", "ZoomOutDown", "ZoomOutLeft", "ZoomOutRight",
129
+ "ZoomOutEasyUp", "ZoomOutEasyDown",
130
+ "BounceOut", "BounceOutUp", "BounceOutDown", "BounceOutLeft", "BounceOutRight",
131
+ "FlipOutXUp", "FlipOutYLeft", "FlipOutXDown", "FlipOutYRight", "FlipOutEasyX", "FlipOutEasyY",
132
+ "StretchOutX", "StretchOutY",
133
+ "RotateOutDownLeft", "RotateOutDownRight", "RotateOutUpLeft", "RotateOutUpRight",
134
+ "RollOutLeft", "RollOutRight",
135
+ "PinwheelOut",
136
+ "LightSpeedOutLeft", "LightSpeedOutRight",
137
+ ]);
138
+
139
+ // Exact reanimated layout-transition builder names.
140
+ export type LayoutPreset =
141
+ | "LinearTransition" | "FadingTransition" | "SequencedTransition"
142
+ | "JumpingTransition" | "CurvedTransition" | "EntryExitTransition";
143
+
144
+ const LayoutPresetSchema = z.enum([
145
+ "LinearTransition", "FadingTransition", "SequencedTransition",
146
+ "JumpingTransition", "CurvedTransition", "EntryExitTransition",
147
+ ]);
148
+
149
+ export type EnteringAnimation = {
150
+ preset: EnteringPreset;
151
+ duration?: number;
152
+ delay?: number;
153
+ easing?: AnimationEasing;
154
+ spring?: SpringConfig;
155
+ };
156
+
157
+ const EnteringAnimationSchema = z.object({
158
+ preset: EnteringPresetSchema,
159
+ duration: z.number().min(0).optional(),
160
+ delay: z.number().min(0).optional(),
161
+ easing: AnimationEasingSchema.optional(),
162
+ spring: SpringConfigSchema.optional(),
163
+ });
164
+
165
+ export type ExitingAnimation = {
166
+ preset: ExitingPreset;
167
+ duration?: number;
168
+ delay?: number;
169
+ easing?: AnimationEasing;
170
+ spring?: SpringConfig;
171
+ };
172
+
173
+ const ExitingAnimationSchema = z.object({
174
+ preset: ExitingPresetSchema,
175
+ duration: z.number().min(0).optional(),
176
+ delay: z.number().min(0).optional(),
177
+ easing: AnimationEasingSchema.optional(),
178
+ spring: SpringConfigSchema.optional(),
179
+ });
180
+
181
+ export type LayoutAnimation = {
182
+ preset: LayoutPreset;
183
+ duration?: number;
184
+ spring?: SpringConfig;
185
+ };
186
+
187
+ const LayoutAnimationSchema = z.object({
188
+ preset: LayoutPresetSchema,
189
+ duration: z.number().min(0).optional(),
190
+ spring: SpringConfigSchema.optional(),
191
+ });
192
+
193
+ // Continuous looping effects — the one piece not named after a reanimated
194
+ // builder. Rendered imperatively with `withRepeat` over `withTiming`.
195
+ export type EffectPreset = "pulse" | "fade" | "rotate" | "shimmer" | "bounce";
196
+
197
+ const EffectPresetSchema = z.enum(["pulse", "fade", "rotate", "shimmer", "bounce"]);
198
+
199
+ export type ElementEffect = {
200
+ preset: EffectPreset;
201
+ duration?: number;
202
+ delay?: number;
203
+ easing?: AnimationEasing;
204
+ loop?: boolean;
205
+ /** pulse: scale bounds (default 0.95 / 1.05). */
206
+ minScale?: number;
207
+ maxScale?: number;
208
+ /** fade: lower opacity bound (default 0.4). */
209
+ minOpacity?: number;
210
+ /** rotate: sweep in degrees (default 360). */
211
+ degrees?: number;
212
+ };
213
+
214
+ const EffectSchema = z.object({
215
+ preset: EffectPresetSchema,
216
+ duration: z.number().min(0).optional(),
217
+ delay: z.number().min(0).optional(),
218
+ easing: AnimationEasingSchema.optional(),
219
+ loop: z.boolean().optional(),
220
+ minScale: z.number().positive().optional(),
221
+ maxScale: z.number().positive().optional(),
222
+ minOpacity: z.number().min(0).max(1).optional(),
223
+ degrees: z.number().optional(),
224
+ });
225
+
226
+ export type ElementAnimation = {
227
+ entering?: EnteringAnimation;
228
+ exiting?: ExitingAnimation;
229
+ layout?: LayoutAnimation;
230
+ effect?: ElementEffect;
231
+ };
232
+
233
+ const ElementAnimationSchema = z.object({
234
+ entering: EnteringAnimationSchema.optional(),
235
+ exiting: ExitingAnimationSchema.optional(),
236
+ layout: LayoutAnimationSchema.optional(),
237
+ effect: EffectSchema.optional(),
238
+ });
239
+
240
+ // Static transform surface — also what `effect` animates at runtime.
241
+ export type ElementTransform = {
242
+ translateX?: number;
243
+ translateY?: number;
244
+ scale?: number;
245
+ scaleX?: number;
246
+ scaleY?: number;
247
+ /** degrees */
248
+ rotate?: number;
249
+ };
250
+
251
+ const TransformSchema = z.object({
252
+ translateX: z.number().optional(),
253
+ translateY: z.number().optional(),
254
+ scale: z.number().optional(),
255
+ scaleX: z.number().optional(),
256
+ scaleY: z.number().optional(),
257
+ rotate: z.number().optional(),
258
+ });
259
+
53
260
  export type BaseBoxProps = {
54
261
  width?: number | string;
55
262
  height?: number | string;
@@ -80,6 +287,8 @@ export type BaseBoxProps = {
80
287
  shadowOpacity?: number;
81
288
  shadowRadius?: number;
82
289
  elevation?: number;
290
+ transform?: ElementTransform;
291
+ animation?: ElementAnimation;
83
292
  };
84
293
 
85
294
  export const BaseBoxPropsSchema = z.object({
@@ -112,4 +321,6 @@ export const BaseBoxPropsSchema = z.object({
112
321
  shadowOpacity: z.number().min(0).max(1).optional(),
113
322
  shadowRadius: z.number().min(0).optional(),
114
323
  elevation: z.number().min(0).optional(),
324
+ transform: TransformSchema.optional(),
325
+ animation: ElementAnimationSchema.optional(),
115
326
  });
@@ -10,13 +10,13 @@ import Animated, {
10
10
  withRepeat,
11
11
  withDelay,
12
12
  runOnJS,
13
- Easing,
14
13
  cancelAnimation,
15
14
  } from "react-native-reanimated";
16
15
  import Svg, { Circle } from "react-native-svg";
17
16
  import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
18
17
  import { UIElement } from "../types";
19
18
  import { RenderContext, dim } from "./shared";
19
+ import { EASING_MAP } from "./buildAnimation";
20
20
 
21
21
  export type ProgressEasing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
22
22
 
@@ -60,14 +60,6 @@ export const ProgressIndicatorElementPropsSchema = BaseBoxPropsSchema.extend({
60
60
 
61
61
  const AnimatedCircle = Animated.createAnimatedComponent(Circle);
62
62
 
63
- // CSS-style cubic-bezier curves matching the selectable easing names.
64
- const EASING_MAP: Record<ProgressEasing, ReturnType<typeof Easing.bezier> | typeof Easing.linear> = {
65
- linear: Easing.linear,
66
- "ease-in": Easing.bezier(0.42, 0, 1, 1),
67
- "ease-out": Easing.bezier(0, 0, 0.58, 1),
68
- "ease-in-out": Easing.bezier(0.42, 0, 0.58, 1),
69
- };
70
-
71
63
  const clamp = (n: number): number => Math.max(0, Math.min(100, n));
72
64
 
73
65
  type ProgressUIElement = Extract<UIElement, { type: "ProgressIndicator" }>;
@@ -0,0 +1,177 @@
1
+ import React from "react";
2
+ import { z } from "zod";
3
+ import { evaluateCondition } from "@rocapine/react-native-onboarding";
4
+ import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
5
+ import { UIElement } from "../types";
6
+ import { RenderContext, dim, interpolate, RichTextStyleContext, InheritedTextStyle } from "./shared";
7
+ import { GradientBox } from "./GradientBox";
8
+
9
+ // Mirror of the headless RichTextElement schema. Kept in lockstep with
10
+ // packages/onboarding/src/steps/ComposableScreen/elements/RichTextElement.ts —
11
+ // TS won't catch drift because this re-declares its own type.
12
+ export type RichTextElementProps = BaseBoxProps & {
13
+ gap?: number;
14
+ alignItems?: "flex-start" | "center" | "flex-end" | "baseline" | "stretch";
15
+ justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-around";
16
+ flexWrap?: "wrap" | "nowrap";
17
+ fontSize?: number;
18
+ fontWeight?: string;
19
+ fontFamily?: string | "inherit";
20
+ fontStyle?: "normal" | "italic";
21
+ color?: string;
22
+ textAlign?: "left" | "center" | "right";
23
+ letterSpacing?: number;
24
+ lineHeight?: number;
25
+ };
26
+
27
+ export const RichTextElementPropsSchema = BaseBoxPropsSchema.extend({
28
+ gap: z.number().optional(),
29
+ alignItems: z.enum(["flex-start", "center", "flex-end", "baseline", "stretch"]).optional(),
30
+ justifyContent: z.enum(["flex-start", "center", "flex-end", "space-between", "space-around"]).optional(),
31
+ flexWrap: z.enum(["wrap", "nowrap"]).optional(),
32
+ fontSize: z.number().optional(),
33
+ fontWeight: z.string().optional(),
34
+ fontFamily: z.string().optional(),
35
+ fontStyle: z.enum(["normal", "italic"]).optional(),
36
+ color: z.string().optional(),
37
+ textAlign: z.enum(["left", "center", "right"]).optional(),
38
+ letterSpacing: z.number().optional(),
39
+ lineHeight: z.number().optional(),
40
+ });
41
+
42
+ type RichTextUIElement = Extract<UIElement, { type: "RichText" }>;
43
+ type TextChild = Extract<UIElement, { type: "Text" }>;
44
+
45
+ type Props = {
46
+ element: RichTextUIElement;
47
+ ctx: RenderContext;
48
+ parentType?: "XStack" | "YStack" | "ZStack" | "RichText";
49
+ };
50
+
51
+ // A plain-text child (no box styling, no motion) is split into one flex item per
52
+ // word so the row wraps word-by-word like real text — the chip pattern from
53
+ // host apps (parseTitleWithChips). A child carrying box styling (backgroundColor
54
+ // / borderRadius / border / padding) or motion is a "chip" and stays atomic so
55
+ // its box renders as a single rounded unit.
56
+ const isFlowingText = (child: TextChild): boolean => {
57
+ const p = child.props;
58
+ return (
59
+ typeof p.content === "string" &&
60
+ p.backgroundColor == null &&
61
+ p.backgroundGradient == null &&
62
+ p.borderRadius == null &&
63
+ p.borderWidth == null &&
64
+ p.padding == null &&
65
+ p.paddingHorizontal == null &&
66
+ p.paddingVertical == null &&
67
+ // margin / explicit size would be applied to *every* word if split, so a
68
+ // child carrying any of them is treated as an atomic chip too.
69
+ p.margin == null &&
70
+ p.marginHorizontal == null &&
71
+ p.marginVertical == null &&
72
+ p.width == null &&
73
+ p.height == null &&
74
+ p.minWidth == null &&
75
+ p.maxWidth == null &&
76
+ p.minHeight == null &&
77
+ p.maxHeight == null &&
78
+ p.animation == null &&
79
+ p.transform == null
80
+ );
81
+ };
82
+
83
+ /**
84
+ * Wrapping flex row of child `Text` elements. Plain-text children are split into
85
+ * per-word flex items so text wraps word-by-word (like a paragraph); box-styled
86
+ * children render as atomic "chips" (padded/rounded/rotated pills) that honor
87
+ * their own box props, `renderWhen`, `expression`, and motion. The result is a
88
+ * title where chips sit inline with naturally-wrapping words.
89
+ *
90
+ * The container's text-style props are published via `RichTextStyleContext` so
91
+ * every child `Text` inherits them as defaults (child overrides win).
92
+ */
93
+ export const RichTextElementComponent = ({ element, ctx, parentType }: Props): React.ReactElement => {
94
+ const p = element.props;
95
+
96
+ const inheritedTextStyle: InheritedTextStyle = {
97
+ fontSize: p.fontSize,
98
+ fontWeight: p.fontWeight,
99
+ fontFamily: p.fontFamily,
100
+ fontStyle: p.fontStyle,
101
+ color: p.color,
102
+ textAlign: p.textAlign,
103
+ letterSpacing: p.letterSpacing,
104
+ lineHeight: p.lineHeight,
105
+ };
106
+
107
+ // Expand children into the actual flex items to render: split flowing text into
108
+ // words, keep chips whole. renderWhen is evaluated once per source child here
109
+ // (flowing-text words then render unconditionally); chips keep their renderWhen
110
+ // and are gated by renderElement.
111
+ const flatVars = Object.fromEntries(
112
+ Object.entries(ctx.variables).map(([k, v]) => [k, v?.value])
113
+ );
114
+ const expanded: UIElement[] = [];
115
+ for (const child of element.children) {
116
+ if (child.renderWhen && !evaluateCondition(child.renderWhen, flatVars)) continue;
117
+
118
+ if (isFlowingText(child)) {
119
+ const raw = child.props.content as string;
120
+ const text = child.props.mode === "expression" ? interpolate(raw, ctx.variables) : raw;
121
+ const tokens = text.split(/(\s+)/).filter((t) => t.length > 0);
122
+ tokens.forEach((tok, i) => {
123
+ expanded.push({
124
+ ...child,
125
+ id: `${child.id}-w${i}`,
126
+ renderWhen: undefined,
127
+ // mode dropped (undefined): content is already interpolated above, and
128
+ // undefined is truer to the schema than the non-enum "plain".
129
+ props: { ...child.props, content: tok, mode: undefined },
130
+ });
131
+ });
132
+ } else {
133
+ // Chip: renderWhen already passed above; null it so renderElement doesn't
134
+ // re-evaluate the same condition (symmetry with the word path).
135
+ expanded.push({ ...child, renderWhen: undefined });
136
+ }
137
+ }
138
+
139
+ return (
140
+ <GradientBox
141
+ gradient={p.backgroundGradient}
142
+ style={{
143
+ flexDirection: "row",
144
+ flexWrap: p.flexWrap ?? "wrap",
145
+ gap: p.gap,
146
+ alignItems: p.alignItems ?? "center",
147
+ justifyContent: p.justifyContent ?? "center",
148
+ flex: p.flex,
149
+ flexShrink: p.flexShrink ?? (parentType === "XStack" ? 1 : undefined),
150
+ flexGrow: p.flexGrow,
151
+ alignSelf: p.alignSelf,
152
+ width: dim(p.width),
153
+ height: dim(p.height),
154
+ minWidth: p.minWidth,
155
+ maxWidth: p.maxWidth,
156
+ minHeight: p.minHeight,
157
+ maxHeight: p.maxHeight,
158
+ padding: p.padding,
159
+ paddingHorizontal: p.paddingHorizontal,
160
+ paddingVertical: p.paddingVertical,
161
+ margin: p.margin,
162
+ marginHorizontal: p.marginHorizontal,
163
+ marginVertical: p.marginVertical,
164
+ backgroundColor: p.backgroundGradient ? undefined : p.backgroundColor,
165
+ borderWidth: p.borderWidth,
166
+ borderRadius: p.borderRadius,
167
+ borderColor: p.borderColor,
168
+ overflow: p.overflow,
169
+ opacity: p.opacity,
170
+ }}
171
+ >
172
+ <RichTextStyleContext.Provider value={inheritedTextStyle}>
173
+ {ctx.renderChildren(expanded, "RichText")}
174
+ </RichTextStyleContext.Provider>
175
+ </GradientBox>
176
+ );
177
+ };
@@ -24,7 +24,7 @@ type StackUIElement = Extract<UIElement, { type: "YStack" | "XStack" }>;
24
24
  type Props = {
25
25
  element: StackUIElement;
26
26
  ctx: RenderContext;
27
- parentType?: "XStack" | "YStack" | "ZStack";
27
+ parentType?: "XStack" | "YStack" | "ZStack" | "RichText";
28
28
  };
29
29
 
30
30
  export const StackElementComponent = ({ element, ctx, parentType }: Props): React.ReactElement => {
@@ -4,7 +4,7 @@ import { Text } from "react-native";
4
4
  import { useResolvedFontStyle } from "@rocapine/react-native-onboarding";
5
5
  import { BaseBoxProps, BaseBoxPropsSchema } from "./BaseBoxProps";
6
6
  import { UIElement } from "../types";
7
- import { RenderContext, interpolate, dim, resolveInheritedFontFamily } from "./shared";
7
+ import { RenderContext, interpolate, dim, resolveInheritedFontFamily, RichTextStyleContext } from "./shared";
8
8
  import { GradientBox } from "./GradientBox";
9
9
 
10
10
  export type TextSpan = {
@@ -14,12 +14,18 @@ export type TextSpan = {
14
14
  fontFamily?: string | "inherit";
15
15
  fontSize?: number;
16
16
  letterSpacing?: number;
17
+ lineHeight?: number;
17
18
  color?: string;
19
+ backgroundColor?: string;
20
+ opacity?: number;
21
+ textTransform?: "none" | "uppercase" | "lowercase" | "capitalize";
18
22
  textDecorationLine?:
19
23
  | "none"
20
24
  | "underline"
21
25
  | "line-through"
22
26
  | "underline line-through";
27
+ textDecorationColor?: string;
28
+ textDecorationStyle?: "solid" | "double" | "dotted" | "dashed";
23
29
  };
24
30
 
25
31
  export const TextSpanSchema = z.object({
@@ -29,10 +35,16 @@ export const TextSpanSchema = z.object({
29
35
  fontFamily: z.string().optional(),
30
36
  fontSize: z.number().optional(),
31
37
  letterSpacing: z.number().optional(),
38
+ lineHeight: z.number().optional(),
32
39
  color: z.string().optional(),
40
+ backgroundColor: z.string().optional(),
41
+ opacity: z.number().min(0).max(1).optional(),
42
+ textTransform: z.enum(["none", "uppercase", "lowercase", "capitalize"]).optional(),
33
43
  textDecorationLine: z
34
44
  .enum(["none", "underline", "line-through", "underline line-through"])
35
45
  .optional(),
46
+ textDecorationColor: z.string().optional(),
47
+ textDecorationStyle: z.enum(["solid", "double", "dotted", "dashed"]).optional(),
36
48
  });
37
49
 
38
50
  export type TextElementProps = BaseBoxProps & {
@@ -86,8 +98,14 @@ const RichTextSpan = ({
86
98
  fontStyle: span.fontStyle,
87
99
  fontSize: span.fontSize,
88
100
  letterSpacing: span.letterSpacing,
101
+ lineHeight: span.lineHeight,
89
102
  color: span.color,
103
+ backgroundColor: span.backgroundColor,
104
+ opacity: span.opacity,
105
+ textTransform: span.textTransform,
90
106
  textDecorationLine: span.textDecorationLine,
107
+ textDecorationColor: span.textDecorationColor,
108
+ textDecorationStyle: span.textDecorationStyle,
91
109
  }}
92
110
  >
93
111
  {span.text}
@@ -100,12 +118,22 @@ type TextUIElement = Extract<UIElement, { type: "Text" }>;
100
118
  type Props = {
101
119
  element: TextUIElement;
102
120
  ctx: RenderContext;
103
- parentType?: "XStack" | "YStack" | "ZStack";
121
+ parentType?: "XStack" | "YStack" | "ZStack" | "RichText";
104
122
  };
105
123
 
106
124
  export const TextElementComponent = ({ element, ctx, parentType }: Props): React.ReactElement => {
107
125
  const { theme, variables } = ctx;
108
126
  const p = element.props;
127
+ // Text-style defaults inherited from a parent `RichText` container (empty when
128
+ // this Text isn't inside one). Element props always win over inherited values.
129
+ const inherited = React.useContext(RichTextStyleContext);
130
+ const fontSize = p.fontSize ?? inherited.fontSize;
131
+ const fontWeight = p.fontWeight ?? inherited.fontWeight;
132
+ const fontStyle = p.fontStyle ?? inherited.fontStyle;
133
+ const color = p.color ?? inherited.color;
134
+ const textAlign = p.textAlign ?? inherited.textAlign;
135
+ const letterSpacing = p.letterSpacing ?? inherited.letterSpacing;
136
+ const lineHeight = p.lineHeight ?? inherited.lineHeight;
109
137
  const isExpression = p.mode === "expression";
110
138
  const content: string | TextSpan[] = Array.isArray(p.content)
111
139
  ? isExpression
@@ -115,10 +143,10 @@ export const TextElementComponent = ({ element, ctx, parentType }: Props): React
115
143
  ? interpolate(p.content, variables)
116
144
  : p.content;
117
145
  const inheritedFontFamily = resolveInheritedFontFamily(
118
- p.fontFamily,
146
+ p.fontFamily ?? inherited.fontFamily,
119
147
  theme.typography.defaultFontFamily
120
148
  );
121
- const resolvedFont = useResolvedFontStyle(inheritedFontFamily, p.fontWeight);
149
+ const resolvedFont = useResolvedFontStyle(inheritedFontFamily, fontWeight);
122
150
 
123
151
  const textNode = (
124
152
  <Text
@@ -133,14 +161,14 @@ export const TextElementComponent = ({ element, ctx, parentType }: Props): React
133
161
  maxWidth: p.backgroundGradient ? undefined : p.maxWidth,
134
162
  minHeight: p.backgroundGradient ? undefined : p.minHeight,
135
163
  maxHeight: p.backgroundGradient ? undefined : p.maxHeight,
136
- fontSize: p.fontSize,
137
- fontWeight: resolvedFont.resolvedToVariant ? undefined : (p.fontWeight as any),
164
+ fontSize,
165
+ fontWeight: resolvedFont.resolvedToVariant ? undefined : (fontWeight as any),
138
166
  fontFamily: resolvedFont.fontFamily,
139
- fontStyle: p.fontStyle,
140
- color: p.color ?? theme.colors.text.primary,
141
- textAlign: p.textAlign,
142
- letterSpacing: p.letterSpacing,
143
- lineHeight: p.lineHeight,
167
+ fontStyle,
168
+ color: color ?? theme.colors.text.primary,
169
+ textAlign,
170
+ letterSpacing,
171
+ lineHeight,
144
172
  backgroundColor: p.backgroundGradient ? undefined : p.backgroundColor,
145
173
  padding: p.backgroundGradient ? undefined : p.padding,
146
174
  paddingHorizontal: p.backgroundGradient ? undefined : p.paddingHorizontal,
@@ -0,0 +1,83 @@
1
+ import * as Reanimated from "react-native-reanimated";
2
+ import { Easing } from "react-native-reanimated";
3
+ import type {
4
+ AnimationEasing,
5
+ EnteringAnimation,
6
+ ExitingAnimation,
7
+ LayoutAnimation,
8
+ } from "@rocapine/react-native-onboarding";
9
+
10
+ // CSS-style cubic-bezier curves matching the selectable easing names. Shared by
11
+ // the animation builders here and by ProgressIndicatorElement (single source).
12
+ export const EASING_MAP: Record<
13
+ AnimationEasing,
14
+ ReturnType<typeof Easing.bezier> | typeof Easing.linear
15
+ > = {
16
+ linear: Easing.linear,
17
+ "ease-in": Easing.bezier(0.42, 0, 1, 1),
18
+ "ease-out": Easing.bezier(0, 0, 0.58, 1),
19
+ "ease-in-out": Easing.bezier(0.42, 0, 0.58, 1),
20
+ };
21
+
22
+ // Reanimated builders are exported by name off the namespace. Looking them up by
23
+ // the schema's `preset` string is what keeps the JSON 1:1 with reanimated — the
24
+ // preset value *is* the builder name. Unknown/typo presets degrade to `undefined`
25
+ // (no animation) instead of crashing, so a payload referencing a preset the host's
26
+ // installed reanimated version lacks still mounts.
27
+ type AnyBuilder = any;
28
+
29
+ const resolveBuilder = (preset: string): AnyBuilder | undefined => {
30
+ const b = (Reanimated as unknown as Record<string, AnyBuilder>)[preset];
31
+ return b ?? undefined;
32
+ };
33
+
34
+ const applySpringOrEasing = (
35
+ builder: AnyBuilder,
36
+ spring?: { damping?: number; stiffness?: number; mass?: number },
37
+ easing?: AnimationEasing
38
+ ): AnyBuilder => {
39
+ // reanimated: `spring` (`.springify()`) and `.easing()` are mutually exclusive.
40
+ // Spring wins, matching the schema contract.
41
+ if (spring) {
42
+ let b = builder.springify();
43
+ if (spring.damping != null) b = b.damping(spring.damping);
44
+ if (spring.stiffness != null) b = b.stiffness(spring.stiffness);
45
+ if (spring.mass != null) b = b.mass(spring.mass);
46
+ return b;
47
+ }
48
+ if (easing) return builder.easing(EASING_MAP[easing]);
49
+ return builder;
50
+ };
51
+
52
+ const buildTransition = (
53
+ cfg: EnteringAnimation | ExitingAnimation | undefined
54
+ ): AnyBuilder | undefined => {
55
+ if (!cfg) return undefined;
56
+ let b = resolveBuilder(cfg.preset);
57
+ if (!b) return undefined;
58
+ if (cfg.duration != null) b = b.duration(cfg.duration);
59
+ if (cfg.delay != null) b = b.delay(cfg.delay);
60
+ return applySpringOrEasing(b, cfg.spring, cfg.easing);
61
+ };
62
+
63
+ export const buildEntering = (cfg: EnteringAnimation | undefined): AnyBuilder | undefined =>
64
+ buildTransition(cfg);
65
+
66
+ export const buildExiting = (cfg: ExitingAnimation | undefined): AnyBuilder | undefined =>
67
+ buildTransition(cfg);
68
+
69
+ export const buildLayout = (cfg: LayoutAnimation | undefined): AnyBuilder | undefined => {
70
+ if (!cfg) return undefined;
71
+ let b = resolveBuilder(cfg.preset);
72
+ if (!b) return undefined;
73
+ // Layout transitions are referenced as static builder objects; calling a
74
+ // modifier returns a configured instance.
75
+ if (cfg.duration != null) b = b.duration(cfg.duration);
76
+ if (cfg.spring) {
77
+ b = b.springify();
78
+ if (cfg.spring.damping != null) b = b.damping(cfg.spring.damping);
79
+ if (cfg.spring.stiffness != null) b = b.stiffness(cfg.spring.stiffness);
80
+ if (cfg.spring.mass != null) b = b.mass(cfg.spring.mass);
81
+ }
82
+ return b;
83
+ };