@legendapp/list 3.0.0-beta.20 → 3.0.0-beta.22
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/animated.native.d.mts +9 -0
- package/animated.native.d.ts +9 -0
- package/animated.native.js +9 -0
- package/animated.native.mjs +7 -0
- package/index.d.mts +33 -2
- package/index.d.ts +33 -2
- package/index.js +197 -24
- package/index.mjs +197 -24
- package/index.native.d.mts +802 -0
- package/index.native.d.ts +802 -0
- package/index.native.js +197 -23
- package/index.native.mjs +197 -23
- package/keyboard-controller.native.d.mts +12 -0
- package/keyboard-controller.native.d.ts +12 -0
- package/keyboard-controller.native.js +69 -0
- package/keyboard-controller.native.mjs +48 -0
- package/keyboard.js +148 -15
- package/keyboard.mjs +149 -16
- package/keyboard.native.d.mts +16 -0
- package/keyboard.native.d.ts +16 -0
- package/keyboard.native.js +361 -0
- package/keyboard.native.mjs +339 -0
- package/package.json +1 -1
- package/reanimated.native.d.mts +18 -0
- package/reanimated.native.d.ts +18 -0
- package/reanimated.native.js +89 -0
- package/reanimated.native.mjs +65 -0
- package/section-list.native.d.mts +112 -0
- package/section-list.native.d.ts +112 -0
- package/section-list.native.js +293 -0
- package/section-list.native.mjs +271 -0
|
@@ -0,0 +1,361 @@
|
|
|
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
|
+
|
|
31
|
+
// src/constants-platform.native.ts
|
|
32
|
+
var f = global.nativeFabricUIManager;
|
|
33
|
+
var IsNewArchitecture = f !== void 0 && f != null;
|
|
34
|
+
|
|
35
|
+
// src/utils/helpers.ts
|
|
36
|
+
function isFunction(obj) {
|
|
37
|
+
return typeof obj === "function";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/hooks/useCombinedRef.ts
|
|
41
|
+
var useCombinedRef = (...refs) => {
|
|
42
|
+
const callback = React.useCallback((element) => {
|
|
43
|
+
for (const ref of refs) {
|
|
44
|
+
if (!ref) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (isFunction(ref)) {
|
|
48
|
+
ref(element);
|
|
49
|
+
} else {
|
|
50
|
+
ref.current = element;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, refs);
|
|
54
|
+
return callback;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/integrations/keyboard.tsx
|
|
58
|
+
var clampProgress = (progress) => {
|
|
59
|
+
"worklet";
|
|
60
|
+
return Math.min(1, Math.max(0, progress));
|
|
61
|
+
};
|
|
62
|
+
var calculateKeyboardInset = (height, safeAreaInsetBottom, isNewArchitecture) => {
|
|
63
|
+
"worklet";
|
|
64
|
+
return isNewArchitecture ? Math.max(0, height - safeAreaInsetBottom) : Math.max(isNewArchitecture ? 0 : -safeAreaInsetBottom, height - safeAreaInsetBottom * 2);
|
|
65
|
+
};
|
|
66
|
+
var calculateEffectiveKeyboardHeight = (keyboardHeight, contentLength, scrollLength, alignItemsAtEnd) => {
|
|
67
|
+
"worklet";
|
|
68
|
+
if (alignItemsAtEnd) {
|
|
69
|
+
return keyboardHeight;
|
|
70
|
+
} else {
|
|
71
|
+
const availableSpace = Math.max(0, scrollLength - contentLength);
|
|
72
|
+
return Math.max(0, keyboardHeight - availableSpace);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var calculateEndPaddingInset = (keyboardHeight, alignItemsAtEndPadding) => {
|
|
76
|
+
"worklet";
|
|
77
|
+
return Math.min(keyboardHeight, alignItemsAtEndPadding);
|
|
78
|
+
};
|
|
79
|
+
var calculateTopInset = (safeAreaInsetTop, isNewArchitecture, extraTopInset) => {
|
|
80
|
+
"worklet";
|
|
81
|
+
return (isNewArchitecture ? 0 : safeAreaInsetTop * 2) + extraTopInset;
|
|
82
|
+
};
|
|
83
|
+
var calculateKeyboardTargetOffset = (startOffset, keyboardHeight, isOpening, progress) => {
|
|
84
|
+
"worklet";
|
|
85
|
+
const normalizedProgress = isOpening ? progress : 1 - progress;
|
|
86
|
+
const delta = (isOpening ? keyboardHeight : -keyboardHeight) * normalizedProgress;
|
|
87
|
+
return Math.max(0, startOffset + delta);
|
|
88
|
+
};
|
|
89
|
+
var KeyboardAvoidingLegendList = React.forwardRef(function KeyboardAvoidingLegendList2(props, forwardedRef) {
|
|
90
|
+
const {
|
|
91
|
+
contentInset: contentInsetProp,
|
|
92
|
+
horizontal,
|
|
93
|
+
onMetricsChange: onMetricsChangeProp,
|
|
94
|
+
onScroll: onScrollProp,
|
|
95
|
+
safeAreaInsets = { bottom: 0, top: 0 },
|
|
96
|
+
style: styleProp,
|
|
97
|
+
...rest
|
|
98
|
+
} = props;
|
|
99
|
+
const { alignItemsAtEnd } = props;
|
|
100
|
+
const styleFlattened = reactNative.StyleSheet.flatten(styleProp);
|
|
101
|
+
const refLegendList = React.useRef(null);
|
|
102
|
+
const combinedRef = useCombinedRef(forwardedRef, refLegendList);
|
|
103
|
+
const isIos = reactNative.Platform.OS === "ios";
|
|
104
|
+
const isAndroid = reactNative.Platform.OS === "android";
|
|
105
|
+
const scrollViewRef = reactNativeReanimated.useAnimatedRef();
|
|
106
|
+
const scrollOffsetY = reactNativeReanimated.useSharedValue(0);
|
|
107
|
+
const animatedOffsetY = reactNativeReanimated.useSharedValue(null);
|
|
108
|
+
const scrollOffsetAtKeyboardStart = reactNativeReanimated.useSharedValue(0);
|
|
109
|
+
const mode = reactNativeReanimated.useSharedValue("idle");
|
|
110
|
+
const keyboardInset = reactNativeReanimated.useSharedValue({ bottom: 0, top: 0 });
|
|
111
|
+
const keyboardHeight = reactNativeReanimated.useSharedValue(0);
|
|
112
|
+
const contentLength = reactNativeReanimated.useSharedValue(0);
|
|
113
|
+
const scrollLength = reactNativeReanimated.useSharedValue(0);
|
|
114
|
+
const alignItemsAtEndPadding = reactNativeReanimated.useSharedValue(0);
|
|
115
|
+
const isOpening = reactNativeReanimated.useSharedValue(false);
|
|
116
|
+
const didInteractive = reactNativeReanimated.useSharedValue(false);
|
|
117
|
+
const { top: safeAreaInsetTop, bottom: safeAreaInsetBottom } = safeAreaInsets;
|
|
118
|
+
const isKeyboardOpen = reactNativeReanimated.useSharedValue(false);
|
|
119
|
+
const scrollHandler = reactNativeReanimated.useAnimatedScrollHandler(
|
|
120
|
+
(event) => {
|
|
121
|
+
if (mode.get() !== "running" || didInteractive.get()) {
|
|
122
|
+
scrollOffsetY.set(event.contentOffset[horizontal ? "x" : "y"]);
|
|
123
|
+
}
|
|
124
|
+
if (onScrollProp) {
|
|
125
|
+
reactNativeReanimated.runOnJS(onScrollProp)(event);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
[onScrollProp, horizontal]
|
|
129
|
+
);
|
|
130
|
+
const setScrollProcessingEnabled = React.useCallback(
|
|
131
|
+
(enabled) => {
|
|
132
|
+
var _a;
|
|
133
|
+
return (_a = refLegendList.current) == null ? void 0 : _a.setScrollProcessingEnabled(enabled);
|
|
134
|
+
},
|
|
135
|
+
[refLegendList]
|
|
136
|
+
);
|
|
137
|
+
const updateScrollMetrics = React.useCallback(() => {
|
|
138
|
+
var _a;
|
|
139
|
+
const state = (_a = refLegendList.current) == null ? void 0 : _a.getState();
|
|
140
|
+
if (!state) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
contentLength.set(state.contentLength);
|
|
144
|
+
scrollLength.set(state.scrollLength);
|
|
145
|
+
}, [contentLength, scrollLength]);
|
|
146
|
+
const handleMetricsChange = React.useCallback(
|
|
147
|
+
(metrics) => {
|
|
148
|
+
updateScrollMetrics();
|
|
149
|
+
const nextPadding = metrics.alignItemsAtEndPadding || 0;
|
|
150
|
+
alignItemsAtEndPadding.set(nextPadding);
|
|
151
|
+
if (!horizontal) {
|
|
152
|
+
reactNativeReanimated.runOnUI((padding, safeInsetTop, isNewArchitecture) => {
|
|
153
|
+
"worklet";
|
|
154
|
+
if (!isKeyboardOpen.get()) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const vKeyboardHeight = keyboardHeight.get();
|
|
158
|
+
const vEffectiveKeyboardHeight = calculateEffectiveKeyboardHeight(
|
|
159
|
+
vKeyboardHeight,
|
|
160
|
+
contentLength.get(),
|
|
161
|
+
scrollLength.get(),
|
|
162
|
+
alignItemsAtEnd
|
|
163
|
+
);
|
|
164
|
+
const vTopInset = calculateEndPaddingInset(vEffectiveKeyboardHeight, padding);
|
|
165
|
+
const topInset = calculateTopInset(safeInsetTop, isNewArchitecture, vTopInset);
|
|
166
|
+
keyboardInset.set({
|
|
167
|
+
bottom: keyboardInset.get().bottom,
|
|
168
|
+
top: topInset
|
|
169
|
+
});
|
|
170
|
+
})(nextPadding, safeAreaInsetTop, IsNewArchitecture);
|
|
171
|
+
}
|
|
172
|
+
onMetricsChangeProp == null ? void 0 : onMetricsChangeProp(metrics);
|
|
173
|
+
},
|
|
174
|
+
[
|
|
175
|
+
alignItemsAtEndPadding,
|
|
176
|
+
horizontal,
|
|
177
|
+
isKeyboardOpen,
|
|
178
|
+
keyboardHeight,
|
|
179
|
+
keyboardInset,
|
|
180
|
+
onMetricsChangeProp,
|
|
181
|
+
safeAreaInsetTop,
|
|
182
|
+
updateScrollMetrics
|
|
183
|
+
]
|
|
184
|
+
);
|
|
185
|
+
reactNativeKeyboardController.useKeyboardHandler(
|
|
186
|
+
// biome-ignore assist/source/useSortedKeys: prefer start/move/end
|
|
187
|
+
{
|
|
188
|
+
onStart: (event) => {
|
|
189
|
+
"worklet";
|
|
190
|
+
mode.set("running");
|
|
191
|
+
const progress = clampProgress(event.progress);
|
|
192
|
+
if (isKeyboardOpen.get() && progress >= 1 && event.height > 0) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (!didInteractive.get()) {
|
|
196
|
+
if (event.height > 0) {
|
|
197
|
+
keyboardHeight.set(event.height - safeAreaInsetBottom);
|
|
198
|
+
}
|
|
199
|
+
isOpening.set(progress > 0);
|
|
200
|
+
scrollOffsetAtKeyboardStart.set(scrollOffsetY.get());
|
|
201
|
+
animatedOffsetY.set(scrollOffsetY.get());
|
|
202
|
+
reactNativeReanimated.runOnJS(setScrollProcessingEnabled)(false);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
onInteractive: (event) => {
|
|
206
|
+
"worklet";
|
|
207
|
+
if (mode.get() !== "running") {
|
|
208
|
+
reactNativeReanimated.runOnJS(setScrollProcessingEnabled)(false);
|
|
209
|
+
}
|
|
210
|
+
mode.set("running");
|
|
211
|
+
if (!didInteractive.get()) {
|
|
212
|
+
if (!isAndroid && !IsNewArchitecture) {
|
|
213
|
+
keyboardInset.set({
|
|
214
|
+
bottom: keyboardInset.get().bottom,
|
|
215
|
+
// Legacy iOS uses a doubled top inset to keep content below the status bar.
|
|
216
|
+
top: calculateTopInset(safeAreaInsetTop, IsNewArchitecture, 0)
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
didInteractive.set(true);
|
|
220
|
+
}
|
|
221
|
+
if (isAndroid && !horizontal) {
|
|
222
|
+
const newInset = calculateKeyboardInset(event.height, safeAreaInsetBottom, IsNewArchitecture);
|
|
223
|
+
keyboardInset.set({ bottom: newInset, top: safeAreaInsetTop * 2 });
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
onMove: (event) => {
|
|
227
|
+
"worklet";
|
|
228
|
+
if (!didInteractive.get()) {
|
|
229
|
+
const progress = clampProgress(event.progress);
|
|
230
|
+
const vIsOpening = isOpening.get();
|
|
231
|
+
const vKeyboardHeight = keyboardHeight.get();
|
|
232
|
+
const vEffectiveKeyboardHeight = calculateEffectiveKeyboardHeight(
|
|
233
|
+
vKeyboardHeight,
|
|
234
|
+
contentLength.get(),
|
|
235
|
+
scrollLength.get(),
|
|
236
|
+
alignItemsAtEnd
|
|
237
|
+
);
|
|
238
|
+
const vAlignItemsPadding = alignItemsAtEndPadding.get();
|
|
239
|
+
const vTopInset = calculateEndPaddingInset(vEffectiveKeyboardHeight, vAlignItemsPadding);
|
|
240
|
+
const targetOffset = calculateKeyboardTargetOffset(
|
|
241
|
+
scrollOffsetAtKeyboardStart.get(),
|
|
242
|
+
vEffectiveKeyboardHeight,
|
|
243
|
+
vIsOpening,
|
|
244
|
+
progress
|
|
245
|
+
);
|
|
246
|
+
scrollOffsetY.set(targetOffset);
|
|
247
|
+
animatedOffsetY.set(targetOffset);
|
|
248
|
+
if (!horizontal) {
|
|
249
|
+
const newInset = calculateKeyboardInset(event.height, safeAreaInsetBottom, IsNewArchitecture);
|
|
250
|
+
const topInset = calculateTopInset(
|
|
251
|
+
safeAreaInsetTop,
|
|
252
|
+
IsNewArchitecture,
|
|
253
|
+
vIsOpening ? vTopInset : 0
|
|
254
|
+
);
|
|
255
|
+
keyboardInset.set({
|
|
256
|
+
bottom: newInset,
|
|
257
|
+
// Add top padding only while opening to keep end-aligned items visible.
|
|
258
|
+
top: topInset
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
onEnd: (event) => {
|
|
264
|
+
"worklet";
|
|
265
|
+
const wasInteractive = didInteractive.get();
|
|
266
|
+
const vMode = mode.get();
|
|
267
|
+
mode.set("idle");
|
|
268
|
+
if (vMode === "running") {
|
|
269
|
+
const progress = clampProgress(event.progress);
|
|
270
|
+
const vKeyboardHeight = keyboardHeight.get();
|
|
271
|
+
const vEffectiveKeyboardHeight = calculateEffectiveKeyboardHeight(
|
|
272
|
+
vKeyboardHeight,
|
|
273
|
+
contentLength.get(),
|
|
274
|
+
scrollLength.get(),
|
|
275
|
+
alignItemsAtEnd
|
|
276
|
+
);
|
|
277
|
+
const vAlignItemsPadding = alignItemsAtEndPadding.get();
|
|
278
|
+
const vTopInset = calculateEndPaddingInset(vEffectiveKeyboardHeight, vAlignItemsPadding);
|
|
279
|
+
const vIsOpening = isOpening.get();
|
|
280
|
+
if (!wasInteractive) {
|
|
281
|
+
const targetOffset = calculateKeyboardTargetOffset(
|
|
282
|
+
scrollOffsetAtKeyboardStart.get(),
|
|
283
|
+
vEffectiveKeyboardHeight,
|
|
284
|
+
vIsOpening,
|
|
285
|
+
progress
|
|
286
|
+
);
|
|
287
|
+
scrollOffsetY.set(targetOffset);
|
|
288
|
+
animatedOffsetY.set(targetOffset);
|
|
289
|
+
}
|
|
290
|
+
reactNativeReanimated.runOnJS(setScrollProcessingEnabled)(true);
|
|
291
|
+
didInteractive.set(false);
|
|
292
|
+
isKeyboardOpen.set(event.height > 0);
|
|
293
|
+
if (!horizontal) {
|
|
294
|
+
const newInset = calculateKeyboardInset(event.height, safeAreaInsetBottom, IsNewArchitecture);
|
|
295
|
+
const topInset = calculateTopInset(
|
|
296
|
+
safeAreaInsetTop,
|
|
297
|
+
IsNewArchitecture,
|
|
298
|
+
event.height > 0 ? vTopInset : 0
|
|
299
|
+
);
|
|
300
|
+
keyboardInset.set({
|
|
301
|
+
bottom: newInset,
|
|
302
|
+
// Preserve end-aligned padding only while the keyboard is visible.
|
|
303
|
+
top: topInset
|
|
304
|
+
});
|
|
305
|
+
if (newInset <= 0) {
|
|
306
|
+
animatedOffsetY.set(scrollOffsetY.get());
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
[alignItemsAtEnd, safeAreaInsetBottom, scrollViewRef]
|
|
313
|
+
);
|
|
314
|
+
const animatedProps = reactNativeReanimated.useAnimatedProps(() => {
|
|
315
|
+
"worklet";
|
|
316
|
+
var _a, _b, _c, _d;
|
|
317
|
+
const vAnimatedOffsetY = animatedOffsetY.get();
|
|
318
|
+
const baseProps = {
|
|
319
|
+
contentOffset: vAnimatedOffsetY === null ? void 0 : {
|
|
320
|
+
x: 0,
|
|
321
|
+
y: vAnimatedOffsetY
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
const { top: keyboardInsetTop, bottom: keyboardInsetBottom } = keyboardInset.get();
|
|
325
|
+
return isIos ? Object.assign(baseProps, {
|
|
326
|
+
contentInset: {
|
|
327
|
+
bottom: ((_a = contentInsetProp == null ? void 0 : contentInsetProp.bottom) != null ? _a : 0) + (horizontal ? 0 : keyboardInsetBottom),
|
|
328
|
+
left: (_b = contentInsetProp == null ? void 0 : contentInsetProp.left) != null ? _b : 0,
|
|
329
|
+
right: (_c = contentInsetProp == null ? void 0 : contentInsetProp.right) != null ? _c : 0,
|
|
330
|
+
top: ((_d = contentInsetProp == null ? void 0 : contentInsetProp.top) != null ? _d : 0) - keyboardInsetTop
|
|
331
|
+
}
|
|
332
|
+
}) : baseProps;
|
|
333
|
+
});
|
|
334
|
+
const style = isAndroid ? reactNativeReanimated.useAnimatedStyle(
|
|
335
|
+
() => {
|
|
336
|
+
var _a;
|
|
337
|
+
return {
|
|
338
|
+
...styleFlattened || {},
|
|
339
|
+
marginBottom: (_a = keyboardInset.get().bottom) != null ? _a : 0
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
[styleProp, keyboardInset]
|
|
343
|
+
) : void 0;
|
|
344
|
+
return /* @__PURE__ */ React__namespace.createElement(
|
|
345
|
+
reanimated.AnimatedLegendList,
|
|
346
|
+
{
|
|
347
|
+
...rest,
|
|
348
|
+
animatedProps,
|
|
349
|
+
keyboardDismissMode: "interactive",
|
|
350
|
+
onMetricsChange: handleMetricsChange,
|
|
351
|
+
onScroll: scrollHandler,
|
|
352
|
+
ref: combinedRef,
|
|
353
|
+
refScrollView: scrollViewRef,
|
|
354
|
+
scrollIndicatorInsets: { bottom: 0, top: 0 },
|
|
355
|
+
style
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
exports.KeyboardAvoidingLegendList = KeyboardAvoidingLegendList;
|
|
361
|
+
exports.LegendList = KeyboardAvoidingLegendList;
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { forwardRef, useRef, useCallback } from 'react';
|
|
3
|
+
import { StyleSheet, Platform } from 'react-native';
|
|
4
|
+
import { useKeyboardHandler } from 'react-native-keyboard-controller';
|
|
5
|
+
import { useAnimatedRef, useSharedValue, useAnimatedScrollHandler, runOnJS, runOnUI, useAnimatedProps, useAnimatedStyle } from 'react-native-reanimated';
|
|
6
|
+
import { AnimatedLegendList } from '@legendapp/list/reanimated';
|
|
7
|
+
|
|
8
|
+
// src/integrations/keyboard.tsx
|
|
9
|
+
|
|
10
|
+
// src/constants-platform.native.ts
|
|
11
|
+
var f = global.nativeFabricUIManager;
|
|
12
|
+
var IsNewArchitecture = f !== void 0 && f != null;
|
|
13
|
+
|
|
14
|
+
// src/utils/helpers.ts
|
|
15
|
+
function isFunction(obj) {
|
|
16
|
+
return typeof obj === "function";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/hooks/useCombinedRef.ts
|
|
20
|
+
var useCombinedRef = (...refs) => {
|
|
21
|
+
const callback = useCallback((element) => {
|
|
22
|
+
for (const ref of refs) {
|
|
23
|
+
if (!ref) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (isFunction(ref)) {
|
|
27
|
+
ref(element);
|
|
28
|
+
} else {
|
|
29
|
+
ref.current = element;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}, refs);
|
|
33
|
+
return callback;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/integrations/keyboard.tsx
|
|
37
|
+
var clampProgress = (progress) => {
|
|
38
|
+
"worklet";
|
|
39
|
+
return Math.min(1, Math.max(0, progress));
|
|
40
|
+
};
|
|
41
|
+
var calculateKeyboardInset = (height, safeAreaInsetBottom, isNewArchitecture) => {
|
|
42
|
+
"worklet";
|
|
43
|
+
return isNewArchitecture ? Math.max(0, height - safeAreaInsetBottom) : Math.max(isNewArchitecture ? 0 : -safeAreaInsetBottom, height - safeAreaInsetBottom * 2);
|
|
44
|
+
};
|
|
45
|
+
var calculateEffectiveKeyboardHeight = (keyboardHeight, contentLength, scrollLength, alignItemsAtEnd) => {
|
|
46
|
+
"worklet";
|
|
47
|
+
if (alignItemsAtEnd) {
|
|
48
|
+
return keyboardHeight;
|
|
49
|
+
} else {
|
|
50
|
+
const availableSpace = Math.max(0, scrollLength - contentLength);
|
|
51
|
+
return Math.max(0, keyboardHeight - availableSpace);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var calculateEndPaddingInset = (keyboardHeight, alignItemsAtEndPadding) => {
|
|
55
|
+
"worklet";
|
|
56
|
+
return Math.min(keyboardHeight, alignItemsAtEndPadding);
|
|
57
|
+
};
|
|
58
|
+
var calculateTopInset = (safeAreaInsetTop, isNewArchitecture, extraTopInset) => {
|
|
59
|
+
"worklet";
|
|
60
|
+
return (isNewArchitecture ? 0 : safeAreaInsetTop * 2) + extraTopInset;
|
|
61
|
+
};
|
|
62
|
+
var calculateKeyboardTargetOffset = (startOffset, keyboardHeight, isOpening, progress) => {
|
|
63
|
+
"worklet";
|
|
64
|
+
const normalizedProgress = isOpening ? progress : 1 - progress;
|
|
65
|
+
const delta = (isOpening ? keyboardHeight : -keyboardHeight) * normalizedProgress;
|
|
66
|
+
return Math.max(0, startOffset + delta);
|
|
67
|
+
};
|
|
68
|
+
var KeyboardAvoidingLegendList = forwardRef(function KeyboardAvoidingLegendList2(props, forwardedRef) {
|
|
69
|
+
const {
|
|
70
|
+
contentInset: contentInsetProp,
|
|
71
|
+
horizontal,
|
|
72
|
+
onMetricsChange: onMetricsChangeProp,
|
|
73
|
+
onScroll: onScrollProp,
|
|
74
|
+
safeAreaInsets = { bottom: 0, top: 0 },
|
|
75
|
+
style: styleProp,
|
|
76
|
+
...rest
|
|
77
|
+
} = props;
|
|
78
|
+
const { alignItemsAtEnd } = props;
|
|
79
|
+
const styleFlattened = StyleSheet.flatten(styleProp);
|
|
80
|
+
const refLegendList = useRef(null);
|
|
81
|
+
const combinedRef = useCombinedRef(forwardedRef, refLegendList);
|
|
82
|
+
const isIos = Platform.OS === "ios";
|
|
83
|
+
const isAndroid = Platform.OS === "android";
|
|
84
|
+
const scrollViewRef = useAnimatedRef();
|
|
85
|
+
const scrollOffsetY = useSharedValue(0);
|
|
86
|
+
const animatedOffsetY = useSharedValue(null);
|
|
87
|
+
const scrollOffsetAtKeyboardStart = useSharedValue(0);
|
|
88
|
+
const mode = useSharedValue("idle");
|
|
89
|
+
const keyboardInset = useSharedValue({ bottom: 0, top: 0 });
|
|
90
|
+
const keyboardHeight = useSharedValue(0);
|
|
91
|
+
const contentLength = useSharedValue(0);
|
|
92
|
+
const scrollLength = useSharedValue(0);
|
|
93
|
+
const alignItemsAtEndPadding = useSharedValue(0);
|
|
94
|
+
const isOpening = useSharedValue(false);
|
|
95
|
+
const didInteractive = useSharedValue(false);
|
|
96
|
+
const { top: safeAreaInsetTop, bottom: safeAreaInsetBottom } = safeAreaInsets;
|
|
97
|
+
const isKeyboardOpen = useSharedValue(false);
|
|
98
|
+
const scrollHandler = useAnimatedScrollHandler(
|
|
99
|
+
(event) => {
|
|
100
|
+
if (mode.get() !== "running" || didInteractive.get()) {
|
|
101
|
+
scrollOffsetY.set(event.contentOffset[horizontal ? "x" : "y"]);
|
|
102
|
+
}
|
|
103
|
+
if (onScrollProp) {
|
|
104
|
+
runOnJS(onScrollProp)(event);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
[onScrollProp, horizontal]
|
|
108
|
+
);
|
|
109
|
+
const setScrollProcessingEnabled = useCallback(
|
|
110
|
+
(enabled) => {
|
|
111
|
+
var _a;
|
|
112
|
+
return (_a = refLegendList.current) == null ? void 0 : _a.setScrollProcessingEnabled(enabled);
|
|
113
|
+
},
|
|
114
|
+
[refLegendList]
|
|
115
|
+
);
|
|
116
|
+
const updateScrollMetrics = useCallback(() => {
|
|
117
|
+
var _a;
|
|
118
|
+
const state = (_a = refLegendList.current) == null ? void 0 : _a.getState();
|
|
119
|
+
if (!state) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
contentLength.set(state.contentLength);
|
|
123
|
+
scrollLength.set(state.scrollLength);
|
|
124
|
+
}, [contentLength, scrollLength]);
|
|
125
|
+
const handleMetricsChange = useCallback(
|
|
126
|
+
(metrics) => {
|
|
127
|
+
updateScrollMetrics();
|
|
128
|
+
const nextPadding = metrics.alignItemsAtEndPadding || 0;
|
|
129
|
+
alignItemsAtEndPadding.set(nextPadding);
|
|
130
|
+
if (!horizontal) {
|
|
131
|
+
runOnUI((padding, safeInsetTop, isNewArchitecture) => {
|
|
132
|
+
"worklet";
|
|
133
|
+
if (!isKeyboardOpen.get()) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const vKeyboardHeight = keyboardHeight.get();
|
|
137
|
+
const vEffectiveKeyboardHeight = calculateEffectiveKeyboardHeight(
|
|
138
|
+
vKeyboardHeight,
|
|
139
|
+
contentLength.get(),
|
|
140
|
+
scrollLength.get(),
|
|
141
|
+
alignItemsAtEnd
|
|
142
|
+
);
|
|
143
|
+
const vTopInset = calculateEndPaddingInset(vEffectiveKeyboardHeight, padding);
|
|
144
|
+
const topInset = calculateTopInset(safeInsetTop, isNewArchitecture, vTopInset);
|
|
145
|
+
keyboardInset.set({
|
|
146
|
+
bottom: keyboardInset.get().bottom,
|
|
147
|
+
top: topInset
|
|
148
|
+
});
|
|
149
|
+
})(nextPadding, safeAreaInsetTop, IsNewArchitecture);
|
|
150
|
+
}
|
|
151
|
+
onMetricsChangeProp == null ? void 0 : onMetricsChangeProp(metrics);
|
|
152
|
+
},
|
|
153
|
+
[
|
|
154
|
+
alignItemsAtEndPadding,
|
|
155
|
+
horizontal,
|
|
156
|
+
isKeyboardOpen,
|
|
157
|
+
keyboardHeight,
|
|
158
|
+
keyboardInset,
|
|
159
|
+
onMetricsChangeProp,
|
|
160
|
+
safeAreaInsetTop,
|
|
161
|
+
updateScrollMetrics
|
|
162
|
+
]
|
|
163
|
+
);
|
|
164
|
+
useKeyboardHandler(
|
|
165
|
+
// biome-ignore assist/source/useSortedKeys: prefer start/move/end
|
|
166
|
+
{
|
|
167
|
+
onStart: (event) => {
|
|
168
|
+
"worklet";
|
|
169
|
+
mode.set("running");
|
|
170
|
+
const progress = clampProgress(event.progress);
|
|
171
|
+
if (isKeyboardOpen.get() && progress >= 1 && event.height > 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!didInteractive.get()) {
|
|
175
|
+
if (event.height > 0) {
|
|
176
|
+
keyboardHeight.set(event.height - safeAreaInsetBottom);
|
|
177
|
+
}
|
|
178
|
+
isOpening.set(progress > 0);
|
|
179
|
+
scrollOffsetAtKeyboardStart.set(scrollOffsetY.get());
|
|
180
|
+
animatedOffsetY.set(scrollOffsetY.get());
|
|
181
|
+
runOnJS(setScrollProcessingEnabled)(false);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
onInteractive: (event) => {
|
|
185
|
+
"worklet";
|
|
186
|
+
if (mode.get() !== "running") {
|
|
187
|
+
runOnJS(setScrollProcessingEnabled)(false);
|
|
188
|
+
}
|
|
189
|
+
mode.set("running");
|
|
190
|
+
if (!didInteractive.get()) {
|
|
191
|
+
if (!isAndroid && !IsNewArchitecture) {
|
|
192
|
+
keyboardInset.set({
|
|
193
|
+
bottom: keyboardInset.get().bottom,
|
|
194
|
+
// Legacy iOS uses a doubled top inset to keep content below the status bar.
|
|
195
|
+
top: calculateTopInset(safeAreaInsetTop, IsNewArchitecture, 0)
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
didInteractive.set(true);
|
|
199
|
+
}
|
|
200
|
+
if (isAndroid && !horizontal) {
|
|
201
|
+
const newInset = calculateKeyboardInset(event.height, safeAreaInsetBottom, IsNewArchitecture);
|
|
202
|
+
keyboardInset.set({ bottom: newInset, top: safeAreaInsetTop * 2 });
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
onMove: (event) => {
|
|
206
|
+
"worklet";
|
|
207
|
+
if (!didInteractive.get()) {
|
|
208
|
+
const progress = clampProgress(event.progress);
|
|
209
|
+
const vIsOpening = isOpening.get();
|
|
210
|
+
const vKeyboardHeight = keyboardHeight.get();
|
|
211
|
+
const vEffectiveKeyboardHeight = calculateEffectiveKeyboardHeight(
|
|
212
|
+
vKeyboardHeight,
|
|
213
|
+
contentLength.get(),
|
|
214
|
+
scrollLength.get(),
|
|
215
|
+
alignItemsAtEnd
|
|
216
|
+
);
|
|
217
|
+
const vAlignItemsPadding = alignItemsAtEndPadding.get();
|
|
218
|
+
const vTopInset = calculateEndPaddingInset(vEffectiveKeyboardHeight, vAlignItemsPadding);
|
|
219
|
+
const targetOffset = calculateKeyboardTargetOffset(
|
|
220
|
+
scrollOffsetAtKeyboardStart.get(),
|
|
221
|
+
vEffectiveKeyboardHeight,
|
|
222
|
+
vIsOpening,
|
|
223
|
+
progress
|
|
224
|
+
);
|
|
225
|
+
scrollOffsetY.set(targetOffset);
|
|
226
|
+
animatedOffsetY.set(targetOffset);
|
|
227
|
+
if (!horizontal) {
|
|
228
|
+
const newInset = calculateKeyboardInset(event.height, safeAreaInsetBottom, IsNewArchitecture);
|
|
229
|
+
const topInset = calculateTopInset(
|
|
230
|
+
safeAreaInsetTop,
|
|
231
|
+
IsNewArchitecture,
|
|
232
|
+
vIsOpening ? vTopInset : 0
|
|
233
|
+
);
|
|
234
|
+
keyboardInset.set({
|
|
235
|
+
bottom: newInset,
|
|
236
|
+
// Add top padding only while opening to keep end-aligned items visible.
|
|
237
|
+
top: topInset
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
onEnd: (event) => {
|
|
243
|
+
"worklet";
|
|
244
|
+
const wasInteractive = didInteractive.get();
|
|
245
|
+
const vMode = mode.get();
|
|
246
|
+
mode.set("idle");
|
|
247
|
+
if (vMode === "running") {
|
|
248
|
+
const progress = clampProgress(event.progress);
|
|
249
|
+
const vKeyboardHeight = keyboardHeight.get();
|
|
250
|
+
const vEffectiveKeyboardHeight = calculateEffectiveKeyboardHeight(
|
|
251
|
+
vKeyboardHeight,
|
|
252
|
+
contentLength.get(),
|
|
253
|
+
scrollLength.get(),
|
|
254
|
+
alignItemsAtEnd
|
|
255
|
+
);
|
|
256
|
+
const vAlignItemsPadding = alignItemsAtEndPadding.get();
|
|
257
|
+
const vTopInset = calculateEndPaddingInset(vEffectiveKeyboardHeight, vAlignItemsPadding);
|
|
258
|
+
const vIsOpening = isOpening.get();
|
|
259
|
+
if (!wasInteractive) {
|
|
260
|
+
const targetOffset = calculateKeyboardTargetOffset(
|
|
261
|
+
scrollOffsetAtKeyboardStart.get(),
|
|
262
|
+
vEffectiveKeyboardHeight,
|
|
263
|
+
vIsOpening,
|
|
264
|
+
progress
|
|
265
|
+
);
|
|
266
|
+
scrollOffsetY.set(targetOffset);
|
|
267
|
+
animatedOffsetY.set(targetOffset);
|
|
268
|
+
}
|
|
269
|
+
runOnJS(setScrollProcessingEnabled)(true);
|
|
270
|
+
didInteractive.set(false);
|
|
271
|
+
isKeyboardOpen.set(event.height > 0);
|
|
272
|
+
if (!horizontal) {
|
|
273
|
+
const newInset = calculateKeyboardInset(event.height, safeAreaInsetBottom, IsNewArchitecture);
|
|
274
|
+
const topInset = calculateTopInset(
|
|
275
|
+
safeAreaInsetTop,
|
|
276
|
+
IsNewArchitecture,
|
|
277
|
+
event.height > 0 ? vTopInset : 0
|
|
278
|
+
);
|
|
279
|
+
keyboardInset.set({
|
|
280
|
+
bottom: newInset,
|
|
281
|
+
// Preserve end-aligned padding only while the keyboard is visible.
|
|
282
|
+
top: topInset
|
|
283
|
+
});
|
|
284
|
+
if (newInset <= 0) {
|
|
285
|
+
animatedOffsetY.set(scrollOffsetY.get());
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
[alignItemsAtEnd, safeAreaInsetBottom, scrollViewRef]
|
|
292
|
+
);
|
|
293
|
+
const animatedProps = useAnimatedProps(() => {
|
|
294
|
+
"worklet";
|
|
295
|
+
var _a, _b, _c, _d;
|
|
296
|
+
const vAnimatedOffsetY = animatedOffsetY.get();
|
|
297
|
+
const baseProps = {
|
|
298
|
+
contentOffset: vAnimatedOffsetY === null ? void 0 : {
|
|
299
|
+
x: 0,
|
|
300
|
+
y: vAnimatedOffsetY
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const { top: keyboardInsetTop, bottom: keyboardInsetBottom } = keyboardInset.get();
|
|
304
|
+
return isIos ? Object.assign(baseProps, {
|
|
305
|
+
contentInset: {
|
|
306
|
+
bottom: ((_a = contentInsetProp == null ? void 0 : contentInsetProp.bottom) != null ? _a : 0) + (horizontal ? 0 : keyboardInsetBottom),
|
|
307
|
+
left: (_b = contentInsetProp == null ? void 0 : contentInsetProp.left) != null ? _b : 0,
|
|
308
|
+
right: (_c = contentInsetProp == null ? void 0 : contentInsetProp.right) != null ? _c : 0,
|
|
309
|
+
top: ((_d = contentInsetProp == null ? void 0 : contentInsetProp.top) != null ? _d : 0) - keyboardInsetTop
|
|
310
|
+
}
|
|
311
|
+
}) : baseProps;
|
|
312
|
+
});
|
|
313
|
+
const style = isAndroid ? useAnimatedStyle(
|
|
314
|
+
() => {
|
|
315
|
+
var _a;
|
|
316
|
+
return {
|
|
317
|
+
...styleFlattened || {},
|
|
318
|
+
marginBottom: (_a = keyboardInset.get().bottom) != null ? _a : 0
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
[styleProp, keyboardInset]
|
|
322
|
+
) : void 0;
|
|
323
|
+
return /* @__PURE__ */ React.createElement(
|
|
324
|
+
AnimatedLegendList,
|
|
325
|
+
{
|
|
326
|
+
...rest,
|
|
327
|
+
animatedProps,
|
|
328
|
+
keyboardDismissMode: "interactive",
|
|
329
|
+
onMetricsChange: handleMetricsChange,
|
|
330
|
+
onScroll: scrollHandler,
|
|
331
|
+
ref: combinedRef,
|
|
332
|
+
refScrollView: scrollViewRef,
|
|
333
|
+
scrollIndicatorInsets: { bottom: 0, top: 0 },
|
|
334
|
+
style
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
export { KeyboardAvoidingLegendList, KeyboardAvoidingLegendList as LegendList };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@legendapp/list",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.22",
|
|
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,
|