import { memo, useRef, useCallback, useMemo } from 'react'; import { GestureResponderEvent, PanResponder, StyleSheet, View, ViewProps } from 'react-native'; import { useLatest } from '../../hooks'; import { circleSize, clockTypes, getAngle, getHours, getHourType, getHourTypeFromOffset, getMinutes, hourTypes, PossibleClockTypes, } from './timeUtils'; import AnalogClockHours from './AnalogClockHours'; import AnimatedClockSwitcher from './AnimatedClockSwitcher'; import AnalogClockMinutes from './AnalogClockMinutes'; import { timePickerClockStyles } from './utils'; type Props = { hours: number; minutes: number; focused: PossibleClockTypes; is24Hour: boolean; onChange: (hoursMinutesAndFocused: { hours: number; minutes: number; focused?: undefined | PossibleClockTypes; }) => any; }; function AnalogClock({ hours, minutes, focused, is24Hour, onChange }: Props) { // const { mode } = useContext(DisplayModeContext); // used to make pointer shorter if hours are selected and above 12 const shortPointer = (hours === 0 || hours > 12) && is24Hour; const clockRef = useRef(null); // Hooks are nice, sometimes... :-).. // We need the latest values, since the onPointerMove uses a closure to the function const hoursRef = useLatest(hours); const onChangeRef = useLatest(onChange); const minutesRef = useLatest(minutes); const focusedRef = useLatest(focused); const is24HourRef = useLatest(is24Hour); // const modeRef = useLatest(mode); const onPointerMove = useCallback( (e: GestureResponderEvent, final: boolean) => { const x = e.nativeEvent.locationX; const y = e.nativeEvent.locationY; const angle = getAngle(x, y, circleSize); if (focusedRef.current === clockTypes.hours) { const hours24 = is24HourRef.current; const previousHourType = getHourType(hoursRef.current); let pickedHours = getHours(angle, previousHourType); if (hours24) { const hourTypeFromOffset = getHourTypeFromOffset(x, y, circleSize); const hours24AndPM = hours24 && hourTypeFromOffset === hourTypes.pm; // Avoiding the "24h" // Should be 12h for 12 hours and PM mode if (hours24AndPM) { pickedHours += 12; } if (pickedHours === 24) { pickedHours = 0; } } if (hoursRef.current !== pickedHours || final) { onChangeRef.current({ hours: pickedHours, minutes: minutesRef.current, focused: final ? clockTypes.minutes : undefined, }); } } else if (focusedRef.current === clockTypes.minutes) { const pickedMinutes = getMinutes(angle); if (minutesRef.current !== pickedMinutes) { onChangeRef.current({ hours: hoursRef.current, minutes: pickedMinutes, }); } } }, [focusedRef, is24HourRef, hoursRef, onChangeRef, minutesRef], ); const panResponder = useRef( PanResponder.create({ onPanResponderGrant: e => onPointerMove(e, false), onPanResponderMove: e => onPointerMove(e, false), onPanResponderRelease: e => onPointerMove(e, true), onStartShouldSetPanResponder: returnTrue, onStartShouldSetPanResponderCapture: () => false, onMoveShouldSetPanResponder: returnTrue, onMoveShouldSetPanResponderCapture: returnTrue, onPanResponderTerminationRequest: returnTrue, onShouldBlockNativeResponder: returnTrue, }), ).current; const { clockStyle, lineStyle, endPointStyle, middlePointContainerStyle, middlePointStyle } = useMemo(() => { const { clock, endPoint, center, line, middlePoint } = timePickerClockStyles; const dynamicSize = focused === clockTypes.hours && shortPointer ? 33 : 0; const pointerNumber = focused === clockTypes.hours ? hours : minutes; const degreesPerNumber = focused === clockTypes.hours ? 30 : 6; return { clockStyle: clock, lineStyle: [ line, { transform: [ { rotate: -90 + pointerNumber * degreesPerNumber + 'deg' }, { translateX: circleSize / 4 - 4 - dynamicSize / 2, }, ], width: circleSize / 2 - 4 - dynamicSize, }, ], endPointStyle: endPoint, middlePointContainerStyle: [StyleSheet.absoluteFill, center], middlePointStyle: middlePoint, }; }, [focused, hours, minutes, shortPointer]); return ( https://github.com/necolas/react-native-web/issues/506 cursor={'pointer'}> } minutes={} /> ); } function returnTrue() { return true; } export default memo(AnalogClock);