@legendapp/list 2.0.15 → 2.0.16

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/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 2.0.16
2
+ - Feat: Add KeyboardAvoidingLegendList component for better keyboard handling integration
3
+ - Fix: Stale containers are not being removed and overlap with new data when using getItemType #335
4
+ - Fix: Suppress keyExtractor warning when using lazy list mode #330
5
+
1
6
  ## 2.0.15
2
7
  - Fix: Container allocation for sticky headers could duplicate containers, causing rendering issues
3
8
  - Fix: Sticky positioned components scrolling out of viewport after scrolling distance exceeded 5000
package/index.js CHANGED
@@ -1572,14 +1572,14 @@ function findAvailableContainers(ctx, state, numNeeded, startBuffered, endBuffer
1572
1572
  continue;
1573
1573
  }
1574
1574
  const key = peek$(ctx, `containerItemKey${u}`);
1575
- let isOk = key === void 0;
1576
- if (!isOk && pendingRemovalSet.has(u)) {
1577
- pendingRemovalSet.delete(u);
1578
- pendingRemovalChanged = true;
1579
- const requiredType = neededTypes[typeIndex];
1580
- isOk = canReuseContainer(u, requiredType);
1581
- }
1582
- if (isOk) {
1575
+ const requiredType = neededTypes[typeIndex];
1576
+ const isPending = key !== void 0 && pendingRemovalSet.has(u);
1577
+ const canUse = key === void 0 || isPending && canReuseContainer(u, requiredType);
1578
+ if (canUse) {
1579
+ if (isPending) {
1580
+ pendingRemovalSet.delete(u);
1581
+ pendingRemovalChanged = true;
1582
+ }
1583
1583
  result.push(u);
1584
1584
  if (requiredItemTypes) {
1585
1585
  typeIndex++;
@@ -2722,6 +2722,7 @@ var LegendList = typedMemo(
2722
2722
  const isChildrenMode = children !== void 0 && dataProp === void 0;
2723
2723
  const processedProps = isChildrenMode ? {
2724
2724
  ...restProps,
2725
+ childrenMode: true,
2725
2726
  data: (isArray(children) ? children : React2__namespace.Children.toArray(children)).flat(1),
2726
2727
  renderItem: ({ item }) => item
2727
2728
  } : {
@@ -2788,6 +2789,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2788
2789
  waitForInitialLayout = true,
2789
2790
  ...rest
2790
2791
  } = props;
2792
+ const { childrenMode } = rest;
2791
2793
  const [renderNum, setRenderNum] = React2.useState(0);
2792
2794
  const initialScroll = initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? { index: initialScrollIndexProp.index || 0, viewOffset: initialScrollIndexProp.viewOffset || 0 } : { index: initialScrollIndexProp || 0, viewOffset: initialScrollOffsetProp || 0 } : void 0;
2793
2795
  const [canRender, setCanRender] = React2__namespace.useState(!IsNewArchitecture);
@@ -2960,7 +2962,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2960
2962
  if (isFirst || didDataChange || numColumnsProp !== peek$(ctx, "numColumns")) {
2961
2963
  refState.current.lastBatchingAction = Date.now();
2962
2964
  if (!keyExtractorProp && !isFirst && didDataChange) {
2963
- __DEV__ && warnDevOnce(
2965
+ __DEV__ && !childrenMode && warnDevOnce(
2964
2966
  "keyExtractor",
2965
2967
  "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior."
2966
2968
  );
package/index.mjs CHANGED
@@ -1551,14 +1551,14 @@ function findAvailableContainers(ctx, state, numNeeded, startBuffered, endBuffer
1551
1551
  continue;
1552
1552
  }
1553
1553
  const key = peek$(ctx, `containerItemKey${u}`);
1554
- let isOk = key === void 0;
1555
- if (!isOk && pendingRemovalSet.has(u)) {
1556
- pendingRemovalSet.delete(u);
1557
- pendingRemovalChanged = true;
1558
- const requiredType = neededTypes[typeIndex];
1559
- isOk = canReuseContainer(u, requiredType);
1560
- }
1561
- if (isOk) {
1554
+ const requiredType = neededTypes[typeIndex];
1555
+ const isPending = key !== void 0 && pendingRemovalSet.has(u);
1556
+ const canUse = key === void 0 || isPending && canReuseContainer(u, requiredType);
1557
+ if (canUse) {
1558
+ if (isPending) {
1559
+ pendingRemovalSet.delete(u);
1560
+ pendingRemovalChanged = true;
1561
+ }
1562
1562
  result.push(u);
1563
1563
  if (requiredItemTypes) {
1564
1564
  typeIndex++;
@@ -2701,6 +2701,7 @@ var LegendList = typedMemo(
2701
2701
  const isChildrenMode = children !== void 0 && dataProp === void 0;
2702
2702
  const processedProps = isChildrenMode ? {
2703
2703
  ...restProps,
2704
+ childrenMode: true,
2704
2705
  data: (isArray(children) ? children : React2.Children.toArray(children)).flat(1),
2705
2706
  renderItem: ({ item }) => item
2706
2707
  } : {
@@ -2767,6 +2768,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2767
2768
  waitForInitialLayout = true,
2768
2769
  ...rest
2769
2770
  } = props;
2771
+ const { childrenMode } = rest;
2770
2772
  const [renderNum, setRenderNum] = useState(0);
2771
2773
  const initialScroll = initialScrollIndexProp || initialScrollOffsetProp ? typeof initialScrollIndexProp === "object" ? { index: initialScrollIndexProp.index || 0, viewOffset: initialScrollIndexProp.viewOffset || 0 } : { index: initialScrollIndexProp || 0, viewOffset: initialScrollOffsetProp || 0 } : void 0;
2772
2774
  const [canRender, setCanRender] = React2.useState(!IsNewArchitecture);
@@ -2939,7 +2941,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2939
2941
  if (isFirst || didDataChange || numColumnsProp !== peek$(ctx, "numColumns")) {
2940
2942
  refState.current.lastBatchingAction = Date.now();
2941
2943
  if (!keyExtractorProp && !isFirst && didDataChange) {
2942
- __DEV__ && warnDevOnce(
2944
+ __DEV__ && !childrenMode && warnDevOnce(
2943
2945
  "keyExtractor",
2944
2946
  "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior."
2945
2947
  );
package/keyboard.d.mts ADDED
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import { Insets } from 'react-native';
3
+ import { ReanimatedScrollEvent } from 'react-native-reanimated/lib/typescript/hook/commonTypes';
4
+ import { LegendListRef } from '@legendapp/list';
5
+ import { AnimatedLegendListProps } from '@legendapp/list/reanimated';
6
+
7
+ declare const KeyboardAvoidingLegendList: <ItemT>(props: Omit<AnimatedLegendListProps<ItemT>, "contentInset" | "onScroll"> & {
8
+ onScroll?: (event: ReanimatedScrollEvent) => void;
9
+ contentInset?: Insets;
10
+ safeAreaInsetBottom?: number;
11
+ } & React.RefAttributes<LegendListRef>) => React.ReactNode;
12
+
13
+ export { KeyboardAvoidingLegendList, KeyboardAvoidingLegendList as LegendList };
package/keyboard.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import { Insets } from 'react-native';
3
+ import { ReanimatedScrollEvent } from 'react-native-reanimated/lib/typescript/hook/commonTypes';
4
+ import { LegendListRef } from '@legendapp/list';
5
+ import { AnimatedLegendListProps } from '@legendapp/list/reanimated';
6
+
7
+ declare const KeyboardAvoidingLegendList: <ItemT>(props: Omit<AnimatedLegendListProps<ItemT>, "contentInset" | "onScroll"> & {
8
+ onScroll?: (event: ReanimatedScrollEvent) => void;
9
+ contentInset?: Insets;
10
+ safeAreaInsetBottom?: number;
11
+ } & React.RefAttributes<LegendListRef>) => React.ReactNode;
12
+
13
+ export { KeyboardAvoidingLegendList, KeyboardAvoidingLegendList as LegendList };
package/keyboard.js ADDED
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var reactNative = require('react-native');
5
+ var reactNativeKeyboardController = require('react-native-keyboard-controller');
6
+ var reactNativeReanimated = require('react-native-reanimated');
7
+ var reanimated = require('@legendapp/list/reanimated');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
28
+
29
+ // src/integrations/keyboard.tsx
30
+ var KeyboardAvoidingLegendList = React.forwardRef(function KeyboardAvoidingLegendList2(props, forwardedRef) {
31
+ const {
32
+ contentInset: contentInsetProp,
33
+ horizontal,
34
+ onScroll: onScrollProp,
35
+ scrollEventThrottle,
36
+ safeAreaInsetBottom = 0,
37
+ ...rest
38
+ } = props;
39
+ const resolvedScrollEventThrottle = scrollEventThrottle != null ? scrollEventThrottle : 16;
40
+ const scrollViewRef = reactNativeReanimated.useAnimatedRef();
41
+ const scrollOffsetY = reactNativeReanimated.useSharedValue(0);
42
+ const scrollOffsetAtKeyboardStart = reactNativeReanimated.useSharedValue(0);
43
+ const keyboardInset = reactNativeReanimated.useSharedValue(0);
44
+ const scrollHandler = reactNativeReanimated.useAnimatedScrollHandler(
45
+ (event) => {
46
+ scrollOffsetY.value = event.contentOffset[horizontal ? "x" : "y"];
47
+ if (onScrollProp) {
48
+ reactNativeReanimated.runOnJS(onScrollProp)(event);
49
+ }
50
+ },
51
+ [onScrollProp, horizontal]
52
+ );
53
+ reactNativeKeyboardController.useKeyboardHandler(
54
+ // biome-ignore assist/source/useSortedKeys: prefer start/move/end
55
+ {
56
+ onStart: () => {
57
+ "worklet";
58
+ scrollOffsetAtKeyboardStart.value = scrollOffsetY.value;
59
+ },
60
+ onMove: (event) => {
61
+ "worklet";
62
+ const targetOffset = scrollOffsetAtKeyboardStart.value + event.height;
63
+ scrollOffsetY.value = targetOffset;
64
+ reactNativeReanimated.scrollTo(scrollViewRef, 0, targetOffset, false);
65
+ if (!horizontal) {
66
+ keyboardInset.value = Math.max(0, event.height - safeAreaInsetBottom);
67
+ }
68
+ },
69
+ onEnd: (event) => {
70
+ "worklet";
71
+ const targetOffset = scrollOffsetAtKeyboardStart.value + event.height;
72
+ scrollOffsetY.value = targetOffset;
73
+ reactNativeReanimated.scrollTo(scrollViewRef, 0, targetOffset, false);
74
+ if (!horizontal) {
75
+ keyboardInset.value = Math.max(0, event.height - safeAreaInsetBottom);
76
+ }
77
+ }
78
+ },
79
+ [scrollViewRef, safeAreaInsetBottom]
80
+ );
81
+ const animatedProps = reactNative.Platform.OS === "ios" ? reactNativeReanimated.useAnimatedProps(() => {
82
+ "worklet";
83
+ var _a, _b, _c, _d;
84
+ return {
85
+ contentInset: {
86
+ bottom: ((_a = contentInsetProp == null ? void 0 : contentInsetProp.bottom) != null ? _a : 0) + (horizontal ? 0 : keyboardInset.value),
87
+ left: (_b = contentInsetProp == null ? void 0 : contentInsetProp.left) != null ? _b : 0,
88
+ right: (_c = contentInsetProp == null ? void 0 : contentInsetProp.right) != null ? _c : 0,
89
+ top: (_d = contentInsetProp == null ? void 0 : contentInsetProp.top) != null ? _d : 0
90
+ }
91
+ };
92
+ }) : void 0;
93
+ const style = reactNative.Platform.OS !== "ios" ? reactNativeReanimated.useAnimatedStyle(() => ({
94
+ marginBottom: keyboardInset.value
95
+ })) : void 0;
96
+ return /* @__PURE__ */ React__namespace.createElement(
97
+ reanimated.AnimatedLegendList,
98
+ {
99
+ ...rest,
100
+ animatedProps,
101
+ keyboardDismissMode: "interactive",
102
+ onScroll: scrollHandler,
103
+ ref: forwardedRef,
104
+ refScrollView: scrollViewRef,
105
+ scrollEventThrottle: resolvedScrollEventThrottle,
106
+ scrollIndicatorInsets: { bottom: 0, top: 0 },
107
+ style
108
+ }
109
+ );
110
+ });
111
+
112
+ exports.KeyboardAvoidingLegendList = KeyboardAvoidingLegendList;
113
+ exports.LegendList = KeyboardAvoidingLegendList;
package/keyboard.mjs ADDED
@@ -0,0 +1,91 @@
1
+ import * as React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import { Platform } from 'react-native';
4
+ import { useKeyboardHandler } from 'react-native-keyboard-controller';
5
+ import { useAnimatedRef, useSharedValue, useAnimatedScrollHandler, runOnJS, scrollTo, useAnimatedProps, useAnimatedStyle } from 'react-native-reanimated';
6
+ import { AnimatedLegendList } from '@legendapp/list/reanimated';
7
+
8
+ // src/integrations/keyboard.tsx
9
+ var KeyboardAvoidingLegendList = forwardRef(function KeyboardAvoidingLegendList2(props, forwardedRef) {
10
+ const {
11
+ contentInset: contentInsetProp,
12
+ horizontal,
13
+ onScroll: onScrollProp,
14
+ scrollEventThrottle,
15
+ safeAreaInsetBottom = 0,
16
+ ...rest
17
+ } = props;
18
+ const resolvedScrollEventThrottle = scrollEventThrottle != null ? scrollEventThrottle : 16;
19
+ const scrollViewRef = useAnimatedRef();
20
+ const scrollOffsetY = useSharedValue(0);
21
+ const scrollOffsetAtKeyboardStart = useSharedValue(0);
22
+ const keyboardInset = useSharedValue(0);
23
+ const scrollHandler = useAnimatedScrollHandler(
24
+ (event) => {
25
+ scrollOffsetY.value = event.contentOffset[horizontal ? "x" : "y"];
26
+ if (onScrollProp) {
27
+ runOnJS(onScrollProp)(event);
28
+ }
29
+ },
30
+ [onScrollProp, horizontal]
31
+ );
32
+ useKeyboardHandler(
33
+ // biome-ignore assist/source/useSortedKeys: prefer start/move/end
34
+ {
35
+ onStart: () => {
36
+ "worklet";
37
+ scrollOffsetAtKeyboardStart.value = scrollOffsetY.value;
38
+ },
39
+ onMove: (event) => {
40
+ "worklet";
41
+ const targetOffset = scrollOffsetAtKeyboardStart.value + event.height;
42
+ scrollOffsetY.value = targetOffset;
43
+ scrollTo(scrollViewRef, 0, targetOffset, false);
44
+ if (!horizontal) {
45
+ keyboardInset.value = Math.max(0, event.height - safeAreaInsetBottom);
46
+ }
47
+ },
48
+ onEnd: (event) => {
49
+ "worklet";
50
+ const targetOffset = scrollOffsetAtKeyboardStart.value + event.height;
51
+ scrollOffsetY.value = targetOffset;
52
+ scrollTo(scrollViewRef, 0, targetOffset, false);
53
+ if (!horizontal) {
54
+ keyboardInset.value = Math.max(0, event.height - safeAreaInsetBottom);
55
+ }
56
+ }
57
+ },
58
+ [scrollViewRef, safeAreaInsetBottom]
59
+ );
60
+ const animatedProps = Platform.OS === "ios" ? useAnimatedProps(() => {
61
+ "worklet";
62
+ var _a, _b, _c, _d;
63
+ return {
64
+ contentInset: {
65
+ bottom: ((_a = contentInsetProp == null ? void 0 : contentInsetProp.bottom) != null ? _a : 0) + (horizontal ? 0 : keyboardInset.value),
66
+ left: (_b = contentInsetProp == null ? void 0 : contentInsetProp.left) != null ? _b : 0,
67
+ right: (_c = contentInsetProp == null ? void 0 : contentInsetProp.right) != null ? _c : 0,
68
+ top: (_d = contentInsetProp == null ? void 0 : contentInsetProp.top) != null ? _d : 0
69
+ }
70
+ };
71
+ }) : void 0;
72
+ const style = Platform.OS !== "ios" ? useAnimatedStyle(() => ({
73
+ marginBottom: keyboardInset.value
74
+ })) : void 0;
75
+ return /* @__PURE__ */ React.createElement(
76
+ AnimatedLegendList,
77
+ {
78
+ ...rest,
79
+ animatedProps,
80
+ keyboardDismissMode: "interactive",
81
+ onScroll: scrollHandler,
82
+ ref: forwardedRef,
83
+ refScrollView: scrollViewRef,
84
+ scrollEventThrottle: resolvedScrollEventThrottle,
85
+ scrollIndicatorInsets: { bottom: 0, top: 0 },
86
+ style
87
+ }
88
+ );
89
+ });
90
+
91
+ export { KeyboardAvoidingLegendList, KeyboardAvoidingLegendList as LegendList };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "2.0.15",
3
+ "version": "2.0.16",
4
4
  "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
5
  "sideEffects": false,
6
6
  "private": false,