@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.
- package/dist/UI/Pages/ComposableScreen/Renderer.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.d.ts +21 -0
- package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.js +140 -0
- package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts +220 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js +83 -0
- package/dist/UI/Pages/ComposableScreen/elements/BaseBoxProps.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts +640 -0
- package/dist/UI/Pages/ComposableScreen/elements/ButtonElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/CarouselElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/CheckboxGroupElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/DatePickerElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/IconElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/ImageElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/InputElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/KeyboardAvoidingViewElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/KeyboardAvoidingViewElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/LottieElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.js +2 -8
- package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/RadioGroupElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.d.ts +305 -0
- package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.js +133 -0
- package/dist/UI/Pages/ComposableScreen/elements/RichTextElement.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/RiveElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/SafeAreaViewElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ScrollViewElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/ScrollViewElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts +161 -1
- package/dist/UI/Pages/ComposableScreen/elements/StackElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts +199 -1
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.js +38 -12
- package/dist/UI/Pages/ComposableScreen/elements/TextElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/VideoElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts +160 -0
- package/dist/UI/Pages/ComposableScreen/elements/ZStackElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.d.ts +9 -0
- package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.d.ts.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.js +106 -0
- package/dist/UI/Pages/ComposableScreen/elements/buildAnimation.js.map +1 -0
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +76 -58
- package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts +12 -1
- package/dist/UI/Pages/ComposableScreen/elements/shared.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/elements/shared.js +6 -1
- package/dist/UI/Pages/ComposableScreen/elements/shared.js.map +1 -1
- package/dist/UI/Pages/ComposableScreen/types.d.ts +11 -0
- package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
- package/dist/UI/Pages/ComposableScreen/types.js +15 -2
- package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
- package/package.json +1 -1
- package/src/UI/Pages/ComposableScreen/Renderer.tsx +1 -1
- package/src/UI/Pages/ComposableScreen/elements/AnimatedBox.tsx +133 -0
- package/src/UI/Pages/ComposableScreen/elements/BaseBoxProps.ts +211 -0
- package/src/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.tsx +1 -9
- package/src/UI/Pages/ComposableScreen/elements/RichTextElement.tsx +177 -0
- package/src/UI/Pages/ComposableScreen/elements/StackElement.tsx +1 -1
- package/src/UI/Pages/ComposableScreen/elements/TextElement.tsx +39 -11
- package/src/UI/Pages/ComposableScreen/elements/buildAnimation.ts +83 -0
- package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +34 -2
- package/src/UI/Pages/ComposableScreen/elements/shared.ts +19 -1
- 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,
|
|
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
|
|
137
|
-
fontWeight: resolvedFont.resolvedToVariant ? undefined : (
|
|
164
|
+
fontSize,
|
|
165
|
+
fontWeight: resolvedFont.resolvedToVariant ? undefined : (fontWeight as any),
|
|
138
166
|
fontFamily: resolvedFont.fontFamily,
|
|
139
|
-
fontStyle
|
|
140
|
-
color:
|
|
141
|
-
textAlign
|
|
142
|
-
letterSpacing
|
|
143
|
-
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
|
+
};
|