@rific/scroll-view 0.9.2 → 1.1.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.
- package/README.md +24 -1
- package/dist/index.d.mts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +36 -12
- package/dist/index.mjs +29 -8
- package/package.json +3 -3
- package/src/PullSearch.tsx +3 -1
- package/src/ScrollViewConfig.tsx +34 -0
- package/src/ScrollViewHeader.tsx +9 -3
- package/src/index.ts +2 -0
- package/src/internal/useScrollHandler.ts +7 -2
- package/src/internal/useStickyHeaders.ts +3 -1
- package/src/useKeyboardInset.ts +4 -2
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Blur-chrome scroll system for React Native. Drop-in replacements for `ScrollView`, `FlatList`, and `SectionList` with floating headers and footers, pull-to-search, keyboard awareness, horizontal paging, and frosted-glass chrome.
|
|
4
4
|
|
|
5
|
+
**[Live demo](https://rific.jaydeaton.com/demos/scroll-view)**
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```sh
|
|
@@ -63,6 +65,27 @@ App-wide defaults for scroll settings. Wrap your root layout to set global defau
|
|
|
63
65
|
|
|
64
66
|
---
|
|
65
67
|
|
|
68
|
+
### `ScrollViewConfigProvider`
|
|
69
|
+
|
|
70
|
+
App-wide component config, set once at startup. Today it holds one thing: the component `ScrollViewHeader` renders for a `backAction` callback. Pass `AppbarBackAction` from [`@rific/feedback-press`](https://www.npmjs.com/package/@rific/feedback-press) and every header's back button gets the same haptics and sound as the rest of your buttons, without touching each screen. This package doesn't depend on feedback-press; you hand it the component.
|
|
71
|
+
|
|
72
|
+
| Prop | Type | Default | Description |
|
|
73
|
+
|------|------|---------|-------------|
|
|
74
|
+
| `BackAction` | `ComponentType<ScrollViewBackActionProps>` | Paper's `Appbar.BackAction` | Rendered for a `backAction` callback. Receives `onPress`, `size`, `style` and `accessibilityLabel` (from `backActionAccessibilityLabel`), the same props the default gets |
|
|
75
|
+
| `children` | `ReactNode` | - | - |
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { AppbarBackAction } from '@rific/feedback-press'
|
|
79
|
+
import { ScrollViewConfigProvider } from '@rific/scroll-view'
|
|
80
|
+
|
|
81
|
+
// Inside FeedbackPressProvider, since AppbarBackAction reads it
|
|
82
|
+
<ScrollViewConfigProvider BackAction={AppbarBackAction}>{children}</ScrollViewConfigProvider>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or call `configureScrollView({ BackAction: AppbarBackAction })` once at module load; `getScrollViewConfig()` reads it back. Like the other `@rific` module configs (`DrawerProvider`, `ResizableInputProvider`), this is plain startup config, not React state: the provider sets it during its own render, before anything under it renders, and changing it later doesn't re-render headers already on screen. A `backAction` element on one header still replaces the configured component there.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
66
89
|
### `ScrollViewProvider`
|
|
67
90
|
|
|
68
91
|
Screen-level provider that owns header/footer state and scroll position. Must wrap `ScrollViewHeader`, `ScrollViewFooter`, and the scroll component.
|
|
@@ -108,7 +131,7 @@ Floating header with blur backdrop, title, back action, trailing action, and bui
|
|
|
108
131
|
| `caption` | `string` | - | Subtitle below title |
|
|
109
132
|
| `centerContent` | `ReactNode` | - | Replaces title/caption with custom content |
|
|
110
133
|
| `children` | `ReactNode` | - | Left-side content (icon buttons, etc.) |
|
|
111
|
-
| `backAction` | `() => void` | - |
|
|
134
|
+
| `backAction` | `(() => void) \| ReactNode` | - | A callback renders a back button (the [configured `BackAction`](#scrollviewconfigprovider), Paper's `Appbar.BackAction` by default) and calls it on press. An element is rendered in that spot instead |
|
|
112
135
|
| `backActionFixed` | `boolean` | settings | Keep back button visible as header scrolls away |
|
|
113
136
|
| `trailingAction` | `ReactNode` | - | Right-side floating action |
|
|
114
137
|
| `trailingActionFixed` | `boolean` | `true` | Keep trailing action visible as header scrolls away |
|
package/dist/index.d.mts
CHANGED
|
@@ -166,6 +166,23 @@ type ScrollViewChromeProviderProps = {
|
|
|
166
166
|
};
|
|
167
167
|
declare const ScrollViewChromeProvider: ({ children, footprint, overhang, snapBack, useIsActive }: ScrollViewChromeProviderProps) => react.JSX.Element;
|
|
168
168
|
|
|
169
|
+
type ScrollViewBackActionProps = {
|
|
170
|
+
accessibilityLabel?: string;
|
|
171
|
+
onPress: () => void;
|
|
172
|
+
size: number;
|
|
173
|
+
style: ViewStyle;
|
|
174
|
+
};
|
|
175
|
+
type ScrollViewConfig = {
|
|
176
|
+
/** Component ScrollViewHeader renders when `backAction` is a plain callback. Pass `AppbarBackAction` from `@rific/feedback-press` to give every header's back button its haptics and sound; omit to keep react-native-paper's `Appbar.BackAction`. A `backAction` element on one header still replaces it there. */
|
|
177
|
+
BackAction?: ComponentType<ScrollViewBackActionProps>;
|
|
178
|
+
};
|
|
179
|
+
declare const configureScrollView: (next: Partial<ScrollViewConfig>) => void;
|
|
180
|
+
declare const getScrollViewConfig: () => ScrollViewConfig;
|
|
181
|
+
declare const ScrollViewConfigProvider: (props: _rific_core.ModuleConfigProviderProps<ScrollViewConfig>) => React.JSX.Element;
|
|
182
|
+
type ScrollViewConfigProviderProps = ScrollViewConfig & {
|
|
183
|
+
children: ReactNode;
|
|
184
|
+
};
|
|
185
|
+
|
|
169
186
|
type ScrollViewContextType = {
|
|
170
187
|
blur: boolean;
|
|
171
188
|
chromeHosted: boolean;
|
|
@@ -285,4 +302,4 @@ declare const useScrollViewChromeReveal: () => () => void;
|
|
|
285
302
|
|
|
286
303
|
declare const useScrollViewSettings: () => _rific_core.SettingsContextValue<ScrollViewSettings>;
|
|
287
304
|
|
|
288
|
-
export { type ChipProps, CustomList, type CustomListProps, FlatList, type FlatListProps, PullSearch, type PullSearchHandle, type PullSearchProps, ScrollView, ScrollViewChromeProvider, type ScrollViewChromeProviderProps, ScrollViewContext, type ScrollViewContextType, ScrollViewFooter, type ScrollViewFooterProps, ScrollViewHeader, type ScrollViewHeaderProps, type ScrollViewProps, ScrollViewProvider, type ScrollViewProviderProps, type ScrollViewSettings, ScrollViewSettingsContext, type ScrollViewSettingsContextType, ScrollViewSettingsProvider, type ScrollViewSettingsProviderProps, SectionList, type SectionListProps, defaultScrollViewSettings, scrollViewActions, scrollViewReducer, useKeyboardInset, useScrollView, useScrollViewChromePin, useScrollViewChromeReveal, useScrollViewChromeStyle, useScrollViewSettings };
|
|
305
|
+
export { type ChipProps, CustomList, type CustomListProps, FlatList, type FlatListProps, PullSearch, type PullSearchHandle, type PullSearchProps, ScrollView, type ScrollViewBackActionProps, ScrollViewChromeProvider, type ScrollViewChromeProviderProps, type ScrollViewConfig, ScrollViewConfigProvider, type ScrollViewConfigProviderProps, ScrollViewContext, type ScrollViewContextType, ScrollViewFooter, type ScrollViewFooterProps, ScrollViewHeader, type ScrollViewHeaderProps, type ScrollViewProps, ScrollViewProvider, type ScrollViewProviderProps, type ScrollViewSettings, ScrollViewSettingsContext, type ScrollViewSettingsContextType, ScrollViewSettingsProvider, type ScrollViewSettingsProviderProps, SectionList, type SectionListProps, configureScrollView, defaultScrollViewSettings, getScrollViewConfig, scrollViewActions, scrollViewReducer, useKeyboardInset, useScrollView, useScrollViewChromePin, useScrollViewChromeReveal, useScrollViewChromeStyle, useScrollViewSettings };
|
package/dist/index.d.ts
CHANGED
|
@@ -166,6 +166,23 @@ type ScrollViewChromeProviderProps = {
|
|
|
166
166
|
};
|
|
167
167
|
declare const ScrollViewChromeProvider: ({ children, footprint, overhang, snapBack, useIsActive }: ScrollViewChromeProviderProps) => react.JSX.Element;
|
|
168
168
|
|
|
169
|
+
type ScrollViewBackActionProps = {
|
|
170
|
+
accessibilityLabel?: string;
|
|
171
|
+
onPress: () => void;
|
|
172
|
+
size: number;
|
|
173
|
+
style: ViewStyle;
|
|
174
|
+
};
|
|
175
|
+
type ScrollViewConfig = {
|
|
176
|
+
/** Component ScrollViewHeader renders when `backAction` is a plain callback. Pass `AppbarBackAction` from `@rific/feedback-press` to give every header's back button its haptics and sound; omit to keep react-native-paper's `Appbar.BackAction`. A `backAction` element on one header still replaces it there. */
|
|
177
|
+
BackAction?: ComponentType<ScrollViewBackActionProps>;
|
|
178
|
+
};
|
|
179
|
+
declare const configureScrollView: (next: Partial<ScrollViewConfig>) => void;
|
|
180
|
+
declare const getScrollViewConfig: () => ScrollViewConfig;
|
|
181
|
+
declare const ScrollViewConfigProvider: (props: _rific_core.ModuleConfigProviderProps<ScrollViewConfig>) => React.JSX.Element;
|
|
182
|
+
type ScrollViewConfigProviderProps = ScrollViewConfig & {
|
|
183
|
+
children: ReactNode;
|
|
184
|
+
};
|
|
185
|
+
|
|
169
186
|
type ScrollViewContextType = {
|
|
170
187
|
blur: boolean;
|
|
171
188
|
chromeHosted: boolean;
|
|
@@ -285,4 +302,4 @@ declare const useScrollViewChromeReveal: () => () => void;
|
|
|
285
302
|
|
|
286
303
|
declare const useScrollViewSettings: () => _rific_core.SettingsContextValue<ScrollViewSettings>;
|
|
287
304
|
|
|
288
|
-
export { type ChipProps, CustomList, type CustomListProps, FlatList, type FlatListProps, PullSearch, type PullSearchHandle, type PullSearchProps, ScrollView, ScrollViewChromeProvider, type ScrollViewChromeProviderProps, ScrollViewContext, type ScrollViewContextType, ScrollViewFooter, type ScrollViewFooterProps, ScrollViewHeader, type ScrollViewHeaderProps, type ScrollViewProps, ScrollViewProvider, type ScrollViewProviderProps, type ScrollViewSettings, ScrollViewSettingsContext, type ScrollViewSettingsContextType, ScrollViewSettingsProvider, type ScrollViewSettingsProviderProps, SectionList, type SectionListProps, defaultScrollViewSettings, scrollViewActions, scrollViewReducer, useKeyboardInset, useScrollView, useScrollViewChromePin, useScrollViewChromeReveal, useScrollViewChromeStyle, useScrollViewSettings };
|
|
305
|
+
export { type ChipProps, CustomList, type CustomListProps, FlatList, type FlatListProps, PullSearch, type PullSearchHandle, type PullSearchProps, ScrollView, type ScrollViewBackActionProps, ScrollViewChromeProvider, type ScrollViewChromeProviderProps, type ScrollViewConfig, ScrollViewConfigProvider, type ScrollViewConfigProviderProps, ScrollViewContext, type ScrollViewContextType, ScrollViewFooter, type ScrollViewFooterProps, ScrollViewHeader, type ScrollViewHeaderProps, type ScrollViewProps, ScrollViewProvider, type ScrollViewProviderProps, type ScrollViewSettings, ScrollViewSettingsContext, type ScrollViewSettingsContextType, ScrollViewSettingsProvider, type ScrollViewSettingsProviderProps, SectionList, type SectionListProps, configureScrollView, defaultScrollViewSettings, getScrollViewConfig, scrollViewActions, scrollViewReducer, useKeyboardInset, useScrollView, useScrollViewChromePin, useScrollViewChromeReveal, useScrollViewChromeStyle, useScrollViewSettings };
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
PullSearch: () => PullSearch,
|
|
36
36
|
ScrollView: () => ScrollView,
|
|
37
37
|
ScrollViewChromeProvider: () => ScrollViewChromeProvider,
|
|
38
|
+
ScrollViewConfigProvider: () => ScrollViewConfigProvider,
|
|
38
39
|
ScrollViewContext: () => ScrollViewContext,
|
|
39
40
|
ScrollViewFooter: () => ScrollViewFooter,
|
|
40
41
|
ScrollViewHeader: () => ScrollViewHeader,
|
|
@@ -42,7 +43,9 @@ __export(index_exports, {
|
|
|
42
43
|
ScrollViewSettingsContext: () => ScrollViewSettingsContext,
|
|
43
44
|
ScrollViewSettingsProvider: () => ScrollViewSettingsProvider,
|
|
44
45
|
SectionList: () => SectionList,
|
|
46
|
+
configureScrollView: () => configureScrollView,
|
|
45
47
|
defaultScrollViewSettings: () => defaultScrollViewSettings,
|
|
48
|
+
getScrollViewConfig: () => getScrollViewConfig,
|
|
46
49
|
scrollViewActions: () => scrollViewActions,
|
|
47
50
|
scrollViewReducer: () => scrollViewReducer,
|
|
48
51
|
useKeyboardInset: () => useKeyboardInset,
|
|
@@ -87,6 +90,7 @@ var styles = import_react_native2.StyleSheet.create({
|
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
// src/internal/useScrollHandler.ts
|
|
93
|
+
var import_core = require("@rific/core");
|
|
90
94
|
var import_react3 = require("react");
|
|
91
95
|
var import_react_native_reanimated3 = require("react-native-reanimated");
|
|
92
96
|
|
|
@@ -335,6 +339,8 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
|
|
|
335
339
|
const { chromeHosted, chromeWritable, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared, stackHeightShared } = (0, import_react3.useContext)(ScrollViewContext);
|
|
336
340
|
const snapUpAccum = (0, import_react_native_reanimated3.useSharedValue)(0);
|
|
337
341
|
const fromBottomBounce = (0, import_react_native_reanimated3.useSharedValue)(false);
|
|
342
|
+
const remountSyncedKey = (0, import_core.useWorkletCallback)((y) => onRemountSynced?.(y));
|
|
343
|
+
const remountSyncRetryKey = (0, import_core.useWorkletCallback)((y) => onRemountSyncRetry?.(y));
|
|
338
344
|
return (0, import_react_native_reanimated3.useAnimatedScrollHandler)(
|
|
339
345
|
{
|
|
340
346
|
onScroll: ({ contentOffset: { x, y }, contentSize: { height: contentHeight }, layoutMeasurement: { height: layoutHeight } }) => {
|
|
@@ -343,9 +349,9 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
|
|
|
343
349
|
const target = remountSyncTarget.value;
|
|
344
350
|
if (Math.abs(y - target) < REMOUNT_SYNC_TOLERANCE) {
|
|
345
351
|
remountSyncTarget.value = null;
|
|
346
|
-
if (onRemountSynced) (0, import_react_native_reanimated3.runOnJS)(
|
|
352
|
+
if (onRemountSynced) (0, import_react_native_reanimated3.runOnJS)(import_core.callWorkletCallback)(remountSyncedKey, y);
|
|
347
353
|
} else if (onRemountSyncRetry) {
|
|
348
|
-
(0, import_react_native_reanimated3.runOnJS)(
|
|
354
|
+
(0, import_react_native_reanimated3.runOnJS)(import_core.callWorkletCallback)(remountSyncRetryKey, y);
|
|
349
355
|
}
|
|
350
356
|
return;
|
|
351
357
|
}
|
|
@@ -429,21 +435,23 @@ var import_react_native_reanimated5 = require("react-native-reanimated");
|
|
|
429
435
|
var import_react_native_safe_area_context = require("react-native-safe-area-context");
|
|
430
436
|
|
|
431
437
|
// src/useKeyboardInset.ts
|
|
438
|
+
var import_core2 = require("@rific/core");
|
|
432
439
|
var import_react4 = require("react");
|
|
433
440
|
var import_react_native4 = require("react-native");
|
|
434
441
|
var import_react_native_keyboard_controller = require("react-native-keyboard-controller");
|
|
435
442
|
var import_react_native_reanimated4 = require("react-native-reanimated");
|
|
436
443
|
var useKeyboardInsetNative = () => {
|
|
437
444
|
const [keyboardHeight, setKeyboardHeight] = (0, import_react4.useState)(0);
|
|
445
|
+
const keyboardHeightKey = (0, import_core2.useWorkletCallback)(setKeyboardHeight);
|
|
438
446
|
(0, import_react_native_keyboard_controller.useKeyboardHandler)(
|
|
439
447
|
{
|
|
440
448
|
onMove: (e) => {
|
|
441
449
|
"worklet";
|
|
442
|
-
(0, import_react_native_reanimated4.runOnJS)(
|
|
450
|
+
(0, import_react_native_reanimated4.runOnJS)(import_core2.callWorkletCallback)(keyboardHeightKey, e.height);
|
|
443
451
|
},
|
|
444
452
|
onEnd: (e) => {
|
|
445
453
|
"worklet";
|
|
446
|
-
(0, import_react_native_reanimated4.runOnJS)(
|
|
454
|
+
(0, import_react_native_reanimated4.runOnJS)(import_core2.callWorkletCallback)(keyboardHeightKey, e.height);
|
|
447
455
|
}
|
|
448
456
|
},
|
|
449
457
|
[]
|
|
@@ -926,6 +934,7 @@ var styles4 = { measureContainer: { left: 0, opacity: 0, position: "absolute", r
|
|
|
926
934
|
var FlatList = (0, import_react9.memo)(FlatListInner);
|
|
927
935
|
|
|
928
936
|
// src/PullSearch.tsx
|
|
937
|
+
var import_core3 = require("@rific/core");
|
|
929
938
|
var import_react10 = require("react");
|
|
930
939
|
var import_react_native9 = require("react-native");
|
|
931
940
|
var import_react_native_paper3 = require("react-native-paper");
|
|
@@ -948,13 +957,14 @@ var PullSearch = (0, import_react10.forwardRef)(function PullSearch2({ debounce,
|
|
|
948
957
|
focus: () => inputRef.current?.focus()
|
|
949
958
|
}));
|
|
950
959
|
const blurInput = (0, import_react10.useCallback)(() => inputRef.current?.blur(), []);
|
|
960
|
+
const blurKey = (0, import_core3.useWorkletCallback)(blurInput);
|
|
951
961
|
(0, import_react_native_reanimated9.useAnimatedReaction)(
|
|
952
962
|
() => {
|
|
953
963
|
if (barHeight === 0) return false;
|
|
954
964
|
return scrollPosition.value >= -(headerHeight ?? 0) + barHeight;
|
|
955
965
|
},
|
|
956
966
|
(isHidden, wasHidden) => {
|
|
957
|
-
if (isHidden && !wasHidden) (0, import_react_native_reanimated9.runOnJS)(
|
|
967
|
+
if (isHidden && !wasHidden) (0, import_react_native_reanimated9.runOnJS)(import_core3.callWorkletCallback)(blurKey);
|
|
958
968
|
},
|
|
959
969
|
// scrollPosition has to be listed too, not just read - see useStickyHeaders' useAnimatedReaction
|
|
960
970
|
// for why: on web, without reanimated's Babel plugin active, an unlisted SharedValue mutation
|
|
@@ -1000,23 +1010,23 @@ var styles5 = import_react_native9.StyleSheet.create({
|
|
|
1000
1010
|
});
|
|
1001
1011
|
|
|
1002
1012
|
// src/redux/scrollViewSlice.ts
|
|
1003
|
-
var
|
|
1013
|
+
var import_core5 = require("@rific/core");
|
|
1004
1014
|
|
|
1005
1015
|
// src/ScrollViewSettingsContext.ts
|
|
1006
|
-
var
|
|
1016
|
+
var import_core4 = require("@rific/core");
|
|
1007
1017
|
var defaultScrollViewSettings = {
|
|
1008
1018
|
backActionFixed: true,
|
|
1009
1019
|
footerFixed: false,
|
|
1010
1020
|
headerFixed: false,
|
|
1011
1021
|
snapBack: false
|
|
1012
1022
|
};
|
|
1013
|
-
var settingsContext = (0,
|
|
1023
|
+
var settingsContext = (0, import_core4.createSettingsContext)(defaultScrollViewSettings);
|
|
1014
1024
|
var ScrollViewSettingsContext = settingsContext.Context;
|
|
1015
1025
|
var scrollViewSettingsProvider = settingsContext.Provider;
|
|
1016
1026
|
var scrollViewUseSettings = settingsContext.useSettings;
|
|
1017
1027
|
|
|
1018
1028
|
// src/redux/scrollViewSlice.ts
|
|
1019
|
-
var scrollViewSlice = (0,
|
|
1029
|
+
var scrollViewSlice = (0, import_core5.createSettingsSlice)("scrollView", {
|
|
1020
1030
|
initialState: defaultScrollViewSettings,
|
|
1021
1031
|
fieldSetters: []
|
|
1022
1032
|
});
|
|
@@ -1131,6 +1141,13 @@ var ScrollViewChromeProvider = ({ children, footprint, overhang = 0, snapBack =
|
|
|
1131
1141
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ScrollViewChromeContext.Provider, { value: chrome, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ScrollViewChromeHostContext.Provider, { value: host, children }) });
|
|
1132
1142
|
};
|
|
1133
1143
|
|
|
1144
|
+
// src/ScrollViewConfig.tsx
|
|
1145
|
+
var import_core6 = require("@rific/core");
|
|
1146
|
+
var scrollViewConfig = (0, import_core6.createModuleConfig)();
|
|
1147
|
+
var configureScrollView = scrollViewConfig.configure;
|
|
1148
|
+
var getScrollViewConfig = scrollViewConfig.getConfig;
|
|
1149
|
+
var ScrollViewConfigProvider = scrollViewConfig.Provider;
|
|
1150
|
+
|
|
1134
1151
|
// src/ScrollViewFooter.tsx
|
|
1135
1152
|
var import_auto_paper3 = require("@rific/auto-paper");
|
|
1136
1153
|
var import_react14 = require("react");
|
|
@@ -1253,6 +1270,8 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
|
|
|
1253
1270
|
}, [headerFixed, headerHeightShared, top, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared]);
|
|
1254
1271
|
const actionTop = top + actionMargin;
|
|
1255
1272
|
const buttonStyle = { borderRadius: actionSize, height: actionSize, margin: 0, width: actionSize };
|
|
1273
|
+
const BackAction = getScrollViewConfig().BackAction ?? import_react_native_paper4.Appbar.BackAction;
|
|
1274
|
+
const backActionElement = typeof backAction === "function" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction;
|
|
1256
1275
|
const actionShadow = { borderRadius: actionSize / 2, elevation: 2, shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.15, shadowRadius: 2 };
|
|
1257
1276
|
const leadingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", left: actionMargin, position: "absolute", top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
|
|
1258
1277
|
const trailingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", position: "absolute", right: actionMargin, top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
|
|
@@ -1272,10 +1291,10 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
|
|
|
1272
1291
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_reanimated13.default.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper4.ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
|
|
1273
1292
|
backAction && (effectiveBackActionFixed ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native_reanimated13.default.View, { style: leadingStyle, children: [
|
|
1274
1293
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ActionBg, { blur, style: actionStyle }),
|
|
1275
|
-
|
|
1294
|
+
backActionElement
|
|
1276
1295
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native_reanimated13.default.View, { style: [leadingStyle, translateStyle], children: [
|
|
1277
1296
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ActionBg, { blur, style: actionStyle }),
|
|
1278
|
-
|
|
1297
|
+
backActionElement
|
|
1279
1298
|
] })),
|
|
1280
1299
|
trailingAction && (trailingActionFixed ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: trailingStyle, children: [
|
|
1281
1300
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ActionBg, { blur, style: actionStyle }),
|
|
@@ -1441,6 +1460,7 @@ var import_react_native_gesture_handler4 = require("react-native-gesture-handler
|
|
|
1441
1460
|
var import_react_native_reanimated16 = __toESM(require("react-native-reanimated"));
|
|
1442
1461
|
|
|
1443
1462
|
// src/internal/useStickyHeaders.ts
|
|
1463
|
+
var import_core7 = require("@rific/core");
|
|
1444
1464
|
var import_react18 = require("react");
|
|
1445
1465
|
var import_react_native_reanimated15 = require("react-native-reanimated");
|
|
1446
1466
|
var import_react_native_safe_area_context5 = require("react-native-safe-area-context");
|
|
@@ -1450,6 +1470,7 @@ function useStickyHeaders(headerFixed, enabled) {
|
|
|
1450
1470
|
const sectionYPositions = (0, import_react_native_reanimated15.useSharedValue)([]);
|
|
1451
1471
|
const sectionHeights = (0, import_react_native_reanimated15.useSharedValue)([]);
|
|
1452
1472
|
const [activeIndex, setActiveIndex] = (0, import_react18.useState)(-1);
|
|
1473
|
+
const activeIndexKey = (0, import_core7.useWorkletCallback)(setActiveIndex);
|
|
1453
1474
|
const contentIndexShared = (0, import_react_native_reanimated15.useSharedValue)(-1);
|
|
1454
1475
|
(0, import_react18.useLayoutEffect)(() => {
|
|
1455
1476
|
contentIndexShared.value = activeIndex;
|
|
@@ -1481,7 +1502,7 @@ function useStickyHeaders(headerFixed, enabled) {
|
|
|
1481
1502
|
return active;
|
|
1482
1503
|
},
|
|
1483
1504
|
(curr, prev) => {
|
|
1484
|
-
if (curr !== prev) (0, import_react_native_reanimated15.runOnJS)(
|
|
1505
|
+
if (curr !== prev) (0, import_react_native_reanimated15.runOnJS)(import_core7.callWorkletCallback)(activeIndexKey, curr);
|
|
1485
1506
|
},
|
|
1486
1507
|
// Same web-only reanimated gap as useAnimatedStyle elsewhere in this file: this "prepare"
|
|
1487
1508
|
// function's own SharedValue reads (sectionYPositions, headerHeightShared, scrollPosition,
|
|
@@ -1743,6 +1764,7 @@ var useScrollViewSettings = scrollViewUseSettings;
|
|
|
1743
1764
|
PullSearch,
|
|
1744
1765
|
ScrollView,
|
|
1745
1766
|
ScrollViewChromeProvider,
|
|
1767
|
+
ScrollViewConfigProvider,
|
|
1746
1768
|
ScrollViewContext,
|
|
1747
1769
|
ScrollViewFooter,
|
|
1748
1770
|
ScrollViewHeader,
|
|
@@ -1750,7 +1772,9 @@ var useScrollViewSettings = scrollViewUseSettings;
|
|
|
1750
1772
|
ScrollViewSettingsContext,
|
|
1751
1773
|
ScrollViewSettingsProvider,
|
|
1752
1774
|
SectionList,
|
|
1775
|
+
configureScrollView,
|
|
1753
1776
|
defaultScrollViewSettings,
|
|
1777
|
+
getScrollViewConfig,
|
|
1754
1778
|
scrollViewActions,
|
|
1755
1779
|
scrollViewReducer,
|
|
1756
1780
|
useKeyboardInset,
|
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,7 @@ var styles = StyleSheet.create({
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
// src/internal/useScrollHandler.ts
|
|
34
|
+
import { callWorkletCallback, useWorkletCallback } from "@rific/core";
|
|
34
35
|
import { useContext as useContext2 } from "react";
|
|
35
36
|
import { runOnJS, useAnimatedScrollHandler, useSharedValue as useSharedValue2, withTiming } from "react-native-reanimated";
|
|
36
37
|
|
|
@@ -279,6 +280,8 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
|
|
|
279
280
|
const { chromeHosted, chromeWritable, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared, stackHeightShared } = useContext2(ScrollViewContext);
|
|
280
281
|
const snapUpAccum = useSharedValue2(0);
|
|
281
282
|
const fromBottomBounce = useSharedValue2(false);
|
|
283
|
+
const remountSyncedKey = useWorkletCallback((y) => onRemountSynced?.(y));
|
|
284
|
+
const remountSyncRetryKey = useWorkletCallback((y) => onRemountSyncRetry?.(y));
|
|
282
285
|
return useAnimatedScrollHandler(
|
|
283
286
|
{
|
|
284
287
|
onScroll: ({ contentOffset: { x, y }, contentSize: { height: contentHeight }, layoutMeasurement: { height: layoutHeight } }) => {
|
|
@@ -287,9 +290,9 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
|
|
|
287
290
|
const target = remountSyncTarget.value;
|
|
288
291
|
if (Math.abs(y - target) < REMOUNT_SYNC_TOLERANCE) {
|
|
289
292
|
remountSyncTarget.value = null;
|
|
290
|
-
if (onRemountSynced) runOnJS(
|
|
293
|
+
if (onRemountSynced) runOnJS(callWorkletCallback)(remountSyncedKey, y);
|
|
291
294
|
} else if (onRemountSyncRetry) {
|
|
292
|
-
runOnJS(
|
|
295
|
+
runOnJS(callWorkletCallback)(remountSyncRetryKey, y);
|
|
293
296
|
}
|
|
294
297
|
return;
|
|
295
298
|
}
|
|
@@ -373,21 +376,23 @@ import { useAnimatedStyle, useSharedValue as useSharedValue3, withTiming as with
|
|
|
373
376
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
374
377
|
|
|
375
378
|
// src/useKeyboardInset.ts
|
|
379
|
+
import { callWorkletCallback as callWorkletCallback2, useWorkletCallback as useWorkletCallback2 } from "@rific/core";
|
|
376
380
|
import { useState as useState2 } from "react";
|
|
377
381
|
import { Platform as Platform2 } from "react-native";
|
|
378
382
|
import { useKeyboardHandler } from "react-native-keyboard-controller";
|
|
379
383
|
import { runOnJS as runOnJS2 } from "react-native-reanimated";
|
|
380
384
|
var useKeyboardInsetNative = () => {
|
|
381
385
|
const [keyboardHeight, setKeyboardHeight] = useState2(0);
|
|
386
|
+
const keyboardHeightKey = useWorkletCallback2(setKeyboardHeight);
|
|
382
387
|
useKeyboardHandler(
|
|
383
388
|
{
|
|
384
389
|
onMove: (e) => {
|
|
385
390
|
"worklet";
|
|
386
|
-
runOnJS2(
|
|
391
|
+
runOnJS2(callWorkletCallback2)(keyboardHeightKey, e.height);
|
|
387
392
|
},
|
|
388
393
|
onEnd: (e) => {
|
|
389
394
|
"worklet";
|
|
390
|
-
runOnJS2(
|
|
395
|
+
runOnJS2(callWorkletCallback2)(keyboardHeightKey, e.height);
|
|
391
396
|
}
|
|
392
397
|
},
|
|
393
398
|
[]
|
|
@@ -870,6 +875,7 @@ var styles4 = { measureContainer: { left: 0, opacity: 0, position: "absolute", r
|
|
|
870
875
|
var FlatList = memo2(FlatListInner);
|
|
871
876
|
|
|
872
877
|
// src/PullSearch.tsx
|
|
878
|
+
import { callWorkletCallback as callWorkletCallback3, useWorkletCallback as useWorkletCallback3 } from "@rific/core";
|
|
873
879
|
import { forwardRef, useCallback as useCallback5, useContext as useContext8, useEffect as useEffect3, useImperativeHandle, useRef as useRef5, useState as useState4 } from "react";
|
|
874
880
|
import { StyleSheet as StyleSheet4 } from "react-native";
|
|
875
881
|
import { Searchbar, useTheme as useTheme3 } from "react-native-paper";
|
|
@@ -892,13 +898,14 @@ var PullSearch = forwardRef(function PullSearch2({ debounce, onChangeText, onHei
|
|
|
892
898
|
focus: () => inputRef.current?.focus()
|
|
893
899
|
}));
|
|
894
900
|
const blurInput = useCallback5(() => inputRef.current?.blur(), []);
|
|
901
|
+
const blurKey = useWorkletCallback3(blurInput);
|
|
895
902
|
useAnimatedReaction(
|
|
896
903
|
() => {
|
|
897
904
|
if (barHeight === 0) return false;
|
|
898
905
|
return scrollPosition.value >= -(headerHeight ?? 0) + barHeight;
|
|
899
906
|
},
|
|
900
907
|
(isHidden, wasHidden) => {
|
|
901
|
-
if (isHidden && !wasHidden) runOnJS3(
|
|
908
|
+
if (isHidden && !wasHidden) runOnJS3(callWorkletCallback3)(blurKey);
|
|
902
909
|
},
|
|
903
910
|
// scrollPosition has to be listed too, not just read - see useStickyHeaders' useAnimatedReaction
|
|
904
911
|
// for why: on web, without reanimated's Babel plugin active, an unlisted SharedValue mutation
|
|
@@ -1075,6 +1082,13 @@ var ScrollViewChromeProvider = ({ children, footprint, overhang = 0, snapBack =
|
|
|
1075
1082
|
return /* @__PURE__ */ jsx8(ScrollViewChromeContext.Provider, { value: chrome, children: /* @__PURE__ */ jsx8(ScrollViewChromeHostContext.Provider, { value: host, children }) });
|
|
1076
1083
|
};
|
|
1077
1084
|
|
|
1085
|
+
// src/ScrollViewConfig.tsx
|
|
1086
|
+
import { createModuleConfig } from "@rific/core";
|
|
1087
|
+
var scrollViewConfig = createModuleConfig();
|
|
1088
|
+
var configureScrollView = scrollViewConfig.configure;
|
|
1089
|
+
var getScrollViewConfig = scrollViewConfig.getConfig;
|
|
1090
|
+
var ScrollViewConfigProvider = scrollViewConfig.Provider;
|
|
1091
|
+
|
|
1078
1092
|
// src/ScrollViewFooter.tsx
|
|
1079
1093
|
import { BlurView as BlurView2 } from "@rific/auto-paper";
|
|
1080
1094
|
import { useContext as useContext10, useEffect as useEffect5, useLayoutEffect as useLayoutEffect5, useRef as useRef7 } from "react";
|
|
@@ -1197,6 +1211,8 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
|
|
|
1197
1211
|
}, [headerFixed, headerHeightShared, top, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared]);
|
|
1198
1212
|
const actionTop = top + actionMargin;
|
|
1199
1213
|
const buttonStyle = { borderRadius: actionSize, height: actionSize, margin: 0, width: actionSize };
|
|
1214
|
+
const BackAction = getScrollViewConfig().BackAction ?? Appbar.BackAction;
|
|
1215
|
+
const backActionElement = typeof backAction === "function" ? /* @__PURE__ */ jsx10(BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction;
|
|
1200
1216
|
const actionShadow = { borderRadius: actionSize / 2, elevation: 2, shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.15, shadowRadius: 2 };
|
|
1201
1217
|
const leadingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", left: actionMargin, position: "absolute", top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
|
|
1202
1218
|
const trailingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", position: "absolute", right: actionMargin, top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
|
|
@@ -1216,10 +1232,10 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
|
|
|
1216
1232
|
/* @__PURE__ */ jsx10(Animated6.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ jsx10(ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
|
|
1217
1233
|
backAction && (effectiveBackActionFixed ? /* @__PURE__ */ jsxs6(Animated6.View, { style: leadingStyle, children: [
|
|
1218
1234
|
/* @__PURE__ */ jsx10(ActionBg, { blur, style: actionStyle }),
|
|
1219
|
-
|
|
1235
|
+
backActionElement
|
|
1220
1236
|
] }) : /* @__PURE__ */ jsxs6(Animated6.View, { style: [leadingStyle, translateStyle], children: [
|
|
1221
1237
|
/* @__PURE__ */ jsx10(ActionBg, { blur, style: actionStyle }),
|
|
1222
|
-
|
|
1238
|
+
backActionElement
|
|
1223
1239
|
] })),
|
|
1224
1240
|
trailingAction && (trailingActionFixed ? /* @__PURE__ */ jsxs6(View6, { style: trailingStyle, children: [
|
|
1225
1241
|
/* @__PURE__ */ jsx10(ActionBg, { blur, style: actionStyle }),
|
|
@@ -1385,6 +1401,7 @@ import { Gesture as Gesture4, GestureDetector as GestureDetector4 } from "react-
|
|
|
1385
1401
|
import Animated7, { runOnUI as runOnUI3, useSharedValue as useSharedValue10 } from "react-native-reanimated";
|
|
1386
1402
|
|
|
1387
1403
|
// src/internal/useStickyHeaders.ts
|
|
1404
|
+
import { callWorkletCallback as callWorkletCallback4, useWorkletCallback as useWorkletCallback4 } from "@rific/core";
|
|
1388
1405
|
import { useCallback as useCallback9, useContext as useContext13, useLayoutEffect as useLayoutEffect8, useRef as useRef10, useState as useState7 } from "react";
|
|
1389
1406
|
import { runOnJS as runOnJS4, useAnimatedReaction as useAnimatedReaction2, useAnimatedStyle as useAnimatedStyle6, useSharedValue as useSharedValue9 } from "react-native-reanimated";
|
|
1390
1407
|
import { useSafeAreaInsets as useSafeAreaInsets5 } from "react-native-safe-area-context";
|
|
@@ -1394,6 +1411,7 @@ function useStickyHeaders(headerFixed, enabled) {
|
|
|
1394
1411
|
const sectionYPositions = useSharedValue9([]);
|
|
1395
1412
|
const sectionHeights = useSharedValue9([]);
|
|
1396
1413
|
const [activeIndex, setActiveIndex] = useState7(-1);
|
|
1414
|
+
const activeIndexKey = useWorkletCallback4(setActiveIndex);
|
|
1397
1415
|
const contentIndexShared = useSharedValue9(-1);
|
|
1398
1416
|
useLayoutEffect8(() => {
|
|
1399
1417
|
contentIndexShared.value = activeIndex;
|
|
@@ -1425,7 +1443,7 @@ function useStickyHeaders(headerFixed, enabled) {
|
|
|
1425
1443
|
return active;
|
|
1426
1444
|
},
|
|
1427
1445
|
(curr, prev) => {
|
|
1428
|
-
if (curr !== prev) runOnJS4(
|
|
1446
|
+
if (curr !== prev) runOnJS4(callWorkletCallback4)(activeIndexKey, curr);
|
|
1429
1447
|
},
|
|
1430
1448
|
// Same web-only reanimated gap as useAnimatedStyle elsewhere in this file: this "prepare"
|
|
1431
1449
|
// function's own SharedValue reads (sectionYPositions, headerHeightShared, scrollPosition,
|
|
@@ -1686,6 +1704,7 @@ export {
|
|
|
1686
1704
|
PullSearch,
|
|
1687
1705
|
ScrollView,
|
|
1688
1706
|
ScrollViewChromeProvider,
|
|
1707
|
+
ScrollViewConfigProvider,
|
|
1689
1708
|
ScrollViewContext,
|
|
1690
1709
|
ScrollViewFooter,
|
|
1691
1710
|
ScrollViewHeader,
|
|
@@ -1693,7 +1712,9 @@ export {
|
|
|
1693
1712
|
ScrollViewSettingsContext,
|
|
1694
1713
|
ScrollViewSettingsProvider,
|
|
1695
1714
|
SectionList,
|
|
1715
|
+
configureScrollView,
|
|
1696
1716
|
defaultScrollViewSettings,
|
|
1717
|
+
getScrollViewConfig,
|
|
1697
1718
|
scrollViewActions,
|
|
1698
1719
|
scrollViewReducer,
|
|
1699
1720
|
useKeyboardInset,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rific/scroll-view",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Blur styled scroll system for React Native. Floating headers/footers, keyboard awareness, and scroll-away chrome",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@infinitetoken/tsconfig": "^0.6.0",
|
|
67
67
|
"@reduxjs/toolkit": "^2.12.0",
|
|
68
68
|
"@rific/auto-paper": "^0.13.3",
|
|
69
|
-
"@rific/core": "^0.
|
|
69
|
+
"@rific/core": "^0.5.0",
|
|
70
70
|
"@testing-library/dom": "^10.4.1",
|
|
71
71
|
"@testing-library/react": "^16.3.2",
|
|
72
72
|
"@types/jest": "^30.0.0",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
94
|
"@rific/auto-paper": ">=0.10.0",
|
|
95
|
-
"@rific/core": ">=0.
|
|
95
|
+
"@rific/core": ">=0.5.0",
|
|
96
96
|
"react": ">=19.0.0",
|
|
97
97
|
"react-native": ">=0.83.0",
|
|
98
98
|
"react-native-gesture-handler": ">=2.0.0 <4.0.0",
|
package/src/PullSearch.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { callWorkletCallback, useWorkletCallback } from '@rific/core'
|
|
1
2
|
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
|
2
3
|
import { type LayoutChangeEvent, StyleSheet, type TextInput } from 'react-native'
|
|
3
4
|
import { Searchbar, useTheme } from 'react-native-paper'
|
|
@@ -38,6 +39,7 @@ export const PullSearch = forwardRef<PullSearchHandle, PullSearchProps>(function
|
|
|
38
39
|
}))
|
|
39
40
|
|
|
40
41
|
const blurInput = useCallback(() => inputRef.current?.blur(), [])
|
|
42
|
+
const blurKey = useWorkletCallback(blurInput)
|
|
41
43
|
|
|
42
44
|
useAnimatedReaction(
|
|
43
45
|
() => {
|
|
@@ -45,7 +47,7 @@ export const PullSearch = forwardRef<PullSearchHandle, PullSearchProps>(function
|
|
|
45
47
|
return scrollPosition.value >= -(headerHeight ?? 0) + barHeight
|
|
46
48
|
},
|
|
47
49
|
(isHidden, wasHidden) => {
|
|
48
|
-
if (isHidden && !wasHidden) runOnJS(
|
|
50
|
+
if (isHidden && !wasHidden) runOnJS(callWorkletCallback)(blurKey)
|
|
49
51
|
},
|
|
50
52
|
// scrollPosition has to be listed too, not just read - see useStickyHeaders' useAnimatedReaction
|
|
51
53
|
// for why: on web, without reanimated's Babel plugin active, an unlisted SharedValue mutation
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createModuleConfig } from '@rific/core'
|
|
2
|
+
import type { ComponentType, ReactNode } from 'react'
|
|
3
|
+
import type { ViewStyle } from 'react-native'
|
|
4
|
+
|
|
5
|
+
// What ScrollViewHeader passes the back action it renders for a plain `backAction` callback.
|
|
6
|
+
// react-native-paper's Appbar.BackAction (the default) takes these, and so does
|
|
7
|
+
// @rific/feedback-press's AppbarBackAction, which wraps it, so either drops in as is.
|
|
8
|
+
export type ScrollViewBackActionProps = {
|
|
9
|
+
accessibilityLabel?: string
|
|
10
|
+
onPress: () => void
|
|
11
|
+
size: number
|
|
12
|
+
style: ViewStyle
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ScrollViewConfig = {
|
|
16
|
+
/** Component ScrollViewHeader renders when `backAction` is a plain callback. Pass `AppbarBackAction` from `@rific/feedback-press` to give every header's back button its haptics and sound; omit to keep react-native-paper's `Appbar.BackAction`. A `backAction` element on one header still replaces it there. */
|
|
17
|
+
BackAction?: ComponentType<ScrollViewBackActionProps>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// @rific/core's createModuleConfig() supplies the module-level config singleton (configure/
|
|
21
|
+
// getConfig/Provider) every @rific package wires up the same way - see its own doc comment for
|
|
22
|
+
// why this is plain module state rather than React Context. It injects a component instead of
|
|
23
|
+
// depending on @rific/feedback-press, same as @rific/resizable-input's TextInputComponent.
|
|
24
|
+
//
|
|
25
|
+
// Not to be confused with ScrollViewProvider (per screen, scroll state) or
|
|
26
|
+
// ScrollViewSettingsProvider (app-wide, persisted user settings): this one is app-wide startup
|
|
27
|
+
// config, mounted once.
|
|
28
|
+
const scrollViewConfig = createModuleConfig<ScrollViewConfig>()
|
|
29
|
+
|
|
30
|
+
export const configureScrollView = scrollViewConfig.configure
|
|
31
|
+
export const getScrollViewConfig = scrollViewConfig.getConfig
|
|
32
|
+
export const ScrollViewConfigProvider = scrollViewConfig.Provider
|
|
33
|
+
|
|
34
|
+
export type ScrollViewConfigProviderProps = ScrollViewConfig & { children: ReactNode }
|
package/src/ScrollViewHeader.tsx
CHANGED
|
@@ -5,13 +5,15 @@ import { Appbar, ProgressBar, Surface, useTheme } from 'react-native-paper'
|
|
|
5
5
|
import Animated, { useAnimatedStyle, useSharedValue } from 'react-native-reanimated'
|
|
6
6
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
7
7
|
|
|
8
|
+
import { getScrollViewConfig } from './ScrollViewConfig'
|
|
8
9
|
import { ScrollViewContext } from './ScrollViewContext'
|
|
9
10
|
import { ScrollViewSettingsContext } from './ScrollViewSettingsContext'
|
|
10
11
|
|
|
11
12
|
export type ScrollViewHeaderProps = {
|
|
12
13
|
actionSize?: number
|
|
13
14
|
actionStyle?: ViewStyle
|
|
14
|
-
// A plain callback renders the default Appbar.BackAction
|
|
15
|
+
// A plain callback renders the default back action (Paper's Appbar.BackAction unless the app set
|
|
16
|
+
// another with configureScrollView({ BackAction }), themed onSurface, sized/positioned by
|
|
15
17
|
// actionSize/iconSize below) - the common case, unchanged from before. Passing a ReactNode
|
|
16
18
|
// instead skips that default entirely and renders the given element in its place, same escape
|
|
17
19
|
// hatch trailingAction already gave the other corner: a caller that wants its own color, icon,
|
|
@@ -131,6 +133,10 @@ export const ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, bac
|
|
|
131
133
|
}, [headerFixed, headerHeightShared, top, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared])
|
|
132
134
|
const actionTop = top + actionMargin
|
|
133
135
|
const buttonStyle = { borderRadius: actionSize, height: actionSize, margin: 0, width: actionSize }
|
|
136
|
+
// A plain callback renders the configured BackAction (configureScrollView / ScrollViewConfigProvider),
|
|
137
|
+
// Paper's Appbar.BackAction by default. Read at render, like every createModuleConfig consumer.
|
|
138
|
+
const BackAction = getScrollViewConfig().BackAction ?? Appbar.BackAction
|
|
139
|
+
const backActionElement = typeof backAction === 'function' ? <BackAction accessibilityLabel={backActionAccessibilityLabel} onPress={backAction} size={iconSize ?? actionSize / 2} style={buttonStyle} /> : backAction
|
|
134
140
|
const actionShadow = { borderRadius: actionSize / 2, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.15, shadowRadius: 2 }
|
|
135
141
|
const leadingStyle = { alignItems: 'center' as const, height: actionSize, justifyContent: 'center' as const, left: actionMargin, position: 'absolute' as const, top: actionTop, width: actionSize, zIndex: 3, ...actionShadow }
|
|
136
142
|
const trailingStyle = { alignItems: 'center' as const, height: actionSize, justifyContent: 'center' as const, position: 'absolute' as const, right: actionMargin, top: actionTop, width: actionSize, zIndex: 3, ...actionShadow }
|
|
@@ -175,12 +181,12 @@ export const ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, bac
|
|
|
175
181
|
(effectiveBackActionFixed ? (
|
|
176
182
|
<Animated.View style={leadingStyle}>
|
|
177
183
|
<ActionBg blur={blur} style={actionStyle} />
|
|
178
|
-
{
|
|
184
|
+
{backActionElement}
|
|
179
185
|
</Animated.View>
|
|
180
186
|
) : (
|
|
181
187
|
<Animated.View style={[leadingStyle, translateStyle]}>
|
|
182
188
|
<ActionBg blur={blur} style={actionStyle} />
|
|
183
|
-
{
|
|
189
|
+
{backActionElement}
|
|
184
190
|
</Animated.View>
|
|
185
191
|
))}
|
|
186
192
|
{trailingAction &&
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type { ScrollViewProps } from './ScrollView'
|
|
|
10
10
|
export { ScrollView } from './ScrollView'
|
|
11
11
|
export type { ScrollViewChromeProviderProps } from './ScrollViewChromeProvider'
|
|
12
12
|
export { ScrollViewChromeProvider } from './ScrollViewChromeProvider'
|
|
13
|
+
export type { ScrollViewBackActionProps, ScrollViewConfig, ScrollViewConfigProviderProps } from './ScrollViewConfig'
|
|
14
|
+
export { configureScrollView, getScrollViewConfig, ScrollViewConfigProvider } from './ScrollViewConfig'
|
|
13
15
|
export type { ScrollViewContextType } from './ScrollViewContext'
|
|
14
16
|
export { ScrollViewContext } from './ScrollViewContext'
|
|
15
17
|
export type { ScrollViewFooterProps } from './ScrollViewFooter'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { callWorkletCallback, useWorkletCallback } from '@rific/core'
|
|
1
2
|
import { useContext } from 'react'
|
|
2
3
|
import type { SharedValue } from 'react-native-reanimated'
|
|
3
4
|
import { runOnJS, useAnimatedScrollHandler, useSharedValue, withTiming } from 'react-native-reanimated'
|
|
@@ -24,6 +25,10 @@ export function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold
|
|
|
24
25
|
const { chromeHosted, chromeWritable, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared, stackHeightShared } = useContext(ScrollViewContext)
|
|
25
26
|
const snapUpAccum = useSharedValue(0)
|
|
26
27
|
const fromBottomBounce = useSharedValue(false)
|
|
28
|
+
// Worklets never get the consumer's callbacks themselves: react-native-worklets holds a runOnJS target natively for as
|
|
29
|
+
// long as the worklet lives, which pinned the list. They call the module-level callWorkletCallback by key.
|
|
30
|
+
const remountSyncedKey = useWorkletCallback((y: number) => onRemountSynced?.(y))
|
|
31
|
+
const remountSyncRetryKey = useWorkletCallback((y: number) => onRemountSyncRetry?.(y))
|
|
27
32
|
|
|
28
33
|
return useAnimatedScrollHandler(
|
|
29
34
|
{
|
|
@@ -33,9 +38,9 @@ export function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold
|
|
|
33
38
|
const target = remountSyncTarget.value
|
|
34
39
|
if (Math.abs(y - target) < REMOUNT_SYNC_TOLERANCE) {
|
|
35
40
|
remountSyncTarget.value = null
|
|
36
|
-
if (onRemountSynced) runOnJS(
|
|
41
|
+
if (onRemountSynced) runOnJS(callWorkletCallback)(remountSyncedKey, y)
|
|
37
42
|
} else if (onRemountSyncRetry) {
|
|
38
|
-
runOnJS(
|
|
43
|
+
runOnJS(callWorkletCallback)(remountSyncRetryKey, y)
|
|
39
44
|
}
|
|
40
45
|
return
|
|
41
46
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { callWorkletCallback, useWorkletCallback } from '@rific/core'
|
|
1
2
|
import { useCallback, useContext, useLayoutEffect, useRef, useState } from 'react'
|
|
2
3
|
import { runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'
|
|
3
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
@@ -19,6 +20,7 @@ export function useStickyHeaders(headerFixed: boolean, enabled: boolean): Sticky
|
|
|
19
20
|
const sectionYPositions = useSharedValue<number[]>([])
|
|
20
21
|
const sectionHeights = useSharedValue<number[]>([])
|
|
21
22
|
const [activeIndex, setActiveIndex] = useState(-1)
|
|
23
|
+
const activeIndexKey = useWorkletCallback(setActiveIndex)
|
|
22
24
|
// Mirrors activeIndex React state but lives on the UI thread for overlayStyle.
|
|
23
25
|
// Updated via useLayoutEffect so content and style always change together.
|
|
24
26
|
const contentIndexShared = useSharedValue(-1)
|
|
@@ -60,7 +62,7 @@ export function useStickyHeaders(headerFixed: boolean, enabled: boolean): Sticky
|
|
|
60
62
|
return active
|
|
61
63
|
},
|
|
62
64
|
(curr, prev) => {
|
|
63
|
-
if (curr !== prev) runOnJS(
|
|
65
|
+
if (curr !== prev) runOnJS(callWorkletCallback)(activeIndexKey, curr)
|
|
64
66
|
},
|
|
65
67
|
// Same web-only reanimated gap as useAnimatedStyle elsewhere in this file: this "prepare"
|
|
66
68
|
// function's own SharedValue reads (sectionYPositions, headerHeightShared, scrollPosition,
|
package/src/useKeyboardInset.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { callWorkletCallback, useWorkletCallback } from '@rific/core'
|
|
1
2
|
import { useState } from 'react'
|
|
2
3
|
import { Platform } from 'react-native'
|
|
3
4
|
import { useKeyboardHandler } from 'react-native-keyboard-controller'
|
|
@@ -5,15 +6,16 @@ import { runOnJS } from 'react-native-reanimated'
|
|
|
5
6
|
|
|
6
7
|
const useKeyboardInsetNative = () => {
|
|
7
8
|
const [keyboardHeight, setKeyboardHeight] = useState(0)
|
|
9
|
+
const keyboardHeightKey = useWorkletCallback(setKeyboardHeight)
|
|
8
10
|
useKeyboardHandler(
|
|
9
11
|
{
|
|
10
12
|
onMove: (e) => {
|
|
11
13
|
'worklet'
|
|
12
|
-
runOnJS(
|
|
14
|
+
runOnJS(callWorkletCallback)(keyboardHeightKey, e.height)
|
|
13
15
|
},
|
|
14
16
|
onEnd: (e) => {
|
|
15
17
|
'worklet'
|
|
16
|
-
runOnJS(
|
|
18
|
+
runOnJS(callWorkletCallback)(keyboardHeightKey, e.height)
|
|
17
19
|
}
|
|
18
20
|
},
|
|
19
21
|
[]
|