@fountain-ui/lab 2.0.0-beta.44 → 2.0.0-beta.46
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/build/commonjs/ComicViewer/ComicViewer.js +45 -8
- package/build/commonjs/ComicViewer/ComicViewer.js.map +1 -1
- package/build/commonjs/ComicViewer/ComicViewerProps.js.map +1 -1
- package/build/commonjs/ComicViewer/FastScroll.js +162 -0
- package/build/commonjs/ComicViewer/FastScroll.js.map +1 -0
- package/build/commonjs/ComicViewer/FastScrollProps.js +6 -0
- package/build/commonjs/ComicViewer/FastScrollProps.js.map +1 -0
- package/build/commonjs/ComicViewer/ViewerItem.js +4 -2
- package/build/commonjs/ComicViewer/ViewerItem.js.map +1 -1
- package/build/commonjs/ComicViewer/index.js.map +1 -1
- package/build/commonjs/ComicViewer/util.js +27 -0
- package/build/commonjs/ComicViewer/util.js.map +1 -0
- package/build/module/ComicViewer/ComicViewer.js +44 -8
- package/build/module/ComicViewer/ComicViewer.js.map +1 -1
- package/build/module/ComicViewer/ComicViewerProps.js.map +1 -1
- package/build/module/ComicViewer/FastScroll.js +141 -0
- package/build/module/ComicViewer/FastScroll.js.map +1 -0
- package/build/module/ComicViewer/FastScrollProps.js +2 -0
- package/build/module/ComicViewer/FastScrollProps.js.map +1 -0
- package/build/module/ComicViewer/ViewerItem.js +4 -2
- package/build/module/ComicViewer/ViewerItem.js.map +1 -1
- package/build/module/ComicViewer/index.js.map +1 -1
- package/build/module/ComicViewer/util.js +15 -0
- package/build/module/ComicViewer/util.js.map +1 -0
- package/build/typescript/AnimatedY/AnimatedY.d.ts +1 -0
- package/build/typescript/BottomSheet/BottomSheetNative.d.ts +1 -0
- package/build/typescript/BottomSheet/BottomSheetWeb.d.ts +1 -0
- package/build/typescript/BottomSheet/TransparentBackdrop.d.ts +1 -0
- package/build/typescript/ComicViewer/ComicViewer.d.ts +1 -0
- package/build/typescript/ComicViewer/ComicViewerProps.d.ts +10 -0
- package/build/typescript/ComicViewer/FastScroll.d.ts +4 -0
- package/build/typescript/ComicViewer/FastScrollProps.d.ts +70 -0
- package/build/typescript/ComicViewer/ReloadButton.d.ts +1 -0
- package/build/typescript/ComicViewer/ViewerItem.d.ts +5 -0
- package/build/typescript/ComicViewer/index.d.ts +1 -0
- package/build/typescript/ComicViewer/util.d.ts +2 -0
- package/build/typescript/DateTimePicker/DateTimePicker.d.ts +1 -0
- package/build/typescript/DateTimePicker/YearPicker.d.ts +1 -0
- package/build/typescript/FlipCard/FlipCard.d.ts +1 -0
- package/build/typescript/StatusBarProvider/StatusBarProvider.d.ts +1 -0
- package/build/typescript/ViewabilityTrackerView/ViewabilityTrackerView.d.ts +1 -0
- package/package.json +3 -3
- package/src/ComicViewer/ComicViewer.tsx +60 -19
- package/src/ComicViewer/ComicViewerProps.ts +12 -0
- package/src/ComicViewer/FastScroll.tsx +158 -0
- package/src/ComicViewer/FastScrollProps.ts +83 -0
- package/src/ComicViewer/ViewerItem.tsx +8 -1
- package/src/ComicViewer/index.ts +6 -0
- package/src/ComicViewer/util.ts +15 -0
- package/yarn-error.log +103 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Position infos with display: 'position'.
|
|
6
|
+
*/
|
|
7
|
+
export declare type AbsolutePosition = {
|
|
8
|
+
top: number;
|
|
9
|
+
bottom: number;
|
|
10
|
+
right: number;
|
|
11
|
+
left: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Durations(millis) for show, hide animation.
|
|
15
|
+
*/
|
|
16
|
+
export declare type VisibleDurations = {
|
|
17
|
+
hideMillis: number;
|
|
18
|
+
showMillis: number;
|
|
19
|
+
};
|
|
20
|
+
export interface FastScrollInstance {
|
|
21
|
+
/**
|
|
22
|
+
* Handle content scroll event, not using fast scroll indicator.
|
|
23
|
+
* @param event Function onScroll event params.
|
|
24
|
+
*/
|
|
25
|
+
onContentScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Set indicator visible state.
|
|
28
|
+
*/
|
|
29
|
+
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
|
30
|
+
/**
|
|
31
|
+
* Return true, if pan gesture state is begin to finalize.
|
|
32
|
+
*/
|
|
33
|
+
getIsIndicatorDragging: () => boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface FastScrollOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Ref for React.forwardRef.
|
|
38
|
+
*/
|
|
39
|
+
ref: RefObject<FastScrollInstance>;
|
|
40
|
+
/**
|
|
41
|
+
* Range within which the indicator can move.
|
|
42
|
+
*/
|
|
43
|
+
movementRange: number;
|
|
44
|
+
/**
|
|
45
|
+
* Scrollbar positions for 'display: position'.
|
|
46
|
+
*/
|
|
47
|
+
absolutePosition?: Partial<AbsolutePosition>;
|
|
48
|
+
/**
|
|
49
|
+
* Durations(millis) for show, hide animation.
|
|
50
|
+
* @default { hide: 200, show: 350 }
|
|
51
|
+
*/
|
|
52
|
+
visibleDurations?: VisibleDurations;
|
|
53
|
+
/**
|
|
54
|
+
* Additional height except contentLength.
|
|
55
|
+
* @default 0
|
|
56
|
+
*/
|
|
57
|
+
additionalLength?: number;
|
|
58
|
+
}
|
|
59
|
+
interface FastScrollProps extends FastScrollOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Total length of scrollable content.
|
|
62
|
+
*/
|
|
63
|
+
contentLength: number;
|
|
64
|
+
/**
|
|
65
|
+
* Scroll content to offset appropriate for the indicator location.
|
|
66
|
+
* @param offset Content offset.
|
|
67
|
+
*/
|
|
68
|
+
scrollContentToOffset: (offset: number) => void;
|
|
69
|
+
}
|
|
70
|
+
export default FastScrollProps;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
1
2
|
import { ImageProps } from '@fountain-ui/core';
|
|
2
3
|
export interface ViewerItemProps {
|
|
3
4
|
/**
|
|
@@ -8,6 +9,10 @@ export interface ViewerItemProps {
|
|
|
8
9
|
* Image height.
|
|
9
10
|
*/
|
|
10
11
|
height: number;
|
|
12
|
+
/**
|
|
13
|
+
* Need invisible paddingTop viewer vertically expanded.
|
|
14
|
+
*/
|
|
15
|
+
invisiblePaddingTop: number;
|
|
11
16
|
/**
|
|
12
17
|
* Image sourceUrl for displaying.
|
|
13
18
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default } from './ComicViewer';
|
|
2
2
|
export type { Dimension, default as ComicViewerProps } from './ComicViewerProps';
|
|
3
3
|
export type { ViewerItemProps } from './ViewerItem';
|
|
4
|
+
export type { AbsolutePosition, FastScrollInstance, FastScrollOptions, VisibleDurations, } from './FastScrollProps';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fountain-ui/lab",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.46",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Fountain-UI Team",
|
|
6
6
|
"description": "Incubator for Fountain-UI React components.",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@emotion/react": "^11.10.0",
|
|
19
19
|
"@emotion/styled": "^11.10.0",
|
|
20
|
-
"@fountain-ui/icons": "^2.0.0-beta.
|
|
20
|
+
"@fountain-ui/icons": "^2.0.0-beta.10",
|
|
21
21
|
"@fountain-ui/utils": "^2.0.0-beta.4",
|
|
22
22
|
"react-native-calendars": "1.1267.0"
|
|
23
23
|
},
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "b0d9e43744035ca199189733ba172320955879c9"
|
|
74
74
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { FlatList, ListRenderItem, ViewToken } from 'react-native';
|
|
2
|
+
import { FlatList, ListRenderItem, NativeScrollEvent, NativeSyntheticEvent, ViewToken } from 'react-native';
|
|
3
3
|
import * as R from 'ramda';
|
|
4
4
|
import { useDebounce } from '@fountain-ui/core';
|
|
5
5
|
import { default as ComicViewerProps, Dimension } from './ComicViewerProps';
|
|
6
6
|
import ViewerItem from './ViewerItem';
|
|
7
|
+
import FastScroll from './FastScroll';
|
|
7
8
|
|
|
8
9
|
const appender = (left: number, right: number): [number, number] => [left + right, left + right];
|
|
9
10
|
const getHeightAccum = (heights: number[]): [number, number[]] => R.mapAccum(appender, 0, heights);
|
|
@@ -46,6 +47,8 @@ const mapImageStateToItemState = (
|
|
|
46
47
|
dimension: imageState.dimension,
|
|
47
48
|
});
|
|
48
49
|
|
|
50
|
+
const mapIndexed = R.addIndex<Dimension>(R.map);
|
|
51
|
+
|
|
49
52
|
const MAXIMUM_WIDTH = 720;
|
|
50
53
|
|
|
51
54
|
const NUMBER_OF_ADJACENT_ITEM = 5;
|
|
@@ -54,6 +57,7 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
54
57
|
const {
|
|
55
58
|
debounceMillis = 100,
|
|
56
59
|
autoHandleErrorCount = 3,
|
|
60
|
+
fastScrollOptions,
|
|
57
61
|
getUrlByIndex,
|
|
58
62
|
initialNumToRender = 1,
|
|
59
63
|
initialScrollPercentage = 0,
|
|
@@ -61,11 +65,15 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
61
65
|
intrinsicDimensions,
|
|
62
66
|
maxContentWidth = MAXIMUM_WIDTH,
|
|
63
67
|
onItemPress,
|
|
68
|
+
onScroll,
|
|
64
69
|
viewportWidth,
|
|
70
|
+
invisiblePaddingTop = 0,
|
|
65
71
|
windowSize = 3,
|
|
66
72
|
...otherProps
|
|
67
73
|
} = props;
|
|
68
74
|
|
|
75
|
+
const fastScrollRef = fastScrollOptions?.ref;
|
|
76
|
+
|
|
69
77
|
const flatListRef = useRef<FlatList>(null);
|
|
70
78
|
|
|
71
79
|
const maybeLoadableItemsIndexRange = useRef<[number, number]>([-1, 0]);
|
|
@@ -86,9 +94,9 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
86
94
|
});
|
|
87
95
|
|
|
88
96
|
const renderedDimensions = useMemo<Array<Dimension>>(() => {
|
|
89
|
-
return
|
|
97
|
+
return mapIndexed((intrinsicDimension, index) => ({
|
|
90
98
|
width: actualImageWidth,
|
|
91
|
-
height: (intrinsicDimension.height * actualImageWidth) / intrinsicDimension.width,
|
|
99
|
+
height: (intrinsicDimension.height * actualImageWidth) / intrinsicDimension.width + (index === 0 ? invisiblePaddingTop : 0),
|
|
92
100
|
}), intrinsicDimensions);
|
|
93
101
|
}, [actualImageWidth]);
|
|
94
102
|
|
|
@@ -132,8 +140,8 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
132
140
|
const filteredIndexes = R.filter(index => {
|
|
133
141
|
const state = imageStatesRef.current[index];
|
|
134
142
|
|
|
135
|
-
return
|
|
136
|
-
|
|
143
|
+
return !state.isNewUrlIncoming
|
|
144
|
+
&& (R.isNil(state.urlState) || state.urlState?.validity === 'invalid');
|
|
137
145
|
}, indexes);
|
|
138
146
|
|
|
139
147
|
updateImageState((imageState, i) => {
|
|
@@ -161,6 +169,8 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
161
169
|
|
|
162
170
|
return imageState;
|
|
163
171
|
});
|
|
172
|
+
} catch (e) {
|
|
173
|
+
// ignore
|
|
164
174
|
} finally {
|
|
165
175
|
updateImageState((imageState, i) => {
|
|
166
176
|
return R.includes(i, filteredIndexes)
|
|
@@ -170,13 +180,14 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
170
180
|
}
|
|
171
181
|
};
|
|
172
182
|
|
|
173
|
-
const loadMaybeLoadableItems =
|
|
183
|
+
const loadMaybeLoadableItems = () => {
|
|
174
184
|
const [startIndex, endIndex] = maybeLoadableItemsIndexRange.current;
|
|
175
185
|
const affectedIndexes = R.range(startIndex, endIndex);
|
|
176
186
|
|
|
177
|
-
|
|
187
|
+
loadUrlByIndex(affectedIndexes);
|
|
178
188
|
};
|
|
179
189
|
|
|
190
|
+
const ignoreDebounce = useRef(true);
|
|
180
191
|
const loadItemsDebounce = useDebounce(loadMaybeLoadableItems, debounceMillis);
|
|
181
192
|
|
|
182
193
|
const onViewableItemsChanged = useRef(({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
|
|
@@ -194,9 +205,29 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
194
205
|
|
|
195
206
|
maybeLoadableItemsIndexRange.current = [startIndex, endIndex + 1];
|
|
196
207
|
|
|
208
|
+
if (ignoreDebounce.current) {
|
|
209
|
+
loadMaybeLoadableItems();
|
|
210
|
+
|
|
211
|
+
ignoreDebounce.current = false;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
197
215
|
loadItemsDebounce();
|
|
198
216
|
});
|
|
199
217
|
|
|
218
|
+
const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
219
|
+
fastScrollRef?.current?.onContentScroll(event);
|
|
220
|
+
|
|
221
|
+
onScroll?.(event);
|
|
222
|
+
}, [onScroll]);
|
|
223
|
+
|
|
224
|
+
const scrollContentToOffset = (offset: number) => {
|
|
225
|
+
flatListRef.current?.scrollToOffset({
|
|
226
|
+
offset,
|
|
227
|
+
animated: false,
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
|
|
200
231
|
const renderItem: ListRenderItem<ItemState> = useCallback(({ item, index }) => {
|
|
201
232
|
const onError = () => {
|
|
202
233
|
updateImageState((imageState, i) => {
|
|
@@ -258,6 +289,7 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
258
289
|
onPress={onItemPress}
|
|
259
290
|
onReloadPress={onReloadPress}
|
|
260
291
|
url={item.url}
|
|
292
|
+
invisiblePaddingTop={index === 0 ? invisiblePaddingTop : 0}
|
|
261
293
|
width={renderedDimensions[index]?.width ?? 0}
|
|
262
294
|
height={renderedDimensions[index]?.height ?? 0}
|
|
263
295
|
reloadButtonVisible={item.reloadButtonVisible}
|
|
@@ -274,17 +306,26 @@ export default function ComicViewer(props: ComicViewerProps) {
|
|
|
274
306
|
}, []);
|
|
275
307
|
|
|
276
308
|
return (
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
309
|
+
<React.Fragment>
|
|
310
|
+
<FlatList
|
|
311
|
+
data={itemStates}
|
|
312
|
+
getItemLayout={getItemLayout}
|
|
313
|
+
initialNumToRender={initialNumToRender}
|
|
314
|
+
keyExtractor={keyExtractor}
|
|
315
|
+
onViewableItemsChanged={onViewableItemsChanged.current}
|
|
316
|
+
ref={flatListRef}
|
|
317
|
+
renderItem={renderItem}
|
|
318
|
+
viewabilityConfig={viewabilityConfig}
|
|
319
|
+
windowSize={windowSize}
|
|
320
|
+
onScroll={handleScroll}
|
|
321
|
+
{...otherProps}
|
|
322
|
+
/>
|
|
323
|
+
|
|
324
|
+
<FastScroll
|
|
325
|
+
{...fastScrollOptions}
|
|
326
|
+
contentLength={totalHeight}
|
|
327
|
+
scrollContentToOffset={scrollContentToOffset}
|
|
328
|
+
/>
|
|
329
|
+
</React.Fragment>
|
|
289
330
|
);
|
|
290
331
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ComponentProps } from '@fountain-ui/core';
|
|
3
3
|
import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
4
|
+
import { FastScrollOptions } from './FastScrollProps';
|
|
4
5
|
|
|
5
6
|
export interface Dimension {
|
|
6
7
|
width: number;
|
|
@@ -44,6 +45,12 @@ export default interface ComicViewerProps extends ComponentProps <{
|
|
|
44
45
|
*/
|
|
45
46
|
intrinsicDimensions: Array<Dimension>;
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Need invisible paddingTop viewer vertically expanded.
|
|
50
|
+
* @default 0
|
|
51
|
+
*/
|
|
52
|
+
invisiblePaddingTop?: number;
|
|
53
|
+
|
|
47
54
|
/**
|
|
48
55
|
* Max value of contents image width size.
|
|
49
56
|
* @default 720
|
|
@@ -61,6 +68,11 @@ export default interface ComicViewerProps extends ComponentProps <{
|
|
|
61
68
|
*/
|
|
62
69
|
windowSize?: number;
|
|
63
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Options for fastscroll component.
|
|
73
|
+
*/
|
|
74
|
+
fastScrollOptions: FastScrollOptions;
|
|
75
|
+
|
|
64
76
|
/**
|
|
65
77
|
* Get contents urls by indexes.
|
|
66
78
|
*/
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React, { useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
+
import { NativeScrollEvent, NativeSyntheticEvent, View } from 'react-native';
|
|
3
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
4
|
+
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withDelay, withTiming } from 'react-native-reanimated';
|
|
5
|
+
import { ChevronDown, ChevronUp } from '@fountain-ui/icons';
|
|
6
|
+
import { StyleSheet } from '@fountain-ui/core';
|
|
7
|
+
import FastScrollProps from './FastScrollProps';
|
|
8
|
+
import { offsetToPercentage, percentageToOffset } from './util';
|
|
9
|
+
|
|
10
|
+
const styles = StyleSheet.create({
|
|
11
|
+
indicator: {
|
|
12
|
+
width: 24,
|
|
13
|
+
height: 40,
|
|
14
|
+
backgroundColor: '#767676',
|
|
15
|
+
flexDirection: 'column',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
borderRadius: 4,
|
|
18
|
+
},
|
|
19
|
+
view: { position: 'absolute' },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const FastScroll = React.forwardRef((props: FastScrollProps, ref) => {
|
|
23
|
+
const {
|
|
24
|
+
absolutePosition,
|
|
25
|
+
additionalLength = 0,
|
|
26
|
+
contentLength,
|
|
27
|
+
movementRange,
|
|
28
|
+
scrollContentToOffset,
|
|
29
|
+
visibleDurations = { hideMillis: 200, showMillis: 350 },
|
|
30
|
+
} = props;
|
|
31
|
+
|
|
32
|
+
const lastIndicatorOffset = useSharedValue(0);
|
|
33
|
+
const indicatorOffset = useSharedValue(lastIndicatorOffset.value);
|
|
34
|
+
|
|
35
|
+
const isIndicatorDragging = useRef(false);
|
|
36
|
+
|
|
37
|
+
const indicatorOpacity = useSharedValue(1);
|
|
38
|
+
const [visible, setVisible] = useState(true);
|
|
39
|
+
|
|
40
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
41
|
+
transform: [{ translateY: indicatorOffset.value }],
|
|
42
|
+
opacity: indicatorOpacity.value,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
const totalContentLength = contentLength + additionalLength;
|
|
46
|
+
|
|
47
|
+
const onContentScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
48
|
+
if (isIndicatorDragging.current === false) {
|
|
49
|
+
const contentPercentage = offsetToPercentage(event.nativeEvent.contentOffset.y, totalContentLength);
|
|
50
|
+
const offset = percentageToOffset(contentPercentage, movementRange);
|
|
51
|
+
|
|
52
|
+
if (offset < 0 || indicatorOffset.value < 0) {
|
|
53
|
+
lastIndicatorOffset.value = 0;
|
|
54
|
+
indicatorOffset.value = 0;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (offset > movementRange || indicatorOffset.value > movementRange) {
|
|
59
|
+
lastIndicatorOffset.value = movementRange;
|
|
60
|
+
indicatorOffset.value = movementRange;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
lastIndicatorOffset.value = offset;
|
|
65
|
+
} else {
|
|
66
|
+
setVisible(true);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const getIsIndicatorDragging = () => isIndicatorDragging.current;
|
|
71
|
+
|
|
72
|
+
useImperativeHandle(
|
|
73
|
+
ref,
|
|
74
|
+
() => ({
|
|
75
|
+
getIsIndicatorDragging,
|
|
76
|
+
onContentScroll,
|
|
77
|
+
setVisible,
|
|
78
|
+
}),
|
|
79
|
+
[],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const handleUpdate = () => {
|
|
83
|
+
const contentPercentage = offsetToPercentage(indicatorOffset.value, movementRange);
|
|
84
|
+
const offset = percentageToOffset(contentPercentage, totalContentLength);
|
|
85
|
+
|
|
86
|
+
scrollContentToOffset(offset);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const setIsIndicatorDragging = (value: boolean) => isIndicatorDragging.current = value;
|
|
90
|
+
|
|
91
|
+
const pan = Gesture.Pan()
|
|
92
|
+
.onBegin((e) => {
|
|
93
|
+
indicatorOffset.value = lastIndicatorOffset.value;
|
|
94
|
+
runOnJS(setIsIndicatorDragging)(true);
|
|
95
|
+
})
|
|
96
|
+
.onUpdate((e) => {
|
|
97
|
+
if (indicatorOffset.value <= 0 && e.translationY < 0) {
|
|
98
|
+
indicatorOffset.value = 0;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (indicatorOffset.value >= movementRange && e.translationY > 0) {
|
|
103
|
+
indicatorOffset.value = movementRange;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
indicatorOffset.value = lastIndicatorOffset.value + e.translationY;
|
|
108
|
+
|
|
109
|
+
runOnJS(handleUpdate)();
|
|
110
|
+
})
|
|
111
|
+
.onFinalize((e) => {
|
|
112
|
+
lastIndicatorOffset.value = indicatorOffset.value;
|
|
113
|
+
runOnJS(setIsIndicatorDragging)(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const hide = () => indicatorOpacity.value = withDelay(0, withTiming(0, { duration: visibleDurations.hideMillis }));
|
|
117
|
+
|
|
118
|
+
const show = () => indicatorOpacity.value = withDelay(0, withTiming(1, { duration: visibleDurations.showMillis }));
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (visible) {
|
|
122
|
+
indicatorOffset.value = lastIndicatorOffset.value;
|
|
123
|
+
show();
|
|
124
|
+
} else {
|
|
125
|
+
hide();
|
|
126
|
+
}
|
|
127
|
+
}, [visible]);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<View
|
|
131
|
+
style={[
|
|
132
|
+
{ height: movementRange },
|
|
133
|
+
styles.view,
|
|
134
|
+
absolutePosition,
|
|
135
|
+
]}
|
|
136
|
+
>
|
|
137
|
+
<GestureDetector gesture={pan}>
|
|
138
|
+
<Animated.View style={[
|
|
139
|
+
animatedStyle,
|
|
140
|
+
styles.indicator,
|
|
141
|
+
]}>
|
|
142
|
+
<ChevronUp
|
|
143
|
+
fill={'#ededed'}
|
|
144
|
+
height={20}
|
|
145
|
+
width={20}
|
|
146
|
+
/>
|
|
147
|
+
<ChevronDown
|
|
148
|
+
fill={'#ededed'}
|
|
149
|
+
height={20}
|
|
150
|
+
width={20}
|
|
151
|
+
/>
|
|
152
|
+
</Animated.View>
|
|
153
|
+
</GestureDetector>
|
|
154
|
+
</View>
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
export default FastScroll;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Position infos with display: 'position'.
|
|
7
|
+
*/
|
|
8
|
+
export type AbsolutePosition = {
|
|
9
|
+
top: number;
|
|
10
|
+
bottom: number;
|
|
11
|
+
right: number;
|
|
12
|
+
left: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Durations(millis) for show, hide animation.
|
|
17
|
+
*/
|
|
18
|
+
export type VisibleDurations = {
|
|
19
|
+
hideMillis: number;
|
|
20
|
+
showMillis: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FastScrollInstance {
|
|
24
|
+
/**
|
|
25
|
+
* Handle content scroll event, not using fast scroll indicator.
|
|
26
|
+
* @param event Function onScroll event params.
|
|
27
|
+
*/
|
|
28
|
+
onContentScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Set indicator visible state.
|
|
32
|
+
*/
|
|
33
|
+
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return true, if pan gesture state is begin to finalize.
|
|
37
|
+
*/
|
|
38
|
+
getIsIndicatorDragging: () => boolean,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FastScrollOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Ref for React.forwardRef.
|
|
44
|
+
*/
|
|
45
|
+
ref: RefObject<FastScrollInstance>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Range within which the indicator can move.
|
|
49
|
+
*/
|
|
50
|
+
movementRange: number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Scrollbar positions for 'display: position'.
|
|
54
|
+
*/
|
|
55
|
+
absolutePosition?: Partial<AbsolutePosition>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Durations(millis) for show, hide animation.
|
|
59
|
+
* @default { hide: 200, show: 350 }
|
|
60
|
+
*/
|
|
61
|
+
visibleDurations?: VisibleDurations;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Additional height except contentLength.
|
|
65
|
+
* @default 0
|
|
66
|
+
*/
|
|
67
|
+
additionalLength?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface FastScrollProps extends FastScrollOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Total length of scrollable content.
|
|
73
|
+
*/
|
|
74
|
+
contentLength: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Scroll content to offset appropriate for the indicator location.
|
|
78
|
+
* @param offset Content offset.
|
|
79
|
+
*/
|
|
80
|
+
scrollContentToOffset: (offset: number) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default FastScrollProps;
|
|
@@ -15,6 +15,11 @@ export interface ViewerItemProps {
|
|
|
15
15
|
*/
|
|
16
16
|
height: number;
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Need invisible paddingTop viewer vertically expanded.
|
|
20
|
+
*/
|
|
21
|
+
invisiblePaddingTop: number;
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* Image sourceUrl for displaying.
|
|
20
25
|
*/
|
|
@@ -52,6 +57,7 @@ export default function ViewerItem(props: ViewerItemProps) {
|
|
|
52
57
|
height,
|
|
53
58
|
url,
|
|
54
59
|
width,
|
|
60
|
+
invisiblePaddingTop,
|
|
55
61
|
onError,
|
|
56
62
|
onLoad,
|
|
57
63
|
onPress,
|
|
@@ -63,9 +69,10 @@ export default function ViewerItem(props: ViewerItemProps) {
|
|
|
63
69
|
view: {
|
|
64
70
|
height,
|
|
65
71
|
width: '100%',
|
|
72
|
+
paddingTop: invisiblePaddingTop,
|
|
66
73
|
},
|
|
67
74
|
image: {
|
|
68
|
-
height,
|
|
75
|
+
height: height - invisiblePaddingTop,
|
|
69
76
|
width,
|
|
70
77
|
},
|
|
71
78
|
};
|
package/src/ComicViewer/index.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export { default } from './ComicViewer';
|
|
2
2
|
export type { Dimension, default as ComicViewerProps } from './ComicViewerProps';
|
|
3
3
|
export type { ViewerItemProps } from './ViewerItem';
|
|
4
|
+
export type {
|
|
5
|
+
AbsolutePosition,
|
|
6
|
+
FastScrollInstance,
|
|
7
|
+
FastScrollOptions,
|
|
8
|
+
VisibleDurations,
|
|
9
|
+
} from './FastScrollProps';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const offsetToPercentage = (offset: number, total: number) => {
|
|
2
|
+
if (offset === 0 || total === 0) {
|
|
3
|
+
return 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
return offset / total * 100;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const percentageToOffset = (percentage: number, total: number) => {
|
|
10
|
+
if (percentage === 0 || total === 0) {
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return percentage / 100 * total;
|
|
15
|
+
};
|