react-native-ruler-view
Version:
⚡ Lightning-fast and customizable Ruler Picker component for React Native
206 lines (204 loc) • 9.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RulerPicker = void 0;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _RulerPickerItem = require("./RulerPickerItem");
var _theme = require("../utils/theme");
var _utils = require("../utils");
var _RulerPicker = require("./RulerPicker.styles");
var _jsxRuntime = require("react/jsx-runtime");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const {
width: windowWidth
} = _reactNative.Dimensions.get('window');
const RulerPicker = ({
width = windowWidth,
height = 300,
min,
max,
step = 1,
initialValue = min,
fractionDigits = 1,
unit = 'cm',
indicatorHeight = 80,
vertical = false,
theme = 'light',
hapticFeedback = false,
animated = true,
gapBetweenSteps = 10,
shortStepHeight = 20,
longStepHeight = 40,
containerStyle,
stepWidth = 2,
valueTextStyle,
unitTextStyle,
decelerationRate = 'normal',
showLabels = true,
accessibility,
onValueChange,
onValueChangeEnd
}) => {
// Validate props
if (min >= max) {
console.error('min must be less than max');
return null;
}
if (initialValue < min || initialValue > max) {
console.error('initialValue must be between min and max');
return null;
}
const listRef = (0, _react.useRef)(null);
const stepTextRef = (0, _react.useRef)(null);
const increasingRef = (0, _react.useRef)(true);
const prevValue = (0, _react.useRef)(initialValue.toFixed(fractionDigits));
const prevMomentumValue = (0, _react.useRef)(initialValue.toFixed(fractionDigits));
const scrollPosition = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
const [, forceUpdate] = (0, _react.useReducer)(x => x + 1, 0);
const activeTheme = typeof theme === 'string' ? _theme.PRESET_THEMES[theme] : theme;
const itemAmount = Math.floor((max - min) / step);
const arrData = (0, _react.useMemo)(() => Array.from({
length: itemAmount + 1
}, (_, i) => i), [itemAmount]);
const styles = (0, _RulerPicker.getStyles)(height, width, vertical, indicatorHeight, stepWidth, longStepHeight, activeTheme);
const announceValue = (0, _react.useCallback)(value => {
if (accessibility?.enabled && accessibility.announceValues) {
const announcement = accessibility.labelFormat ? accessibility.labelFormat.replace('${value}', value) : `Value: ${value}${unit}`;
_reactNative.AccessibilityInfo.announceForAccessibility(announcement);
}
}, [accessibility, unit]);
const valueCallback = (0, _react.useCallback)(({
value
}) => {
const newStep = (0, _utils.calculateCurrentValue)(value, stepWidth, gapBetweenSteps, min, max, step, fractionDigits);
if (parseFloat(newStep) > parseFloat(prevValue.current)) {
increasingRef.current = true;
} else if (parseFloat(newStep) < parseFloat(prevValue.current)) {
increasingRef.current = false;
}
if (prevValue.current !== newStep) {
if (hapticFeedback && _reactNative.Platform.OS !== 'web') {
_reactNative.Vibration.vibrate(1);
}
onValueChange?.(parseFloat(newStep));
stepTextRef.current?.setNativeProps({
text: newStep
});
announceValue(newStep);
}
forceUpdate(); // Forces a re-render
prevValue.current = newStep;
}, [announceValue, fractionDigits, gapBetweenSteps, hapticFeedback, max, min, onValueChange, step, stepWidth]);
(0, _react.useEffect)(() => {
scrollPosition.addListener(valueCallback);
return () => scrollPosition.removeAllListeners();
}, [scrollPosition, valueCallback]);
(0, _react.useEffect)(() => {
const initialOffset = (0, _utils.getInitialOffset)(initialValue, min, step, stepWidth, gapBetweenSteps);
listRef.current?.scrollToOffset({
offset: initialOffset,
animated: false
});
}, [gapBetweenSteps, initialValue, min, step, stepWidth]);
const scrollHandler = _reactNative.Animated.event([{
nativeEvent: {
contentOffset: vertical ? {
y: scrollPosition
} : {
x: scrollPosition
}
}
}], {
useNativeDriver: true
});
const renderSeparator = (value = 0) => {
const separatorHeight = vertical ? value || height * 0.65 : undefined;
const separatorWidth = vertical ? undefined : width * 0.472;
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: {
height: separatorHeight,
width: separatorWidth
}
});
};
const renderItem = (0, _react.useCallback)(({
item: index
}) => {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_RulerPickerItem.RulerPickerItem, {
isLast: index === arrData.length - 1,
index: index,
shortStepHeight: shortStepHeight,
longStepHeight: longStepHeight,
gapBetweenSteps: gapBetweenSteps,
stepWidth: stepWidth,
shortStepColor: activeTheme.shortStepColor,
longStepColor: activeTheme.longStepColor,
vertical: vertical,
animated: animated
});
}, [activeTheme, animated, arrData.length, gapBetweenSteps, longStepHeight, shortStepHeight, stepWidth, vertical]);
const onMomentumScrollEnd = (0, _react.useCallback)(event => {
const offset = vertical ? event.nativeEvent.contentOffset.y : event.nativeEvent.contentOffset.x;
const newStep = (0, _utils.calculateCurrentValue)(offset, stepWidth, gapBetweenSteps, min, max, step, fractionDigits);
if (prevMomentumValue.current !== newStep) {
onValueChangeEnd?.(newStep);
announceValue(newStep);
}
prevMomentumValue.current = newStep;
}, [announceValue, fractionDigits, gapBetweenSteps, stepWidth, max, min, onValueChangeEnd, step, vertical]);
const getLabel = (value, color) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.text, {
color: color
}],
numberOfLines: 1,
adjustsFontSizeToFit: true,
children: value
});
const getLabelNumber = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
pointerEvents: "none",
style: [styles.displayTextContainer],
children: [showLabels && getLabel(parseInt(prevValue.current) - step * 2 >= min ? (parseInt(prevValue.current) - step * 2).toString() : '', 'lightgray'), showLabels && getLabel(parseInt(prevValue.current) - step >= min ? (parseInt(prevValue.current) - step).toString() : '', 'gray'), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.selectedText,
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
style: [styles.valueText, valueTextStyle],
numberOfLines: 1,
adjustsFontSizeToFit: true,
children: [parseInt(prevValue.current).toFixed(fractionDigits), ' ', unit && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.unitText, unitTextStyle],
children: unit
})]
})
}), showLabels && getLabel(parseInt(prevValue.current) + step >= max + step ? '' : (parseInt(prevValue.current) + step).toString(), 'gray'), showLabels && getLabel(parseInt(prevValue.current) + step * 2 >= max + step ? '' : (parseInt(prevValue.current) + step * 2).toString(), 'lightgray')]
});
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.container,
accessible: accessibility?.enabled,
accessibilityRole: "adjustable",
children: [getLabelNumber(), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.FlatList, {
ref: listRef,
data: arrData,
keyExtractor: (_, index) => index.toString(),
renderItem: renderItem,
style: [styles.rulerContainer, containerStyle],
contentContainerStyle: styles.rulerContent,
ListHeaderComponent: () => renderSeparator(),
ListFooterComponent: () => renderSeparator(vertical ? height * 0.1 : 0),
onScroll: scrollHandler,
onMomentumScrollEnd: onMomentumScrollEnd,
snapToOffsets: arrData.map((_, index) => index * (stepWidth + gapBetweenSteps)),
snapToAlignment: "start",
decelerationRate: decelerationRate,
scrollEventThrottle: 16,
showsHorizontalScrollIndicator: false,
showsVerticalScrollIndicator: false,
horizontal: !vertical
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.indicator
})]
});
};
exports.RulerPicker = RulerPicker;
//# sourceMappingURL=RulerPicker.js.map