@rocapine/react-native-onboarding-ui 1.38.1 → 1.39.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 (23) hide show
  1. package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.d.ts.map +1 -1
  2. package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.js +9 -3
  3. package/dist/UI/Pages/ComposableScreen/elements/AnimatedBox.js.map +1 -1
  4. package/dist/UI/Pages/ComposableScreen/elements/AnimatedTextElement.d.ts +319 -0
  5. package/dist/UI/Pages/ComposableScreen/elements/AnimatedTextElement.d.ts.map +1 -0
  6. package/dist/UI/Pages/ComposableScreen/elements/AnimatedTextElement.js +173 -0
  7. package/dist/UI/Pages/ComposableScreen/elements/AnimatedTextElement.js.map +1 -0
  8. package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.d.ts.map +1 -1
  9. package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.js +31 -21
  10. package/dist/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.js.map +1 -1
  11. package/dist/UI/Pages/ComposableScreen/elements/renderElement.d.ts.map +1 -1
  12. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js +4 -0
  13. package/dist/UI/Pages/ComposableScreen/elements/renderElement.js.map +1 -1
  14. package/dist/UI/Pages/ComposableScreen/types.d.ts +8 -0
  15. package/dist/UI/Pages/ComposableScreen/types.d.ts.map +1 -1
  16. package/dist/UI/Pages/ComposableScreen/types.js +8 -0
  17. package/dist/UI/Pages/ComposableScreen/types.js.map +1 -1
  18. package/package.json +1 -1
  19. package/src/UI/Pages/ComposableScreen/elements/AnimatedBox.tsx +9 -3
  20. package/src/UI/Pages/ComposableScreen/elements/AnimatedTextElement.tsx +191 -0
  21. package/src/UI/Pages/ComposableScreen/elements/ProgressIndicatorElement.tsx +51 -28
  22. package/src/UI/Pages/ComposableScreen/elements/renderElement.tsx +5 -0
  23. package/src/UI/Pages/ComposableScreen/types.ts +19 -0
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useState } from "react";
2
- import { View, Text } from "react-native";
1
+ import React, { useEffect } from "react";
2
+ import { View, TextInput } from "react-native";
3
3
  import { z } from "zod";
4
4
  import Animated, {
5
5
  useSharedValue,
@@ -67,6 +67,11 @@ export const ProgressIndicatorElementPropsSchema = BaseBoxPropsSchema.extend({
67
67
  });
68
68
 
69
69
  const AnimatedCircle = Animated.createAnimatedComponent(Circle);
70
+ // Native TextInput label driven from a worklet (like AnimatedText) so the
71
+ // `showLabel` value updates on the UI thread with NO React re-render — a
72
+ // `useState` label would re-render this component on every step hop and churn
73
+ // the reanimated mapper scheduler (destabilizing other on-screen animations).
74
+ const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
70
75
 
71
76
  const clamp = (n: number, min: number, max: number): number => Math.max(min, Math.min(max, n));
72
77
 
@@ -98,11 +103,6 @@ export const ProgressIndicatorElementComponent = ({ element, ctx }: Props): Reac
98
103
  const trackColor = props.trackColor ?? theme.colors.neutral.lower;
99
104
  const labelColor = props.labelColor ?? theme.colors.text.primary;
100
105
 
101
- // Snap a raw value to `step` within [minValue, maxValue]. The label and the
102
- // written variable carry the snapped value, not a percentage.
103
- const snap = (v: number): number =>
104
- clamp(minValue + Math.round((v - minValue) / step) * step, minValue, maxValue);
105
-
106
106
  // Bound variable value (input mode, non-autoplay) or static value. `progress`
107
107
  // is the value in [minValue, maxValue]; the fill fraction is derived from it.
108
108
  const boundRaw = props.variableName ? variables[props.variableName]?.value : undefined;
@@ -110,9 +110,10 @@ export const ProgressIndicatorElementComponent = ({ element, ctx }: Props): Reac
110
110
  const target = autoplay ? maxValue : clamp(boundValue ?? props.value ?? initialValue, minValue, maxValue);
111
111
 
112
112
  const progress = useSharedValue(initialValue);
113
- const [displayValue, setDisplayValue] = useState(snap(initialValue));
114
113
 
115
- // Mirror the animated value into a label + (autoplay) the bound variable.
114
+ // (autoplay) Write the step-snapped value to the bound variable. The label is
115
+ // rendered natively (see labelAnimatedProps below), so it does NOT go through
116
+ // React state — this reaction's ONLY job is the variable write.
116
117
  // Reaction input is the *step-snapped* value, so the JS callback fires only
117
118
  // when the snapped value changes ((maxValue-minValue)/step hops/sweep) rather
118
119
  // than every frame — avoids a per-frame context write storm (setVariable
@@ -126,15 +127,11 @@ export const ProgressIndicatorElementComponent = ({ element, ctx }: Props): Reac
126
127
  };
127
128
  const writesVariable = autoplay && !!variableName;
128
129
  // The dependency array is REQUIRED. Without it reanimated tears down and
129
- // rebuilds this mapper on EVERY render. A looping `showLabel` indicator
130
- // re-renders continuously (one setDisplayValue per step hop), so its mapper
131
- // would be start/stopped indefinitely perpetual churn on the UI-thread mapper
132
- // scheduler that destabilizes *other* running animations on the same screen
133
- // (they visibly reset mid/after a sweep — "finishes then resets for no reason").
134
- // Recreating also resets `prev` to undefined, defeating the `snapped === prev`
135
- // guard so the JS callbacks over-fire. Keying on the values the worklet branches
136
- // on (incl. minValue/maxValue/step) keeps the mapper stable; the JS fns it calls
137
- // (setDisplayValue, setVariable via writeVariable) are already stable across renders.
130
+ // rebuilds this mapper on EVERY render, resetting `prev` to undefined and
131
+ // defeating the `snapped === prev` guard so the JS callback over-fires.
132
+ // Keying on the values the worklet branches on (minValue/maxValue/step) keeps
133
+ // the mapper stable; the JS fn it calls (setVariable via writeVariable) is
134
+ // already stable across renders.
138
135
  useAnimatedReaction(
139
136
  () => {
140
137
  // Inline snap (worklet — can't call the JS `snap` closure). Captures the
@@ -144,12 +141,23 @@ export const ProgressIndicatorElementComponent = ({ element, ctx }: Props): Reac
144
141
  },
145
142
  (snapped, prev) => {
146
143
  if (snapped === prev) return;
147
- if (showLabel) runOnJS(setDisplayValue)(snapped);
148
144
  if (writesVariable) runOnJS(writeVariable)(snapped);
149
145
  },
150
- [showLabel, writesVariable, variableName, minValue, maxValue, step]
146
+ [writesVariable, variableName, minValue, maxValue, step]
151
147
  );
152
148
 
149
+ // Native label text, formatted on the UI thread (snapped value + suffix).
150
+ // Mirrors AnimatedText: returns `defaultValue` too so a re-render reconcile
151
+ // can't revert the uncontrolled TextInput to a stale mount-time value.
152
+ const labelAnimatedProps = useAnimatedProps(() => {
153
+ const snapped = Math.max(
154
+ minValue,
155
+ Math.min(maxValue, minValue + Math.round((progress.value - minValue) / step) * step)
156
+ );
157
+ const t = `${snapped}${labelSuffix}`;
158
+ return { text: t, defaultValue: t } as object;
159
+ }, [minValue, maxValue, step, labelSuffix]);
160
+
153
161
  // Autoplay: animate initialValue -> maxValue, optionally looping, after `delay`.
154
162
  useEffect(() => {
155
163
  if (!autoplay) return;
@@ -231,16 +239,24 @@ export const ProgressIndicatorElementComponent = ({ element, ctx }: Props): Reac
231
239
  </Svg>
232
240
  {props.showLabel ? (
233
241
  <View style={{ position: "absolute", alignItems: "center", justifyContent: "center" }}>
234
- <Text
242
+ <AnimatedTextInput
243
+ editable={false}
244
+ pointerEvents="none"
245
+ caretHidden
246
+ contextMenuHidden
247
+ underlineColorAndroid="transparent"
248
+ accessibilityRole="text"
249
+ animatedProps={labelAnimatedProps}
235
250
  style={{
251
+ padding: 0,
252
+ includeFontPadding: false,
253
+ textAlign: "center",
236
254
  color: labelColor,
237
255
  fontSize: theme.typography.textStyles.heading2.fontSize,
238
256
  fontWeight: theme.typography.fontWeight.bold,
239
257
  fontFamily: theme.typography.textStyles.heading2.fontFamily,
240
258
  }}
241
- >
242
- {displayValue}{labelSuffix}
243
- </Text>
259
+ />
244
260
  </View>
245
261
  ) : null}
246
262
  </View>
@@ -269,8 +285,17 @@ export const ProgressIndicatorElementComponent = ({ element, ctx }: Props): Reac
269
285
  />
270
286
  </View>
271
287
  {props.showLabel ? (
272
- <Text
288
+ <AnimatedTextInput
289
+ editable={false}
290
+ pointerEvents="none"
291
+ caretHidden
292
+ contextMenuHidden
293
+ underlineColorAndroid="transparent"
294
+ accessibilityRole="text"
295
+ animatedProps={labelAnimatedProps}
273
296
  style={{
297
+ padding: 0,
298
+ includeFontPadding: false,
274
299
  marginLeft: 12,
275
300
  color: labelColor,
276
301
  fontSize: theme.typography.textStyles.label.fontSize,
@@ -279,9 +304,7 @@ export const ProgressIndicatorElementComponent = ({ element, ctx }: Props): Reac
279
304
  minWidth: 44,
280
305
  textAlign: "right",
281
306
  }}
282
- >
283
- {displayValue}{labelSuffix}
284
- </Text>
307
+ />
285
308
  ) : null}
286
309
  </View>
287
310
  );
@@ -26,6 +26,7 @@ import { SafeAreaViewElementComponent } from "./SafeAreaViewElement";
26
26
  import { ScrollViewElementComponent } from "./ScrollViewElement";
27
27
  import { KeyboardAvoidingViewElementComponent } from "./KeyboardAvoidingViewElement";
28
28
  import { ProgressIndicatorElementComponent } from "./ProgressIndicatorElement";
29
+ import { AnimatedTextElementComponent } from "./AnimatedTextElement";
29
30
  import { AnimatedBox } from "./AnimatedBox";
30
31
 
31
32
  // Element types that own their own press / focus / scroll handling. The generic
@@ -137,6 +138,10 @@ export const renderElement = (
137
138
  return <ProgressIndicatorElementComponent key={element.id} element={element} ctx={ctx} />;
138
139
  }
139
140
 
141
+ if (element.type === "AnimatedText") {
142
+ return <AnimatedTextElementComponent key={element.id} element={element} ctx={ctx} />;
143
+ }
144
+
140
145
  return null;
141
146
  })();
142
147
 
@@ -37,6 +37,10 @@ import {
37
37
  type ProgressIndicatorElementProps,
38
38
  ProgressIndicatorElementPropsSchema,
39
39
  } from "./elements/ProgressIndicatorElement";
40
+ import {
41
+ type AnimatedTextElementProps,
42
+ AnimatedTextElementPropsSchema,
43
+ } from "./elements/AnimatedTextElement";
40
44
 
41
45
  export type { BaseBoxProps } from "./elements/BaseBoxProps";
42
46
  export { BaseBoxPropsSchema } from "./elements/BaseBoxProps";
@@ -70,6 +74,7 @@ export type {
70
74
  KeyboardAvoidingBehavior,
71
75
  } from "./elements/KeyboardAvoidingViewElement";
72
76
  export type { ProgressIndicatorElementProps, ProgressEasing } from "./elements/ProgressIndicatorElement";
77
+ export type { AnimatedTextElementProps } from "./elements/AnimatedTextElement";
73
78
 
74
79
  // UIElement union — must live here (not in elements/) to avoid circular deps
75
80
  // because the Stack variant's children: UIElement[] references itself.
@@ -227,6 +232,13 @@ export type UIElement =
227
232
  renderWhen?: LeafCondition | ConditionGroup;
228
233
  type: "ProgressIndicator";
229
234
  props: ProgressIndicatorElementProps;
235
+ }
236
+ | {
237
+ id: string;
238
+ name?: string;
239
+ renderWhen?: LeafCondition | ConditionGroup;
240
+ type: "AnimatedText";
241
+ props: AnimatedTextElementProps;
230
242
  };
231
243
 
232
244
  // The `Text` variant, extracted so `RichText` can restrict its children to
@@ -390,6 +402,13 @@ export const UIElementSchema: z.ZodType<UIElement> = z.lazy(() =>
390
402
  type: z.literal("ProgressIndicator"),
391
403
  props: ProgressIndicatorElementPropsSchema,
392
404
  }),
405
+ z.object({
406
+ id: z.string(),
407
+ name: z.string().optional(),
408
+ renderWhen: z.union([LeafConditionSchema, ConditionGroupSchema]).optional(),
409
+ type: z.literal("AnimatedText"),
410
+ props: AnimatedTextElementPropsSchema,
411
+ }),
393
412
  ])
394
413
  );
395
414