react-native-laminar 1.4.2 → 1.4.3

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 (35) hide show
  1. package/lib/commonjs/hooks/use-inline-auto-width.js +105 -18
  2. package/lib/commonjs/hooks/use-inline-auto-width.js.map +1 -1
  3. package/lib/commonjs/index.js +5 -1
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/view/morph-viewport.js +2 -0
  6. package/lib/commonjs/view/morph-viewport.js.map +1 -1
  7. package/lib/commonjs/view/text-run.js +30 -12
  8. package/lib/commonjs/view/text-run.js.map +1 -1
  9. package/lib/module/hooks/use-inline-auto-width.js +106 -19
  10. package/lib/module/hooks/use-inline-auto-width.js.map +1 -1
  11. package/lib/module/index.js +5 -1
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/view/morph-viewport.js +2 -0
  14. package/lib/module/view/morph-viewport.js.map +1 -1
  15. package/lib/module/view/text-run.js +30 -12
  16. package/lib/module/view/text-run.js.map +1 -1
  17. package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts +2 -0
  18. package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts.map +1 -1
  19. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/view/morph-viewport.d.ts +3 -2
  21. package/lib/typescript/commonjs/view/morph-viewport.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/view/text-run.d.ts +2 -1
  23. package/lib/typescript/commonjs/view/text-run.d.ts.map +1 -1
  24. package/lib/typescript/module/hooks/use-inline-auto-width.d.ts +2 -0
  25. package/lib/typescript/module/hooks/use-inline-auto-width.d.ts.map +1 -1
  26. package/lib/typescript/module/index.d.ts.map +1 -1
  27. package/lib/typescript/module/view/morph-viewport.d.ts +3 -2
  28. package/lib/typescript/module/view/morph-viewport.d.ts.map +1 -1
  29. package/lib/typescript/module/view/text-run.d.ts +2 -1
  30. package/lib/typescript/module/view/text-run.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/hooks/use-inline-auto-width.ts +160 -18
  33. package/src/index.tsx +10 -1
  34. package/src/view/morph-viewport.tsx +7 -2
  35. package/src/view/text-run.tsx +49 -15
package/src/index.tsx CHANGED
@@ -79,7 +79,13 @@ export const Laminar = React.memo(function Laminar({
79
79
  textStyle,
80
80
  variant,
81
81
  ]);
82
- const { captureLayout, animatedWidthStyle, shouldMeasure } =
82
+ const {
83
+ captureLayout,
84
+ captureVisibleLayout,
85
+ animatedWidthStyle,
86
+ isReady: isAutoSizeReady,
87
+ shouldMeasure,
88
+ } =
83
89
  useInlineAutoWidth({
84
90
  enabled: autoSize,
85
91
  driveToWidth: motionRecipe.driveNumber,
@@ -103,9 +109,11 @@ export const Laminar = React.memo(function Laminar({
103
109
  align={align}
104
110
  containerStyle={containerStyle}
105
111
  animatedWidthStyle={animatedWidthStyle}
112
+ onVisibleLayout={autoSize ? captureVisibleLayout : undefined}
106
113
  measurement={
107
114
  shouldMeasure ? (
108
115
  <View
116
+ key={measurementKey}
109
117
  onLayout={captureLayout}
110
118
  style={{ flexDirection: "row", alignSelf: "flex-start" }}
111
119
  >
@@ -157,6 +165,7 @@ export const Laminar = React.memo(function Laminar({
157
165
  leading={resolvedLeading}
158
166
  leadingKey={resolvedLeadingKey}
159
167
  leadingGap={leadingGap}
168
+ ready={!autoSize || isAutoSizeReady}
160
169
  textStyle={textStyle}
161
170
  className={className}
162
171
  />
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo } from "react";
2
- import type { StyleProp, ViewStyle } from "react-native";
2
+ import type { LayoutChangeEvent, StyleProp, ViewStyle } from "react-native";
3
3
  import { View } from "react-native";
4
4
  import Animated from "react-native-reanimated";
5
5
  import type { LaminarAlign } from "../types";
@@ -52,6 +52,7 @@ type MorphViewportProps = {
52
52
  readonly align: LaminarAlign;
53
53
  readonly containerStyle?: StyleProp<ViewStyle>;
54
54
  readonly animatedWidthStyle?: React.ComponentProps<typeof Animated.View>["style"];
55
+ readonly onVisibleLayout?: (event: LayoutChangeEvent) => void;
55
56
  readonly measurement?: React.ReactNode;
56
57
  readonly children: React.ReactNode;
57
58
  };
@@ -64,6 +65,7 @@ export const MorphViewport = React.memo(
64
65
  align,
65
66
  containerStyle,
66
67
  animatedWidthStyle,
68
+ onVisibleLayout,
67
69
  measurement,
68
70
  children,
69
71
  }: MorphViewportProps) => {
@@ -91,7 +93,10 @@ export const MorphViewport = React.memo(
91
93
  >
92
94
  {measurement}
93
95
  </View>
94
- <Animated.View style={[resolvedViewportStyle, animatedWidthStyle]}>
96
+ <Animated.View
97
+ onLayout={onVisibleLayout}
98
+ style={[resolvedViewportStyle, animatedWidthStyle]}
99
+ >
95
100
  {children}
96
101
  </Animated.View>
97
102
  </>
@@ -17,6 +17,7 @@ type TextRunProps = {
17
17
  readonly leading?: ReactNode;
18
18
  readonly leadingKey?: string | number;
19
19
  readonly leadingGap?: number;
20
+ readonly ready?: boolean;
20
21
  readonly textStyle?: StyleProp<TextStyle>;
21
22
  readonly className?: string;
22
23
  };
@@ -30,25 +31,58 @@ export const TextRun = React.memo(
30
31
  leading,
31
32
  leadingKey,
32
33
  leadingGap = 0,
34
+ ready = true,
33
35
  textStyle,
34
36
  className,
35
37
  }: TextRunProps) => {
36
38
  // namespace ids per instance so repeated strings do not collide
37
39
  const scopeId = useId();
38
- const lastValueRef = useRef(value);
39
- const lastLeadingPresenceRef = useRef(Boolean(leading));
40
- const lastLeadingKeyRef = useRef(leadingKey);
40
+ const hasDisplayedRef = useRef(false);
41
+ const displayedValueRef = useRef(value);
42
+ const displayedLeadingRef = useRef(leading);
43
+ const displayedLeadingKeyRef = useRef(leadingKey);
44
+ const displayedLeadingGapRef = useRef(leadingGap);
45
+ const shouldDisplayTarget = ready || !hasDisplayedRef.current;
46
+ const visibleValue = shouldDisplayTarget
47
+ ? value
48
+ : displayedValueRef.current;
49
+ const visibleLeading = shouldDisplayTarget
50
+ ? leading
51
+ : displayedLeadingRef.current;
52
+ const visibleLeadingKey = shouldDisplayTarget
53
+ ? leadingKey
54
+ : displayedLeadingKeyRef.current;
55
+ const visibleLeadingGap = shouldDisplayTarget
56
+ ? leadingGap
57
+ : displayedLeadingGapRef.current;
58
+
59
+ if (shouldDisplayTarget) {
60
+ hasDisplayedRef.current = true;
61
+ displayedValueRef.current = value;
62
+ displayedLeadingRef.current = leading;
63
+ displayedLeadingKeyRef.current = leadingKey;
64
+ displayedLeadingGapRef.current = leadingGap;
65
+ }
66
+
67
+ const lastValueRef = useRef(visibleValue);
68
+ const lastLeadingPresenceRef = useRef(Boolean(visibleLeading));
69
+ const lastLeadingKeyRef = useRef(visibleLeadingKey);
41
70
  const hasAnimatedRef = useRef(false);
42
71
  const isLeadingSwap =
43
- Boolean(leading) &&
72
+ Boolean(visibleLeading) &&
44
73
  lastLeadingPresenceRef.current &&
45
- leadingKey !== lastLeadingKeyRef.current;
74
+ visibleLeadingKey !== lastLeadingKeyRef.current;
46
75
  // update the worklet flag after commit so render never writes a shared value
47
76
  const leadingSwapProgress = useSharedValue(0);
48
77
  useLayoutEffect(() => {
49
78
  leadingSwapProgress.value = isLeadingSwap ? 1 : 0;
50
79
  }, [isLeadingSwap, leadingSwapProgress]);
51
- const glyphs = useTextGlyphs(value, scopeId, leading, leadingKey);
80
+ const glyphs = useTextGlyphs(
81
+ visibleValue,
82
+ scopeId,
83
+ visibleLeading,
84
+ visibleLeadingKey
85
+ );
52
86
  const elementEnterTransition = useMemo(
53
87
  () =>
54
88
  createLeadingEnterTransition({
@@ -62,11 +96,11 @@ export const TextRun = React.memo(
62
96
  createLeadingExitTransition({
63
97
  durationMs: motionRecipe.durationMs,
64
98
  easing: motionRecipe.easing,
65
- leadingGap,
99
+ leadingGap: visibleLeadingGap,
66
100
  scale: leadingSwapProgress,
67
101
  }),
68
102
  [
69
- leadingGap,
103
+ visibleLeadingGap,
70
104
  leadingSwapProgress,
71
105
  motionRecipe.durationMs,
72
106
  motionRecipe.easing,
@@ -75,14 +109,14 @@ export const TextRun = React.memo(
75
109
 
76
110
  // mark the first value as settled and later changes as eligible for motion
77
111
  if (
78
- value !== lastValueRef.current ||
79
- Boolean(leading) !== lastLeadingPresenceRef.current ||
80
- leadingKey !== lastLeadingKeyRef.current
112
+ visibleValue !== lastValueRef.current ||
113
+ Boolean(visibleLeading) !== lastLeadingPresenceRef.current ||
114
+ visibleLeadingKey !== lastLeadingKeyRef.current
81
115
  ) {
82
116
  hasAnimatedRef.current = true;
83
- lastValueRef.current = value;
84
- lastLeadingPresenceRef.current = Boolean(leading);
85
- lastLeadingKeyRef.current = leadingKey;
117
+ lastValueRef.current = visibleValue;
118
+ lastLeadingPresenceRef.current = Boolean(visibleLeading);
119
+ lastLeadingKeyRef.current = visibleLeadingKey;
86
120
  }
87
121
 
88
122
  const hasAnimated = hasAnimatedRef.current;
@@ -103,7 +137,7 @@ export const TextRun = React.memo(
103
137
  }
104
138
  exitTransition={motionRecipe.exitTransition}
105
139
  elementExitTransition={elementExitTransition}
106
- leadingGap={leadingGap}
140
+ leadingGap={visibleLeadingGap}
107
141
  align={align}
108
142
  textStyle={textStyle}
109
143
  className={className}