@momo-kits/slider 0.73.3-beta.5 → 0.74.2-react-native.1
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/Label.tsx +33 -0
- package/helpers.ts +35 -0
- package/hooks.tsx +218 -0
- package/index.tsx +438 -0
- package/package.json +11 -10
- package/publish.sh +1 -1
- package/styles.ts +36 -0
- package/DefaultLabel.js +0 -83
- package/DefaultLabel.web.js +0 -83
- package/DefaultMarker.js +0 -67
- package/DefaultMarker.web.js +0 -75
- package/Slider.js +0 -781
- package/Slider.web.js +0 -761
- package/converters.js +0 -80
- package/index.js +0 -3
package/Label.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
ForwardRefRenderFunction,
|
|
4
|
+
useImperativeHandle,
|
|
5
|
+
useState,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import {View, ViewProps} from 'react-native';
|
|
8
|
+
import {Shadow, Text} from '@momo-kits/foundation';
|
|
9
|
+
import styles from './styles';
|
|
10
|
+
|
|
11
|
+
export type LabelRef = {
|
|
12
|
+
setValue: (value: number) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const Label: ForwardRefRenderFunction<LabelRef, ViewProps> = (props, ref) => {
|
|
16
|
+
const [value, _setValue] = useState(Number.NaN);
|
|
17
|
+
|
|
18
|
+
useImperativeHandle(ref, () => ({
|
|
19
|
+
setValue,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const setValue = (newValue: number) => {
|
|
23
|
+
_setValue(newValue);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<View {...props} style={[styles.label, Shadow.Light]}>
|
|
28
|
+
<Text typography={'label_s_medium'}>{value}</Text>
|
|
29
|
+
</View>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default forwardRef(Label);
|
package/helpers.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const isLowCloser = (
|
|
2
|
+
downX: number,
|
|
3
|
+
lowPosition: number,
|
|
4
|
+
highPosition: number,
|
|
5
|
+
): boolean => {
|
|
6
|
+
if (lowPosition === highPosition) {
|
|
7
|
+
return downX < lowPosition;
|
|
8
|
+
}
|
|
9
|
+
const distanceFromLow = Math.abs(downX - lowPosition);
|
|
10
|
+
const distanceFromHigh = Math.abs(downX - highPosition);
|
|
11
|
+
return distanceFromLow < distanceFromHigh;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const clamp = (value: number, min: number, max: number): number => {
|
|
15
|
+
return Math.min(Math.max(value, min), max);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getValueForPosition = (
|
|
19
|
+
positionInView: number,
|
|
20
|
+
containerWidth: number,
|
|
21
|
+
thumbWidth: number,
|
|
22
|
+
min: number,
|
|
23
|
+
max: number,
|
|
24
|
+
step: number,
|
|
25
|
+
): number => {
|
|
26
|
+
const availableSpace = containerWidth - thumbWidth;
|
|
27
|
+
const relStepUnit = step / (max - min);
|
|
28
|
+
let relPosition = (positionInView - thumbWidth / 2) / availableSpace;
|
|
29
|
+
const relOffset = relPosition % relStepUnit;
|
|
30
|
+
relPosition -= relOffset;
|
|
31
|
+
if (relOffset / relStepUnit >= 0.5) {
|
|
32
|
+
relPosition += relStepUnit;
|
|
33
|
+
}
|
|
34
|
+
return clamp(min + Math.round(relPosition / relStepUnit) * step, min, max);
|
|
35
|
+
};
|
package/hooks.tsx
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
MutableRefObject,
|
|
3
|
+
useCallback,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import {Animated, I18nManager, ViewStyle} from 'react-native';
|
|
9
|
+
import {clamp} from './helpers';
|
|
10
|
+
import styles from './styles';
|
|
11
|
+
import FollowerContainer, {LabelRef} from './Label';
|
|
12
|
+
import {Spacing} from '@momo-kits/foundation';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* low and high state variables are fallbacks for props (props are not required).
|
|
16
|
+
* This hook ensures that current low and high are not out of [min, max] range.
|
|
17
|
+
* It returns an object which contains:
|
|
18
|
+
* - ref containing correct low, high, min, max and step to work with.
|
|
19
|
+
* - setLow and setHigh setters
|
|
20
|
+
* @param lowProp
|
|
21
|
+
* @param highProp
|
|
22
|
+
* @param min
|
|
23
|
+
* @param max
|
|
24
|
+
* @param step
|
|
25
|
+
* @returns {{inPropsRef: React.MutableRefObject<{high: (*|number), low: (*|number)}>, setLow: (function(number): undefined), setHigh: (function(number): undefined)}}
|
|
26
|
+
*/
|
|
27
|
+
export const useLowHigh = (
|
|
28
|
+
lowProp: number | undefined,
|
|
29
|
+
highProp: number | undefined,
|
|
30
|
+
min: number,
|
|
31
|
+
max: number,
|
|
32
|
+
step: number,
|
|
33
|
+
) => {
|
|
34
|
+
const validLowProp = lowProp === undefined ? min : clamp(lowProp, min, max);
|
|
35
|
+
const validHighProp =
|
|
36
|
+
highProp === undefined ? max : clamp(highProp, min, max);
|
|
37
|
+
const inPropsRef = useRef({
|
|
38
|
+
low: validLowProp,
|
|
39
|
+
high: validHighProp,
|
|
40
|
+
step,
|
|
41
|
+
// These 2 fields will be overwritten below.
|
|
42
|
+
min: validLowProp,
|
|
43
|
+
max: validHighProp,
|
|
44
|
+
});
|
|
45
|
+
const {low: lowState, high: highState} = inPropsRef.current;
|
|
46
|
+
const inPropsRefPrev = {lowPrev: lowState, highPrev: highState};
|
|
47
|
+
|
|
48
|
+
// Props have higher priority.
|
|
49
|
+
// If no props are passed, use internal state variables.
|
|
50
|
+
const low = clamp(lowProp === undefined ? lowState : lowProp, min, max);
|
|
51
|
+
const high = clamp(highProp === undefined ? highState : highProp, min, max);
|
|
52
|
+
|
|
53
|
+
// Always update values of refs so pan responder will have updated values
|
|
54
|
+
Object.assign(inPropsRef.current, {low, high, min, max});
|
|
55
|
+
|
|
56
|
+
const setLow = (value: number) => (inPropsRef.current.low = value);
|
|
57
|
+
const setHigh = (value: number) => (inPropsRef.current.high = value);
|
|
58
|
+
return {inPropsRef, inPropsRefPrev, setLow, setHigh};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sets the current value of widthRef and calls the callback with new width parameter.
|
|
63
|
+
* @param widthRef
|
|
64
|
+
* @param callback
|
|
65
|
+
* @returns {function({nativeEvent: *}): void}
|
|
66
|
+
*/
|
|
67
|
+
export const useWidthLayout = (
|
|
68
|
+
widthRef: MutableRefObject<number>,
|
|
69
|
+
callback?: (width: number) => void,
|
|
70
|
+
) => {
|
|
71
|
+
return useCallback(
|
|
72
|
+
({nativeEvent}) => {
|
|
73
|
+
const {
|
|
74
|
+
layout: {width},
|
|
75
|
+
} = nativeEvent;
|
|
76
|
+
const {current: w} = widthRef;
|
|
77
|
+
if (w !== width) {
|
|
78
|
+
widthRef.current = width;
|
|
79
|
+
if (callback) {
|
|
80
|
+
callback(width);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[callback, widthRef],
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* This hook creates a component which follows the thumb.
|
|
90
|
+
* Content renderer is passed to FollowerContainer which re-renders only it's content with setValue method.
|
|
91
|
+
* This allows to re-render only follower, instead of the whole slider with all children (thumb, rail, etc.).
|
|
92
|
+
* Returned update function should be called every time follower should be updated.
|
|
93
|
+
* @param containerWidthRef
|
|
94
|
+
* @param gestureStateRef
|
|
95
|
+
* @param isPressed
|
|
96
|
+
* @param allowOverflow
|
|
97
|
+
* @param disabledLow
|
|
98
|
+
* @param disabledHigh
|
|
99
|
+
* @returns {[JSX.Element, function(*, *=): void]|*[]}
|
|
100
|
+
*/
|
|
101
|
+
export const useThumbFollower = (
|
|
102
|
+
containerWidthRef: MutableRefObject<number>,
|
|
103
|
+
gestureStateRef: MutableRefObject<{
|
|
104
|
+
isLow: boolean;
|
|
105
|
+
lastValue: number;
|
|
106
|
+
lastPosition: number;
|
|
107
|
+
}>,
|
|
108
|
+
isPressed: boolean,
|
|
109
|
+
allowOverflow: boolean,
|
|
110
|
+
disabledLow: boolean,
|
|
111
|
+
disabledHigh: boolean,
|
|
112
|
+
) => {
|
|
113
|
+
const xRef = useRef(new Animated.Value(0));
|
|
114
|
+
const widthRef = useRef(0);
|
|
115
|
+
const contentContainerRef = useRef<LabelRef>(null);
|
|
116
|
+
|
|
117
|
+
const {current: x} = xRef;
|
|
118
|
+
|
|
119
|
+
const update = useCallback(
|
|
120
|
+
(thumbPositionInView, value) => {
|
|
121
|
+
if (
|
|
122
|
+
(gestureStateRef.current.isLow && disabledLow) ||
|
|
123
|
+
(!gestureStateRef.current.isLow && disabledHigh)
|
|
124
|
+
) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const {current: width} = widthRef;
|
|
129
|
+
const {current: containerWidth} = containerWidthRef;
|
|
130
|
+
const position = thumbPositionInView - width / 2;
|
|
131
|
+
xRef.current.setValue(
|
|
132
|
+
allowOverflow ? position : clamp(position, 0, containerWidth - width),
|
|
133
|
+
);
|
|
134
|
+
contentContainerRef.current?.setValue(value);
|
|
135
|
+
},
|
|
136
|
+
[widthRef, containerWidthRef, allowOverflow, disabledLow, disabledHigh], // Include dependencies here.
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const handleLayout = useWidthLayout(widthRef, () => {
|
|
140
|
+
update(
|
|
141
|
+
gestureStateRef.current.lastPosition,
|
|
142
|
+
gestureStateRef.current.lastValue,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const transform = {transform: [{translateX: x}]};
|
|
147
|
+
|
|
148
|
+
const shouldDisplayLabel =
|
|
149
|
+
isPressed &&
|
|
150
|
+
((gestureStateRef.current.isLow && !disabledLow) ||
|
|
151
|
+
(!gestureStateRef.current.isLow && !disabledHigh));
|
|
152
|
+
|
|
153
|
+
const follower = (
|
|
154
|
+
<Animated.View style={[transform, {opacity: shouldDisplayLabel ? 1 : 0}]}>
|
|
155
|
+
<FollowerContainer onLayout={handleLayout} ref={contentContainerRef} />
|
|
156
|
+
</Animated.View>
|
|
157
|
+
);
|
|
158
|
+
return [follower, update];
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
interface InProps {
|
|
162
|
+
low: number;
|
|
163
|
+
high: number;
|
|
164
|
+
min: number;
|
|
165
|
+
max: number;
|
|
166
|
+
step: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const useSelectedRail = (
|
|
170
|
+
inPropsRef: MutableRefObject<InProps>,
|
|
171
|
+
containerWidthRef: MutableRefObject<number>,
|
|
172
|
+
thumbWidth: number,
|
|
173
|
+
disableRange: boolean,
|
|
174
|
+
) => {
|
|
175
|
+
const {current: left} = useRef(new Animated.Value(0));
|
|
176
|
+
const {current: right} = useRef(new Animated.Value(0));
|
|
177
|
+
const update = useCallback(() => {
|
|
178
|
+
const {low, high, min, max} = inPropsRef.current;
|
|
179
|
+
const {current: containerWidth} = containerWidthRef;
|
|
180
|
+
const fullScale = (max - min) / (containerWidth - thumbWidth);
|
|
181
|
+
const leftValue = (low - min) / fullScale;
|
|
182
|
+
const rightValue = (max - high) / fullScale;
|
|
183
|
+
left.setValue(disableRange ? 0 : leftValue);
|
|
184
|
+
right.setValue(
|
|
185
|
+
disableRange ? containerWidth - thumbWidth - leftValue : rightValue,
|
|
186
|
+
);
|
|
187
|
+
}, [inPropsRef, containerWidthRef, disableRange, thumbWidth, left, right]);
|
|
188
|
+
const styles = useMemo(
|
|
189
|
+
() => ({
|
|
190
|
+
position: 'absolute',
|
|
191
|
+
left: left,
|
|
192
|
+
right: right,
|
|
193
|
+
}),
|
|
194
|
+
[left, right],
|
|
195
|
+
);
|
|
196
|
+
return [styles, update];
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param floating
|
|
201
|
+
* @returns {{onLayout: ((function({nativeEvent: *}): void)|undefined), style: [*, {top}]}}
|
|
202
|
+
*/
|
|
203
|
+
export const useLabelContainerProps = (floating: boolean) => {
|
|
204
|
+
const [labelContainerHeight, setLabelContainerHeight] = useState(0);
|
|
205
|
+
const onLayout = useCallback(({nativeEvent}) => {
|
|
206
|
+
const {
|
|
207
|
+
layout: {height},
|
|
208
|
+
} = nativeEvent;
|
|
209
|
+
setLabelContainerHeight(height);
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
const top = floating ? -(labelContainerHeight + Spacing.XS) : Spacing.XS;
|
|
213
|
+
const style = [
|
|
214
|
+
floating ? styles.labelFloatingContainer : styles.labelFixedContainer,
|
|
215
|
+
{top},
|
|
216
|
+
];
|
|
217
|
+
return {style, onLayout: onLayout};
|
|
218
|
+
};
|