UNPKG

react-native-lucky-wheel

Version:
432 lines (380 loc) 17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var d3Shape = _interopRequireWildcard(require("d3-shape")); var _randomcolor = _interopRequireDefault(require("randomcolor")); var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg")); var _Knob = _interopRequireDefault(require("./Knob")); var _utils = _interopRequireDefault(require("../utils")); var _index = require("../index"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const AnimatedSvg = _reactNative.Animated.createAnimatedComponent(_reactNativeSvg.default); const { width } = _reactNative.Dimensions.get('screen'); const ONE_TURN = 360; const LuckyWheel = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => { const rotate = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current; const spinVelocity = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current; const containerRef = (0, _react.useRef)(null); const [isSpinning, setIsSpinning] = (0, _react.useState)(false); const [spinCount, setSpinCount] = (0, _react.useState)(1); const [winnerLastOffset, setWinnerLastOffset] = (0, _react.useState)(0); const [px, setPx] = (0, _react.useState)(0); const [py, setPy] = (0, _react.useState)(0); const rotateInterpolated = rotate.interpolate({ inputRange: [-ONE_TURN, 0, ONE_TURN], outputRange: [`-${ONE_TURN}deg`, '0deg', `${ONE_TURN}deg`] }); const outerRadius = (0, _react.useMemo)(() => props.size / 2 - props.outerRadius, [props.size, props.outerRadius]); const DURATION_IN_MS = (0, _react.useMemo)(() => props.duration * 1000, [props.duration]); const SLICE_COUNT = (0, _react.useMemo)(() => props.slices.length, [props.slices]); const SLICE_ANGLE = (0, _react.useMemo)(() => ONE_TURN / SLICE_COUNT, [SLICE_COUNT]); const SLICE_ANGLE_CENTER = (0, _react.useMemo)(() => SLICE_ANGLE / 2, [SLICE_ANGLE]); const SLICE_PAYLOAD = (0, _react.useMemo)(() => { const data = Array.from({ length: SLICE_COUNT }).fill(1); const arcs = d3Shape.pie()(data); const instance = d3Shape.arc().padAngle(props.padAngle).outerRadius(outerRadius).innerRadius(props.innerRadius); const colors = (0, _randomcolor.default)({ ...props.backgroundColorOptions, count: SLICE_COUNT }); return arcs.map((arc, index) => { return { path: instance(arc), color: props.slices[index].color ?? colors[index % colors.length], text: props.slices[index].text, textStyle: props.slices[index].textStyle, centroid: instance.centroid(arc) }; }); }, [props.padAngle, props.innerRadius, outerRadius, props.backgroundColorOptions, props.slices, SLICE_COUNT]); const knobAnim = _reactNative.Animated.modulo(_reactNative.Animated.divide(_reactNative.Animated.modulo(_reactNative.Animated.subtract(rotate, SLICE_ANGLE_CENTER), ONE_TURN), new _reactNative.Animated.Value(SLICE_ANGLE)), 1); const knobInterpolated = knobAnim.interpolate({ inputRange: [-1, -0.5, -0.0001, 0.0001, 0.5, 1], outputRange: ['0deg', '0deg', '35deg', '-35deg', '0deg', '0deg'] }); const startSpinning = (0, _react.useCallback)(() => { if (!isSpinning) setIsSpinning(true); const isGestureSpinning = spinVelocity._value !== 0; const isSpinningValid = Math.abs(spinVelocity._value) >= 1; const velocity = isGestureSpinning && props.enablePhysics && isSpinningValid ? Math.round(Math.abs(spinVelocity._value)) : 1; const WINNER_INDEX = props.winnerIndex ?? Math.floor(Math.random() * SLICE_COUNT); const WINNER = props.slices[WINNER_INDEX]; const WINNER_ANGLE = WINNER_INDEX * (ONE_TURN / SLICE_COUNT); const WINNER_ANGLE_OFFSET = _utils.default.randomNumber(-SLICE_ANGLE_CENTER + SLICE_ANGLE / 3, SLICE_ANGLE_CENTER - SLICE_ANGLE / 3); const EXTRA_SPIN_DEGREE = ONE_TURN * props.duration * spinCount * velocity; const TARGET_ANGLE = ONE_TURN - WINNER_ANGLE + EXTRA_SPIN_DEGREE - winnerLastOffset + WINNER_ANGLE_OFFSET; const isEndlessSpinning = props.waitWinner && !props.winnerIndex; if (isEndlessSpinning) { _reactNative.Animated.loop(_reactNative.Animated.timing(rotate, { toValue: EXTRA_SPIN_DEGREE * 4, duration: DURATION_IN_MS, useNativeDriver: true, easing: _reactNative.Easing.linear })).start(); } else { _reactNative.Animated.timing(rotate, { toValue: TARGET_ANGLE, duration: DURATION_IN_MS, easing: props.easing === 'out' ? _reactNative.Easing.out(_reactNative.Easing.cubic) : _reactNative.Easing.inOut(_reactNative.Easing.cubic), useNativeDriver: true }).start(() => { setIsSpinning(false); spinVelocity.setValue(0); if (props.onSpinningEnd) props.onSpinningEnd(WINNER); }); } if (props.onSpinningStart) props.onSpinningStart(); setWinnerLastOffset(WINNER_ANGLE_OFFSET); setSpinCount(spinCount + 1); }, [DURATION_IN_MS, SLICE_ANGLE, SLICE_ANGLE_CENTER, SLICE_COUNT, isSpinning, props, rotate, spinCount, spinVelocity, winnerLastOffset]); (0, _react.useEffect)(() => { if (isSpinning && props.waitWinner && props.winnerIndex) { rotate.resetAnimation(() => { startSpinning(); }); } }, [isSpinning, props.waitWinner, props.winnerIndex, rotate, startSpinning]); (0, _react.useEffect)(() => { if (props.onKnobTick) { knobAnim.addListener(_ref => { let { value } = _ref; if (value > 0.7) { if (props.onKnobTick) props.onKnobTick(); } }); } else { knobAnim.removeAllListeners(); } }, [knobAnim, props]); (0, _react.useImperativeHandle)(ref, () => ({ start: () => { startSpinning(); }, stop: () => { rotate.stopAnimation(); }, reset: () => { rotate.resetAnimation(); } })); const _panResponder = _reactNative.PanResponder.create({ onMoveShouldSetPanResponder: () => true, onMoveShouldSetPanResponderCapture: () => true, onPanResponderMove: (_, gestureState) => { if (!props.enableGesture || isSpinning) { return; } const mappedX = gestureState.moveX - px; const mappedY = gestureState.moveY - py; const x = mappedX < 0 ? 0 : mappedX > 300 ? props.size : mappedX; const y = mappedY < 0 ? 0 : mappedY > 300 ? props.size : mappedY; const top = y < props.size / 2; const bottom = y > props.size / 2; const left = x < props.size / 2; const right = x > props.size / 2; // console.log({x, y, top, bottom, left, right}); const isMoveRight = gestureState.vx > 0; const isMoveLeft = gestureState.vx < 0; const isMoveUp = gestureState.vy < 0; const isMoveBottom = gestureState.vy > 0; // console.log({isMoveRight, isMoveLeft, isMoveUp, isMoveBottom}); const isSpinRight = top && isMoveRight || bottom && isMoveLeft || top && right && isMoveBottom || bottom && left && isMoveUp; const isSpinLeft = (top && isMoveLeft || bottom && isMoveRight || top && left && isMoveBottom || bottom && right && isMoveUp) && !isSpinRight; // console.log({isSpinRight, isSpinLeft}); const isSingleTouch = gestureState.numberActiveTouches === 1; // const isRightMove = gestureState.vx < 0; const isClockwiseEnabled = props.gestureType === _index.GestureTypes.CLOCKWISE || props.gestureType === _index.GestureTypes.MULTIDIRECTIONAL; const isAntiClockwiseEnabled = props.gestureType === _index.GestureTypes.ANTI_CLOCKWISE || props.gestureType === _index.GestureTypes.MULTIDIRECTIONAL; // set spin velocity if (isSpinRight && isClockwiseEnabled || isSpinLeft && isAntiClockwiseEnabled) { if (rotate._value === 0) { spinVelocity.setValue(gestureState.vx); } else { spinVelocity.setValue((gestureState.vx + spinVelocity._value) / 2); } } const slowDivider = 15; if (isClockwiseEnabled && isSingleTouch && isSpinRight) { rotate.setValue((rotate._value + x / slowDivider) % 360); } if (isAntiClockwiseEnabled && isSingleTouch && isSpinLeft) { rotate.setValue((rotate._value - x / slowDivider) % 360); } }, onPanResponderRelease: () => { if (!props.enableGesture || isSpinning) { return; } const isVelocityFastEnough = Math.abs(spinVelocity._value) > props.minimumSpinVelocity; if (isVelocityFastEnough) { startSpinning(); } } }); const _renderKnob = () => { return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, { style: [styles.knob, { transform: [{ rotate: knobInterpolated }] }] }, props.customKnob ? props.customKnob({ size: props.knobSize, color: props.knobColor }) : /*#__PURE__*/_react.default.createElement(_Knob.default, { size: props.knobSize, color: props.knobColor })); }; const _renderText = params => { var _params$payload$textS2, _props$textStyle2; if (props.source) return null; if (props.customText) { return props.customText(params); } if (props.textAngle === _index.TextAngles.VERTICAL) { var _params$payload$textS, _props$textStyle; return /*#__PURE__*/_react.default.createElement(_reactNativeSvg.G, { rotation: params.i * ONE_TURN / SLICE_COUNT + SLICE_ANGLE_CENTER + 90, origin: `${params.x}, ${params.y}` }, /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Text, _extends({ x: params.x - props.size / 7, y: params.y, fill: ((_params$payload$textS = params.payload.textStyle) === null || _params$payload$textS === void 0 ? void 0 : _params$payload$textS.color) ?? ((_props$textStyle = props.textStyle) === null || _props$textStyle === void 0 ? void 0 : _props$textStyle.color) ?? styles.text.color }, props.textStyle, params.payload.textStyle), params.payload.text)); } return /*#__PURE__*/_react.default.createElement(_reactNativeSvg.G, { rotation: params.i * ONE_TURN / SLICE_COUNT + SLICE_ANGLE_CENTER, origin: `${params.x}, ${params.y}` }, /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Text, _extends({ x: params.x - params.payload.text.length * 5, y: params.y - props.size / 8, fill: ((_params$payload$textS2 = params.payload.textStyle) === null || _params$payload$textS2 === void 0 ? void 0 : _params$payload$textS2.color) ?? ((_props$textStyle2 = props.textStyle) === null || _props$textStyle2 === void 0 ? void 0 : _props$textStyle2.color) ?? styles.text.color }, props.textStyle, params.payload.textStyle), params.payload.text)); }; const _renderOuterDots = (x, y, i) => { if (!props.enableOuterDots) { return null; } return /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Circle, { origin: `${x}, ${y}`, rotation: i * ONE_TURN / SLICE_COUNT + SLICE_ANGLE_CENTER, cx: x + outerRadius + 2.5, cy: y + SLICE_ANGLE - SLICE_COUNT * 2 + props.innerRadius / 2 + props.innerRadius / 100, r: "4", fill: props.dotColor }); }; const _renderSlice = (path, color) => { return /*#__PURE__*/_react.default.createElement(_reactNativeSvg.Path, { d: path, strokeWidth: 2, fill: color }); }; const _renderCircle = () => { if (!props.enableInnerShadow) return false; return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, { style: { ...styles.wheel, ...styles.circle, width: props.size - (props.size - outerRadius * 2), height: props.size - (props.size - outerRadius * 2), borderRadius: props.size / 2 } }); }; const _renderWheel = () => { if (props.source) { return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.Image, _extends({ ref: containerRef, onLayout: () => { var _containerRef$current; containerRef === null || containerRef === void 0 ? void 0 : (_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.measure((_fx, _fy, _wheelWidth, _wheelHeight, pxSize, pySize) => { setPx(pxSize); setPy(pySize); }); }, style: { ...styles.wheel, width: props.size, height: props.size, transform: [{ rotate: rotateInterpolated }] }, source: props.source }, _panResponder.panHandlers)); } return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, _extends({ ref: containerRef, onLayout: () => { if (containerRef.current) { containerRef.current.measure((_fx, _fy, _wheelWidth, _wheelHeight, pxSize, pySize) => { setPx(pxSize); setPy(pySize); }); } }, style: { ...styles.wheel, transform: [{ rotate: rotateInterpolated }], backgroundColor: props.backgroundColor, width: props.size, height: props.size, borderRadius: props.size / 2 } }, _panResponder.panHandlers), /*#__PURE__*/_react.default.createElement(AnimatedSvg, { style: { transform: [{ rotate: `-${SLICE_ANGLE_CENTER}deg` }] } }, /*#__PURE__*/_react.default.createElement(_reactNativeSvg.G, { y: props.size / 2, x: props.size / 2 }, SLICE_PAYLOAD.map((payload, i) => { const [x, y] = payload.centroid; return /*#__PURE__*/_react.default.createElement(_reactNativeSvg.G, { key: `arc-${i}` }, _renderSlice(payload.path, payload.color), _renderText({ x, y, payload, i }), _renderOuterDots(x, y, i)); }))), _renderCircle()); }; return /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: styles.container }, _renderKnob(), /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: { transform: [{ rotate: `${props.offset}deg` }] } }, _renderWheel())); }); const defaultProps = { duration: 4, innerRadius: 30, outerRadius: 13, padAngle: 0.01, backgroundColor: '#FFF', size: width - 40, textAngle: _index.TextAngles.VERTICAL, backgroundColorOptions: { luminosity: 'dark', hue: 'random' }, knobSize: 30, knobColor: '#FF0000', textStyle: {}, easing: _index.EasingTypes.OUT, dotColor: '#000', minimumSpinVelocity: 1, enableGesture: false, enableOuterDots: true, enablePhysics: false, gestureType: _index.GestureTypes.CLOCKWISE, offset: 0, waitWinner: false, enableInnerShadow: true }; LuckyWheel.defaultProps = defaultProps; const styles = _reactNative.StyleSheet.create({ container: { justifyContent: 'center', alignItems: 'center', zIndex: 1 }, wheel: { justifyContent: 'center', alignItems: 'center' }, circle: { position: 'absolute', backgroundColor: 'transparent', borderColor: '#000', borderWidth: 15, opacity: 0.3 }, knob: { justifyContent: 'flex-end', zIndex: 1 }, text: { fontSize: 20, fontWeight: 'bold', color: '#FFF' } }); var _default = LuckyWheel; exports.default = _default; //# sourceMappingURL=LuckyWheel.js.map