@utilitywarehouse/hearth-react-native 0.27.1 → 0.27.2-testid-fix-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.
Files changed (60) hide show
  1. package/.storybook/vitest.setup.ts +35 -3
  2. package/.turbo/turbo-build.log +5 -4
  3. package/CHANGELOG.md +15 -0
  4. package/build/components/Button/ButtonRoot.js +8 -0
  5. package/build/components/Carousel/Carousel.js +6 -1
  6. package/build/components/DatePicker/TimePicker.d.ts +3 -0
  7. package/build/components/DatePicker/TimePicker.js +84 -0
  8. package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
  9. package/build/components/DatePicker/time-picker/animated-math.js +19 -0
  10. package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
  11. package/build/components/DatePicker/time-picker/period-native.js +17 -0
  12. package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
  13. package/build/components/DatePicker/time-picker/period-picker.js +10 -0
  14. package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
  15. package/build/components/DatePicker/time-picker/period-web.js +21 -0
  16. package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
  17. package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
  18. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
  19. package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
  20. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
  21. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
  22. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
  23. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
  24. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
  25. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
  26. package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
  27. package/build/components/DatePicker/time-picker/wheel-web.js +146 -0
  28. package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
  29. package/build/components/DatePicker/time-picker/wheel.js +10 -0
  30. package/build/components/List/List.js +2 -2
  31. package/build/components/Modal/Modal.js +16 -11
  32. package/build/components/SegmentedControl/SegmentedControl.js +4 -1
  33. package/build/components/SegmentedControl/SegmentedControlOption.js +4 -1
  34. package/build/components/TimePicker/TimePickerWheel.js +9 -1
  35. package/build/components/Toast/Toast.context.js +1 -1
  36. package/build/components/VerificationInput/VerificationInput.js +11 -22
  37. package/build/components/VerificationInput/VerificationInput.utils.d.ts +8 -0
  38. package/build/components/VerificationInput/VerificationInput.utils.js +17 -0
  39. package/build/components/VerificationInput/VerificationInput.utils.test.d.ts +1 -0
  40. package/build/components/VerificationInput/VerificationInput.utils.test.js +36 -0
  41. package/docs/changelog.mdx +113 -0
  42. package/package.json +5 -4
  43. package/src/components/Button/Button.stories.tsx +43 -7
  44. package/src/components/Button/ButtonRoot.tsx +8 -0
  45. package/src/components/Carousel/Carousel.tsx +6 -2
  46. package/src/components/IconContainer/IconContainer.stories.tsx +35 -30
  47. package/src/components/List/List.tsx +5 -4
  48. package/src/components/Modal/Modal.tsx +31 -16
  49. package/src/components/SegmentedControl/SegmentedControl.tsx +4 -1
  50. package/src/components/SegmentedControl/SegmentedControlOption.tsx +5 -4
  51. package/src/components/TimePicker/TimePickerWheel.tsx +11 -4
  52. package/src/components/Toast/Toast.context.tsx +1 -1
  53. package/src/components/VerificationInput/VerificationInput.stories.tsx +33 -0
  54. package/src/components/VerificationInput/VerificationInput.tsx +18 -29
  55. package/src/components/VerificationInput/VerificationInput.utils.test.ts +48 -0
  56. package/src/components/VerificationInput/VerificationInput.utils.ts +32 -0
  57. package/tsconfig.eslint.json +2 -1
  58. package/vitest.config.js +11 -13
  59. package/vitest.unit.config.ts +9 -0
  60. package/.turbo/turbo-lint.log +0 -72
@@ -1,7 +1,39 @@
1
- import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
2
1
  import { setProjectAnnotations } from '@storybook/react-native-web-vite';
3
- import * as projectAnnotations from './preview';
2
+ import { vi } from 'vitest';
3
+
4
+ // react-native-unistyles/mocks relies on Jest globals.
5
+ if (!(globalThis as { jest?: unknown }).jest) {
6
+ (globalThis as any).jest = vi;
7
+ }
8
+
9
+ await import('react-native-unistyles/mocks');
10
+ const { StyleSheet } = await import('react-native-unistyles');
11
+ const { breakpoints } = await import('../src/core/breakpoints');
12
+ const { themes } = await import('../src/core/themes');
13
+
14
+ vi.mock('../src/core', async () => {
15
+ const unistyles = await import('react-native-unistyles');
16
+
17
+ return {
18
+ breakpoints,
19
+ themes,
20
+ StyleSheet: unistyles.StyleSheet,
21
+ UnistylesRuntime: unistyles.UnistylesRuntime,
22
+ };
23
+ });
24
+
25
+ StyleSheet.configure({
26
+ breakpoints,
27
+ themes,
28
+ settings: {
29
+ initialTheme: 'light',
30
+ adaptiveThemes: false,
31
+ },
32
+ });
33
+
34
+ const a11yAddonAnnotations = await import('@storybook/addon-a11y/preview');
35
+ const projectAnnotations = await import('./preview');
4
36
 
5
37
  // This is an important step to apply the right configuration when testing your stories.
6
38
  // More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
7
- setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
39
+ setProjectAnnotations([a11yAddonAnnotations as any, projectAnnotations as any]);
@@ -1,4 +1,5 @@
1
-
2
- > @utilitywarehouse/hearth-react-native@0.27.1 build /home/runner/work/hearth/hearth/packages/react-native
3
- > tsc
4
-
1
+
2
+ 
3
+ > @utilitywarehouse/hearth-react-native@0.27.2-testid-fix-1 build /Users/filmondaniels/Projects/Work/hearth/packages/react-native
4
+ > tsc
5
+
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.27.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1003](https://github.com/utilitywarehouse/hearth/pull/1003) [`cdb95ea`](https://github.com/utilitywarehouse/hearth/commit/cdb95eabb279adaf348487ae3fb4a20e600e039e) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Correct `VerificationInput` focus progression after editing an empty slot
8
+
9
+ Fixed an issue where entering a value after selecting an empty verification slot could move focus to the wrong slot. Focus now moves to the slot immediately after the one that was actually updated.
10
+
11
+ **Components affected**:
12
+ - `VerificationInput`
13
+
14
+ **Developer changes**:
15
+
16
+ No changes required.
17
+
3
18
  ## 0.27.1
4
19
 
5
20
  ### Patch Changes
@@ -108,6 +108,14 @@ const styles = StyleSheet.create(theme => ({
108
108
  paddingHorizontal: 0,
109
109
  },
110
110
  },
111
+ {
112
+ size: 'md',
113
+ paddingNone: true,
114
+ variant: 'ghost',
115
+ styles: {
116
+ paddingHorizontal: 0,
117
+ },
118
+ },
111
119
  // Variant Color Schemes
112
120
  // Emphasis
113
121
  // Emphasis Yellow
@@ -213,7 +213,12 @@ const Carousel = ({ centered = false, children, disabled = false, inactiveItemOp
213
213
  const controls = (_jsx(CarouselControls, { style: [styles.controls, controlsStyle], itemStyle: controlsItemStyle, activeItemStyle: controlsActiveItemStyle, showNavigation: showNavigation, accessibilityHidden: controlsAccessibilityHidden }));
214
214
  // Render for web using ScrollView with scroll snap
215
215
  if (isWeb) {
216
- return (_jsx(CarouselContext.Provider, { value: context, children: _jsxs(View, { style: style, children: [_jsx(ScrollView, { horizontal: true, onScroll: handleWebScroll, onMomentumScrollEnd: handleWebScrollEnd, onScrollEndDrag: handleWebScrollEnd, ref: scrollViewRef, scrollEnabled: !disabled, pointerEvents: disabled ? 'none' : 'auto', scrollEventThrottle: 16, showsHorizontalScrollIndicator: false, snapToInterval: itemWidth || width, snapToAlignment: centered ? 'center' : 'start', decelerationRate: "fast", style: [styles.webContainer, webContainerStyles, itemsStyle], contentContainerStyle: [styles.webContentContainer, webContentContainerStyle], ...props, children: carouselItems.map((item, index) => cloneElement(item, {
216
+ return (_jsx(CarouselContext.Provider, { value: context, children: _jsxs(View, { style: style, children: [_jsx(ScrollView, { horizontal: true, onScroll: handleWebScroll, onMomentumScrollEnd: handleWebScrollEnd, onScrollEndDrag: handleWebScrollEnd, ref: scrollViewRef, scrollEnabled: !disabled, scrollEventThrottle: 16, showsHorizontalScrollIndicator: false, snapToInterval: itemWidth || width, snapToAlignment: centered ? 'center' : 'start', decelerationRate: "fast", style: [
217
+ styles.webContainer,
218
+ webContainerStyles,
219
+ itemsStyle,
220
+ { pointerEvents: disabled ? 'none' : 'auto' },
221
+ ], contentContainerStyle: [styles.webContentContainer, webContentContainerStyle], ...props, children: carouselItems.map((item, index) => cloneElement(item, {
217
222
  active: index === activeIndex,
218
223
  inactiveOpacity: inactiveItemOpacity,
219
224
  key: item?.key || item.props?.id || index,
@@ -0,0 +1,3 @@
1
+ export type Period = 'AM' | 'PM';
2
+ declare const TimePicker: () => import("react/jsx-runtime").JSX.Element;
3
+ export default TimePicker;
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import dayjs from 'dayjs';
3
+ import { useCallback, useMemo } from 'react';
4
+ import { ScrollView, View } from 'react-native';
5
+ import { StyleSheet } from 'react-native-unistyles';
6
+ import { BodyText } from '../BodyText';
7
+ import { useDatePickerContext } from './DatePicker.context';
8
+ import { CONTAINER_HEIGHT } from './enums';
9
+ import PeriodPicker from './time-picker/period-picker';
10
+ import Wheel from './time-picker/wheel';
11
+ import { formatNumber, getParsedDate } from './utils';
12
+ const createNumberList = (num, numerals, startFrom = 0) => {
13
+ return Array.from({ length: num }, (_, i) => ({
14
+ value: i + startFrom,
15
+ text: i + startFrom < 10
16
+ ? `${formatNumber(0, numerals)}${formatNumber(i + startFrom, numerals)}`
17
+ : `${formatNumber(i + startFrom, numerals)}`,
18
+ }));
19
+ };
20
+ const TimePicker = () => {
21
+ const { currentDate, date, onSelectDate, timeZone, numerals = 'latn', use12Hours, } = useDatePickerContext();
22
+ const hours = useMemo(() => createNumberList(use12Hours ? 12 : 24, numerals, use12Hours ? 1 : 0), [numerals, use12Hours]);
23
+ const minutes = useMemo(() => createNumberList(60, numerals), [numerals]);
24
+ const { hour, hour12, minute, period } = getParsedDate(date || currentDate);
25
+ const handleChangeHour = useCallback((value) => {
26
+ let hour24 = value;
27
+ if (use12Hours) {
28
+ if (period === 'AM' && value === 12) {
29
+ hour24 = 0;
30
+ }
31
+ else if (period === 'PM' && value < 12) {
32
+ hour24 = value + 12;
33
+ }
34
+ else {
35
+ hour24 = value;
36
+ }
37
+ }
38
+ const newDate = dayjs.tz(date, timeZone).hour(hour24).minute(minute);
39
+ onSelectDate(newDate);
40
+ }, [date, onSelectDate, timeZone, use12Hours, period, minute]);
41
+ const handleChangeMinute = useCallback((value) => {
42
+ const newDate = dayjs.tz(date, timeZone).minute(value);
43
+ onSelectDate(newDate);
44
+ }, [date, onSelectDate, timeZone]);
45
+ const handlePeriodChange = useCallback((newPeriod) => {
46
+ let newHour = hour12;
47
+ if (newPeriod === 'PM' && hour12 < 12) {
48
+ newHour = hour12 + 12;
49
+ }
50
+ else if (newPeriod === 'AM' && hour12 === 12) {
51
+ newHour = 0;
52
+ }
53
+ else if (newPeriod === 'AM' && hour >= 12) {
54
+ newHour = hour12;
55
+ }
56
+ const newDate = dayjs.tz(date || currentDate, timeZone).hour(newHour);
57
+ onSelectDate(newDate);
58
+ }, [date, currentDate, onSelectDate, timeZone, hour, hour12]);
59
+ return (_jsxs(ScrollView, { horizontal: true, scrollEnabled: false, contentContainerStyle: styles.container, testID: "time-selector", children: [_jsxs(View, { style: styles.timePickerContainer, children: [_jsx(View, { style: styles.wheelContainer, children: _jsx(Wheel, { value: use12Hours ? hour12 : hour, items: hours, setValue: handleChangeHour }) }), _jsx(BodyText, { style: styles.timeSeparator, children: ":" }), _jsx(View, { style: styles.wheelContainer, children: _jsx(Wheel, { value: minute, items: minutes, setValue: handleChangeMinute }) })] }), use12Hours && period ? (_jsx(View, { style: styles.periodContainer, children: _jsx(PeriodPicker, { value: period, setValue: handlePeriodChange }) })) : null] }));
60
+ };
61
+ const styles = StyleSheet.create({
62
+ container: {
63
+ flex: 1,
64
+ alignItems: 'center',
65
+ justifyContent: 'center',
66
+ },
67
+ wheelContainer: {
68
+ flex: 1,
69
+ },
70
+ timePickerContainer: {
71
+ alignItems: 'center',
72
+ justifyContent: 'center',
73
+ width: CONTAINER_HEIGHT / 2,
74
+ height: CONTAINER_HEIGHT / 2,
75
+ flexDirection: 'row',
76
+ },
77
+ timeSeparator: {
78
+ marginHorizontal: 5,
79
+ },
80
+ periodContainer: {
81
+ marginLeft: 10,
82
+ },
83
+ });
84
+ export default TimePicker;
@@ -0,0 +1,4 @@
1
+ import { Animated } from 'react-native';
2
+ declare function sin(animated: Animated.Animated): Animated.AnimatedAddition<string | number>;
3
+ declare function normalize(animated: Animated.Animated): Animated.Animated;
4
+ export { sin, normalize };
@@ -0,0 +1,19 @@
1
+ import { Animated } from 'react-native';
2
+ const FACTORIAL_3 = 3 * 2;
3
+ const FACTORIAL_5 = 5 * 4 * FACTORIAL_3;
4
+ const FACTORIAL_7 = 7 * 6 * FACTORIAL_5;
5
+ function sin(animated) {
6
+ const normalized = normalize(animated);
7
+ const square = Animated.multiply(normalized, normalized);
8
+ const pow3 = Animated.multiply(normalized, square);
9
+ const pow5 = Animated.multiply(pow3, square);
10
+ const pow7 = Animated.multiply(pow5, square);
11
+ return Animated.add(Animated.add(normalized, Animated.multiply(pow3, -1 / FACTORIAL_3)), Animated.add(Animated.multiply(pow5, 1 / FACTORIAL_5), Animated.multiply(pow7, -1 / FACTORIAL_7)));
12
+ }
13
+ function normalize(animated) {
14
+ return Animated.add(Animated.modulo(Animated.add(animated, Math.PI), Math.PI * 2), -Math.PI).interpolate({
15
+ inputRange: [-Math.PI, -Math.PI / 2, Math.PI / 2, Math.PI],
16
+ outputRange: [0, -Math.PI / 2, Math.PI / 2, 0],
17
+ });
18
+ }
19
+ export { sin, normalize };
@@ -0,0 +1,6 @@
1
+ interface PeriodProps {
2
+ value: string;
3
+ setValue?: (value: any) => void;
4
+ }
5
+ declare const _default: import("react").MemoExoticComponent<({ value, setValue }: PeriodProps) => import("react/jsx-runtime").JSX.Element>;
6
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import WheelPicker from './wheel-picker';
4
+ const options = [
5
+ { value: 'AM', text: 'AM' },
6
+ { value: 'PM', text: 'PM' },
7
+ ];
8
+ const PeriodNative = ({ value, setValue = () => { } }) => {
9
+ return (_jsx(WheelPicker, { value: value, options: options, onChange: setValue,
10
+ //containerStyle={defaultStyles.container}
11
+ itemHeight: 44, decelerationRate: "fast" }));
12
+ };
13
+ const customComparator = (prev, next) => {
14
+ const areEqual = prev.value === next.value && prev.setValue === next.setValue;
15
+ return areEqual;
16
+ };
17
+ export default memo(PeriodNative, customComparator);
@@ -0,0 +1,6 @@
1
+ type PeriodProps = {
2
+ value: string;
3
+ setValue?: (value: any) => void;
4
+ };
5
+ declare const _default: import("react").MemoExoticComponent<(props: PeriodProps) => import("react/jsx-runtime").JSX.Element>;
6
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import { Platform } from 'react-native';
4
+ import PeriodNative from './period-native';
5
+ import PeriodWeb from './period-web';
6
+ const PeriodPicker = (props) => {
7
+ const Component = Platform.OS === 'web' ? PeriodWeb : PeriodNative;
8
+ return _jsx(Component, { ...props });
9
+ };
10
+ export default memo(PeriodPicker);
@@ -0,0 +1,6 @@
1
+ interface PeriodProps {
2
+ value: string;
3
+ setValue?: (value: any) => void;
4
+ }
5
+ declare const _default: import("react").MemoExoticComponent<({ value, setValue }: PeriodProps) => import("react/jsx-runtime").JSX.Element>;
6
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import { Pressable, View } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { BodyText } from '../../BodyText';
6
+ const PeriodWeb = ({ value, setValue = () => { } }) => {
7
+ return (_jsx(Pressable, { onPress: () => setValue(value == 'AM' ? 'PM' : 'AM'), children: _jsx(View, { style: [styles.period], children: _jsx(BodyText, { children: value }) }) }));
8
+ };
9
+ const styles = StyleSheet.create({
10
+ period: {
11
+ width: 65,
12
+ height: 44,
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ },
16
+ });
17
+ const customComparator = (prev, next) => {
18
+ const areEqual = prev.value === next.value && prev.setValue === next.setValue;
19
+ return areEqual;
20
+ };
21
+ export default memo(PeriodWeb, customComparator);
@@ -0,0 +1,8 @@
1
+ import { PickerOption } from '../DatePicker.props';
2
+ interface WheelProps {
3
+ value: number | string;
4
+ setValue?: (value: any) => void;
5
+ items: PickerOption[];
6
+ }
7
+ declare const _default: import("react").MemoExoticComponent<({ value, setValue, items }: WheelProps) => import("react/jsx-runtime").JSX.Element>;
8
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import { Platform } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import WheelPicker from './wheel-picker';
6
+ const WheelNative = ({ value, setValue = () => { }, items }) => {
7
+ return (_jsx(WheelPicker, { value: value, options: items, onChange: setValue, containerStyle: styles.container, itemHeight: 44, decelerationRate: "fast" }));
8
+ };
9
+ const styles = StyleSheet.create({
10
+ container: {
11
+ display: 'flex',
12
+ ...Platform.select({
13
+ web: {
14
+ userSelect: 'none',
15
+ },
16
+ }),
17
+ },
18
+ });
19
+ export default memo(WheelNative);
@@ -0,0 +1,2 @@
1
+ import WheelPicker from './wheel-picker';
2
+ export default WheelPicker;
@@ -0,0 +1,2 @@
1
+ import WheelPicker from './wheel-picker';
2
+ export default WheelPicker;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { Animated, StyleProp, ViewStyle } from 'react-native';
3
+ import { PickerOption } from '../../DatePicker.props';
4
+ interface ItemProps {
5
+ style: StyleProp<ViewStyle>;
6
+ option: PickerOption | null;
7
+ height: number;
8
+ index: number;
9
+ currentScrollIndex: Animated.AnimatedAddition<number>;
10
+ visibleRest: number;
11
+ rotationFunction: (x: number) => number;
12
+ opacityFunction: (x: number) => number;
13
+ scaleFunction: (x: number) => number;
14
+ }
15
+ declare const _default: React.NamedExoticComponent<ItemProps>;
16
+ export default _default;
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Animated } from 'react-native';
4
+ import { BodyText } from '../../../BodyText';
5
+ import styles from './wheel-picker.style';
6
+ const WheelPickerItem = ({ style, height, option, index, visibleRest, currentScrollIndex, opacityFunction, rotationFunction, scaleFunction, }) => {
7
+ const relativeScrollIndex = Animated.subtract(index, currentScrollIndex);
8
+ const translateY = relativeScrollIndex.interpolate({
9
+ inputRange: (() => {
10
+ const range = [0];
11
+ for (let i = 1; i <= visibleRest + 1; i++) {
12
+ range.unshift(-i);
13
+ range.push(i);
14
+ }
15
+ return range;
16
+ })(),
17
+ outputRange: (() => {
18
+ const range = [0];
19
+ for (let i = 1; i <= visibleRest + 1; i++) {
20
+ let y = (height / 2) * (1 - Math.sin(Math.PI / 2 - rotationFunction(i)));
21
+ for (let j = 1; j < i; j++) {
22
+ y += height * (1 - Math.sin(Math.PI / 2 - rotationFunction(j)));
23
+ }
24
+ range.unshift(y);
25
+ range.push(-y);
26
+ }
27
+ return range;
28
+ })(),
29
+ });
30
+ const opacity = relativeScrollIndex.interpolate({
31
+ inputRange: (() => {
32
+ const range = [0];
33
+ for (let i = 1; i <= visibleRest + 1; i++) {
34
+ range.unshift(-i);
35
+ range.push(i);
36
+ }
37
+ return range;
38
+ })(),
39
+ outputRange: (() => {
40
+ const range = [1];
41
+ for (let x = 1; x <= visibleRest + 1; x++) {
42
+ const y = opacityFunction(x);
43
+ range.unshift(y);
44
+ range.push(y);
45
+ }
46
+ return range;
47
+ })(),
48
+ });
49
+ const scale = relativeScrollIndex.interpolate({
50
+ inputRange: (() => {
51
+ const range = [0];
52
+ for (let i = 1; i <= visibleRest + 1; i++) {
53
+ range.unshift(-i);
54
+ range.push(i);
55
+ }
56
+ return range;
57
+ })(),
58
+ outputRange: (() => {
59
+ const range = [1.0];
60
+ for (let x = 1; x <= visibleRest + 1; x++) {
61
+ const y = scaleFunction(x);
62
+ range.unshift(y);
63
+ range.push(y);
64
+ }
65
+ return range;
66
+ })(),
67
+ });
68
+ const rotateX = relativeScrollIndex.interpolate({
69
+ inputRange: (() => {
70
+ const range = [0];
71
+ for (let i = 1; i <= visibleRest + 1; i++) {
72
+ range.unshift(-i);
73
+ range.push(i);
74
+ }
75
+ return range;
76
+ })(),
77
+ outputRange: (() => {
78
+ const range = ['0deg'];
79
+ for (let x = 1; x <= visibleRest + 1; x++) {
80
+ const y = rotationFunction(x);
81
+ range.unshift(`${y}deg`);
82
+ range.push(`${y}deg`);
83
+ }
84
+ return range;
85
+ })(),
86
+ });
87
+ return (_jsx(Animated.View, { style: [
88
+ styles.option,
89
+ style,
90
+ {
91
+ height,
92
+ opacity,
93
+ transform: [{ translateY }, { rotateX }, { scale }],
94
+ },
95
+ ], children: _jsx(BodyText, { children: option?.text }) }));
96
+ };
97
+ export default React.memo(WheelPickerItem);
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { FlatListProps, StyleProp, ViewProps, ViewStyle } from 'react-native';
3
+ import { PickerOption } from '../../DatePicker.props';
4
+ interface Props {
5
+ value: number | string;
6
+ options: PickerOption[];
7
+ onChange: (index: number | string) => void;
8
+ selectedIndicatorStyle?: StyleProp<ViewStyle>;
9
+ itemStyle?: ViewStyle;
10
+ itemHeight?: number;
11
+ containerStyle?: ViewStyle;
12
+ containerProps?: Omit<ViewProps, 'style'>;
13
+ scaleFunction?: (x: number) => number;
14
+ rotationFunction?: (x: number) => number;
15
+ opacityFunction?: (x: number) => number;
16
+ visibleRest?: number;
17
+ decelerationRate?: 'normal' | 'fast' | number;
18
+ flatListProps?: Omit<FlatListProps<PickerOption | null>, 'data' | 'renderItem'>;
19
+ }
20
+ declare const _default: React.NamedExoticComponent<Props>;
21
+ export default _default;
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memo, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { Animated, Platform, View, } from 'react-native';
4
+ import WheelPickerItem from './wheel-picker-item';
5
+ import styles from './wheel-picker.style';
6
+ const WheelPicker = ({ value, options, onChange, selectedIndicatorStyle = {}, containerStyle = {}, itemStyle = {}, itemHeight = 40, scaleFunction = (x) => 1.0 ** x, rotationFunction = (x) => 1 - Math.pow(1 / 2, x), opacityFunction = (x) => Math.pow(1 / 3, x), visibleRest = 2, decelerationRate = 'normal', containerProps = {}, flatListProps = {}, }) => {
7
+ const momentumStarted = useRef(false);
8
+ const selectedIndex = options.findIndex(item => item.value === value);
9
+ const flatListRef = useRef(null);
10
+ const [scrollY] = useState(new Animated.Value(selectedIndex * itemHeight));
11
+ const containerHeight = (1 + visibleRest * 2) * itemHeight;
12
+ const paddedOptions = useMemo(() => {
13
+ const array = [...options];
14
+ for (let i = 0; i < visibleRest; i++) {
15
+ array.unshift(null);
16
+ array.push(null);
17
+ }
18
+ return array;
19
+ }, [options, visibleRest]);
20
+ const offsets = useMemo(() => [...Array(paddedOptions.length)].map((_, i) => i * itemHeight), [paddedOptions, itemHeight]);
21
+ const currentScrollIndex = useMemo(() => Animated.add(Animated.divide(scrollY, itemHeight), visibleRest), [visibleRest, scrollY, itemHeight]);
22
+ const handleScrollEnd = (event) => {
23
+ const offsetY = Math.min(itemHeight * (options.length - 1), Math.max(event.nativeEvent.contentOffset.y, 0));
24
+ let index = Math.floor(offsetY / itemHeight);
25
+ const remainder = offsetY % itemHeight;
26
+ if (remainder > itemHeight / 2) {
27
+ index++;
28
+ }
29
+ if (index !== selectedIndex) {
30
+ onChange(options[index]?.value || 0);
31
+ }
32
+ };
33
+ const handleMomentumScrollBegin = () => {
34
+ momentumStarted.current = true;
35
+ };
36
+ const handleMomentumScrollEnd = (event) => {
37
+ momentumStarted.current = false;
38
+ handleScrollEnd(event);
39
+ };
40
+ const handleScrollEndDrag = (event) => {
41
+ // Capture the offset value immediately
42
+ const offsetY = event.nativeEvent.contentOffset?.y;
43
+ // We'll start a short timer to see if momentum scroll begins
44
+ setTimeout(() => {
45
+ // If momentum scroll hasn't started within the timeout,
46
+ // then it was a slow scroll that won't trigger momentum
47
+ if (!momentumStarted.current && offsetY !== undefined) {
48
+ // Create a synthetic event with just the data we need
49
+ const syntheticEvent = {
50
+ nativeEvent: {
51
+ contentOffset: { y: offsetY },
52
+ },
53
+ };
54
+ handleScrollEnd(syntheticEvent);
55
+ }
56
+ }, 50);
57
+ };
58
+ useEffect(() => {
59
+ if (selectedIndex < 0 || selectedIndex >= options.length) {
60
+ throw new Error(`Selected index ${selectedIndex} is out of bounds [0, ${options.length - 1}]`);
61
+ }
62
+ }, [selectedIndex, options]);
63
+ /**
64
+ * If selectedIndex is changed from outside (not via onChange) we need to scroll to the specified index.
65
+ * This ensures that what the user sees as selected in the picker always corresponds to the value state.
66
+ */
67
+ useEffect(() => {
68
+ flatListRef.current?.scrollToIndex({
69
+ index: selectedIndex,
70
+ animated: Platform.OS === 'ios',
71
+ });
72
+ }, [selectedIndex, itemHeight]);
73
+ return (_jsxs(View, { style: [styles.container, { height: containerHeight }, containerStyle], ...containerProps, children: [_jsx(View, { style: [
74
+ styles.selectedIndicator,
75
+ selectedIndicatorStyle,
76
+ {
77
+ transform: [{ translateY: -itemHeight / 2 }],
78
+ height: itemHeight,
79
+ },
80
+ ] }), _jsx(Animated.FlatList, { ...flatListProps, ref: flatListRef, nestedScrollEnabled: true, style: styles.scrollView, showsVerticalScrollIndicator: false, onScroll: Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
81
+ useNativeDriver: true,
82
+ }), onScrollEndDrag: handleScrollEndDrag, onMomentumScrollBegin: handleMomentumScrollBegin, onMomentumScrollEnd: handleMomentumScrollEnd, snapToOffsets: offsets, decelerationRate: decelerationRate, initialScrollIndex: selectedIndex, getItemLayout: (_, index) => ({
83
+ length: itemHeight,
84
+ offset: itemHeight * index,
85
+ index,
86
+ }), data: paddedOptions, keyExtractor: (item, index) => item ? `${item.value}-${item.text}-${index}` : `null-${index}`, renderItem: ({ item: option, index }) => (_jsx(WheelPickerItem, { index: index, option: option, style: itemStyle, height: itemHeight, currentScrollIndex: currentScrollIndex, scaleFunction: scaleFunction, rotationFunction: rotationFunction, opacityFunction: opacityFunction, visibleRest: visibleRest }, `option-${index}`)) })] }));
87
+ };
88
+ export default memo(WheelPicker);
@@ -0,0 +1,23 @@
1
+ declare const _default: {
2
+ container: {
3
+ position: "relative";
4
+ };
5
+ selectedIndicator: {
6
+ position: "absolute";
7
+ width: "100%";
8
+ top: "50%";
9
+ };
10
+ scrollView: {
11
+ overflow: "hidden";
12
+ flex: number;
13
+ };
14
+ option: {
15
+ alignItems: "center";
16
+ justifyContent: "center";
17
+ paddingHorizontal: number;
18
+ zIndex: number;
19
+ };
20
+ } & {
21
+ useVariants: (variants: never) => void;
22
+ };
23
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ export default StyleSheet.create({
3
+ container: {
4
+ position: 'relative',
5
+ },
6
+ selectedIndicator: {
7
+ position: 'absolute',
8
+ width: '100%',
9
+ top: '50%',
10
+ },
11
+ scrollView: {
12
+ overflow: 'hidden',
13
+ flex: 1,
14
+ },
15
+ option: {
16
+ alignItems: 'center',
17
+ justifyContent: 'center',
18
+ paddingHorizontal: 16,
19
+ zIndex: 100,
20
+ },
21
+ });
@@ -0,0 +1,8 @@
1
+ import { PickerOption } from '../DatePicker.props';
2
+ interface WheelProps {
3
+ value: number | string;
4
+ setValue?: (value: any) => void;
5
+ items: PickerOption[];
6
+ }
7
+ declare const _default: import("react").MemoExoticComponent<({ value, setValue, items }: WheelProps) => import("react/jsx-runtime").JSX.Element>;
8
+ export default _default;