@r0b0t3d/react-native-collapsible 1.5.3 → 1.6.0-alpha.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 (127) hide show
  1. package/lib/commonjs/components/CollapsibleContainer.js +19 -21
  2. package/lib/commonjs/components/CollapsibleContainer.js.map +1 -1
  3. package/lib/commonjs/components/CollapsibleView.js +38 -30
  4. package/lib/commonjs/components/CollapsibleView.js.map +1 -1
  5. package/lib/commonjs/components/header/AnimatedTopView.js +5 -7
  6. package/lib/commonjs/components/header/AnimatedTopView.js.map +1 -1
  7. package/lib/commonjs/components/header/CollapsibleHeaderConsumer.js +10 -13
  8. package/lib/commonjs/components/header/CollapsibleHeaderConsumer.js.map +1 -1
  9. package/lib/commonjs/components/header/CollapsibleHeaderContainer.js +22 -17
  10. package/lib/commonjs/components/header/CollapsibleHeaderContainer.js.map +1 -1
  11. package/lib/commonjs/components/header/CollapsibleHeaderContainerProvider.js +12 -15
  12. package/lib/commonjs/components/header/CollapsibleHeaderContainerProvider.js.map +1 -1
  13. package/lib/commonjs/components/header/StickyView.js +18 -25
  14. package/lib/commonjs/components/header/StickyView.js.map +1 -1
  15. package/lib/commonjs/components/scrollable/CollapsibleFlatList.js +27 -41
  16. package/lib/commonjs/components/scrollable/CollapsibleFlatList.js.map +1 -1
  17. package/lib/commonjs/components/scrollable/CollapsibleScrollView.js +12 -15
  18. package/lib/commonjs/components/scrollable/CollapsibleScrollView.js.map +1 -1
  19. package/lib/commonjs/components/scrollable/CollapsibleSectionList.js +22 -36
  20. package/lib/commonjs/components/scrollable/CollapsibleSectionList.js.map +1 -1
  21. package/lib/commonjs/components/scrollable/useAnimatedScroll.js +44 -24
  22. package/lib/commonjs/components/scrollable/useAnimatedScroll.js.map +1 -1
  23. package/lib/commonjs/hooks/useCollapsibleContext.js +1 -2
  24. package/lib/commonjs/hooks/useCollapsibleContext.js.map +1 -1
  25. package/lib/commonjs/hooks/useCollapsibleHeaderConsumerContext.js +1 -2
  26. package/lib/commonjs/hooks/useCollapsibleHeaderConsumerContext.js.map +1 -1
  27. package/lib/commonjs/hooks/useCollapsibleHeaderContext.js +1 -2
  28. package/lib/commonjs/hooks/useCollapsibleHeaderContext.js.map +1 -1
  29. package/lib/commonjs/hooks/useInternalCollapsibleContext.js +1 -2
  30. package/lib/commonjs/hooks/useInternalCollapsibleContext.js.map +1 -1
  31. package/lib/commonjs/hooks/useKeyboardShowEvent.js.map +1 -1
  32. package/lib/commonjs/index.js +3 -12
  33. package/lib/commonjs/index.js.map +1 -1
  34. package/lib/commonjs/plugins/CollapsibleFlashList.js +14 -17
  35. package/lib/commonjs/plugins/CollapsibleFlashList.js.map +1 -1
  36. package/lib/commonjs/plugins/CollapsibleLegendList.js +15 -15
  37. package/lib/commonjs/plugins/CollapsibleLegendList.js.map +1 -1
  38. package/lib/commonjs/types.js.map +1 -1
  39. package/lib/commonjs/utils/debounce.js +2 -6
  40. package/lib/commonjs/utils/debounce.js.map +1 -1
  41. package/lib/commonjs/utils/useSharedValueRef.js +33 -3
  42. package/lib/commonjs/utils/useSharedValueRef.js.map +1 -1
  43. package/lib/commonjs/withCollapsibleContext.js +3 -4
  44. package/lib/commonjs/withCollapsibleContext.js.map +1 -1
  45. package/lib/module/components/CollapsibleContainer.js +18 -19
  46. package/lib/module/components/CollapsibleContainer.js.map +1 -1
  47. package/lib/module/components/CollapsibleView.js +38 -29
  48. package/lib/module/components/CollapsibleView.js.map +1 -1
  49. package/lib/module/components/header/AnimatedTopView.js +3 -4
  50. package/lib/module/components/header/AnimatedTopView.js.map +1 -1
  51. package/lib/module/components/header/CollapsibleHeaderConsumer.js +8 -10
  52. package/lib/module/components/header/CollapsibleHeaderConsumer.js.map +1 -1
  53. package/lib/module/components/header/CollapsibleHeaderContainer.js +21 -15
  54. package/lib/module/components/header/CollapsibleHeaderContainer.js.map +1 -1
  55. package/lib/module/components/header/CollapsibleHeaderContainerProvider.js +10 -12
  56. package/lib/module/components/header/CollapsibleHeaderContainerProvider.js.map +1 -1
  57. package/lib/module/components/header/StickyView.js +18 -23
  58. package/lib/module/components/header/StickyView.js.map +1 -1
  59. package/lib/module/components/scrollable/CollapsibleFlatList.js +27 -41
  60. package/lib/module/components/scrollable/CollapsibleFlatList.js.map +1 -1
  61. package/lib/module/components/scrollable/CollapsibleScrollView.js +10 -12
  62. package/lib/module/components/scrollable/CollapsibleScrollView.js.map +1 -1
  63. package/lib/module/components/scrollable/CollapsibleSectionList.js +22 -35
  64. package/lib/module/components/scrollable/CollapsibleSectionList.js.map +1 -1
  65. package/lib/module/components/scrollable/useAnimatedScroll.js +44 -23
  66. package/lib/module/components/scrollable/useAnimatedScroll.js.map +1 -1
  67. package/lib/module/hooks/useCollapsibleContext.js.map +1 -1
  68. package/lib/module/hooks/useCollapsibleHeaderConsumerContext.js.map +1 -1
  69. package/lib/module/hooks/useCollapsibleHeaderContext.js.map +1 -1
  70. package/lib/module/hooks/useInternalCollapsibleContext.js.map +1 -1
  71. package/lib/module/hooks/useKeyboardShowEvent.js.map +1 -1
  72. package/lib/module/index.js +0 -1
  73. package/lib/module/index.js.map +1 -1
  74. package/lib/module/plugins/CollapsibleFlashList.js +12 -14
  75. package/lib/module/plugins/CollapsibleFlashList.js.map +1 -1
  76. package/lib/module/plugins/CollapsibleLegendList.js +13 -12
  77. package/lib/module/plugins/CollapsibleLegendList.js.map +1 -1
  78. package/lib/module/types.js.map +1 -1
  79. package/lib/module/utils/debounce.js +2 -6
  80. package/lib/module/utils/debounce.js.map +1 -1
  81. package/lib/module/utils/useSharedValueRef.js +33 -3
  82. package/lib/module/utils/useSharedValueRef.js.map +1 -1
  83. package/lib/module/withCollapsibleContext.js +2 -2
  84. package/lib/module/withCollapsibleContext.js.map +1 -1
  85. package/lib/typescript/components/CollapsibleContainer.d.ts.map +1 -1
  86. package/lib/typescript/components/CollapsibleView.d.ts +4 -4
  87. package/lib/typescript/components/CollapsibleView.d.ts.map +1 -1
  88. package/lib/typescript/components/header/AnimatedTopView.d.ts +2 -2
  89. package/lib/typescript/components/header/AnimatedTopView.d.ts.map +1 -1
  90. package/lib/typescript/components/header/CollapsibleHeaderContainer.d.ts.map +1 -1
  91. package/lib/typescript/components/header/StickyView.d.ts.map +1 -1
  92. package/lib/typescript/components/scrollable/CollapsibleFlatList.d.ts.map +1 -1
  93. package/lib/typescript/components/scrollable/CollapsibleSectionList.d.ts.map +1 -1
  94. package/lib/typescript/components/scrollable/useAnimatedScroll.d.ts +2 -2
  95. package/lib/typescript/components/scrollable/useAnimatedScroll.d.ts.map +1 -1
  96. package/lib/typescript/hooks/useCollapsibleContext.d.ts +0 -1
  97. package/lib/typescript/hooks/useCollapsibleContext.d.ts.map +1 -1
  98. package/lib/typescript/hooks/useCollapsibleHeaderContext.d.ts +2 -3
  99. package/lib/typescript/hooks/useCollapsibleHeaderContext.d.ts.map +1 -1
  100. package/lib/typescript/hooks/useInternalCollapsibleContext.d.ts +0 -1
  101. package/lib/typescript/hooks/useInternalCollapsibleContext.d.ts.map +1 -1
  102. package/lib/typescript/index.d.ts +0 -1
  103. package/lib/typescript/index.d.ts.map +1 -1
  104. package/lib/typescript/plugins/CollapsibleFlashList.d.ts.map +1 -1
  105. package/lib/typescript/plugins/CollapsibleLegendList.d.ts.map +1 -1
  106. package/lib/typescript/types.d.ts +12 -12
  107. package/lib/typescript/types.d.ts.map +1 -1
  108. package/lib/typescript/utils/debounce.d.ts.map +1 -1
  109. package/lib/typescript/utils/useSharedValueRef.d.ts +2 -2
  110. package/lib/typescript/utils/useSharedValueRef.d.ts.map +1 -1
  111. package/lib/typescript/withCollapsibleContext.d.ts.map +1 -1
  112. package/package.json +54 -18
  113. package/src/components/CollapsibleContainer.tsx +12 -3
  114. package/src/components/CollapsibleView.tsx +31 -10
  115. package/src/components/header/AnimatedTopView.tsx +5 -2
  116. package/src/components/header/CollapsibleHeaderContainer.tsx +18 -13
  117. package/src/components/header/StickyView.tsx +8 -15
  118. package/src/components/scrollable/CollapsibleFlatList.tsx +27 -46
  119. package/src/components/scrollable/CollapsibleSectionList.tsx +21 -41
  120. package/src/components/scrollable/useAnimatedScroll.ts +36 -22
  121. package/src/hooks/useCollapsibleHeaderContext.ts +2 -2
  122. package/src/index.tsx +0 -2
  123. package/src/plugins/CollapsibleFlashList.tsx +9 -9
  124. package/src/plugins/CollapsibleLegendList.tsx +8 -2
  125. package/src/types.ts +12 -12
  126. package/src/utils/useSharedValueRef.ts +37 -6
  127. package/src/withCollapsibleContext.tsx +7 -4
package/package.json CHANGED
@@ -1,12 +1,33 @@
1
1
  {
2
2
  "name": "@r0b0t3d/react-native-collapsible",
3
- "version": "1.5.3",
3
+ "version": "1.6.0-alpha.0",
4
4
  "description": "Fully customizable collapsible views",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
7
7
  "types": "lib/typescript/index.d.ts",
8
8
  "react-native": "src/index",
9
9
  "source": "src/index",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./lib/typescript/index.d.ts",
13
+ "react-native": "./src/index.tsx",
14
+ "import": "./lib/module/index.js",
15
+ "require": "./lib/commonjs/index.js"
16
+ },
17
+ "./plugins/CollapsibleFlashList": {
18
+ "types": "./lib/typescript/plugins/CollapsibleFlashList.d.ts",
19
+ "react-native": "./src/plugins/CollapsibleFlashList.tsx",
20
+ "import": "./lib/module/plugins/CollapsibleFlashList.js",
21
+ "require": "./lib/commonjs/plugins/CollapsibleFlashList.js"
22
+ },
23
+ "./plugins/CollapsibleLegendList": {
24
+ "types": "./lib/typescript/plugins/CollapsibleLegendList.d.ts",
25
+ "react-native": "./src/plugins/CollapsibleLegendList.tsx",
26
+ "import": "./lib/module/plugins/CollapsibleLegendList.js",
27
+ "require": "./lib/commonjs/plugins/CollapsibleLegendList.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
10
31
  "files": [
11
32
  "src",
12
33
  "lib",
@@ -30,6 +51,7 @@
30
51
  "test": "jest",
31
52
  "typecheck": "tsc --noEmit",
32
53
  "lint": "eslint \"**/*.{js,ts,tsx}\"",
54
+ "lint:fix": "eslint \"**/*.{js,ts,tsx}\" --fix",
33
55
  "prepack": "bob build",
34
56
  "release": "release-it",
35
57
  "example": "yarn --cwd example",
@@ -53,42 +75,55 @@
53
75
  "devDependencies": {
54
76
  "@commitlint/config-conventional": "^17.0.2",
55
77
  "@evilmartians/lefthook": "^1.2.2",
56
- "@legendapp/list": "^1.0.7",
57
- "@react-native-community/eslint-config": "^3.0.2",
78
+ "@legendapp/list": "^3.0.6",
79
+ "@react-native-community/eslint-config": "^3.2.0",
58
80
  "@release-it/conventional-changelog": "^5.0.0",
59
- "@shopify/flash-list": "^1.8.0",
81
+ "@shopify/flash-list": "^2.3.2",
60
82
  "@types/jest": "^28.1.2",
61
- "@types/react": "~17.0.21",
83
+ "@types/react": "~19.2.17",
62
84
  "@types/react-native": "0.70.0",
63
85
  "commitlint": "^17.0.2",
64
86
  "del-cli": "^5.0.0",
65
87
  "eslint": "^8.4.1",
66
- "eslint-config-prettier": "^8.5.0",
67
- "eslint-plugin-prettier": "^4.0.0",
88
+ "eslint-config-prettier": "^10.1.8",
89
+ "eslint-plugin-prettier": "^5.5.6",
68
90
  "jest": "^28.1.1",
69
91
  "pod-install": "^0.1.0",
70
- "prettier": "^2.0.5",
71
- "react": "18.2.0",
72
- "react-native": "0.71.8",
92
+ "prettier": "^3.8.4",
93
+ "react": "19.2.3",
94
+ "react-native": "0.86.0",
73
95
  "react-native-builder-bob": "^0.20.0",
74
- "react-native-reanimated": "^3.3.0",
75
- "release-it": "^15.0.0",
76
- "typescript": "^5.0.2"
77
- },
78
- "resolutions": {
79
- "@types/react": "17.0.21"
96
+ "react-native-reanimated": "^4.4.1",
97
+ "release-it": "^20.2.0",
98
+ "typescript": "^6.0.3"
80
99
  },
81
100
  "peerDependencies": {
82
- "react": "*",
101
+ "@legendapp/list": "*",
102
+ "@shopify/flash-list": "*",
103
+ "react": ">=18.0.0",
83
104
  "react-native": "*",
84
105
  "react-native-gesture-handler": "*",
85
106
  "react-native-reanimated": ">=3.0.0"
86
107
  },
108
+ "peerDependenciesMeta": {
109
+ "@legendapp/list": {
110
+ "optional": true
111
+ },
112
+ "@shopify/flash-list": {
113
+ "optional": true
114
+ }
115
+ },
87
116
  "engines": {
88
117
  "node": ">= 16.0.0"
89
118
  },
90
119
  "jest": {
91
120
  "preset": "react-native",
121
+ "setupFiles": [
122
+ "<rootDir>/jest.setup.js"
123
+ ],
124
+ "moduleNameMapper": {
125
+ "^react-native-reanimated$": "<rootDir>/jest.setup.js"
126
+ },
92
127
  "modulePathIgnorePatterns": [
93
128
  "<rootDir>/example/node_modules",
94
129
  "<rootDir>/lib/"
@@ -155,7 +190,8 @@
155
190
  [
156
191
  "typescript",
157
192
  {
158
- "project": "tsconfig.build.json"
193
+ "project": "tsconfig.build.json",
194
+ "tsc": "node_modules/.bin/tsc"
159
195
  }
160
196
  ]
161
197
  ]
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable react-hooks/exhaustive-deps */
2
- import React, { useLayoutEffect } from 'react';
2
+ import React, { useCallback, useLayoutEffect } from 'react';
3
3
  import {
4
4
  KeyboardAvoidingView,
5
5
  KeyboardAvoidingViewProps,
@@ -28,7 +28,7 @@ function CollapsibleContainer({
28
28
  const { containerHeight, containerRef } = useInternalCollapsibleContext();
29
29
  const { scrollY, scrollTo } = useCollapsibleContext();
30
30
 
31
- useKeyboardShowEvent(() => {
31
+ const handleKeyboardShow = useCallback(() => {
32
32
  textInputRefs.some((ref) => {
33
33
  const isFocusedFunc = ref.current.isFocused;
34
34
  const isFocused =
@@ -51,7 +51,16 @@ function CollapsibleContainer({
51
51
  }
52
52
  return isFocused;
53
53
  });
54
- });
54
+ }, [
55
+ textInputRefs,
56
+ containerRef,
57
+ containerHeight,
58
+ scrollY,
59
+ scrollTo,
60
+ keyboardAvoidingViewProps?.keyboardVerticalOffset,
61
+ ]);
62
+
63
+ useKeyboardShowEvent(handleKeyboardShow);
55
64
 
56
65
  return (
57
66
  <KeyboardAvoidingViewComponent
@@ -1,6 +1,11 @@
1
1
  /* eslint-disable react-hooks/exhaustive-deps */
2
- /* eslint-disable react-hooks/rules-of-hooks */
3
- import React, { ReactNode, useCallback, useEffect, useMemo } from 'react';
2
+ import React, {
3
+ ReactNode,
4
+ useCallback,
5
+ useEffect,
6
+ useId,
7
+ useMemo,
8
+ } from 'react';
4
9
  import {
5
10
  LayoutChangeEvent,
6
11
  Pressable,
@@ -19,16 +24,17 @@ import Animated, {
19
24
  interpolate,
20
25
  useAnimatedReaction,
21
26
  runOnJS,
27
+ SharedValue,
22
28
  } from 'react-native-reanimated';
23
29
 
24
30
  export type CollapsibleHeaderProps = {
25
31
  onToggle: () => void;
26
- collapsed: Animated.SharedValue<number>;
32
+ collapsed: SharedValue<number>;
27
33
  };
28
34
 
29
35
  type Props = {
30
36
  initialState?: 'collapsed' | 'expanded';
31
- collapseState?: Animated.SharedValue<number>;
37
+ collapseState?: SharedValue<number>;
32
38
  renderHeader: (props: CollapsibleHeaderProps) => ReactNode;
33
39
  children: ReactNode;
34
40
  containerStyle?: StyleProp<ViewStyle>;
@@ -38,10 +44,9 @@ type Props = {
38
44
  onToggle?: (isExpand: boolean) => void;
39
45
  };
40
46
 
41
- let key = 0;
42
47
  export default function CollapsibleView({
43
48
  initialState = 'collapsed',
44
- collapseState = useSharedValue(0),
49
+ collapseState: collapseStateProp,
45
50
  renderHeader,
46
51
  children,
47
52
  containerStyle,
@@ -49,16 +54,32 @@ export default function CollapsibleView({
49
54
  expandedBackgroundColor,
50
55
  onToggle,
51
56
  }: Props) {
57
+ // Shared value is always allocated; unused when caller provides one.
58
+ // The allocation cost is negligible and this keeps hook order stable
59
+ // across renders and React StrictMode double-invocation.
60
+ const collapseStateInternal = useSharedValue(0);
61
+ const collapseState = collapseStateProp ?? collapseStateInternal;
52
62
  const actualHeight = useSharedValue(11110);
53
- const contentKey = useMemo(() => `collapsible-view-${key++}`, []);
63
+ const instanceId = useId();
64
+ const contentKey = useMemo(
65
+ () => `collapsible-view-${instanceId}`,
66
+ [instanceId]
67
+ );
54
68
 
55
69
  useEffect(() => {
70
+ // `initialState` is only honored when the caller does not pass a
71
+ // `collapseState` of their own. When a caller-controlled SV is
72
+ // provided, mutating it from here would silently clobber whatever
73
+ // value the caller is driving from gesture state or animations.
74
+ if (collapseStateProp) {
75
+ return;
76
+ }
56
77
  const newValue = initialState === 'collapsed' ? 0 : 1;
57
78
  if (newValue === collapseState.value) {
58
79
  return;
59
80
  }
60
81
  collapseState.value = newValue;
61
- }, [initialState]);
82
+ }, [initialState, collapseStateProp]);
62
83
 
63
84
  const handleToggle = useCallback(() => {
64
85
  collapseState.value = withSpring(collapseState.value === 0 ? 1 : 0, {
@@ -80,7 +101,7 @@ export default function CollapsibleView({
80
101
  overshootClamping: true,
81
102
  }),
82
103
  }),
83
- [actualHeight, contentKey]
104
+ [actualHeight, contentKey, collapseState]
84
105
  );
85
106
 
86
107
  const contentHeight = useAnimatedStyle(
@@ -114,7 +135,7 @@ export default function CollapsibleView({
114
135
  };
115
136
  }
116
137
  return {};
117
- }, []);
138
+ }, [collapseState, collapsedBackgroundColor, expandedBackgroundColor]);
118
139
 
119
140
  const headerProps = useMemo(
120
141
  () => ({ onToggle: handleToggle, collapsed: collapseState }),
@@ -1,8 +1,11 @@
1
1
  import React from 'react';
2
- import Animated, { useAnimatedStyle } from 'react-native-reanimated';
2
+ import Animated, {
3
+ SharedValue,
4
+ useAnimatedStyle,
5
+ } from 'react-native-reanimated';
3
6
 
4
7
  type Props = {
5
- height: Animated.SharedValue<number>;
8
+ height: SharedValue<number>;
6
9
  };
7
10
 
8
11
  export default function AnimatedTopView({ height }: Props) {
@@ -1,4 +1,4 @@
1
- import React, { ReactNode, useEffect, useMemo } from 'react';
1
+ import React, { ReactNode, useEffect, useId, useMemo, useRef } from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
3
  import useCollapsibleHeaderConsumerContext from '../../hooks/useCollapsibleHeaderConsumerContext';
4
4
  import CollapsibleHeaderContainerProvider from './CollapsibleHeaderContainerProvider';
@@ -8,25 +8,30 @@ type Props = {
8
8
  containerStyle?: StyleProp<ViewStyle>;
9
9
  };
10
10
 
11
- let key = 0;
11
+ // Module-scoped monotonic counter used only to assign zIndex to *new* instances
12
+ // (see useInstanceZIndex below). Bounded by app lifetime; wraparound after ~100k
13
+ // instances may cause one frame of incorrect layering.
14
+ let nextZIndex = 0;
15
+ function useInstanceZIndex(): number {
16
+ const ref = useRef<number | null>(null);
17
+ if (ref.current === null) {
18
+ ref.current = nextZIndex++;
19
+ }
20
+ return ref.current;
21
+ }
12
22
 
13
23
  export default function CollapsibleHeaderContainer({
14
24
  children,
15
25
  containerStyle,
16
26
  }: Props) {
17
- const originalKey = useMemo(() => key++, []);
18
- const contentKey = useMemo(
19
- () => `collapsible-header-${originalKey}`,
20
- [originalKey]
21
- );
27
+ const contentKey = `collapsible-header-${useId()}`;
28
+ const instanceZIndex = useInstanceZIndex();
22
29
  const { mount, unmount, update } = useCollapsibleHeaderConsumerContext();
23
30
 
24
- const internalStyle = useMemo(() => {
25
- return {
26
- zIndex: 100000 - key - originalKey,
27
- };
28
- // eslint-disable-next-line react-hooks/exhaustive-deps
29
- }, [key, originalKey]);
31
+ const internalStyle = useMemo(
32
+ () => ({ zIndex: 100000 - instanceZIndex }),
33
+ [instanceZIndex]
34
+ );
30
35
 
31
36
  const content = useMemo(() => {
32
37
  return (
@@ -1,5 +1,4 @@
1
- /* eslint-disable react-hooks/exhaustive-deps */
2
- import React, { useCallback, useEffect, useMemo } from 'react';
1
+ import React, { useEffect, useId } from 'react';
3
2
  import {
4
3
  LayoutChangeEvent,
5
4
  LayoutRectangle,
@@ -8,7 +7,6 @@ import {
8
7
  ViewStyle,
9
8
  } from 'react-native';
10
9
  import Animated, {
11
- Extrapolate,
12
10
  interpolate,
13
11
  useAnimatedStyle,
14
12
  useDerivedValue,
@@ -22,24 +20,20 @@ type Props = {
22
20
  stickyRef?: React.MutableRefObject<any>;
23
21
  };
24
22
 
25
- let stickyKey = 0;
26
-
27
23
  export default function StickyView({ children, style, stickyRef }: Props) {
28
- const key = useMemo(() => `sticky_${stickyKey++}`, []);
24
+ const key = `sticky_${useId()}`;
29
25
  const { handleStickyViewLayout, animatedY } = useCollapsibleHeaderContext();
30
26
  const currentLayout = useSharedValue<LayoutRectangle | undefined>(undefined);
31
27
 
32
28
  useEffect(() => {
33
29
  return () => handleStickyViewLayout(key, undefined);
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34
31
  }, [key]);
35
32
 
36
- const handleLayout = useCallback(
37
- ({ nativeEvent: { layout } }: LayoutChangeEvent) => {
38
- currentLayout.value = layout;
39
- handleStickyViewLayout(key, layout);
40
- },
41
- [key, handleStickyViewLayout]
42
- );
33
+ const handleLayout = ({ nativeEvent: { layout } }: LayoutChangeEvent) => {
34
+ currentLayout.set(layout);
35
+ handleStickyViewLayout(key, layout);
36
+ };
43
37
 
44
38
  const translateY = useDerivedValue(() => {
45
39
  if (!currentLayout.value) {
@@ -52,7 +46,7 @@ export default function StickyView({ children, style, stickyRef }: Props) {
52
46
  animatedY.value,
53
47
  [0, topValue, topValue + stickyHeight + 100000],
54
48
  [0, 0, stickyHeight + 100000],
55
- Extrapolate.CLAMP
49
+ 'clamp'
56
50
  );
57
51
  }, []);
58
52
 
@@ -69,7 +63,6 @@ export default function StickyView({ children, style, stickyRef }: Props) {
69
63
  return (
70
64
  <Animated.View
71
65
  key={key}
72
- // @ts-ignore
73
66
  ref={stickyRef}
74
67
  onLayout={handleLayout}
75
68
  style={[styles.container, style, animatedStyle]}
@@ -1,4 +1,3 @@
1
- /* eslint-disable react-hooks/exhaustive-deps */
2
1
  import React, {
3
2
  useCallback,
4
3
  useEffect,
@@ -6,10 +5,12 @@ import React, {
6
5
  useRef,
7
6
  useState,
8
7
  } from 'react';
9
- import { FlatListProps, View, StyleSheet, FlatList } from 'react-native';
8
+ import { FlatListProps, StyleSheet } from 'react-native';
9
+ import type { ScrollToIndexParams } from '../../types';
10
10
  import Animated, {
11
11
  runOnJS,
12
12
  useAnimatedReaction,
13
+ useAnimatedStyle,
13
14
  } from 'react-native-reanimated';
14
15
  import useAnimatedScroll from './useAnimatedScroll';
15
16
  import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
@@ -17,8 +18,6 @@ import type { CollapsibleProps } from '../../types';
17
18
  import AnimatedTopView from '../header/AnimatedTopView';
18
19
  import useCollapsibleContext from '../../hooks/useCollapsibleContext';
19
20
 
20
- const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
21
-
22
21
  type Props<Data> = Omit<FlatListProps<Data>, 'scrollEnabled'> &
23
22
  CollapsibleProps;
24
23
 
@@ -30,10 +29,6 @@ export default function CollapsibleFlatList<Data>({
30
29
  const { contentMinHeight, scrollViewRef, fixedHeaderHeight } =
31
30
  useInternalCollapsibleContext();
32
31
  const mounted = useRef(true);
33
- const contentHeight = useRef(0);
34
- const [internalContentMinHeight, setInternalContentMinHeight] = useState(
35
- contentMinHeight.value
36
- );
37
32
  const [internalProgressViewOffset, setInternalProgressViewOffset] =
38
33
  useState(0);
39
34
 
@@ -48,10 +43,12 @@ export default function CollapsibleFlatList<Data>({
48
43
  offset: yValue,
49
44
  animated,
50
45
  });
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
51
47
  }, []);
52
48
 
53
- const scrollToIndex = useCallback((params) => {
49
+ const scrollToIndex = useCallback((params: ScrollToIndexParams) => {
54
50
  scrollViewRef.current?.scrollToIndex?.(params);
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
52
  }, []);
56
53
 
57
54
  const scrollToLocation = useCallback(() => {
@@ -65,34 +62,12 @@ export default function CollapsibleFlatList<Data>({
65
62
  scrollToLocation,
66
63
  });
67
64
 
68
- const handleInternalContentHeight = useCallback((value: number) => {
69
- if (mounted.current) {
70
- setInternalContentMinHeight(value);
71
- }
72
- }, []);
73
-
74
65
  const handleInternalProgressViewOffset = useCallback((value: number) => {
75
66
  if (mounted.current) {
76
67
  setInternalProgressViewOffset(value);
77
68
  }
78
69
  }, []);
79
70
 
80
- useAnimatedReaction(
81
- () => {
82
- return contentMinHeight.value;
83
- },
84
- (result, previous) => {
85
- if (result !== previous) {
86
- if (
87
- contentHeight.current < result &&
88
- internalContentMinHeight !== result
89
- ) {
90
- runOnJS(handleInternalContentHeight)(result);
91
- }
92
- }
93
- }
94
- );
95
-
96
71
  useAnimatedReaction(
97
72
  () => {
98
73
  return fixedHeaderHeight.value;
@@ -104,32 +79,39 @@ export default function CollapsibleFlatList<Data>({
104
79
  }
105
80
  );
106
81
 
107
- const contentContainerStyle = useMemo(
108
- () => [
109
- styles.contentContainer,
110
- { minHeight: internalContentMinHeight },
111
- props.contentContainerStyle,
112
- ],
113
- [props.contentContainerStyle, internalContentMinHeight]
82
+ // Animate minHeight on the UI thread instead of bouncing through React state.
83
+ // Skip applying minHeight when the value is 0 to avoid forcing a 0-height
84
+ // layout on the first frame before the container has been measured.
85
+ const listHeaderStyle = useAnimatedStyle(
86
+ () => ({
87
+ minHeight:
88
+ contentMinHeight.value > 0 ? contentMinHeight.value : undefined,
89
+ }),
90
+ []
114
91
  );
115
92
 
116
- const handleContentSizeChange = useCallback((_, height) => {
117
- contentHeight.current = height;
118
- }, []);
93
+ const contentContainerStyle = useMemo(
94
+ () => [styles.contentContainer, props.contentContainerStyle],
95
+ [props.contentContainerStyle]
96
+ );
119
97
 
120
98
  const handleScrollToIndexFailed = useCallback(() => {}, []);
121
99
 
100
+ const ListHeaderComponent = props.ListHeaderComponent;
122
101
  function renderListHeader() {
123
102
  return (
124
- <View>
103
+ <Animated.View style={listHeaderStyle}>
125
104
  <AnimatedTopView height={headerHeight} />
126
- {props.ListHeaderComponent}
127
- </View>
105
+ {ListHeaderComponent &&
106
+ (React.isValidElement(ListHeaderComponent)
107
+ ? ListHeaderComponent
108
+ : React.createElement(ListHeaderComponent as React.ComponentType))}
109
+ </Animated.View>
128
110
  );
129
111
  }
130
112
 
131
113
  return (
132
- <AnimatedFlatList
114
+ <Animated.FlatList
133
115
  ref={scrollViewRef}
134
116
  keyboardDismissMode="on-drag"
135
117
  keyboardShouldPersistTaps="handled"
@@ -140,7 +122,6 @@ export default function CollapsibleFlatList<Data>({
140
122
  contentContainerStyle={contentContainerStyle}
141
123
  onScroll={scrollHandler}
142
124
  ListHeaderComponent={renderListHeader()}
143
- onContentSizeChange={handleContentSizeChange}
144
125
  //@ts-ignore
145
126
  simultaneousHandlers={[]}
146
127
  progressViewOffset={internalProgressViewOffset}
@@ -6,7 +6,6 @@ import React, {
6
6
  useState,
7
7
  } from 'react';
8
8
  import {
9
- View,
10
9
  StyleSheet,
11
10
  SectionList,
12
11
  SectionListProps,
@@ -15,6 +14,7 @@ import {
15
14
  import Animated, {
16
15
  runOnJS,
17
16
  useAnimatedReaction,
17
+ useAnimatedStyle,
18
18
  } from 'react-native-reanimated';
19
19
  import useAnimatedScroll from './useAnimatedScroll';
20
20
  import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
@@ -35,10 +35,6 @@ export default function CollapsibleSectionList<Data>({
35
35
  const { contentMinHeight, scrollViewRef, fixedHeaderHeight } =
36
36
  useInternalCollapsibleContext();
37
37
  const mounted = useRef(true);
38
- const contentHeight = useRef(0);
39
- const [internalContentMinHeight, setInternalContentMinHeight] = useState(
40
- contentMinHeight.value
41
- );
42
38
  const [internalProgressViewOffset, setInternalProgressViewOffset] =
43
39
  useState(0);
44
40
 
@@ -70,34 +66,12 @@ export default function CollapsibleSectionList<Data>({
70
66
  scrollToLocation,
71
67
  });
72
68
 
73
- const handleInternalContentHeight = useCallback((value: number) => {
74
- if (mounted.current) {
75
- setInternalContentMinHeight(value);
76
- }
77
- }, []);
78
-
79
69
  const handleInternalProgressViewOffset = useCallback((value: number) => {
80
70
  if (mounted.current) {
81
71
  setInternalProgressViewOffset(value);
82
72
  }
83
73
  }, []);
84
74
 
85
- useAnimatedReaction(
86
- () => {
87
- return contentMinHeight.value;
88
- },
89
- (result, previous) => {
90
- if (result !== previous) {
91
- if (
92
- contentHeight.current < result &&
93
- internalContentMinHeight !== result
94
- ) {
95
- runOnJS(handleInternalContentHeight)(result);
96
- }
97
- }
98
- }
99
- );
100
-
101
75
  useAnimatedReaction(
102
76
  () => {
103
77
  return fixedHeaderHeight.value;
@@ -109,27 +83,34 @@ export default function CollapsibleSectionList<Data>({
109
83
  }
110
84
  );
111
85
 
112
- const contentContainerStyle = useMemo(
113
- () => [
114
- styles.contentContainer,
115
- { minHeight: internalContentMinHeight },
116
- props.contentContainerStyle,
117
- ],
118
- [props.contentContainerStyle, internalContentMinHeight]
86
+ // Animate minHeight on the UI thread instead of bouncing through React state.
87
+ // Skip applying minHeight when the value is 0 to avoid forcing a 0-height
88
+ // layout on the first frame before the container has been measured.
89
+ const listHeaderStyle = useAnimatedStyle(
90
+ () => ({
91
+ minHeight:
92
+ contentMinHeight.value > 0 ? contentMinHeight.value : undefined,
93
+ }),
94
+ []
119
95
  );
120
96
 
121
- const handleContentSizeChange = useCallback((_, height) => {
122
- contentHeight.current = height;
123
- }, []);
97
+ const contentContainerStyle = useMemo(
98
+ () => [styles.contentContainer, props.contentContainerStyle],
99
+ [props.contentContainerStyle]
100
+ );
124
101
 
125
102
  const handleScrollToIndexFailed = useCallback(() => {}, []);
126
103
 
104
+ const ListHeaderComponent = props.ListHeaderComponent;
127
105
  function renderListHeader() {
128
106
  return (
129
- <View>
107
+ <Animated.View style={listHeaderStyle}>
130
108
  <AnimatedTopView height={headerHeight} />
131
- {props.ListHeaderComponent}
132
- </View>
109
+ {ListHeaderComponent &&
110
+ (React.isValidElement(ListHeaderComponent)
111
+ ? ListHeaderComponent
112
+ : React.createElement(ListHeaderComponent as React.ComponentType))}
113
+ </Animated.View>
133
114
  );
134
115
  }
135
116
 
@@ -145,7 +126,6 @@ export default function CollapsibleSectionList<Data>({
145
126
  contentContainerStyle={contentContainerStyle}
146
127
  onScroll={scrollHandler}
147
128
  ListHeaderComponent={renderListHeader()}
148
- onContentSizeChange={handleContentSizeChange}
149
129
  //@ts-ignore
150
130
  simultaneousHandlers={[]}
151
131
  progressViewOffset={internalProgressViewOffset}