rnnovaui 0.9.1 → 0.9.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnnovaui",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -3,93 +3,241 @@ import React, { memo, useEffect, useRef, useState } from "react";
3
3
  import { Animated, StyleSheet, View } from "react-native";
4
4
 
5
5
  import { useUIAccordion } from "./UIAccordion";
6
-
7
6
  import { useUIAccordionItem } from "./UIAccordionItem";
8
7
 
9
8
  export const UIAccordionContent = memo(function UIAccordionContent({
10
9
  children,
11
-
12
10
  style,
13
-
14
11
  innerStyle,
15
-
16
12
  testID,
13
+ onLayout,
17
14
  }) {
18
15
  const accordion = useUIAccordion();
19
16
 
20
17
  const item = useUIAccordionItem();
21
18
 
22
- const progress = useRef(new Animated.Value(item.open ? 1 : 0)).current;
19
+ const [contentHeight, setContentHeight] = useState(0);
23
20
 
24
21
  const [mounted, setMounted] = useState(item.open);
25
22
 
23
+ const heightAnimation = useRef(new Animated.Value(item.open ? 1 : 0)).current;
24
+
25
+ const opacityAnimation = useRef(
26
+ new Animated.Value(item.open ? 1 : 0),
27
+ ).current;
28
+
29
+ const previousOpen = useRef(item.open);
30
+
31
+ /*
32
+ * -----------------------------------------------
33
+ * CONTENT MEASUREMENT
34
+ * -----------------------------------------------
35
+ *
36
+ * We only update the cached height when the
37
+ * actual content height changes.
38
+ */
39
+
40
+ const handleContentLayout = (event) => {
41
+ const height = Math.ceil(event.nativeEvent.layout.height);
42
+
43
+ if (height > 0 && height !== contentHeight) {
44
+ setContentHeight(height);
45
+ }
46
+
47
+ onLayout?.(event);
48
+ };
49
+
50
+ /*
51
+ * -----------------------------------------------
52
+ * OPEN / CLOSE ANIMATION
53
+ * -----------------------------------------------
54
+ */
55
+
26
56
  useEffect(() => {
57
+ const wasOpen = previousOpen.current;
58
+
59
+ previousOpen.current = item.open;
60
+
61
+ /*
62
+ * Nothing changed.
63
+ */
64
+ if (wasOpen === item.open) {
65
+ return;
66
+ }
67
+
68
+ /*
69
+ * ---------------------------------------------
70
+ * OPEN
71
+ * ---------------------------------------------
72
+ */
73
+
27
74
  if (item.open) {
28
75
  setMounted(true);
29
76
 
77
+ /*
78
+ * If content has not been measured yet,
79
+ * wait for onLayout.
80
+ */
81
+ if (contentHeight <= 0) {
82
+ heightAnimation.setValue(0);
83
+
84
+ opacityAnimation.setValue(0);
85
+
86
+ return;
87
+ }
88
+
30
89
  if (!accordion.animated) {
31
- progress.setValue(1);
90
+ heightAnimation.setValue(1);
91
+
92
+ opacityAnimation.setValue(1);
93
+
32
94
  return;
33
95
  }
34
96
 
35
- Animated.timing(progress, {
36
- toValue: 1,
97
+ /*
98
+ * Start exactly from closed.
99
+ */
100
+ heightAnimation.setValue(0);
37
101
 
38
- duration: accordion.animationDuration,
102
+ opacityAnimation.setValue(0);
39
103
 
40
- useNativeDriver: false,
41
- }).start();
104
+ Animated.parallel([
105
+ Animated.timing(heightAnimation, {
106
+ toValue: 1,
107
+
108
+ duration: accordion.animationDuration,
109
+
110
+ useNativeDriver: false,
111
+ }),
112
+
113
+ Animated.timing(opacityAnimation, {
114
+ toValue: 1,
115
+
116
+ duration: Math.min(accordion.animationDuration, 160),
117
+
118
+ useNativeDriver: false,
119
+ }),
120
+ ]).start();
42
121
 
43
122
  return;
44
123
  }
45
124
 
125
+ /*
126
+ * ---------------------------------------------
127
+ * CLOSE
128
+ * ---------------------------------------------
129
+ */
130
+
46
131
  if (!accordion.animated) {
47
- progress.setValue(0);
132
+ heightAnimation.setValue(0);
133
+
134
+ opacityAnimation.setValue(0);
135
+
48
136
  setMounted(false);
137
+
49
138
  return;
50
139
  }
51
140
 
52
- Animated.timing(progress, {
53
- toValue: 0,
141
+ Animated.parallel([
142
+ Animated.timing(heightAnimation, {
143
+ toValue: 0,
54
144
 
55
- duration: accordion.animationDuration,
145
+ duration: accordion.animationDuration,
56
146
 
57
- useNativeDriver: false,
58
- }).start(() => {
59
- setMounted(false);
147
+ useNativeDriver: false,
148
+ }),
149
+
150
+ Animated.timing(opacityAnimation, {
151
+ toValue: 0,
152
+
153
+ duration: Math.min(accordion.animationDuration, 150),
154
+
155
+ useNativeDriver: false,
156
+ }),
157
+ ]).start(({ finished }) => {
158
+ if (finished) {
159
+ setMounted(false);
160
+ }
60
161
  });
61
- }, [item.open, accordion.animated, accordion.animationDuration, progress]);
162
+ }, [
163
+ item.open,
164
+ contentHeight,
165
+ accordion.animated,
166
+ accordion.animationDuration,
167
+ heightAnimation,
168
+ opacityAnimation,
169
+ ]);
170
+
171
+ /*
172
+ * -----------------------------------------------
173
+ * IMPORTANT:
174
+ *
175
+ * Keep the content mounted while measuring.
176
+ *
177
+ * We use a wrapper whose height is animated.
178
+ * The inner content itself keeps its natural
179
+ * height.
180
+ * -----------------------------------------------
181
+ */
62
182
 
63
183
  if (!mounted) {
64
- return null;
184
+ /*
185
+ * Keep a non-visible measurement copy mounted
186
+ * only when the component has never measured.
187
+ *
188
+ * Once contentHeight is known, don't keep
189
+ * measurement content around.
190
+ */
191
+ if (contentHeight > 0) {
192
+ return null;
193
+ }
194
+
195
+ return (
196
+ <View style={styles.measureOnly} pointerEvents="none">
197
+ <View onLayout={handleContentLayout}>{children}</View>
198
+ </View>
199
+ );
65
200
  }
66
201
 
202
+ /*
203
+ * -----------------------------------------------
204
+ * ANIMATED HEIGHT
205
+ * -----------------------------------------------
206
+ */
207
+
208
+ const animatedHeight =
209
+ contentHeight > 0
210
+ ? heightAnimation.interpolate({
211
+ inputRange: [0, 1],
212
+
213
+ outputRange: [0, contentHeight],
214
+ })
215
+ : 0;
216
+
67
217
  return (
68
218
  <Animated.View
69
219
  testID={testID}
70
220
  style={[
71
- styles.animatedContainer,
221
+ styles.container,
72
222
 
73
223
  {
74
- opacity: progress,
224
+ height: animatedHeight,
75
225
 
76
- maxHeight: progress.interpolate({
77
- inputRange: [0, 1],
78
-
79
- outputRange: [0, 1000],
80
- }),
226
+ opacity: opacityAnimation,
81
227
  },
82
228
 
83
229
  style,
84
230
  ]}
85
231
  >
86
- <View style={[styles.inner, innerStyle]}>{children}</View>
232
+ <View style={[styles.inner, innerStyle]} onLayout={handleContentLayout}>
233
+ {children}
234
+ </View>
87
235
  </Animated.View>
88
236
  );
89
237
  });
90
238
 
91
239
  const styles = StyleSheet.create({
92
- animatedContainer: {
240
+ container: {
93
241
  width: "100%",
94
242
 
95
243
  overflow: "hidden",
@@ -98,6 +246,18 @@ const styles = StyleSheet.create({
98
246
  inner: {
99
247
  width: "100%",
100
248
  },
249
+
250
+ measureOnly: {
251
+ position: "absolute",
252
+
253
+ left: 0,
254
+
255
+ right: 0,
256
+
257
+ opacity: 0,
258
+
259
+ zIndex: -1,
260
+ },
101
261
  });
102
262
 
103
263
  UIAccordionContent.displayName = "UIAccordion.Content";