From 2a02f5f8dd8be1a065f5ab032d67a57788492477 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 4 May 2026 22:54:50 +0100 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Add=20separateAmPmPicker=20prop?= =?UTF-8?q?=20with=20hourLimit=20support=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When use12HourPicker is enabled, separateAmPmPicker renders AM/PM as a dedicated scrollable column after seconds instead of appending it to each hour. The hours column shows 12, 1, 2, …, 11 in clock order. Public API (onDurationChange, latestDuration, setValue) keeps using 0–23 hours. hourLimit is honoured in this mode: the hour column's rows grey based on the currently selected AM/PM, and momentum-scroll snaps to the nearest valid hour in that half. The AM/PM column is intentionally limit-free so users can always toggle halves to reach any valid hour. Wraparound limits (max < min) are handled the same as in the existing 12-hour mode. The TimerPickerModal width calculation now counts the AM/PM column when present, so default styles work without manual pickerColumnWidth tweaks. maximumHours remains ignored in separate mode (warns at runtime). --- README.md | 1 + examples/example-expo/App.tsx | 99 +++++++- .../DurationScroll/DurationScroll.tsx | 61 +++-- src/components/DurationScroll/types.ts | 4 + src/components/PickerItem/PickerItem.tsx | 56 ++++- src/components/TimerPicker/TimerPicker.tsx | 121 +++++++++- src/components/TimerPicker/types.ts | 5 + .../TimerPickerModal/TimerPickerModal.tsx | 6 +- src/tests/PickerItem.test.tsx | 213 +++++++++++++++++- src/tests/TimerPicker.test.tsx | 196 +++++++++++++++- src/tests/generateNumbers.test.ts | 98 +++++++- src/tests/snapSeparateAmPmHour.test.ts | 85 +++++++ src/utils/generateNumbers.ts | 61 +++++ src/utils/getNearestInRange.ts | 19 ++ src/utils/separateAmPmHour.ts | 17 ++ src/utils/snapSeparateAmPmHour.ts | 42 ++++ 16 files changed, 1043 insertions(+), 41 deletions(-) create mode 100644 src/tests/snapSeparateAmPmHour.test.ts create mode 100644 src/utils/getNearestInRange.ts create mode 100644 src/utils/separateAmPmHour.ts create mode 100644 src/utils/snapSeparateAmPmHour.ts diff --git a/README.md b/README.md index d46a364..f672984 100644 --- a/README.md +++ b/README.md @@ -566,6 +566,7 @@ return ( | aggressivelyGetLatestDuration | Set to True to ask DurationScroll to aggressively update the latestDuration ref | Boolean | false | false | | allowFontScaling | Allow font in the picker to scale with accessibility settings | Boolean | false | false | | use12HourPicker | Switch the hour picker to 12-hour format with an AM / PM label | Boolean | false | false | +| separateAmPmPicker | When `use12HourPicker` is true, render AM/PM as a dedicated scrollable column after seconds (instead of appending it to each hour). Hours are emitted via `onDurationChange` as a 0–23 value as usual. `hourLimit` is honoured: rows in the **hour** column grey/snap based on the currently selected AM/PM. The AM/PM column itself is always freely toggleable so users can switch halves to reach any valid hour. Note: `maximumHours` is currently ignored in this mode — the column always shows the full 12-hour cycle. | Boolean | false | false | | amLabel | Set the AM label if using the 12-hour picker | String | am | false | | pmLabel | Set the PM label if using the 12-hour picker | String | pm | false | | repeatDayNumbersNTimes | Set the number of times the list of days is repeated in the picker | Number | 3 | false | diff --git a/examples/example-expo/App.tsx b/examples/example-expo/App.tsx index 84a3acc..798b7fd 100644 --- a/examples/example-expo/App.tsx +++ b/examples/example-expo/App.tsx @@ -31,10 +31,13 @@ export default function App() { const [showPickerExample1, setShowPickerExample1] = useState(false); const [showPickerExample2, setShowPickerExample2] = useState(false); const [showPickerExample3, setShowPickerExample3] = useState(false); + const [showPickerSeparateAmPm, setShowPickerSeparateAmPm] = useState(false); const [alarmStringExample1, setAlarmStringExample1] = useState(null); const [alarmStringExample2, setAlarmStringExample2] = useState(null); const [alarmStringExample3, setAlarmStringExample3] = useState("00:00:00"); + const [alarmStringSeparateAmPm, setAlarmStringSeparateAmPm] = useState(null); const [hourLimitTestValue, setHourLimitTestValue] = useState(20); + const [hourLimitSeparateTestValue, setHourLimitSeparateTestValue] = useState(20); // N.B. Uncomment this to use audio (requires development build) // useEffect(() => { @@ -170,6 +173,49 @@ export default function App() { ); + const renderExampleSeparateAmPm = ( + + + {alarmStringSeparateAmPm !== null ? "Alarm set for" : "No alarm set"} + + setShowPickerSeparateAmPm(true)}> + + {alarmStringSeparateAmPm !== null ? ( + {alarmStringSeparateAmPm} + ) : null} + setShowPickerSeparateAmPm(true)}> + + {"Set Alarm 🔔"} + + + + + {"use12HourPicker + separateAmPmPicker"} + setShowPickerSeparateAmPm(false)} + onConfirm={(pickedDuration) => { + setAlarmStringSeparateAmPm(formatTime(pickedDuration)); + setShowPickerSeparateAmPm(false); + }} + padHoursWithZero={false} + pickerFeedback={pickerFeedback} + pmLabel="PM" + separateAmPmPicker + setIsVisible={setShowPickerSeparateAmPm} + styles={{ theme: "light" }} + use12HourPicker + visible={showPickerSeparateAmPm} + /> + + ); + const renderExample3 = ( @@ -313,9 +359,48 @@ export default function App() { ); - const pageIndicesWithDarkBackground = [0, 3, 5]; + const renderHourLimitSeparateTest = ( + + + Cross-midnight hourLimit + separate AM/PM + + + {"use12HourPicker + separateAmPmPicker\nhourLimit={ min: 20, max: 5 } (8 PM – 5 AM)"} + + + { + "Hour rows grey based on current AM/PM.\nAM/PM rows grey based on current hour.\nMomentum-scroll snaps within each column." + } + + + Live reported hour value: + {formatLiveValue(hourLimitSeparateTestValue)} + + setHourLimitSeparateTestValue(d.hours)} + padHoursWithZero={false} + padWithNItems={2} + pickerFeedback={pickerFeedback} + pmLabel="PM" + separateAmPmPicker + styles={{ + pickerItem: { fontSize: 28 }, + pickerLabel: { fontSize: 22 }, + theme: "dark", + }} + use12HourPicker + /> + + ); + + const pageIndicesWithDarkBackground = [0, 4, 6, 7]; const isDarkBackground = pageIndicesWithDarkBackground.includes(currentPageIndex); - const isFinalPage = currentPageIndex === 5; + const isFinalPage = currentPageIndex === 7; const isFirstPage = currentPageIndex === 0; const renderNavigationArrows = ( @@ -384,10 +469,12 @@ export default function App() { > {renderExample1} {renderExample2} + {renderExampleSeparateAmPm} {renderExample3} {renderExample4} {renderExample5} {renderHourLimitTest} + {renderHourLimitSeparateTest} {renderNavigationArrows} @@ -419,6 +506,11 @@ const styles = StyleSheet.create({ color: "#C2C2C2", }, buttonLight: { borderColor: "#8C8C8C", color: "#8C8C8C" }, + captionLight: { + color: "#8C8C8C", + fontSize: 13, + marginTop: 24, + }, chevronPressable: { alignItems: "center", bottom: 0, @@ -468,6 +560,9 @@ const styles = StyleSheet.create({ backgroundColor: "#202020", paddingHorizontal: 20, }, + pageSeparateAmPmContainer: { + backgroundColor: "#F1F1F1", + }, root: { flex: 1, }, diff --git a/src/components/DurationScroll/DurationScroll.tsx b/src/components/DurationScroll/DurationScroll.tsx index e89775d..655d182 100644 --- a/src/components/DurationScroll/DurationScroll.tsx +++ b/src/components/DurationScroll/DurationScroll.tsx @@ -12,11 +12,16 @@ import { View, Text, FlatList as RNFlatList } from "react-native"; import type { ViewabilityConfigCallbackPairs, FlatListProps } from "react-native"; import { colorToRgba } from "../../utils/colorToRgba"; -import { generate12HourNumbers, generateNumbers } from "../../utils/generateNumbers"; +import { + generate12HourCycleNumbers, + generate12HourNumbers, + generateAmPmItems, + generateNumbers, +} from "../../utils/generateNumbers"; import { getAdjustedLimit } from "../../utils/getAdjustedLimit"; import { getDurationAndIndexFromScrollOffset } from "../../utils/getDurationAndIndexFromScrollOffset"; import { getInitialScrollIndex } from "../../utils/getInitialScrollIndex"; -import { isWithinLimit } from "../../utils/isWithinLimit"; +import { getNearestInRange } from "../../utils/getNearestInRange"; import PickerItem from "../PickerItem"; import type { DurationScrollProps, DurationScrollRef, ExpoAvAudioInstance } from "./types"; @@ -30,6 +35,8 @@ const DurationScroll = forwardRef((props amLabel, Audio, clickSoundAsset, + combinedHourLimit, + currentAmPm, decelerationRate = 0.88, disableInfiniteScroll = false, FlatList = RNFlatList, @@ -37,6 +44,7 @@ const DurationScroll = forwardRef((props initialValue = 0, interval, is12HourPicker, + isAmPmPicker, isDisabled, label, limit, @@ -54,6 +62,7 @@ const DurationScroll = forwardRef((props repeatNumbersNTimes = 3, repeatNumbersNTimesNotExplicitlySet, selectedValue, + separateAmPmPicker, styles, testID, } = props; @@ -113,7 +122,27 @@ const DurationScroll = forwardRef((props ]); const numbersForFlatList = useMemo(() => { + if (isAmPmPicker) { + return generateAmPmItems({ + amLabel: amLabel ?? "am", + padWithNItems, + pmLabel: pmLabel ?? "pm", + }); + } + if (is12HourPicker) { + // When AM/PM is rendered as a separate column, the hour column shows + // 12, 1, 2, ..., 11 (no AM/PM suffix). + if (separateAmPmPicker) { + return generate12HourCycleNumbers({ + disableInfiniteScroll, + interval, + padNumbersWithZero, + padWithNItems, + repeatNTimes: safeRepeatNumbersNTimes, + }); + } + return generate12HourNumbers({ disableInfiniteScroll, interval, @@ -131,13 +160,17 @@ const DurationScroll = forwardRef((props repeatNTimes: safeRepeatNumbersNTimes, }); }, [ + amLabel, disableInfiniteScroll, is12HourPicker, + isAmPmPicker, interval, numberOfItems, padNumbersWithZero, padWithNItems, + pmLabel, safeRepeatNumbersNTimes, + separateAmPmPicker, ]); const initialScrollIndex = useMemo( @@ -229,11 +262,15 @@ const DurationScroll = forwardRef((props adjustedLimitedMin={adjustedLimited.min} allowFontScaling={allowFontScaling} amLabel={amLabel} + combinedHourLimit={combinedHourLimit} + currentAmPm={currentAmPm} is12HourPicker={is12HourPicker} + isAmPmPicker={isAmPmPicker} item={item} pickerAmPmPositionStyle={labelPositionStyle} pmLabel={pmLabel} selectedValue={selectedValue} + separateAmPmPicker={separateAmPmPicker} styles={styles} /> ), @@ -242,30 +279,20 @@ const DurationScroll = forwardRef((props adjustedLimited.min, allowFontScaling, amLabel, + combinedHourLimit, + currentAmPm, is12HourPicker, + isAmPmPicker, labelPositionStyle, pmLabel, selectedValue, + separateAmPmPicker, styles, ] ); - // returns the in-range value that's closest (in scroll distance) to `value`, - // honouring wraparound limits where max < min. const getNearestInRangeValue = useCallback( - (value: number) => { - const { max, min } = adjustedLimited; - if (isWithinLimit(value, min, max)) return value; - - if (max < min) { - // wraparound: `value` lies in the gap between max and min - const distanceForwardToMin = min - value; - const distanceBackwardToMax = value - max; - return distanceForwardToMin <= distanceBackwardToMax ? min : max; - } - - return value > max ? max : min; - }, + (value: number) => getNearestInRange(value, adjustedLimited.min, adjustedLimited.max), [adjustedLimited] ); diff --git a/src/components/DurationScroll/types.ts b/src/components/DurationScroll/types.ts index 0bd2030..9d53c5d 100644 --- a/src/components/DurationScroll/types.ts +++ b/src/components/DurationScroll/types.ts @@ -12,6 +12,8 @@ export interface DurationScrollProps { amLabel?: string; Audio?: any; clickSoundAsset?: SoundAsset; + combinedHourLimit?: Limit; + currentAmPm?: number; decelerationRate?: number | "normal" | "fast"; disableInfiniteScroll?: boolean; FlatList?: any; @@ -19,6 +21,7 @@ export interface DurationScrollProps { initialValue?: number; interval: number; is12HourPicker?: boolean; + isAmPmPicker?: boolean; isDisabled?: boolean; label?: string | React.ReactElement; limit?: Limit; @@ -36,6 +39,7 @@ export interface DurationScrollProps { repeatNumbersNTimes?: number; repeatNumbersNTimesNotExplicitlySet: boolean; selectedValue?: number; + separateAmPmPicker?: boolean; styles: ReturnType; testID?: string; } diff --git a/src/components/PickerItem/PickerItem.tsx b/src/components/PickerItem/PickerItem.tsx index 56fc9fb..647e75c 100644 --- a/src/components/PickerItem/PickerItem.tsx +++ b/src/components/PickerItem/PickerItem.tsx @@ -3,6 +3,8 @@ import React from "react"; import { View, Text } from "react-native"; import { isWithinLimit } from "../../utils/isWithinLimit"; +import { combineToHour24 } from "../../utils/separateAmPmHour"; +import type { Limit } from "../DurationScroll/types"; import type { generateStyles } from "../TimerPicker/styles"; interface PickerItemProps { @@ -10,34 +12,59 @@ interface PickerItemProps { adjustedLimitedMin: number; allowFontScaling: boolean; amLabel?: string; + combinedHourLimit?: Limit; + currentAmPm?: number; is12HourPicker?: boolean; + isAmPmPicker?: boolean; item: string; pickerAmPmPositionStyle?: { left: "50%"; marginLeft: number }; pmLabel?: string; selectedValue?: number; + separateAmPmPicker?: boolean; styles: ReturnType; } +const isCombinedHourInRange = (hour24: number, limit: Limit | undefined): boolean => { + if (!limit || (limit.min === undefined && limit.max === undefined)) return true; + return isWithinLimit(hour24, limit.min ?? 0, limit.max ?? 23); +}; + const PickerItem = React.memo( ({ adjustedLimitedMax, adjustedLimitedMin, allowFontScaling, amLabel, + combinedHourLimit, + currentAmPm, is12HourPicker, + isAmPmPicker, item, pickerAmPmPositionStyle, pmLabel, selectedValue, + separateAmPmPicker, styles, }) => { let stringItem = item; let intItem: number; let isAm: boolean | undefined; - if (!is12HourPicker) { - intItem = parseInt(item); - } else { + if (isAmPmPicker) { + // Compare to the amLabel/pmLabel passed in; padding rows are empty strings. + if (item === amLabel) { + intItem = 0; + } else if (item === pmLabel) { + intItem = 1; + } else { + intItem = NaN; + } + } else if (is12HourPicker && separateAmPmPicker) { + // Hour column in clock-face form (12, 1, 2, ..., 11). The "12" slot represents + // the noon/midnight cycle index 0; every other display value matches its index. + const parsed = parseInt(item); + intItem = isNaN(parsed) ? NaN : parsed === 12 ? 0 : parsed; + } else if (is12HourPicker) { isAm = item.includes("AM"); stringItem = item.replace(/\s[AP]M/g, ""); intItem = parseInt(stringItem); @@ -48,10 +75,27 @@ const PickerItem = React.memo( } else if (isAm && intItem === 12) { intItem = 0; } + } else { + intItem = parseInt(item); } - const isSelected = intItem === selectedValue; - const isDisabled = !isWithinLimit(intItem, adjustedLimitedMin, adjustedLimitedMax); + const isSelected = !isNaN(intItem) && intItem === selectedValue; + + let isDisabled: boolean; + if (isAmPmPicker) { + // The AM/PM column is always freely toggleable so users can navigate to a + // different half of the clock; the hour column handles all limit enforcement + // (greying + snap-back) once AM/PM has been chosen. + isDisabled = false; + } else if (is12HourPicker && separateAmPmPicker) { + // Hour cycle row folds into 24h with the current AM/PM. + isDisabled = + !isNaN(intItem) && + currentAmPm !== undefined && + !isCombinedHourInRange(combineToHour24(intItem, currentAmPm), combinedHourLimit); + } else { + isDisabled = !isWithinLimit(intItem, adjustedLimitedMin, adjustedLimitedMax); + } return ( @@ -65,7 +109,7 @@ const PickerItem = React.memo( > {stringItem} - {is12HourPicker && ( + {is12HourPicker && !separateAmPmPicker && ( {isAm ? amLabel : pmLabel} diff --git a/src/components/TimerPicker/TimerPicker.tsx b/src/components/TimerPicker/TimerPicker.tsx index 43af37c..a17d67d 100644 --- a/src/components/TimerPicker/TimerPicker.tsx +++ b/src/components/TimerPicker/TimerPicker.tsx @@ -10,6 +10,8 @@ import React, { import { View } from "react-native"; import { getSafeInitialValue } from "../../utils/getSafeInitialValue"; +import { combineToHour24, splitHour24 } from "../../utils/separateAmPmHour"; +import { findNearestValidCycleIdx } from "../../utils/snapSeparateAmPmHour"; import DurationScroll from "../DurationScroll"; import type { DurationScrollRef } from "../DurationScroll"; import { generateStyles } from "./styles"; @@ -75,11 +77,14 @@ const TimerPicker = forwardRef((props, ref) => secondLabel, secondLimit, secondsPickerIsDisabled = false, + separateAmPmPicker = false, styles: customStyles, use12HourPicker = false, ...otherProps } = props; + const useSeparateAmPm = use12HourPicker && separateAmPmPicker; + useEffect(() => { if (otherProps.Audio) { console.warn( @@ -107,12 +112,20 @@ const TimerPicker = forwardRef((props, ref) => ); } } + if (use12HourPicker && separateAmPmPicker && maximumHours !== 23) { + console.warn( + '"maximumHours" is currently ignored when "separateAmPmPicker" is enabled. The hours column always shows the full 12-hour clock cycle (12, 1–11).' + ); + } }, [ otherProps.Audio, otherProps.Haptics, otherProps.clickSoundAsset, customStyles?.labelOffsetPercentage, customStyles?.pickerLabelGap, + maximumHours, + separateAmPmPicker, + use12HourPicker, ]); const safePadWithNItems = useMemo(() => { @@ -149,40 +162,91 @@ const TimerPicker = forwardRef((props, ref) => [customStyles] ); + const initialHourSplit = useMemo( + () => splitHour24(safeInitialValue.hours), + [safeInitialValue.hours] + ); + const [selectedDays, setSelectedDays] = useState(safeInitialValue.days); - const [selectedHours, setSelectedHours] = useState(safeInitialValue.hours); + const [selectedHours, setSelectedHours] = useState( + useSeparateAmPm ? initialHourSplit.cycleIdx : safeInitialValue.hours + ); + const [selectedAmPm, setSelectedAmPm] = useState(initialHourSplit.amPm); const [selectedMinutes, setSelectedMinutes] = useState(safeInitialValue.minutes); const [selectedSeconds, setSelectedSeconds] = useState(safeInitialValue.seconds); + // Mirror selectedAmPm into a ref so the hour-cycle snap handler always reads the + // freshest AM/PM context without forcing a callback re-creation per state change. + const selectedAmPmRef = useRef(selectedAmPm); useEffect(() => { + selectedAmPmRef.current = selectedAmPm; + }, [selectedAmPm]); + + useEffect(() => { + const hours = useSeparateAmPm ? combineToHour24(selectedHours, selectedAmPm) : selectedHours; onDurationChange?.({ days: selectedDays, - hours: selectedHours, + hours, minutes: selectedMinutes, seconds: selectedSeconds, }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedDays, selectedHours, selectedMinutes, selectedSeconds]); + }, [selectedDays, selectedHours, selectedAmPm, selectedMinutes, selectedSeconds]); const daysDurationScrollRef = useRef(null); const hoursDurationScrollRef = useRef(null); + const amPmDurationScrollRef = useRef(null); const minutesDurationScrollRef = useRef(null); const secondsDurationScrollRef = useRef(null); + // Snap-aware hour-cycle change handler (separate AM/PM mode). When the user lands on + // a cycleIdx whose combined 24h value falls outside hourLimit, snap to the nearest + // valid cycleIdx given the current AM/PM. If no value in the column's range is valid, + // do not snap. The AM/PM column is intentionally limit-free so users can always + // toggle halves to reach any valid hour. + const handleHourCycleChange = (rawCycleIdx: number) => { + const snapped = findNearestValidCycleIdx(rawCycleIdx, selectedAmPmRef.current, hourLimit); + setSelectedHours(snapped); + if (snapped !== rawCycleIdx) { + hoursDurationScrollRef.current?.setValue(snapped, { animated: true }); + } + }; + + // In separateAmPmPicker mode the public `latestDuration.hours` must combine the cycle + // index and the AM/PM flag back into a 24-hour value. + const combinedHoursLatestDuration = useMemo( + () => ({ + get current() { + const cycleIdx = hoursDurationScrollRef.current?.latestDuration.current ?? 0; + const amPm = amPmDurationScrollRef.current?.latestDuration.current ?? 0; + return combineToHour24(cycleIdx, amPm); + }, + }), + [] + ); + useImperativeHandle(ref, () => ({ latestDuration: { days: daysDurationScrollRef.current?.latestDuration, - hours: hoursDurationScrollRef.current?.latestDuration, + hours: useSeparateAmPm + ? (combinedHoursLatestDuration as { current: number }) + : hoursDurationScrollRef.current?.latestDuration, minutes: minutesDurationScrollRef.current?.latestDuration, seconds: secondsDurationScrollRef.current?.latestDuration, }, reset: (options) => { setSelectedDays(safeInitialValue.days); - setSelectedHours(safeInitialValue.hours); + if (useSeparateAmPm) { + setSelectedHours(initialHourSplit.cycleIdx); + setSelectedAmPm(initialHourSplit.amPm); + } else { + setSelectedHours(safeInitialValue.hours); + } setSelectedMinutes(safeInitialValue.minutes); setSelectedSeconds(safeInitialValue.seconds); daysDurationScrollRef.current?.reset(options); hoursDurationScrollRef.current?.reset(options); + amPmDurationScrollRef.current?.reset(options); minutesDurationScrollRef.current?.reset(options); secondsDurationScrollRef.current?.reset(options); }, @@ -192,8 +256,16 @@ const TimerPicker = forwardRef((props, ref) => daysDurationScrollRef.current?.setValue(value.days, options); } if (value.hours !== undefined) { - setSelectedHours(value.hours); - hoursDurationScrollRef.current?.setValue(value.hours, options); + if (useSeparateAmPm) { + const split = splitHour24(value.hours); + setSelectedHours(split.cycleIdx); + setSelectedAmPm(split.amPm); + hoursDurationScrollRef.current?.setValue(split.cycleIdx, options); + amPmDurationScrollRef.current?.setValue(split.amPm, options); + } else { + setSelectedHours(value.hours); + hoursDurationScrollRef.current?.setValue(value.hours, options); + } } if (value.minutes !== undefined) { setSelectedMinutes(value.minutes); @@ -239,16 +311,18 @@ const TimerPicker = forwardRef((props, ref) => aggressivelyGetLatestDuration={aggressivelyGetLatestDuration} allowFontScaling={allowFontScaling} amLabel={amLabel} + combinedHourLimit={useSeparateAmPm ? hourLimit : undefined} + currentAmPm={useSeparateAmPm ? selectedAmPm : undefined} decelerationRate={decelerationRate} disableInfiniteScroll={disableInfiniteScroll} - initialValue={safeInitialValue.hours} + initialValue={useSeparateAmPm ? initialHourSplit.cycleIdx : safeInitialValue.hours} interval={hourInterval} is12HourPicker={use12HourPicker} isDisabled={hoursPickerIsDisabled} label={hourLabel ?? (!use12HourPicker ? "h" : undefined)} - limit={hourLimit} - maximumValue={maximumHours} - onDurationChange={setSelectedHours} + limit={useSeparateAmPm ? undefined : hourLimit} + maximumValue={useSeparateAmPm ? 11 : maximumHours} + onDurationChange={useSeparateAmPm ? handleHourCycleChange : setSelectedHours} padNumbersWithZero={padHoursWithZero} padWithNItems={safePadWithNItems} pickerColumnWidth={resolvePerColumn(pickerColumnWidth, "hours")} @@ -257,6 +331,7 @@ const TimerPicker = forwardRef((props, ref) => repeatNumbersNTimes={repeatHourNumbersNTimes} repeatNumbersNTimesNotExplicitlySet={props?.repeatHourNumbersNTimes === undefined} selectedValue={selectedHours} + separateAmPmPicker={separateAmPmPicker} styles={styles} testID="duration-scroll-hour" {...otherProps} @@ -314,6 +389,30 @@ const TimerPicker = forwardRef((props, ref) => {...otherProps} /> ) : null} + {useSeparateAmPm ? ( + + ) : null} ); }); diff --git a/src/components/TimerPicker/types.ts b/src/components/TimerPicker/types.ts index 8144ada..488af61 100644 --- a/src/components/TimerPicker/types.ts +++ b/src/components/TimerPicker/types.ts @@ -94,6 +94,11 @@ export interface TimerPickerProps { secondLabel?: string | React.ReactElement; secondLimit?: Limit; secondsPickerIsDisabled?: boolean; + /** + * Render AM/PM as a separate scrollable column instead of appending it to each hour. + * Only takes effect when `use12HourPicker` is true. Defaults to `false`. + */ + separateAmPmPicker?: boolean; styles?: CustomTimerPickerStyles; use12HourPicker?: boolean; } diff --git a/src/components/TimerPickerModal/TimerPickerModal.tsx b/src/components/TimerPickerModal/TimerPickerModal.tsx index 022568e..20e42e2 100644 --- a/src/components/TimerPickerModal/TimerPickerModal.tsx +++ b/src/components/TimerPickerModal/TimerPickerModal.tsx @@ -60,12 +60,16 @@ const TimerPickerModal = forwardRef( } = props; const pickerColumnWidth = customStyles?.pickerColumnWidth; + const hasSeparateAmPmColumn = Boolean( + otherProps.use12HourPicker && otherProps.separateAmPmPicker && !hideHours + ); const totalColumnWidth = (!hideDays ? resolveColumnWidth(pickerColumnWidth, "days") : 0) + (!hideHours ? resolveColumnWidth(pickerColumnWidth, "hours") : 0) + (!hideMinutes ? resolveColumnWidth(pickerColumnWidth, "minutes") : 0) + - (!hideSeconds ? resolveColumnWidth(pickerColumnWidth, "seconds") : 0); + (!hideSeconds ? resolveColumnWidth(pickerColumnWidth, "seconds") : 0) + + (hasSeparateAmPmColumn ? DEFAULT_COLUMN_WIDTH : 0); const styles = generateStyles(customStyles, { hasModalTitle: Boolean(modalTitle), diff --git a/src/tests/PickerItem.test.tsx b/src/tests/PickerItem.test.tsx index 44e0e9c..fbc7790 100644 --- a/src/tests/PickerItem.test.tsx +++ b/src/tests/PickerItem.test.tsx @@ -3,6 +3,7 @@ import React from "react"; import { render } from "@testing-library/react-native"; import type { ReactTestInstance } from "react-test-renderer"; +import type { Limit } from "../components/DurationScroll/types"; import PickerItem from "../components/PickerItem"; import type { generateStyles } from "../components/TimerPicker/styles"; @@ -23,20 +24,30 @@ const styles = { const renderItem = (props: { adjustedLimitedMax: number; adjustedLimitedMin: number; + amLabel?: string; + combinedHourLimit?: Limit; + currentAmPm?: number; is12HourPicker?: boolean; + isAmPmPicker?: boolean; item: string; + pmLabel?: string; selectedValue?: number; + separateAmPmPicker?: boolean; }) => render( ); @@ -174,4 +185,202 @@ describe("PickerItem", () => { expect(isDisabledStyle(getByText("20"))).toBe(false); }); }); + + describe("12-hour cycle picker (separate AM/PM)", () => { + it("marks '12' as selected when selectedValue is 0", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + is12HourPicker: true, + item: "12", + selectedValue: 0, + separateAmPmPicker: true, + }); + expect(isSelectedStyle(getByText("12"))).toBe(true); + }); + + it("does not mark '12' as selected when selectedValue is 12", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + is12HourPicker: true, + item: "12", + selectedValue: 12, + separateAmPmPicker: true, + }); + expect(isSelectedStyle(getByText("12"))).toBe(false); + }); + + it("marks '03' as selected when selectedValue is 3", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + is12HourPicker: true, + item: "03", + selectedValue: 3, + separateAmPmPicker: true, + }); + expect(isSelectedStyle(getByText("03"))).toBe(true); + }); + + it("does not grey items when no combinedHourLimit is provided", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 5, + adjustedLimitedMin: 0, + currentAmPm: 0, + is12HourPicker: true, + item: "11", + separateAmPmPicker: true, + }); + expect(isDisabledStyle(getByText("11"))).toBe(false); + }); + + describe("greying with normal hourLimit { min: 9, max: 17 }", () => { + const limit = { max: 17, min: 9 }; + + it.each([ + ["12", 0, true], // 12 AM = 0, out of range + ["08", 0, true], + ["09", 0, false], + ["11", 0, false], + ])("AM (currentAmPm=0): row %s greyed=%s", (row, currentAmPm, greyed) => { + const { getByText } = renderItem({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + combinedHourLimit: limit, + currentAmPm, + is12HourPicker: true, + item: row, + separateAmPmPicker: true, + }); + expect(isDisabledStyle(getByText(row))).toBe(greyed); + }); + + it.each([ + ["12", 1, false], // 12 PM = 12, in range + ["05", 1, false], // 5 PM = 17, in range + ["06", 1, true], // 6 PM = 18, out of range + ["11", 1, true], + ])("PM (currentAmPm=1): row %s greyed=%s", (row, currentAmPm, greyed) => { + const { getByText } = renderItem({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + combinedHourLimit: limit, + currentAmPm, + is12HourPicker: true, + item: row, + separateAmPmPicker: true, + }); + expect(isDisabledStyle(getByText(row))).toBe(greyed); + }); + }); + + describe("greying with wraparound hourLimit { min: 20, max: 5 }", () => { + const limit = { max: 5, min: 20 }; + + it.each([ + ["12", 0, false], // 12 AM = 0, in range + ["05", 0, false], // 5 AM = 5, in range + ["06", 0, true], // 6 AM = 6, out of range + ["11", 0, true], + ])("AM (currentAmPm=0): row %s greyed=%s", (row, currentAmPm, greyed) => { + const { getByText } = renderItem({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + combinedHourLimit: limit, + currentAmPm, + is12HourPicker: true, + item: row, + separateAmPmPicker: true, + }); + expect(isDisabledStyle(getByText(row))).toBe(greyed); + }); + + it.each([ + ["12", 1, true], // 12 PM = 12, out of range + ["07", 1, true], + ["08", 1, false], // 8 PM = 20, in range + ["11", 1, false], // 11 PM = 23, in range + ])("PM (currentAmPm=1): row %s greyed=%s", (row, currentAmPm, greyed) => { + const { getByText } = renderItem({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + combinedHourLimit: limit, + currentAmPm, + is12HourPicker: true, + item: row, + separateAmPmPicker: true, + }); + expect(isDisabledStyle(getByText(row))).toBe(greyed); + }); + }); + }); + + describe("AM/PM picker", () => { + it("marks AM as selected when selectedValue is 0", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + isAmPmPicker: true, + item: "AM", + selectedValue: 0, + }); + expect(isSelectedStyle(getByText("AM"))).toBe(true); + }); + + it("marks PM as selected when selectedValue is 1", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + isAmPmPicker: true, + item: "PM", + selectedValue: 1, + }); + expect(isSelectedStyle(getByText("PM"))).toBe(true); + }); + + it("does not mark PM as selected when selectedValue is 0", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + isAmPmPicker: true, + item: "PM", + selectedValue: 0, + }); + expect(isSelectedStyle(getByText("PM"))).toBe(false); + }); + + it("respects custom amLabel/pmLabel for matching", () => { + const { getByText } = renderItem({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + amLabel: "오전", + isAmPmPicker: true, + item: "오전", + pmLabel: "오후", + selectedValue: 0, + }); + expect(isSelectedStyle(getByText("오전"))).toBe(true); + }); + + describe("greying is disabled (AM/PM is always freely toggleable)", () => { + // The AM/PM column is intentionally limit-free so the user can always toggle + // halves to reach any valid hour. The hour column does the limit enforcement. + it.each([ + ["AM", { max: 17, min: 9 }], + ["PM", { max: 17, min: 9 }], + ["AM", { max: 5, min: 20 }], + ["PM", { max: 5, min: 20 }], + ])("row %s is never greyed", (row, limit) => { + const { getByText } = renderItem({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + combinedHourLimit: limit, + isAmPmPicker: true, + item: row, + }); + expect(isDisabledStyle(getByText(row))).toBe(false); + }); + }); + }); }); diff --git a/src/tests/TimerPicker.test.tsx b/src/tests/TimerPicker.test.tsx index 4b1df50..62de259 100644 --- a/src/tests/TimerPicker.test.tsx +++ b/src/tests/TimerPicker.test.tsx @@ -1,10 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import React from "react"; -import { render } from "@testing-library/react-native"; +import { act, render, within } from "@testing-library/react-native"; import { FlatList } from "react-native"; import TimerPicker from "../components/TimerPicker"; +import type { TimerPickerRef } from "../components/TimerPicker"; describe("TimerPicker", () => { it("renders without crashing", () => { @@ -106,4 +107,197 @@ describe("TimerPicker", () => { expect(queryByTestId("duration-scroll-minute")).toBeNull(); expect(queryByTestId("duration-scroll-second")).toBeNull(); }); + + describe("separateAmPmPicker", () => { + it("does not render an AM/PM column when separateAmPmPicker is false", () => { + const { queryByTestId } = render(); + expect(queryByTestId("duration-scroll-am-pm")).toBeNull(); + }); + + it("does not render an AM/PM column when use12HourPicker is false", () => { + const { queryByTestId } = render(); + expect(queryByTestId("duration-scroll-am-pm")).toBeNull(); + }); + + it("renders the AM/PM column when both flags are set", () => { + const { getByTestId } = render(); + expect(getByTestId("duration-scroll-am-pm")).toBeDefined(); + }); + + it("emits the initial 24-hour value through onDurationChange", () => { + const onDurationChange = jest.fn(); + render( + + ); + expect(onDurationChange).toHaveBeenCalledWith({ + days: 0, + hours: 17, + minutes: 0, + seconds: 0, + }); + }); + + it("renders 12-hour cycle hour items (no AM/PM suffix)", () => { + const { getByText, queryByText } = render( + + ); + // "12" appears as the noon/midnight slot + expect(getByText("12")).toBeDefined(); + // No suffixed strings like "12 AM" or "01 PM" + expect(queryByText("12 AM")).toBeNull(); + expect(queryByText("01 PM")).toBeNull(); + }); + + it("renders AM and PM rows in the AM/PM column", () => { + const { getAllByText } = render( + + ); + // Defaults are "am"/"pm"; we override here so the labels match exactly. + expect(getAllByText("AM").length).toBeGreaterThan(0); + expect(getAllByText("PM").length).toBeGreaterThan(0); + }); + + it("greys hour rows whose 24h value falls outside hourLimit (given current AM/PM)", () => { + // hourLimit { 9, 17 } with currentAmPm=AM (initial 9 AM = 9): + // - hour row "08" represents 8 AM = 8, out of range → greyed + // - hour row "09" represents 9 AM = 9, in range → not greyed + // - hour row "11" represents 11 AM = 11, in range → not greyed + const { getByTestId } = render( + + ); + const hoursColumn = within(getByTestId("duration-scroll-hour")); + const isGreyed = (el: { props: { style?: unknown } }) => { + const style = el.props.style; + const arr = Array.isArray(style) ? style : [style]; + return arr.some((s) => s && (s as { opacity?: number }).opacity === 0.2); + }; + + expect(hoursColumn.getAllByText("08").every(isGreyed)).toBe(true); + expect(hoursColumn.getAllByText("09").every((el) => !isGreyed(el))).toBe(true); + expect(hoursColumn.getAllByText("11").every((el) => !isGreyed(el))).toBe(true); + }); + + it("preserves the original use12HourPicker behaviour when separateAmPmPicker is false", () => { + const onDurationChange = jest.fn(); + render( + + ); + expect(onDurationChange).toHaveBeenCalledWith({ + days: 0, + hours: 17, + minutes: 0, + seconds: 0, + }); + }); + + describe("initial-value round-trip", () => { + it.each([ + [0, "12 AM"], + [1, "1 AM"], + [11, "11 AM"], + [12, "12 PM"], + [13, "1 PM"], + [17, "5 PM"], + [23, "11 PM"], + ])("emits hours=%i (%s) through onDurationChange", (hours) => { + const onDurationChange = jest.fn(); + render( + + ); + expect(onDurationChange).toHaveBeenLastCalledWith(expect.objectContaining({ hours })); + }); + }); + + describe("setValue ref API", () => { + it.each([[0], [1], [11], [12], [13], [17], [23]])( + "setValue({ hours: %i }) emits hours=%i", + (hours) => { + const onDurationChange = jest.fn(); + const ref = React.createRef(); + // Pick an initial that differs from every test case to guarantee a state change. + render( + + ); + onDurationChange.mockClear(); + act(() => { + ref.current?.setValue({ hours }); + }); + expect(onDurationChange).toHaveBeenLastCalledWith(expect.objectContaining({ hours })); + } + ); + }); + + describe("ignored prop warnings", () => { + let warnSpy: jest.SpyInstance; + + beforeEach(() => { + warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + it("warns when maximumHours is set with separateAmPmPicker", () => { + render(); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('"maximumHours" is currently ignored') + ); + }); + + it("does not warn when maximumHours is the default", () => { + render(); + expect(warnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('"maximumHours" is currently ignored') + ); + }); + + it("does not warn about hourLimit anymore (it now works in separate mode)", () => { + render(); + expect(warnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('"hourLimit" is currently ignored') + ); + }); + + it("does not warn when separateAmPmPicker is off", () => { + render(); + expect(warnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('"maximumHours" is currently ignored') + ); + }); + }); + }); }); diff --git a/src/tests/generateNumbers.test.ts b/src/tests/generateNumbers.test.ts index ed39add..5aec12c 100644 --- a/src/tests/generateNumbers.test.ts +++ b/src/tests/generateNumbers.test.ts @@ -1,4 +1,9 @@ -import { generateNumbers, generate12HourNumbers } from "../utils/generateNumbers"; +import { + generateNumbers, + generate12HourNumbers, + generate12HourCycleNumbers, + generateAmPmItems, +} from "../utils/generateNumbers"; describe("generateNumbers", () => { describe("basic functionality", () => { @@ -375,3 +380,94 @@ describe("generate12HourNumbers", () => { }); }); }); + +describe("generate12HourCycleNumbers", () => { + it("generates 12 items in clock order with index 0 = 12", () => { + const result = generate12HourCycleNumbers({ + disableInfiniteScroll: true, + interval: 1, + padNumbersWithZero: true, + padWithNItems: 0, + repeatNTimes: 1, + }); + expect(result).toHaveLength(12); + expect(result[0]).toBe("12"); + expect(result[1]).toBe("01"); + expect(result[11]).toBe("11"); + }); + + it("respects 2-hour interval (12, 02, 04, 06, 08, 10)", () => { + const result = generate12HourCycleNumbers({ + disableInfiniteScroll: true, + interval: 2, + padNumbersWithZero: true, + padWithNItems: 0, + repeatNTimes: 1, + }); + expect(result).toEqual(["12", "02", "04", "06", "08", "10"]); + }); + + it("pads with figure space when padNumbersWithZero is false", () => { + const result = generate12HourCycleNumbers({ + disableInfiniteScroll: true, + interval: 1, + padWithNItems: 0, + repeatNTimes: 1, + }); + expect(result[0]).toBe("12"); + expect(result[1]).toBe(" 1"); + expect(result[9]).toBe(" 9"); + expect(result[10]).toBe("10"); + }); + + it("adds padding rows when infinite scroll is disabled", () => { + const result = generate12HourCycleNumbers({ + disableInfiniteScroll: true, + interval: 6, + padNumbersWithZero: true, + padWithNItems: 2, + repeatNTimes: 1, + }); + expect(result).toEqual(["", "", "12", "06", "", ""]); + }); + + it("repeats the cycle when repeatNTimes is 2 with infinite scroll", () => { + const result = generate12HourCycleNumbers({ + disableInfiniteScroll: false, + interval: 6, + padNumbersWithZero: true, + padWithNItems: 0, + repeatNTimes: 2, + }); + expect(result).toEqual(["12", "06", "12", "06"]); + }); +}); + +describe("generateAmPmItems", () => { + it("returns am and pm with no padding", () => { + expect(generateAmPmItems({ amLabel: "AM", padWithNItems: 0, pmLabel: "PM" })).toEqual([ + "AM", + "PM", + ]); + }); + + it("pads symmetrically when padWithNItems is set", () => { + expect(generateAmPmItems({ amLabel: "AM", padWithNItems: 2, pmLabel: "PM" })).toEqual([ + "", + "", + "AM", + "PM", + "", + "", + ]); + }); + + it("uses custom labels", () => { + expect(generateAmPmItems({ amLabel: "오전", padWithNItems: 1, pmLabel: "오후" })).toEqual([ + "", + "오전", + "오후", + "", + ]); + }); +}); diff --git a/src/tests/snapSeparateAmPmHour.test.ts b/src/tests/snapSeparateAmPmHour.test.ts new file mode 100644 index 0000000..415b2ef --- /dev/null +++ b/src/tests/snapSeparateAmPmHour.test.ts @@ -0,0 +1,85 @@ +import { findNearestValidCycleIdx } from "../utils/snapSeparateAmPmHour"; + +describe("findNearestValidCycleIdx", () => { + describe("no limit", () => { + it.each([0, 5, 11])("returns the input cycleIdx %i unchanged", (cycleIdx) => { + expect(findNearestValidCycleIdx(cycleIdx, 0, undefined)).toBe(cycleIdx); + expect(findNearestValidCycleIdx(cycleIdx, 1, undefined)).toBe(cycleIdx); + }); + + it("treats an empty Limit object as no limit", () => { + expect(findNearestValidCycleIdx(7, 0, {})).toBe(7); + }); + }); + + describe("normal limit { min: 9, max: 17 }", () => { + const limit = { max: 17, min: 9 }; + + it.each([ + [0, 9], // 12 AM (=0) invalid → snap to 9 AM + [1, 9], + [8, 9], + [9, 9], + [10, 10], + [11, 11], + ])("AM cycleIdx %i snaps to %i", (input, expected) => { + expect(findNearestValidCycleIdx(input, 0, limit)).toBe(expected); + }); + + it.each([ + [0, 0], // 12 PM (=12) is valid + [1, 1], // 1 PM = 13, valid + [5, 5], // 5 PM = 17, valid + [6, 5], // 6 PM = 18, invalid → nearest valid PM cycleIdx is 5 + [11, 5], + ])("PM cycleIdx %i snaps to %i", (input, expected) => { + expect(findNearestValidCycleIdx(input, 1, limit)).toBe(expected); + }); + }); + + describe("wraparound limit { min: 20, max: 5 } (8 PM – 5 AM)", () => { + const limit = { max: 5, min: 20 }; + + it.each([ + [0, 0], // 12 AM = 0, valid + [5, 5], // 5 AM = 5, valid + [6, 5], // 6 AM = 6, invalid → nearest valid AM is 5 + [10, 5], + [11, 5], + ])("AM cycleIdx %i snaps to %i", (input, expected) => { + expect(findNearestValidCycleIdx(input, 0, limit)).toBe(expected); + }); + + it.each([ + [0, 8], // 12 PM = 12, invalid → nearest valid PM is cycleIdx 8 (= 20) + [7, 8], + [8, 8], // 8 PM = 20, valid + [11, 11], // 11 PM = 23, valid + ])("PM cycleIdx %i snaps to %i", (input, expected) => { + expect(findNearestValidCycleIdx(input, 1, limit)).toBe(expected); + }); + }); + + describe("limit excludes one full half", () => { + it("returns input unchanged when no AM cycleIdx is valid (limit forces PM)", () => { + // limit { min: 13, max: 17 } means only PM hours 13-17 are valid + // No AM cycleIdx (which gives 0 or 1-11) can land in [13, 17]. + expect(findNearestValidCycleIdx(5, 0, { max: 17, min: 13 })).toBe(5); + }); + + it("returns input unchanged when no PM cycleIdx is valid (limit forces AM)", () => { + // limit { min: 1, max: 5 } means only AM hours 1-5 are valid; no PM combo works. + expect(findNearestValidCycleIdx(7, 1, { max: 5, min: 1 })).toBe(7); + }); + }); + + it("respects limit with only min set (max defaults to 23)", () => { + expect(findNearestValidCycleIdx(0, 0, { min: 12 })).toBe(0); // No AM works; falls through to input + expect(findNearestValidCycleIdx(0, 1, { min: 12 })).toBe(0); // 12 PM = 12, valid + }); + + it("respects limit with only max set (min defaults to 0)", () => { + expect(findNearestValidCycleIdx(0, 0, { max: 5 })).toBe(0); // 12 AM = 0, valid + expect(findNearestValidCycleIdx(11, 0, { max: 5 })).toBe(5); + }); +}); diff --git a/src/utils/generateNumbers.ts b/src/utils/generateNumbers.ts index d9eff4b..c99b0c6 100644 --- a/src/utils/generateNumbers.ts +++ b/src/utils/generateNumbers.ts @@ -128,3 +128,64 @@ export const generate12HourNumbers = (options: { return numbers; }; + +/** + * Generates an array of formatted hour strings for the 12-hour clock cycle (no AM/PM suffix), + * intended for the hours column when the AM/PM column is rendered separately. + * + * Index 0 always represents the noon/midnight slot and displays as "12"; subsequent indices + * follow the supplied interval starting from 1. Internal value 0 → "12", value k → display k. + * + * @example + * // Generate the full 12-hour cycle padded with zeros + * generate12HourCycleNumbers({ + * interval: 1, + * padWithNItems: 0, + * padNumbersWithZero: true, + * repeatNTimes: 1, + * disableInfiniteScroll: true, + * }) + * // Returns: ['12', '01', '02', ..., '11'] + */ +export const generate12HourCycleNumbers = (options: { + disableInfiniteScroll?: boolean; + interval: number; + padNumbersWithZero?: boolean; + padWithNItems: number; + repeatNTimes: number; +}) => { + let numbers: string[] = []; + + for (let i = 0; i < 12; i += options.interval) { + const hour = i === 0 ? 12 : i; + numbers.push(padNumber(hour, { padWithZero: options.padNumbersWithZero })); + } + + if (options.repeatNTimes > 1) { + numbers = Array(options.repeatNTimes).fill(numbers).flat(); + } + + if (options.disableInfiniteScroll || options.repeatNTimes === 1) { + numbers.push(...Array(options.padWithNItems).fill("")); + numbers.unshift(...Array(options.padWithNItems).fill("")); + } + + return numbers; +}; + +/** + * Generates the items shown in the standalone AM/PM picker column. The column is non-looping + * and always padded so that AM and PM line up with the centre row. + * + * @example + * generateAmPmItems({ amLabel: 'AM', pmLabel: 'PM', padWithNItems: 1 }) + * // Returns: ['', 'AM', 'PM', ''] + */ +export const generateAmPmItems = (options: { + amLabel: string; + padWithNItems: number; + pmLabel: string; +}) => { + const padding: string[] = Array(options.padWithNItems).fill(""); + return [...padding, options.amLabel, options.pmLabel, ...padding]; +}; diff --git a/src/utils/getNearestInRange.ts b/src/utils/getNearestInRange.ts new file mode 100644 index 0000000..7314f50 --- /dev/null +++ b/src/utils/getNearestInRange.ts @@ -0,0 +1,19 @@ +import { isWithinLimit } from "./isWithinLimit"; + +/** + * Returns the in-range value closest (in scroll distance) to `value`, honouring + * wraparound limits where `max < min`. When `value` is already in range it is + * returned unchanged. + */ +export const getNearestInRange = (value: number, min: number, max: number): number => { + if (isWithinLimit(value, min, max)) return value; + + if (max < min) { + // wraparound: value lies in the gap between max and min + const distanceForwardToMin = min - value; + const distanceBackwardToMax = value - max; + return distanceForwardToMin <= distanceBackwardToMax ? min : max; + } + + return value > max ? max : min; +}; diff --git a/src/utils/separateAmPmHour.ts b/src/utils/separateAmPmHour.ts new file mode 100644 index 0000000..b212988 --- /dev/null +++ b/src/utils/separateAmPmHour.ts @@ -0,0 +1,17 @@ +/** + * Helpers for the `separateAmPmPicker` representation. The hour column tracks a + * 0–11 cycle index (where 0 is the noon/midnight "12" slot) and the AM/PM column + * tracks 0 (AM) or 1 (PM). The public API always exposes 24-hour values. + */ + +export const splitHour24 = (hour24: number): { amPm: 0 | 1; cycleIdx: number } => { + if (hour24 === 0) return { amPm: 0, cycleIdx: 0 }; + if (hour24 === 12) return { amPm: 1, cycleIdx: 0 }; + if (hour24 < 12) return { amPm: 0, cycleIdx: hour24 }; + return { amPm: 1, cycleIdx: hour24 - 12 }; +}; + +export const combineToHour24 = (cycleIdx: number, amPm: number): number => { + if (cycleIdx === 0) return amPm === 1 ? 12 : 0; + return cycleIdx + (amPm === 1 ? 12 : 0); +}; diff --git a/src/utils/snapSeparateAmPmHour.ts b/src/utils/snapSeparateAmPmHour.ts new file mode 100644 index 0000000..cd36c49 --- /dev/null +++ b/src/utils/snapSeparateAmPmHour.ts @@ -0,0 +1,42 @@ +import type { Limit } from "../components/DurationScroll/types"; +import { isWithinLimit } from "./isWithinLimit"; +import { combineToHour24 } from "./separateAmPmHour"; + +const resolveLimit = (limit: Limit | undefined): { max: number; min: number } | null => { + if (!limit || (limit.min === undefined && limit.max === undefined)) return null; + return { + max: limit.max ?? 23, + min: limit.min ?? 0, + }; +}; + +/** + * Returns the cycleIdx in [0, 11] nearest to `rawCycleIdx` such that + * `(cycleIdx, currentAmPm)` is within `hourLimit`. If no value in the column's + * range satisfies the limit, `rawCycleIdx` is returned unchanged (no snap). + */ +export const findNearestValidCycleIdx = ( + rawCycleIdx: number, + currentAmPm: number, + hourLimit: Limit | undefined +): number => { + const resolved = resolveLimit(hourLimit); + if (!resolved) return rawCycleIdx; + + if (isWithinLimit(combineToHour24(rawCycleIdx, currentAmPm), resolved.min, resolved.max)) { + return rawCycleIdx; + } + + let best: number | null = null; + let bestDistance = Infinity; + for (let i = 0; i <= 11; i++) { + if (!isWithinLimit(combineToHour24(i, currentAmPm), resolved.min, resolved.max)) continue; + const distance = Math.abs(i - rawCycleIdx); + if (distance < bestDistance) { + bestDistance = distance; + best = i; + } + } + + return best ?? rawCycleIdx; +}; From 5751edabd04e4e0742181b6b4c3629442193ef09 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 4 May 2026 23:11:18 +0100 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=8E=A8=20Add=20separateAmPmItem=20/?= =?UTF-8?q?=20selectedSeparateAmPmItem=20style=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + examples/example-expo/App.tsx | 6 +- src/components/PickerItem/PickerItem.tsx | 2 + src/components/TimerPicker/styles.ts | 8 +++ src/tests/DurationScroll.test.tsx | 2 + src/tests/PickerItem.test.tsx | 72 ++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f672984..599c33a 100644 --- a/README.md +++ b/README.md @@ -610,6 +610,8 @@ For deeper style customization, you can supply the following custom styles to ad | pickerLabel | Style for the picker's labels | TextStyle | | pickerAmPmContainer | Style for the picker's labels | ViewStyle | | pickerAmPmLabel | Style for the picker's labels | TextStyle | +| separateAmPmItem | Style for rows in the standalone AM/PM column when `separateAmPmPicker` is enabled. Layered on top of `pickerItem` | TextStyle | +| selectedSeparateAmPmItem | Style for the centred (selected) row in the standalone AM/PM column when `separateAmPmPicker` is enabled. Layered on top of `separateAmPmItem` | TextStyle | | pickerItemContainer | Container for each number in the picker | ViewStyle & { height?: number } | | pickerItem | Style for each number | TextStyle | | disabledPickerItem | Style for any numbers outside any set limits | TextStyle | diff --git a/examples/example-expo/App.tsx b/examples/example-expo/App.tsx index 798b7fd..39ca9d3 100644 --- a/examples/example-expo/App.tsx +++ b/examples/example-expo/App.tsx @@ -209,7 +209,11 @@ export default function App() { pmLabel="PM" separateAmPmPicker setIsVisible={setShowPickerSeparateAmPm} - styles={{ theme: "light" }} + styles={{ + selectedSeparateAmPmItem: { color: "#1B6EF1" }, + separateAmPmItem: { fontSize: 16, fontWeight: "600" }, + theme: "light", + }} use12HourPicker visible={showPickerSeparateAmPm} /> diff --git a/src/components/PickerItem/PickerItem.tsx b/src/components/PickerItem/PickerItem.tsx index 647e75c..9aec3c0 100644 --- a/src/components/PickerItem/PickerItem.tsx +++ b/src/components/PickerItem/PickerItem.tsx @@ -105,6 +105,8 @@ const PickerItem = React.memo( styles.pickerItem, isSelected && styles.selectedPickerItem, isDisabled ? styles.disabledPickerItem : {}, + isAmPmPicker && styles.separateAmPmItem, + isAmPmPicker && isSelected && styles.selectedSeparateAmPmItem, ]} > {stringItem} diff --git a/src/components/TimerPicker/styles.ts b/src/components/TimerPicker/styles.ts index cf4da5f..f60f1d2 100644 --- a/src/components/TimerPicker/styles.ts +++ b/src/components/TimerPicker/styles.ts @@ -25,6 +25,8 @@ export interface CustomTimerPickerStyles { pickerLabelContainer?: ViewStyle; pickerLabelGap?: PerColumnValue; selectedPickerItem?: TextStyle; + selectedSeparateAmPmItem?: TextStyle; + separateAmPmItem?: TextStyle; text?: TextStyle; theme?: "light" | "dark"; } @@ -167,5 +169,11 @@ export const generateStyles = (customStyles: CustomTimerPickerStyles | undefined ...customStyles?.pickerItem, ...customStyles?.selectedPickerItem, }, + selectedSeparateAmPmItem: { + ...customStyles?.selectedSeparateAmPmItem, + }, + separateAmPmItem: { + ...customStyles?.separateAmPmItem, + }, }); }; diff --git a/src/tests/DurationScroll.test.tsx b/src/tests/DurationScroll.test.tsx index 664fa5f..0e2c076 100644 --- a/src/tests/DurationScroll.test.tsx +++ b/src/tests/DurationScroll.test.tsx @@ -18,6 +18,8 @@ describe("DurationScroll", () => { pickerItemContainer: {}, pickerLabel: {}, pickerLabelContainer: {}, + selectedSeparateAmPmItem: {}, + separateAmPmItem: {}, } as ReturnType; it("renders without crashing", () => { diff --git a/src/tests/PickerItem.test.tsx b/src/tests/PickerItem.test.tsx index fbc7790..c1560be 100644 --- a/src/tests/PickerItem.test.tsx +++ b/src/tests/PickerItem.test.tsx @@ -19,6 +19,8 @@ const styles = { pickerLabel: {}, pickerLabelContainer: {}, selectedPickerItem: { fontWeight: "bold" as const }, + selectedSeparateAmPmItem: {}, + separateAmPmItem: {}, } as ReturnType; const renderItem = (props: { @@ -363,6 +365,76 @@ describe("PickerItem", () => { expect(isSelectedStyle(getByText("오전"))).toBe(true); }); + describe("custom AM/PM styling", () => { + const customStyles = { + ...styles, + selectedSeparateAmPmItem: { color: "tomato" } as const, + separateAmPmItem: { fontSize: 14 } as const, + } as ReturnType; + + const renderWithCustom = (props: Parameters[0]) => + render( + + ); + + it("applies separateAmPmItem to AM/PM rows", () => { + const { getByText } = renderWithCustom({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + isAmPmPicker: true, + item: "AM", + }); + const flat = (getByText("AM").props.style as Array>).flat(); + expect(flat.some((s) => s && s.fontSize === 14)).toBe(true); + }); + + it("applies selectedSeparateAmPmItem only to the selected AM/PM row", () => { + const { getByText: getSelected } = renderWithCustom({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + isAmPmPicker: true, + item: "AM", + selectedValue: 0, + }); + const selectedFlat = ( + getSelected("AM").props.style as Array> + ).flat(); + expect(selectedFlat.some((s) => s && s.color === "tomato")).toBe(true); + + const { getByText: getUnselected } = renderWithCustom({ + adjustedLimitedMax: 1, + adjustedLimitedMin: 0, + isAmPmPicker: true, + item: "PM", + selectedValue: 0, + }); + const unselectedFlat = ( + getUnselected("PM").props.style as Array> + ).flat(); + expect(unselectedFlat.some((s) => s && s.color === "tomato")).toBe(false); + }); + + it("does not apply separateAmPmItem to non-AM/PM rows", () => { + const { getByText } = renderWithCustom({ + adjustedLimitedMax: 11, + adjustedLimitedMin: 0, + item: "05", + }); + const flat = (getByText("05").props.style as Array>).flat(); + expect(flat.some((s) => s && s.fontSize === 14)).toBe(false); + }); + }); + describe("greying is disabled (AM/PM is always freely toggleable)", () => { // The AM/PM column is intentionally limit-free so the user can always toggle // halves to reach any valid hour. The hour column does the limit enforcement. From 04c0ca77aca11b2503eda685f94a934e3f54d5bd Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 4 May 2026 23:57:06 +0100 Subject: [PATCH 3/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Consolidate=20snap=20+?= =?UTF-8?q?=20greying=20via=20callbacks;=20honour=20hourInterval;=20tidy?= =?UTF-8?q?=20refs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DurationScroll gains `getValidValue` + `isItemDisabled` callback props. TimerPicker injects cross-column context (current AM/PM, hourLimit, hourInterval) through these instead of plumbing combinedHourLimit / currentAmPm through DurationScroll → PickerItem. - One snap path: DurationScroll's onMomentumScrollEnd calls the validate callback, no more reentrant setValue from TimerPicker. - findNearestValidCycleIdx now takes interval and iterates 0..11 step interval, so hourInterval > 1 no longer snaps to a non-rendered cycleIdx. Adds parameterised interval coverage to the test suite. - Drop dead selectedAmPmRef + sync useEffect. - Replace the synthetic `RefObject` cast on latestDuration.hours with a proper `LatestDurationRef = { readonly current: number }` type used by both TimerPickerRef and TimerPickerModalRef. - Add "amPm" to PickerColumn so pickerColumnWidth.amPm works and the modal width math routes through the existing resolveColumnWidth helper. --- README.md | 2 +- .../DurationScroll/DurationScroll.tsx | 17 ++-- src/components/DurationScroll/types.ts | 15 ++- src/components/PickerItem/PickerItem.tsx | 25 +---- src/components/TimerPicker/TimerPicker.tsx | 44 ++++----- src/components/TimerPicker/styles.ts | 2 +- src/components/TimerPicker/types.ts | 11 ++- .../TimerPickerModal/TimerPickerModal.tsx | 2 +- src/components/TimerPickerModal/types.ts | 12 +-- src/tests/PickerItem.test.tsx | 97 ++++++------------- src/tests/snapSeparateAmPmHour.test.ts | 31 ++++++ src/utils/snapSeparateAmPmHour.ts | 12 ++- 12 files changed, 134 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index 599c33a..7cec30b 100644 --- a/README.md +++ b/README.md @@ -566,7 +566,7 @@ return ( | aggressivelyGetLatestDuration | Set to True to ask DurationScroll to aggressively update the latestDuration ref | Boolean | false | false | | allowFontScaling | Allow font in the picker to scale with accessibility settings | Boolean | false | false | | use12HourPicker | Switch the hour picker to 12-hour format with an AM / PM label | Boolean | false | false | -| separateAmPmPicker | When `use12HourPicker` is true, render AM/PM as a dedicated scrollable column after seconds (instead of appending it to each hour). Hours are emitted via `onDurationChange` as a 0–23 value as usual. `hourLimit` is honoured: rows in the **hour** column grey/snap based on the currently selected AM/PM. The AM/PM column itself is always freely toggleable so users can switch halves to reach any valid hour. Note: `maximumHours` is currently ignored in this mode — the column always shows the full 12-hour cycle. | Boolean | false | false | +| separateAmPmPicker | When `use12HourPicker` is true, render AM/PM as a dedicated scrollable column after seconds (instead of appending it to each hour). Hours are emitted via `onDurationChange` as a 0–23 value as usual. `hourLimit` and `hourInterval` are honoured: rows in the **hour** column grey/snap based on the currently selected AM/PM. The AM/PM column itself is always freely toggleable so users can switch halves to reach any valid hour. The AM/PM column width can be customised via `pickerColumnWidth.amPm`. Note: `maximumHours` is currently ignored in this mode — the column always shows the full 12-hour cycle. | Boolean | false | false | | amLabel | Set the AM label if using the 12-hour picker | String | am | false | | pmLabel | Set the PM label if using the 12-hour picker | String | pm | false | | repeatDayNumbersNTimes | Set the number of times the list of days is repeated in the picker | Number | 3 | false | diff --git a/src/components/DurationScroll/DurationScroll.tsx b/src/components/DurationScroll/DurationScroll.tsx index 655d182..e1b4872 100644 --- a/src/components/DurationScroll/DurationScroll.tsx +++ b/src/components/DurationScroll/DurationScroll.tsx @@ -35,17 +35,17 @@ const DurationScroll = forwardRef((props amLabel, Audio, clickSoundAsset, - combinedHourLimit, - currentAmPm, decelerationRate = 0.88, disableInfiniteScroll = false, FlatList = RNFlatList, + getValidValue, Haptics, initialValue = 0, interval, is12HourPicker, isAmPmPicker, isDisabled, + isItemDisabled, label, limit, LinearGradient, @@ -262,10 +262,9 @@ const DurationScroll = forwardRef((props adjustedLimitedMin={adjustedLimited.min} allowFontScaling={allowFontScaling} amLabel={amLabel} - combinedHourLimit={combinedHourLimit} - currentAmPm={currentAmPm} is12HourPicker={is12HourPicker} isAmPmPicker={isAmPmPicker} + isItemDisabled={isItemDisabled} item={item} pickerAmPmPositionStyle={labelPositionStyle} pmLabel={pmLabel} @@ -279,10 +278,9 @@ const DurationScroll = forwardRef((props adjustedLimited.min, allowFontScaling, amLabel, - combinedHourLimit, - currentAmPm, is12HourPicker, isAmPmPicker, + isItemDisabled, labelPositionStyle, pmLabel, selectedValue, @@ -292,8 +290,11 @@ const DurationScroll = forwardRef((props ); const getNearestInRangeValue = useCallback( - (value: number) => getNearestInRange(value, adjustedLimited.min, adjustedLimited.max), - [adjustedLimited] + (value: number) => + getValidValue + ? getValidValue(value) + : getNearestInRange(value, adjustedLimited.min, adjustedLimited.max), + [adjustedLimited, getValidValue] ); const onScroll = useCallback["onScroll"]>>( diff --git a/src/components/DurationScroll/types.ts b/src/components/DurationScroll/types.ts index 9d53c5d..c63223b 100644 --- a/src/components/DurationScroll/types.ts +++ b/src/components/DurationScroll/types.ts @@ -12,17 +12,28 @@ export interface DurationScrollProps { amLabel?: string; Audio?: any; clickSoundAsset?: SoundAsset; - combinedHourLimit?: Limit; - currentAmPm?: number; decelerationRate?: number | "normal" | "fast"; disableInfiniteScroll?: boolean; FlatList?: any; + /** + * Optional override for the column's snap-to-valid logic. When provided, + * `onMomentumScrollEnd` runs the raw value through this function instead of + * the default `limit`-based clamp. Used by `TimerPicker` to inject cross-column + * context (e.g. AM/PM) into hour-column snapping. + */ + getValidValue?: (rawValue: number) => number; Haptics?: any; initialValue?: number; interval: number; is12HourPicker?: boolean; isAmPmPicker?: boolean; isDisabled?: boolean; + /** + * Optional override for per-row "is this disabled?" decision. When provided, + * `PickerItem` calls this with the parsed row value instead of comparing against + * the column-local `limit`. Used by `TimerPicker` for combined-hour greying. + */ + isItemDisabled?: (value: number) => boolean; label?: string | React.ReactElement; limit?: Limit; LinearGradient?: any; diff --git a/src/components/PickerItem/PickerItem.tsx b/src/components/PickerItem/PickerItem.tsx index 9aec3c0..96841a4 100644 --- a/src/components/PickerItem/PickerItem.tsx +++ b/src/components/PickerItem/PickerItem.tsx @@ -3,8 +3,6 @@ import React from "react"; import { View, Text } from "react-native"; import { isWithinLimit } from "../../utils/isWithinLimit"; -import { combineToHour24 } from "../../utils/separateAmPmHour"; -import type { Limit } from "../DurationScroll/types"; import type { generateStyles } from "../TimerPicker/styles"; interface PickerItemProps { @@ -12,10 +10,9 @@ interface PickerItemProps { adjustedLimitedMin: number; allowFontScaling: boolean; amLabel?: string; - combinedHourLimit?: Limit; - currentAmPm?: number; is12HourPicker?: boolean; isAmPmPicker?: boolean; + isItemDisabled?: (value: number) => boolean; item: string; pickerAmPmPositionStyle?: { left: "50%"; marginLeft: number }; pmLabel?: string; @@ -24,21 +21,15 @@ interface PickerItemProps { styles: ReturnType; } -const isCombinedHourInRange = (hour24: number, limit: Limit | undefined): boolean => { - if (!limit || (limit.min === undefined && limit.max === undefined)) return true; - return isWithinLimit(hour24, limit.min ?? 0, limit.max ?? 23); -}; - const PickerItem = React.memo( ({ adjustedLimitedMax, adjustedLimitedMin, allowFontScaling, amLabel, - combinedHourLimit, - currentAmPm, is12HourPicker, isAmPmPicker, + isItemDisabled, item, pickerAmPmPositionStyle, pmLabel, @@ -83,16 +74,10 @@ const PickerItem = React.memo( let isDisabled: boolean; if (isAmPmPicker) { - // The AM/PM column is always freely toggleable so users can navigate to a - // different half of the clock; the hour column handles all limit enforcement - // (greying + snap-back) once AM/PM has been chosen. + // AM/PM is always freely toggleable; the hour column does all limit enforcement. isDisabled = false; - } else if (is12HourPicker && separateAmPmPicker) { - // Hour cycle row folds into 24h with the current AM/PM. - isDisabled = - !isNaN(intItem) && - currentAmPm !== undefined && - !isCombinedHourInRange(combineToHour24(intItem, currentAmPm), combinedHourLimit); + } else if (isItemDisabled) { + isDisabled = !isNaN(intItem) && isItemDisabled(intItem); } else { isDisabled = !isWithinLimit(intItem, adjustedLimitedMin, adjustedLimitedMax); } diff --git a/src/components/TimerPicker/TimerPicker.tsx b/src/components/TimerPicker/TimerPicker.tsx index a17d67d..f0b501d 100644 --- a/src/components/TimerPicker/TimerPicker.tsx +++ b/src/components/TimerPicker/TimerPicker.tsx @@ -10,6 +10,7 @@ import React, { import { View } from "react-native"; import { getSafeInitialValue } from "../../utils/getSafeInitialValue"; +import { isWithinLimit } from "../../utils/isWithinLimit"; import { combineToHour24, splitHour24 } from "../../utils/separateAmPmHour"; import { findNearestValidCycleIdx } from "../../utils/snapSeparateAmPmHour"; import DurationScroll from "../DurationScroll"; @@ -175,13 +176,6 @@ const TimerPicker = forwardRef((props, ref) => const [selectedMinutes, setSelectedMinutes] = useState(safeInitialValue.minutes); const [selectedSeconds, setSelectedSeconds] = useState(safeInitialValue.seconds); - // Mirror selectedAmPm into a ref so the hour-cycle snap handler always reads the - // freshest AM/PM context without forcing a callback re-creation per state change. - const selectedAmPmRef = useRef(selectedAmPm); - useEffect(() => { - selectedAmPmRef.current = selectedAmPm; - }, [selectedAmPm]); - useEffect(() => { const hours = useSeparateAmPm ? combineToHour24(selectedHours, selectedAmPm) : selectedHours; onDurationChange?.({ @@ -199,22 +193,27 @@ const TimerPicker = forwardRef((props, ref) => const minutesDurationScrollRef = useRef(null); const secondsDurationScrollRef = useRef(null); - // Snap-aware hour-cycle change handler (separate AM/PM mode). When the user lands on - // a cycleIdx whose combined 24h value falls outside hourLimit, snap to the nearest - // valid cycleIdx given the current AM/PM. If no value in the column's range is valid, - // do not snap. The AM/PM column is intentionally limit-free so users can always - // toggle halves to reach any valid hour. - const handleHourCycleChange = (rawCycleIdx: number) => { - const snapped = findNearestValidCycleIdx(rawCycleIdx, selectedAmPmRef.current, hourLimit); - setSelectedHours(snapped); - if (snapped !== rawCycleIdx) { - hoursDurationScrollRef.current?.setValue(snapped, { animated: true }); + // In separateAmPmPicker mode the hour column's snap and greying both depend on the + // currently selected AM/PM. These callbacks fold the cross-column context in; + // DurationScroll uses `getValidValue` for momentum-scroll snapping and `isItemDisabled` + // for per-row greying via PickerItem. + const getValidHourCycleIdx = (rawCycleIdx: number) => + findNearestValidCycleIdx(rawCycleIdx, selectedAmPm, hourLimit, hourInterval); + + const isHourCycleDisabled = (cycleIdx: number) => { + if (!hourLimit || (hourLimit.min === undefined && hourLimit.max === undefined)) { + return false; } + return !isWithinLimit( + combineToHour24(cycleIdx, selectedAmPm), + hourLimit.min ?? 0, + hourLimit.max ?? 23 + ); }; // In separateAmPmPicker mode the public `latestDuration.hours` must combine the cycle // index and the AM/PM flag back into a 24-hour value. - const combinedHoursLatestDuration = useMemo( + const combinedHoursLatestDuration = useMemo<{ readonly current: number }>( () => ({ get current() { const cycleIdx = hoursDurationScrollRef.current?.latestDuration.current ?? 0; @@ -229,7 +228,7 @@ const TimerPicker = forwardRef((props, ref) => latestDuration: { days: daysDurationScrollRef.current?.latestDuration, hours: useSeparateAmPm - ? (combinedHoursLatestDuration as { current: number }) + ? combinedHoursLatestDuration : hoursDurationScrollRef.current?.latestDuration, minutes: minutesDurationScrollRef.current?.latestDuration, seconds: secondsDurationScrollRef.current?.latestDuration, @@ -311,18 +310,18 @@ const TimerPicker = forwardRef((props, ref) => aggressivelyGetLatestDuration={aggressivelyGetLatestDuration} allowFontScaling={allowFontScaling} amLabel={amLabel} - combinedHourLimit={useSeparateAmPm ? hourLimit : undefined} - currentAmPm={useSeparateAmPm ? selectedAmPm : undefined} decelerationRate={decelerationRate} disableInfiniteScroll={disableInfiniteScroll} + getValidValue={useSeparateAmPm ? getValidHourCycleIdx : undefined} initialValue={useSeparateAmPm ? initialHourSplit.cycleIdx : safeInitialValue.hours} interval={hourInterval} is12HourPicker={use12HourPicker} isDisabled={hoursPickerIsDisabled} + isItemDisabled={useSeparateAmPm ? isHourCycleDisabled : undefined} label={hourLabel ?? (!use12HourPicker ? "h" : undefined)} limit={useSeparateAmPm ? undefined : hourLimit} maximumValue={useSeparateAmPm ? 11 : maximumHours} - onDurationChange={useSeparateAmPm ? handleHourCycleChange : setSelectedHours} + onDurationChange={setSelectedHours} padNumbersWithZero={padHoursWithZero} padWithNItems={safePadWithNItems} pickerColumnWidth={resolvePerColumn(pickerColumnWidth, "hours")} @@ -404,6 +403,7 @@ const TimerPicker = forwardRef((props, ref) => maximumValue={1} onDurationChange={setSelectedAmPm} padWithNItems={safePadWithNItems} + pickerColumnWidth={resolvePerColumn(pickerColumnWidth, "amPm")} pmLabel={pmLabel} repeatNumbersNTimes={1} repeatNumbersNTimesNotExplicitlySet={false} diff --git a/src/components/TimerPicker/styles.ts b/src/components/TimerPicker/styles.ts index f60f1d2..13b5dfb 100644 --- a/src/components/TimerPicker/styles.ts +++ b/src/components/TimerPicker/styles.ts @@ -1,7 +1,7 @@ import { StyleSheet } from "react-native"; import type { TextStyle, ViewStyle } from "react-native"; -export type PickerColumn = "days" | "hours" | "minutes" | "seconds"; +export type PickerColumn = "amPm" | "days" | "hours" | "minutes" | "seconds"; export type PerColumnValue = number | Partial>; diff --git a/src/components/TimerPicker/types.ts b/src/components/TimerPicker/types.ts index 488af61..0962f57 100644 --- a/src/components/TimerPicker/types.ts +++ b/src/components/TimerPicker/types.ts @@ -1,17 +1,18 @@ import type React from "react"; -import { type RefObject } from "react"; import type { View } from "react-native"; import type { LinearGradientProps, SoundAsset, Limit } from "../DurationScroll/types"; import type { CustomTimerPickerStyles } from "./styles"; +export type LatestDurationRef = { readonly current: number }; + export interface TimerPickerRef { latestDuration: { - days: RefObject | undefined; - hours: RefObject | undefined; - minutes: RefObject | undefined; - seconds: RefObject | undefined; + days: LatestDurationRef | undefined; + hours: LatestDurationRef | undefined; + minutes: LatestDurationRef | undefined; + seconds: LatestDurationRef | undefined; }; reset: (options?: { animated?: boolean }) => void; setValue: ( diff --git a/src/components/TimerPickerModal/TimerPickerModal.tsx b/src/components/TimerPickerModal/TimerPickerModal.tsx index 20e42e2..5fd5ca9 100644 --- a/src/components/TimerPickerModal/TimerPickerModal.tsx +++ b/src/components/TimerPickerModal/TimerPickerModal.tsx @@ -69,7 +69,7 @@ const TimerPickerModal = forwardRef( (!hideHours ? resolveColumnWidth(pickerColumnWidth, "hours") : 0) + (!hideMinutes ? resolveColumnWidth(pickerColumnWidth, "minutes") : 0) + (!hideSeconds ? resolveColumnWidth(pickerColumnWidth, "seconds") : 0) + - (hasSeparateAmPmColumn ? DEFAULT_COLUMN_WIDTH : 0); + (hasSeparateAmPmColumn ? resolveColumnWidth(pickerColumnWidth, "amPm") : 0); const styles = generateStyles(customStyles, { hasModalTitle: Boolean(modalTitle), diff --git a/src/components/TimerPickerModal/types.ts b/src/components/TimerPickerModal/types.ts index 1413213..fe7ab48 100644 --- a/src/components/TimerPickerModal/types.ts +++ b/src/components/TimerPickerModal/types.ts @@ -1,18 +1,18 @@ import type React from "react"; -import { type RefObject, type ReactElement } from "react"; +import { type ReactElement } from "react"; import type { View, TouchableOpacity, Text } from "react-native"; import type Modal from "../Modal"; -import type { TimerPickerProps } from "../TimerPicker/types"; +import type { LatestDurationRef, TimerPickerProps } from "../TimerPicker/types"; import type { CustomTimerPickerModalStyles } from "./styles"; export interface TimerPickerModalRef { latestDuration: { - days: RefObject | undefined; - hours: RefObject | undefined; - minutes: RefObject | undefined; - seconds: RefObject | undefined; + days: LatestDurationRef | undefined; + hours: LatestDurationRef | undefined; + minutes: LatestDurationRef | undefined; + seconds: LatestDurationRef | undefined; }; reset: (options?: { animated?: boolean }) => void; setValue: ( diff --git a/src/tests/PickerItem.test.tsx b/src/tests/PickerItem.test.tsx index c1560be..7da8ade 100644 --- a/src/tests/PickerItem.test.tsx +++ b/src/tests/PickerItem.test.tsx @@ -3,7 +3,6 @@ import React from "react"; import { render } from "@testing-library/react-native"; import type { ReactTestInstance } from "react-test-renderer"; -import type { Limit } from "../components/DurationScroll/types"; import PickerItem from "../components/PickerItem"; import type { generateStyles } from "../components/TimerPicker/styles"; @@ -27,10 +26,9 @@ const renderItem = (props: { adjustedLimitedMax: number; adjustedLimitedMin: number; amLabel?: string; - combinedHourLimit?: Limit; - currentAmPm?: number; is12HourPicker?: boolean; isAmPmPicker?: boolean; + isItemDisabled?: (value: number) => boolean; item: string; pmLabel?: string; selectedValue?: number; @@ -42,10 +40,9 @@ const renderItem = (props: { adjustedLimitedMin={props.adjustedLimitedMin} allowFontScaling={false} amLabel={props.amLabel ?? "AM"} - combinedHourLimit={props.combinedHourLimit} - currentAmPm={props.currentAmPm} is12HourPicker={props.is12HourPicker} isAmPmPicker={props.isAmPmPicker} + isItemDisabled={props.isItemDisabled} item={props.item} pmLabel={props.pmLabel ?? "PM"} selectedValue={props.selectedValue} @@ -225,11 +222,12 @@ describe("PickerItem", () => { expect(isSelectedStyle(getByText("03"))).toBe(true); }); - it("does not grey items when no combinedHourLimit is provided", () => { + it("falls back to column-local limit when no isItemDisabled callback is provided", () => { + // In actual usage, the cycle hour column receives limit={undefined}, so + // adjustedLimited covers 0..11 and no row is greyed by default. const { getByText } = renderItem({ - adjustedLimitedMax: 5, + adjustedLimitedMax: 11, adjustedLimitedMin: 0, - currentAmPm: 0, is12HourPicker: true, item: "11", separateAmPmPicker: true, @@ -237,83 +235,55 @@ describe("PickerItem", () => { expect(isDisabledStyle(getByText("11"))).toBe(false); }); - describe("greying with normal hourLimit { min: 9, max: 17 }", () => { - const limit = { max: 17, min: 9 }; - - it.each([ - ["12", 0, true], // 12 AM = 0, out of range - ["08", 0, true], - ["09", 0, false], - ["11", 0, false], - ])("AM (currentAmPm=0): row %s greyed=%s", (row, currentAmPm, greyed) => { + describe("greying via isItemDisabled callback", () => { + it("calls isItemDisabled with the parsed cycleIdx ('12' → 0)", () => { + const isItemDisabled = jest.fn((value: number) => value === 0); const { getByText } = renderItem({ adjustedLimitedMax: 11, adjustedLimitedMin: 0, - combinedHourLimit: limit, - currentAmPm, is12HourPicker: true, - item: row, + isItemDisabled, + item: "12", separateAmPmPicker: true, }); - expect(isDisabledStyle(getByText(row))).toBe(greyed); + expect(isItemDisabled).toHaveBeenCalledWith(0); + expect(isDisabledStyle(getByText("12"))).toBe(true); }); - it.each([ - ["12", 1, false], // 12 PM = 12, in range - ["05", 1, false], // 5 PM = 17, in range - ["06", 1, true], // 6 PM = 18, out of range - ["11", 1, true], - ])("PM (currentAmPm=1): row %s greyed=%s", (row, currentAmPm, greyed) => { + it("calls isItemDisabled with the parsed cycleIdx ('05' → 5)", () => { + const isItemDisabled = jest.fn(() => false); const { getByText } = renderItem({ adjustedLimitedMax: 11, adjustedLimitedMin: 0, - combinedHourLimit: limit, - currentAmPm, is12HourPicker: true, - item: row, + isItemDisabled, + item: "05", separateAmPmPicker: true, }); - expect(isDisabledStyle(getByText(row))).toBe(greyed); + expect(isItemDisabled).toHaveBeenCalledWith(5); + expect(isDisabledStyle(getByText("05"))).toBe(false); }); - }); - describe("greying with wraparound hourLimit { min: 20, max: 5 }", () => { - const limit = { max: 5, min: 20 }; - - it.each([ - ["12", 0, false], // 12 AM = 0, in range - ["05", 0, false], // 5 AM = 5, in range - ["06", 0, true], // 6 AM = 6, out of range - ["11", 0, true], - ])("AM (currentAmPm=0): row %s greyed=%s", (row, currentAmPm, greyed) => { - const { getByText } = renderItem({ + it("greys when callback returns true, not when false", () => { + const { getByText: a } = renderItem({ adjustedLimitedMax: 11, adjustedLimitedMin: 0, - combinedHourLimit: limit, - currentAmPm, is12HourPicker: true, - item: row, + isItemDisabled: () => true, + item: "07", separateAmPmPicker: true, }); - expect(isDisabledStyle(getByText(row))).toBe(greyed); - }); + expect(isDisabledStyle(a("07"))).toBe(true); - it.each([ - ["12", 1, true], // 12 PM = 12, out of range - ["07", 1, true], - ["08", 1, false], // 8 PM = 20, in range - ["11", 1, false], // 11 PM = 23, in range - ])("PM (currentAmPm=1): row %s greyed=%s", (row, currentAmPm, greyed) => { - const { getByText } = renderItem({ + const { getByText: b } = renderItem({ adjustedLimitedMax: 11, adjustedLimitedMin: 0, - combinedHourLimit: limit, - currentAmPm, is12HourPicker: true, - item: row, + isItemDisabled: () => false, + item: "07", separateAmPmPicker: true, }); - expect(isDisabledStyle(getByText(row))).toBe(greyed); + expect(isDisabledStyle(b("07"))).toBe(false); }); }); }); @@ -438,17 +408,14 @@ describe("PickerItem", () => { describe("greying is disabled (AM/PM is always freely toggleable)", () => { // The AM/PM column is intentionally limit-free so the user can always toggle // halves to reach any valid hour. The hour column does the limit enforcement. - it.each([ - ["AM", { max: 17, min: 9 }], - ["PM", { max: 17, min: 9 }], - ["AM", { max: 5, min: 20 }], - ["PM", { max: 5, min: 20 }], - ])("row %s is never greyed", (row, limit) => { + // Even with an isItemDisabled callback that always returns true, AM/PM rows + // never grey. + it.each([["AM"], ["PM"]])("row %s is never greyed", (row) => { const { getByText } = renderItem({ adjustedLimitedMax: 1, adjustedLimitedMin: 0, - combinedHourLimit: limit, isAmPmPicker: true, + isItemDisabled: () => true, item: row, }); expect(isDisabledStyle(getByText(row))).toBe(false); diff --git a/src/tests/snapSeparateAmPmHour.test.ts b/src/tests/snapSeparateAmPmHour.test.ts index 415b2ef..8dafeb0 100644 --- a/src/tests/snapSeparateAmPmHour.test.ts +++ b/src/tests/snapSeparateAmPmHour.test.ts @@ -82,4 +82,35 @@ describe("findNearestValidCycleIdx", () => { expect(findNearestValidCycleIdx(0, 0, { max: 5 })).toBe(0); // 12 AM = 0, valid expect(findNearestValidCycleIdx(11, 0, { max: 5 })).toBe(5); }); + + describe("with hourInterval", () => { + // generate12HourCycleNumbers iterates `i = 0; i < 12; i += interval`. The snap + // helper must mirror that so it never returns a cycleIdx not present in the column. + it.each([ + // interval=2 → cycle indices [0, 2, 4, 6, 8, 10] + [2, 0, 0, { max: 17, min: 9 }, 10], // raw 0 AM invalid; nearest valid AM cycleIdx is 10 + [2, 5, 0, { max: 17, min: 9 }, 10], // raw 5 (not in column) invalid; nearest is 10 + [2, 4, 1, { max: 17, min: 9 }, 4], // raw 4 PM = 16, valid → unchanged + // interval=3 → cycle indices [0, 3, 6, 9] + [3, 0, 0, { max: 17, min: 9 }, 9], // 12 AM invalid; only 9 AM in column is in range + [3, 6, 1, { max: 17, min: 9 }, 3], // 6 PM=18 invalid; nearest valid PM cycleIdx is 3 (3 PM=15) + // interval=4 → cycle indices [0, 4, 8] + [4, 0, 0, { max: 17, min: 9 }, 0], // no AM cycleIdx in column is valid → no snap (returns input) + [4, 0, 1, { max: 17, min: 9 }, 0], // 12 PM=12 IS in [9,17] → unchanged + [4, 0, 1, { max: 17, min: 13 }, 4], // 12 PM=12 invalid in [13,17]; nearest PM is 4 (4 PM=16) + ])("interval=%i, raw=%i, amPm=%i, limit=%p → %i", (interval, raw, amPm, limit, expected) => { + expect(findNearestValidCycleIdx(raw, amPm, limit, interval)).toBe(expected); + }); + + it("never returns a cycleIdx outside the rendered set when called with a valid raw", () => { + // interval=2: rendered cycle indices are {0,2,4,6,8,10}. The raw cycleIdx in real + // usage always comes from getDurationAndIndexFromScrollOffset (= multiple of interval). + const limit = { max: 17, min: 9 }; + const renderedSet = [0, 2, 4, 6, 8, 10]; + for (const raw of renderedSet) { + const result = findNearestValidCycleIdx(raw, 0, limit, 2); + expect(renderedSet.includes(result)).toBe(true); + } + }); + }); }); diff --git a/src/utils/snapSeparateAmPmHour.ts b/src/utils/snapSeparateAmPmHour.ts index cd36c49..d2b78d0 100644 --- a/src/utils/snapSeparateAmPmHour.ts +++ b/src/utils/snapSeparateAmPmHour.ts @@ -11,14 +11,16 @@ const resolveLimit = (limit: Limit | undefined): { max: number; min: number } | }; /** - * Returns the cycleIdx in [0, 11] nearest to `rawCycleIdx` such that - * `(cycleIdx, currentAmPm)` is within `hourLimit`. If no value in the column's - * range satisfies the limit, `rawCycleIdx` is returned unchanged (no snap). + * Returns the cycleIdx in the rendered hour column nearest to `rawCycleIdx` such that + * `(cycleIdx, currentAmPm)` is within `hourLimit`. Iterates in `interval` steps to mirror + * the values actually rendered by `generate12HourCycleNumbers`. If no value in the column + * satisfies the limit, `rawCycleIdx` is returned unchanged. */ export const findNearestValidCycleIdx = ( rawCycleIdx: number, currentAmPm: number, - hourLimit: Limit | undefined + hourLimit: Limit | undefined, + interval = 1 ): number => { const resolved = resolveLimit(hourLimit); if (!resolved) return rawCycleIdx; @@ -29,7 +31,7 @@ export const findNearestValidCycleIdx = ( let best: number | null = null; let bestDistance = Infinity; - for (let i = 0; i <= 11; i++) { + for (let i = 0; i < 12; i += interval) { if (!isWithinLimit(combineToHour24(i, currentAmPm), resolved.min, resolved.max)) continue; const distance = Math.abs(i - rawCycleIdx); if (distance < bestDistance) { From c32deb0c11aebd34e88467f4f788ee917e2ecc15 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 5 May 2026 09:05:37 +0100 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Rename=20cycleIdx?= =?UTF-8?q?=20=E2=86=92=20hourSlot=20for=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cycleIdx was internal jargon. hourSlot reads naturally as 'a slot in the 12-hour clock-face column' where slot 0 is the 12 (noon/midnight) position. splitHour24 returns { amPm, hourSlot }; findNearestValidHourSlot replaces findNearestValidCycleIdx; getValidHourSlot/isHourSlotDisabled in TimerPicker. --- src/components/PickerItem/PickerItem.tsx | 4 +- src/components/TimerPicker/TimerPicker.tsx | 28 ++++----- src/tests/PickerItem.test.tsx | 4 +- src/tests/snapSeparateAmPmHour.test.ts | 72 +++++++++++----------- src/utils/separateAmPmHour.ts | 23 +++---- src/utils/snapSeparateAmPmHour.ts | 23 +++---- 6 files changed, 78 insertions(+), 76 deletions(-) diff --git a/src/components/PickerItem/PickerItem.tsx b/src/components/PickerItem/PickerItem.tsx index 96841a4..0d1b21d 100644 --- a/src/components/PickerItem/PickerItem.tsx +++ b/src/components/PickerItem/PickerItem.tsx @@ -51,8 +51,8 @@ const PickerItem = React.memo( intItem = NaN; } } else if (is12HourPicker && separateAmPmPicker) { - // Hour column in clock-face form (12, 1, 2, ..., 11). The "12" slot represents - // the noon/midnight cycle index 0; every other display value matches its index. + // Hour column in clock-face form (12, 1, 2, ..., 11). The "12" slot is hourSlot 0 + // (noon/midnight); every other display value matches its slot index. const parsed = parseInt(item); intItem = isNaN(parsed) ? NaN : parsed === 12 ? 0 : parsed; } else if (is12HourPicker) { diff --git a/src/components/TimerPicker/TimerPicker.tsx b/src/components/TimerPicker/TimerPicker.tsx index f0b501d..f6729ad 100644 --- a/src/components/TimerPicker/TimerPicker.tsx +++ b/src/components/TimerPicker/TimerPicker.tsx @@ -12,7 +12,7 @@ import { View } from "react-native"; import { getSafeInitialValue } from "../../utils/getSafeInitialValue"; import { isWithinLimit } from "../../utils/isWithinLimit"; import { combineToHour24, splitHour24 } from "../../utils/separateAmPmHour"; -import { findNearestValidCycleIdx } from "../../utils/snapSeparateAmPmHour"; +import { findNearestValidHourSlot } from "../../utils/snapSeparateAmPmHour"; import DurationScroll from "../DurationScroll"; import type { DurationScrollRef } from "../DurationScroll"; import { generateStyles } from "./styles"; @@ -170,7 +170,7 @@ const TimerPicker = forwardRef((props, ref) => const [selectedDays, setSelectedDays] = useState(safeInitialValue.days); const [selectedHours, setSelectedHours] = useState( - useSeparateAmPm ? initialHourSplit.cycleIdx : safeInitialValue.hours + useSeparateAmPm ? initialHourSplit.hourSlot : safeInitialValue.hours ); const [selectedAmPm, setSelectedAmPm] = useState(initialHourSplit.amPm); const [selectedMinutes, setSelectedMinutes] = useState(safeInitialValue.minutes); @@ -197,15 +197,15 @@ const TimerPicker = forwardRef((props, ref) => // currently selected AM/PM. These callbacks fold the cross-column context in; // DurationScroll uses `getValidValue` for momentum-scroll snapping and `isItemDisabled` // for per-row greying via PickerItem. - const getValidHourCycleIdx = (rawCycleIdx: number) => - findNearestValidCycleIdx(rawCycleIdx, selectedAmPm, hourLimit, hourInterval); + const getValidHourSlot = (rawHourSlot: number) => + findNearestValidHourSlot(rawHourSlot, selectedAmPm, hourLimit, hourInterval); - const isHourCycleDisabled = (cycleIdx: number) => { + const isHourSlotDisabled = (hourSlot: number) => { if (!hourLimit || (hourLimit.min === undefined && hourLimit.max === undefined)) { return false; } return !isWithinLimit( - combineToHour24(cycleIdx, selectedAmPm), + combineToHour24(hourSlot, selectedAmPm), hourLimit.min ?? 0, hourLimit.max ?? 23 ); @@ -216,9 +216,9 @@ const TimerPicker = forwardRef((props, ref) => const combinedHoursLatestDuration = useMemo<{ readonly current: number }>( () => ({ get current() { - const cycleIdx = hoursDurationScrollRef.current?.latestDuration.current ?? 0; + const hourSlot = hoursDurationScrollRef.current?.latestDuration.current ?? 0; const amPm = amPmDurationScrollRef.current?.latestDuration.current ?? 0; - return combineToHour24(cycleIdx, amPm); + return combineToHour24(hourSlot, amPm); }, }), [] @@ -236,7 +236,7 @@ const TimerPicker = forwardRef((props, ref) => reset: (options) => { setSelectedDays(safeInitialValue.days); if (useSeparateAmPm) { - setSelectedHours(initialHourSplit.cycleIdx); + setSelectedHours(initialHourSplit.hourSlot); setSelectedAmPm(initialHourSplit.amPm); } else { setSelectedHours(safeInitialValue.hours); @@ -257,9 +257,9 @@ const TimerPicker = forwardRef((props, ref) => if (value.hours !== undefined) { if (useSeparateAmPm) { const split = splitHour24(value.hours); - setSelectedHours(split.cycleIdx); + setSelectedHours(split.hourSlot); setSelectedAmPm(split.amPm); - hoursDurationScrollRef.current?.setValue(split.cycleIdx, options); + hoursDurationScrollRef.current?.setValue(split.hourSlot, options); amPmDurationScrollRef.current?.setValue(split.amPm, options); } else { setSelectedHours(value.hours); @@ -312,12 +312,12 @@ const TimerPicker = forwardRef((props, ref) => amLabel={amLabel} decelerationRate={decelerationRate} disableInfiniteScroll={disableInfiniteScroll} - getValidValue={useSeparateAmPm ? getValidHourCycleIdx : undefined} - initialValue={useSeparateAmPm ? initialHourSplit.cycleIdx : safeInitialValue.hours} + getValidValue={useSeparateAmPm ? getValidHourSlot : undefined} + initialValue={useSeparateAmPm ? initialHourSplit.hourSlot : safeInitialValue.hours} interval={hourInterval} is12HourPicker={use12HourPicker} isDisabled={hoursPickerIsDisabled} - isItemDisabled={useSeparateAmPm ? isHourCycleDisabled : undefined} + isItemDisabled={useSeparateAmPm ? isHourSlotDisabled : undefined} label={hourLabel ?? (!use12HourPicker ? "h" : undefined)} limit={useSeparateAmPm ? undefined : hourLimit} maximumValue={useSeparateAmPm ? 11 : maximumHours} diff --git a/src/tests/PickerItem.test.tsx b/src/tests/PickerItem.test.tsx index 7da8ade..db51a18 100644 --- a/src/tests/PickerItem.test.tsx +++ b/src/tests/PickerItem.test.tsx @@ -236,7 +236,7 @@ describe("PickerItem", () => { }); describe("greying via isItemDisabled callback", () => { - it("calls isItemDisabled with the parsed cycleIdx ('12' → 0)", () => { + it("calls isItemDisabled with the parsed hourSlot ('12' → 0)", () => { const isItemDisabled = jest.fn((value: number) => value === 0); const { getByText } = renderItem({ adjustedLimitedMax: 11, @@ -250,7 +250,7 @@ describe("PickerItem", () => { expect(isDisabledStyle(getByText("12"))).toBe(true); }); - it("calls isItemDisabled with the parsed cycleIdx ('05' → 5)", () => { + it("calls isItemDisabled with the parsed hourSlot ('05' → 5)", () => { const isItemDisabled = jest.fn(() => false); const { getByText } = renderItem({ adjustedLimitedMax: 11, diff --git a/src/tests/snapSeparateAmPmHour.test.ts b/src/tests/snapSeparateAmPmHour.test.ts index 8dafeb0..0826f0d 100644 --- a/src/tests/snapSeparateAmPmHour.test.ts +++ b/src/tests/snapSeparateAmPmHour.test.ts @@ -1,14 +1,14 @@ -import { findNearestValidCycleIdx } from "../utils/snapSeparateAmPmHour"; +import { findNearestValidHourSlot } from "../utils/snapSeparateAmPmHour"; -describe("findNearestValidCycleIdx", () => { +describe("findNearestValidHourSlot", () => { describe("no limit", () => { - it.each([0, 5, 11])("returns the input cycleIdx %i unchanged", (cycleIdx) => { - expect(findNearestValidCycleIdx(cycleIdx, 0, undefined)).toBe(cycleIdx); - expect(findNearestValidCycleIdx(cycleIdx, 1, undefined)).toBe(cycleIdx); + it.each([0, 5, 11])("returns the input hourSlot %i unchanged", (hourSlot) => { + expect(findNearestValidHourSlot(hourSlot, 0, undefined)).toBe(hourSlot); + expect(findNearestValidHourSlot(hourSlot, 1, undefined)).toBe(hourSlot); }); it("treats an empty Limit object as no limit", () => { - expect(findNearestValidCycleIdx(7, 0, {})).toBe(7); + expect(findNearestValidHourSlot(7, 0, {})).toBe(7); }); }); @@ -22,18 +22,18 @@ describe("findNearestValidCycleIdx", () => { [9, 9], [10, 10], [11, 11], - ])("AM cycleIdx %i snaps to %i", (input, expected) => { - expect(findNearestValidCycleIdx(input, 0, limit)).toBe(expected); + ])("AM hourSlot %i snaps to %i", (input, expected) => { + expect(findNearestValidHourSlot(input, 0, limit)).toBe(expected); }); it.each([ [0, 0], // 12 PM (=12) is valid [1, 1], // 1 PM = 13, valid [5, 5], // 5 PM = 17, valid - [6, 5], // 6 PM = 18, invalid → nearest valid PM cycleIdx is 5 + [6, 5], // 6 PM = 18, invalid → nearest valid PM hourSlot is 5 [11, 5], - ])("PM cycleIdx %i snaps to %i", (input, expected) => { - expect(findNearestValidCycleIdx(input, 1, limit)).toBe(expected); + ])("PM hourSlot %i snaps to %i", (input, expected) => { + expect(findNearestValidHourSlot(input, 1, limit)).toBe(expected); }); }); @@ -46,69 +46,69 @@ describe("findNearestValidCycleIdx", () => { [6, 5], // 6 AM = 6, invalid → nearest valid AM is 5 [10, 5], [11, 5], - ])("AM cycleIdx %i snaps to %i", (input, expected) => { - expect(findNearestValidCycleIdx(input, 0, limit)).toBe(expected); + ])("AM hourSlot %i snaps to %i", (input, expected) => { + expect(findNearestValidHourSlot(input, 0, limit)).toBe(expected); }); it.each([ - [0, 8], // 12 PM = 12, invalid → nearest valid PM is cycleIdx 8 (= 20) + [0, 8], // 12 PM = 12, invalid → nearest valid PM is hourSlot 8 (= 20) [7, 8], [8, 8], // 8 PM = 20, valid [11, 11], // 11 PM = 23, valid - ])("PM cycleIdx %i snaps to %i", (input, expected) => { - expect(findNearestValidCycleIdx(input, 1, limit)).toBe(expected); + ])("PM hourSlot %i snaps to %i", (input, expected) => { + expect(findNearestValidHourSlot(input, 1, limit)).toBe(expected); }); }); describe("limit excludes one full half", () => { - it("returns input unchanged when no AM cycleIdx is valid (limit forces PM)", () => { + it("returns input unchanged when no AM hourSlot is valid (limit forces PM)", () => { // limit { min: 13, max: 17 } means only PM hours 13-17 are valid - // No AM cycleIdx (which gives 0 or 1-11) can land in [13, 17]. - expect(findNearestValidCycleIdx(5, 0, { max: 17, min: 13 })).toBe(5); + // No AM hourSlot (which gives 0 or 1-11) can land in [13, 17]. + expect(findNearestValidHourSlot(5, 0, { max: 17, min: 13 })).toBe(5); }); - it("returns input unchanged when no PM cycleIdx is valid (limit forces AM)", () => { + it("returns input unchanged when no PM hourSlot is valid (limit forces AM)", () => { // limit { min: 1, max: 5 } means only AM hours 1-5 are valid; no PM combo works. - expect(findNearestValidCycleIdx(7, 1, { max: 5, min: 1 })).toBe(7); + expect(findNearestValidHourSlot(7, 1, { max: 5, min: 1 })).toBe(7); }); }); it("respects limit with only min set (max defaults to 23)", () => { - expect(findNearestValidCycleIdx(0, 0, { min: 12 })).toBe(0); // No AM works; falls through to input - expect(findNearestValidCycleIdx(0, 1, { min: 12 })).toBe(0); // 12 PM = 12, valid + expect(findNearestValidHourSlot(0, 0, { min: 12 })).toBe(0); // No AM works; falls through to input + expect(findNearestValidHourSlot(0, 1, { min: 12 })).toBe(0); // 12 PM = 12, valid }); it("respects limit with only max set (min defaults to 0)", () => { - expect(findNearestValidCycleIdx(0, 0, { max: 5 })).toBe(0); // 12 AM = 0, valid - expect(findNearestValidCycleIdx(11, 0, { max: 5 })).toBe(5); + expect(findNearestValidHourSlot(0, 0, { max: 5 })).toBe(0); // 12 AM = 0, valid + expect(findNearestValidHourSlot(11, 0, { max: 5 })).toBe(5); }); describe("with hourInterval", () => { // generate12HourCycleNumbers iterates `i = 0; i < 12; i += interval`. The snap - // helper must mirror that so it never returns a cycleIdx not present in the column. + // helper must mirror that so it never returns an hourSlot not present in the column. it.each([ - // interval=2 → cycle indices [0, 2, 4, 6, 8, 10] - [2, 0, 0, { max: 17, min: 9 }, 10], // raw 0 AM invalid; nearest valid AM cycleIdx is 10 + // interval=2 → hour slots [0, 2, 4, 6, 8, 10] + [2, 0, 0, { max: 17, min: 9 }, 10], // raw 0 AM invalid; nearest valid AM hourSlot is 10 [2, 5, 0, { max: 17, min: 9 }, 10], // raw 5 (not in column) invalid; nearest is 10 [2, 4, 1, { max: 17, min: 9 }, 4], // raw 4 PM = 16, valid → unchanged - // interval=3 → cycle indices [0, 3, 6, 9] + // interval=3 → hour slots [0, 3, 6, 9] [3, 0, 0, { max: 17, min: 9 }, 9], // 12 AM invalid; only 9 AM in column is in range - [3, 6, 1, { max: 17, min: 9 }, 3], // 6 PM=18 invalid; nearest valid PM cycleIdx is 3 (3 PM=15) - // interval=4 → cycle indices [0, 4, 8] - [4, 0, 0, { max: 17, min: 9 }, 0], // no AM cycleIdx in column is valid → no snap (returns input) + [3, 6, 1, { max: 17, min: 9 }, 3], // 6 PM=18 invalid; nearest valid PM hourSlot is 3 (3 PM=15) + // interval=4 → hour slots [0, 4, 8] + [4, 0, 0, { max: 17, min: 9 }, 0], // no AM hourSlot in column is valid → no snap (returns input) [4, 0, 1, { max: 17, min: 9 }, 0], // 12 PM=12 IS in [9,17] → unchanged [4, 0, 1, { max: 17, min: 13 }, 4], // 12 PM=12 invalid in [13,17]; nearest PM is 4 (4 PM=16) ])("interval=%i, raw=%i, amPm=%i, limit=%p → %i", (interval, raw, amPm, limit, expected) => { - expect(findNearestValidCycleIdx(raw, amPm, limit, interval)).toBe(expected); + expect(findNearestValidHourSlot(raw, amPm, limit, interval)).toBe(expected); }); - it("never returns a cycleIdx outside the rendered set when called with a valid raw", () => { - // interval=2: rendered cycle indices are {0,2,4,6,8,10}. The raw cycleIdx in real + it("never returns an hourSlot outside the rendered set when called with a valid raw", () => { + // interval=2: rendered hour slots are {0,2,4,6,8,10}. The raw hourSlot in real // usage always comes from getDurationAndIndexFromScrollOffset (= multiple of interval). const limit = { max: 17, min: 9 }; const renderedSet = [0, 2, 4, 6, 8, 10]; for (const raw of renderedSet) { - const result = findNearestValidCycleIdx(raw, 0, limit, 2); + const result = findNearestValidHourSlot(raw, 0, limit, 2); expect(renderedSet.includes(result)).toBe(true); } }); diff --git a/src/utils/separateAmPmHour.ts b/src/utils/separateAmPmHour.ts index b212988..2508185 100644 --- a/src/utils/separateAmPmHour.ts +++ b/src/utils/separateAmPmHour.ts @@ -1,17 +1,18 @@ /** - * Helpers for the `separateAmPmPicker` representation. The hour column tracks a - * 0–11 cycle index (where 0 is the noon/midnight "12" slot) and the AM/PM column - * tracks 0 (AM) or 1 (PM). The public API always exposes 24-hour values. + * Helpers for the `separateAmPmPicker` representation. The hour column tracks an + * `hourSlot` index in [0, 11] where slot 0 is the noon/midnight "12" position and + * slots 1–11 display as themselves. The AM/PM column tracks 0 (AM) or 1 (PM). + * The public API always exposes 24-hour values. */ -export const splitHour24 = (hour24: number): { amPm: 0 | 1; cycleIdx: number } => { - if (hour24 === 0) return { amPm: 0, cycleIdx: 0 }; - if (hour24 === 12) return { amPm: 1, cycleIdx: 0 }; - if (hour24 < 12) return { amPm: 0, cycleIdx: hour24 }; - return { amPm: 1, cycleIdx: hour24 - 12 }; +export const splitHour24 = (hour24: number): { amPm: 0 | 1; hourSlot: number } => { + if (hour24 === 0) return { amPm: 0, hourSlot: 0 }; + if (hour24 === 12) return { amPm: 1, hourSlot: 0 }; + if (hour24 < 12) return { amPm: 0, hourSlot: hour24 }; + return { amPm: 1, hourSlot: hour24 - 12 }; }; -export const combineToHour24 = (cycleIdx: number, amPm: number): number => { - if (cycleIdx === 0) return amPm === 1 ? 12 : 0; - return cycleIdx + (amPm === 1 ? 12 : 0); +export const combineToHour24 = (hourSlot: number, amPm: number): number => { + if (hourSlot === 0) return amPm === 1 ? 12 : 0; + return hourSlot + (amPm === 1 ? 12 : 0); }; diff --git a/src/utils/snapSeparateAmPmHour.ts b/src/utils/snapSeparateAmPmHour.ts index d2b78d0..aa9831d 100644 --- a/src/utils/snapSeparateAmPmHour.ts +++ b/src/utils/snapSeparateAmPmHour.ts @@ -11,34 +11,35 @@ const resolveLimit = (limit: Limit | undefined): { max: number; min: number } | }; /** - * Returns the cycleIdx in the rendered hour column nearest to `rawCycleIdx` such that - * `(cycleIdx, currentAmPm)` is within `hourLimit`. Iterates in `interval` steps to mirror - * the values actually rendered by `generate12HourCycleNumbers`. If no value in the column - * satisfies the limit, `rawCycleIdx` is returned unchanged. + * Returns the hour slot in the rendered 12-hour clock-face column nearest to + * `rawHourSlot` such that `(hourSlot, currentAmPm)` is within `hourLimit`. Iterates + * in `interval` steps to mirror the values actually rendered by + * `generate12HourCycleNumbers`. If no value in the column satisfies the limit, + * `rawHourSlot` is returned unchanged. */ -export const findNearestValidCycleIdx = ( - rawCycleIdx: number, +export const findNearestValidHourSlot = ( + rawHourSlot: number, currentAmPm: number, hourLimit: Limit | undefined, interval = 1 ): number => { const resolved = resolveLimit(hourLimit); - if (!resolved) return rawCycleIdx; + if (!resolved) return rawHourSlot; - if (isWithinLimit(combineToHour24(rawCycleIdx, currentAmPm), resolved.min, resolved.max)) { - return rawCycleIdx; + if (isWithinLimit(combineToHour24(rawHourSlot, currentAmPm), resolved.min, resolved.max)) { + return rawHourSlot; } let best: number | null = null; let bestDistance = Infinity; for (let i = 0; i < 12; i += interval) { if (!isWithinLimit(combineToHour24(i, currentAmPm), resolved.min, resolved.max)) continue; - const distance = Math.abs(i - rawCycleIdx); + const distance = Math.abs(i - rawHourSlot); if (distance < bestDistance) { bestDistance = distance; best = i; } } - return best ?? rawCycleIdx; + return best ?? rawHourSlot; }; From bf917b6894c77d209368111bd32e8f17801b2306 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 31 Aug 2026 21:49:32 +0100 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=94=96=20Bump=20version=20to=202.7.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014JJ2weaFu4jhu2XirMkbWe --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eac866f..df193c3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/troberts-28" }, "license": "MIT", - "version": "2.6.3", + "version": "2.7.0", "main": "dist/commonjs/index.js", "module": "dist/module/index.js", "types": "dist/typescript/index.d.ts", From cd7009426f30ca18b8c620919df524597ee8bb38 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 31 Aug 2026 21:49:32 +0100 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=91=B7=20Run=20unit=20tests=20on=20PR?= =?UTF-8?q?s=20into=20develop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feature PRs target develop, but the workflow only listened for main, so they merged with no CI at all. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014JJ2weaFu4jhu2XirMkbWe --- .github/workflows/unit-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 16d8f6c..8739b45 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -3,6 +3,7 @@ on: pull_request: branches: - main + - develop types: - opened # When PR is first created - synchronize # When new commits are pushed to the PR From d9ca7245d4024825212890c69014578bd3f84dc7 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 31 Aug 2026 21:52:39 +0100 Subject: [PATCH 7/8] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20expo=20examp?= =?UTF-8?q?le=20to=20SDK=2057?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit expo 55 → 57, react-native 0.83.6 → 0.86.3, react 19.2.0 → 19.2.3, and the expo-* modules aligned via `expo install --fix` (which pins worklets to 0.10.1, the SDK 57 build, rather than latest 0.12.1). SDK 57 is a no-op upgrade; SDK 56 held the breaking changes. Of those, only one applies here: the top-level `splash` key is gone from the app config schema, so that config moves into an expo-splash-screen plugin entry. expo-doctor 21/21. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014JJ2weaFu4jhu2XirMkbWe --- examples/example-expo/app.json | 19 +- examples/example-expo/package.json | 17 +- yarn.lock | 1647 +++++++++++++++++----------- 3 files changed, 1032 insertions(+), 651 deletions(-) diff --git a/examples/example-expo/app.json b/examples/example-expo/app.json index 4a091df..429439a 100644 --- a/examples/example-expo/app.json +++ b/examples/example-expo/app.json @@ -4,11 +4,6 @@ "slug": "timer-picker-example", "version": "1.0.0", "icon": "./assets/icon.png", - "splash": { - "image": "./assets/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true @@ -23,7 +18,19 @@ "web": { "favicon": "./assets/favicon.png" }, - "plugins": ["expo-asset", "expo-font", "react-native-audio-api"], + "plugins": [ + "expo-asset", + "expo-font", + "react-native-audio-api", + [ + "expo-splash-screen", + { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + } + ] + ], "experiments": { "reactCompiler": true } diff --git a/examples/example-expo/package.json b/examples/example-expo/package.json index 8ebd052..b35ef95 100644 --- a/examples/example-expo/package.json +++ b/examples/example-expo/package.json @@ -13,15 +13,16 @@ "dependencies": { "@expo/vector-icons": "^15.1.1", "@react-native-masked-view/masked-view": "0.3.2", - "expo": "^55.0.19", - "expo-asset": "~55.0.16", - "expo-font": "~55.0.6", - "expo-haptics": "~55.0.14", - "expo-linear-gradient": "~55.0.13", - "react": "19.2.0", - "react-native": "0.83.6", + "expo": "~57.0.18", + "expo-asset": "~57.0.15", + "expo-font": "~57.0.2", + "expo-haptics": "~57.0.2", + "expo-linear-gradient": "~57.0.1", + "expo-splash-screen": "~57.0.8", + "react": "19.2.3", + "react-native": "0.86.3", "react-native-audio-api": "^0.12.1", - "react-native-worklets": "0.7.4" + "react-native-worklets": "0.10.1" }, "private": true } diff --git a/yarn.lock b/yarn.lock index 1911d61..06b1116 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,6 +37,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.29.7" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.27.2": version: 7.27.2 resolution: "@babel/compat-data@npm:7.27.2" @@ -44,6 +55,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/compat-data@npm:7.29.7" + checksum: 10c0/47913f05e08a45a1c9df38c02b4b49e391005085b489432647a1abe112e5d9c75e3be8ea5972b7f6da4ec5d1339922ceb9ea02b8a25d4ed1cb8636e5261f344e + languageName: node + linkType: hard + "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.14.0, @babel/core@npm:^7.18.5, @babel/core@npm:^7.20.0, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.2": version: 7.27.1 resolution: "@babel/core@npm:7.27.1" @@ -143,6 +161,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/generator@npm:7.29.8" + dependencies: + "@babel/parser": "npm:^7.29.8" + "@babel/types": "npm:^7.29.8" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/7b896696314a659652393b76d78276e236acd0f7fae40a9a1af7f01c76aeafc630dd0966aad6f9386d35d7c674a8e6e2d8e217c44d25fb11460e68afa9ba8441 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-annotate-as-pure@npm:7.27.1" @@ -152,12 +183,12 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.27.3": - version: 7.27.3 - resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" +"@babel/helper-annotate-as-pure@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.29.7" dependencies: - "@babel/types": "npm:^7.27.3" - checksum: 10c0/94996ce0a05b7229f956033e6dcd69393db2b0886d0db6aff41e704390402b8cdcca11f61449cb4f86cfd9e61b5ad3a73e4fa661eeed7846b125bd1c33dbc633 + "@babel/types": "npm:^7.29.7" + checksum: 10c0/c56536b52d17632d89d49db2063ed6102f0e3bbadf6a0ccb74e6599d6a77173b644c7fe8c3ef17c7a162709d55b75ee5145ef6db917d16ba7f375fbffcf2e942 languageName: node linkType: hard @@ -174,6 +205,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-compilation-targets@npm:7.29.7" + dependencies: + "@babel/compat-data": "npm:^7.29.7" + "@babel/helper-validator-option": "npm:^7.29.7" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/4c15fd4c69a0a7047799a28a88460c19cede0a0ee8af994ea169114986f4af48b92c7393a4a3fee0456c11a656eece3448a6ed06354453d6c27cccf17195453b + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-create-class-features-plugin@npm:7.27.1" @@ -191,6 +235,23 @@ __metadata: languageName: node linkType: hard +"@babel/helper-create-class-features-plugin@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-create-class-features-plugin@npm:7.29.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.29.7" + "@babel/helper-member-expression-to-functions": "npm:^7.29.7" + "@babel/helper-optimise-call-expression": "npm:^7.29.7" + "@babel/helper-replace-supers": "npm:^7.29.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/75f34905b5e708b473f1e9b33e07b2fcc8f4c60676df8bc74541bb91c77f387c32a948dd04d5071e469ba454d72d0a872e3ace40fbb1d1e7aaa8569efcf09ed4 + languageName: node + linkType: hard + "@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.27.1" @@ -235,6 +296,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-member-expression-to-functions@npm:7.27.1" @@ -245,6 +313,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/eef7940ce0797208854a5af1049a98fee9abbffb5c619640c69ff5a555f8e3552295bb18756490b02bc6af7df8c1babcb83f12203aac2deb9dfecfc78846e12d + languageName: node + linkType: hard + "@babel/helper-module-imports@npm:^7.25.9, @babel/helper-module-imports@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-module-imports@npm:7.27.1" @@ -255,6 +333,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-module-transforms@npm:7.27.1" @@ -281,6 +369,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-transforms@npm:7.29.7" + dependencies: + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ee5a2172c24a42be696836f4b0d947489c9729d8adf5821885cf77d1ad5333e3c447368e9a71f67df1099570490553dccf9f888ef0a92a48aa63cb086bd8c7e1 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" @@ -290,6 +391,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-optimise-call-expression@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-optimise-call-expression@npm:7.29.7" + dependencies: + "@babel/types": "npm:^7.29.7" + checksum: 10c0/fd0244b9bfbb487db02d59aa2703c6991d654ea5f3f39d912682842bdca2e87b5ae8643b0ce8069bf5fbee39d1aa9db7abefeb5e6ba1aa650dca12777cf5b7e2 + languageName: node + linkType: hard + "@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": version: 7.27.1 resolution: "@babel/helper-plugin-utils@npm:7.27.1" @@ -297,6 +407,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-plugin-utils@npm:7.29.7" + checksum: 10c0/380477a06133274a2759f9355929cb60a95e8b8fee624a1ae1fa349e1d1645b89daca456f72833f6d1062bffa12ee4271c5bf0cc5a61c0166cdc24c7591e2408 + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.18.9, @babel/helper-remap-async-to-generator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-remap-async-to-generator@npm:7.27.1" @@ -323,6 +440,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-replace-supers@npm:7.29.7" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.29.7" + "@babel/helper-optimise-call-expression": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/1c7ae37797f226e965ab85f6affa53d25a10c169c604a4daeb36f9df09e673471e6522f631c13761cf9fbafeca2ea14c241dea8d723a51039d561beb01d86ac4 + languageName: node + linkType: hard + "@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0, @babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1" @@ -333,6 +463,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8c59493621487fc491f27adfc200af82a6aca3b9a5511e4e6050f8716593b4b243472cb56c8d2016e828b7ae12d605a819205aa8600ca08ee291dcd58d65c832 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-string-parser@npm:7.27.1" @@ -340,6 +480,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-validator-identifier@npm:7.27.1" @@ -354,6 +501,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-validator-option@npm:7.27.1" @@ -361,6 +515,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-option@npm:7.29.7" + checksum: 10c0/d2a06c6d0ac40ba4a2f219fc2cab249c7a94bacdb2686273b7f9598571c908809b48468ff588915a346e6cc7296f60b581023d1d498b747fed06f779d335c2cc + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-wrap-function@npm:7.27.1" @@ -425,6 +586,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/parser@npm:7.29.8" + dependencies: + "@babel/types": "npm:^7.29.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/acc890c5e6a6dd40863a47b50bac111d7185ee6fbbe163ebe11d5214854ca2adb901462ad4d718a65090ef84bd2230e9e8ab45a2e0caccc685f1f57ab0bb1e28 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.27.1" @@ -750,6 +922,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-jsx@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/1736000de183538ba8eef34520105508860e48b0c763254ba9158af5e814ed8bbceeedbb4281fbda33de787ae5b3870e92f60c6ae7131e7d322e451d57387896 + languageName: node + linkType: hard + "@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" @@ -849,6 +1032,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-typescript@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-syntax-typescript@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c49883b0327e8683b770dc823205af5c697da216e590dcf5bf53f3f031e7e381de450b164f8f99853f0837a3de5cb793298e2be6697a0f6e452bb9dd34b5165e + languageName: node + linkType: hard + "@babel/plugin-syntax-unicode-sets-regex@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-syntax-unicode-sets-regex@npm:7.18.6" @@ -861,7 +1055,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:7.27.1, @babel/plugin-transform-arrow-functions@npm:^7.0.0, @babel/plugin-transform-arrow-functions@npm:^7.24.7, @babel/plugin-transform-arrow-functions@npm:^7.27.1": +"@babel/plugin-transform-arrow-functions@npm:^7.0.0, @babel/plugin-transform-arrow-functions@npm:^7.24.7, @babel/plugin-transform-arrow-functions@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" dependencies: @@ -920,7 +1114,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:7.27.1, @babel/plugin-transform-class-properties@npm:^7.25.4, @babel/plugin-transform-class-properties@npm:^7.27.1": +"@babel/plugin-transform-class-properties@npm:^7.25.4, @babel/plugin-transform-class-properties@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-class-properties@npm:7.27.1" dependencies: @@ -932,6 +1126,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-class-properties@npm:^7.28.6": + version: 7.29.7 + resolution: "@babel/plugin-transform-class-properties@npm:7.29.7" + dependencies: + "@babel/helper-create-class-features-plugin": "npm:^7.29.7" + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/c370700423439aa9f0c1f8c4b97f2ef7c2dc46a1b04ec3b10e83e6bae5e4e2159f56d8e4376c9d669b3cf827650cc3740170a36e3924e3e9970d27fd85f4e48a + languageName: node + linkType: hard + "@babel/plugin-transform-class-static-block@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-class-static-block@npm:7.27.1" @@ -944,22 +1150,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:7.28.4": - version: 7.28.4 - resolution: "@babel/plugin-transform-classes@npm:7.28.4" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.3" - "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/helper-replace-supers": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/76687ed37216ff012c599870dc00183fb716f22e1a02fe9481943664c0e4d0d88c3da347dc3fe290d4728f4d47cd594ffa621d23845e2bb8ab446e586308e066 - languageName: node - linkType: hard - "@babel/plugin-transform-classes@npm:^7.0.0, @babel/plugin-transform-classes@npm:^7.25.4, @babel/plugin-transform-classes@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-classes@npm:7.27.1" @@ -976,6 +1166,22 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-classes@npm:^7.28.6": + version: 7.29.7 + resolution: "@babel/plugin-transform-classes@npm:7.29.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.29.7" + "@babel/helper-compilation-targets": "npm:^7.29.7" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/helper-plugin-utils": "npm:^7.29.7" + "@babel/helper-replace-supers": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/53a55bc5348d82ca744dbfcfedf33ab79877e609a5308f43976de8c240bd09cee195a535bf54a01b28d7080eebe759735b1c6cf39f252eef469eefc1d838d2a2 + languageName: node + linkType: hard + "@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.24.7, @babel/plugin-transform-computed-properties@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-computed-properties@npm:7.27.1" @@ -1184,6 +1390,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-commonjs@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.29.7" + dependencies: + "@babel/helper-module-transforms": "npm:^7.29.7" + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/9791cb524438b2a8ba6cb8715788fa1e202fbecd4e76b3ccab0af0819fd69212b40ae30d72ac377012f7149889f7792ed8bf91e97bbe9113ca9641f8ad3bf332 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-systemjs@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-modules-systemjs@npm:7.27.1" @@ -1233,7 +1451,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.27.1": +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1" dependencies: @@ -1244,6 +1462,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.28.6": + version: 7.29.7 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/b0c186fe38bc66830e1be76f06fabbae8a655d3896a841ba5ffa12d6c40bb9c8a6ecd38a7e2196034b1d7470653109b1ceac1ef5e46a5fdc9291d14afa56e0d0 + languageName: node + linkType: hard + "@babel/plugin-transform-numeric-separator@npm:^7.24.7, @babel/plugin-transform-numeric-separator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-numeric-separator@npm:7.27.1" @@ -1292,7 +1521,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:7.27.1, @babel/plugin-transform-optional-chaining@npm:^7.24.8, @babel/plugin-transform-optional-chaining@npm:^7.27.1": +"@babel/plugin-transform-optional-chaining@npm:^7.24.8, @babel/plugin-transform-optional-chaining@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-optional-chaining@npm:7.27.1" dependencies: @@ -1304,6 +1533,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-optional-chaining@npm:^7.28.6": + version: 7.29.7 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/71feacf9a7083030f4c69bf4e91db75f2fceae28e58c58b63db040006d908e15bc1e85a464f24ad659f17702e91a64eabbff9f1dd555ba33d78bb1af8a1d697a + languageName: node + linkType: hard + "@babel/plugin-transform-parameters@npm:^7.0.0, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.24.7, @babel/plugin-transform-parameters@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-parameters@npm:7.27.1" @@ -1410,6 +1651,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx@npm:^7.28.6": + version: 7.29.7 + resolution: "@babel/plugin-transform-react-jsx@npm:7.29.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.29.7" + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-plugin-utils": "npm:^7.29.7" + "@babel/plugin-syntax-jsx": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/ae6487c3deafcffda8a2c1b1eb77e0b7121630fa1a9646cecd73dbbdd8271532a0f7f0e8c01f69b6d39a36d8d79eb67e2d51031369c9055f18f7d8e70d9b5446 + languageName: node + linkType: hard + "@babel/plugin-transform-react-pure-annotations@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.27.1" @@ -1472,7 +1728,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:7.27.1, @babel/plugin-transform-shorthand-properties@npm:^7.0.0, @babel/plugin-transform-shorthand-properties@npm:^7.24.7, @babel/plugin-transform-shorthand-properties@npm:^7.27.1": +"@babel/plugin-transform-shorthand-properties@npm:^7.0.0, @babel/plugin-transform-shorthand-properties@npm:^7.24.7, @babel/plugin-transform-shorthand-properties@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.27.1" dependencies: @@ -1506,7 +1762,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:7.27.1, @babel/plugin-transform-template-literals@npm:^7.0.0, @babel/plugin-transform-template-literals@npm:^7.27.1": +"@babel/plugin-transform-template-literals@npm:^7.0.0, @babel/plugin-transform-template-literals@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-template-literals@npm:7.27.1" dependencies: @@ -1543,6 +1799,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typescript@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-typescript@npm:7.29.7" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.29.7" + "@babel/helper-create-class-features-plugin": "npm:^7.29.7" + "@babel/helper-plugin-utils": "npm:^7.29.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.29.7" + "@babel/plugin-syntax-typescript": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/8bf6a89c6827af6f11d4b189f1f97a64b8d754cc4caa5cbae16a6a8b113294d7f310dd40efd82e87ebcff2a1c683584b872d6d8960abe88d48f441d42f94c4d1 + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-escapes@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-unicode-escapes@npm:7.27.1" @@ -1566,7 +1837,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:7.27.1, @babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.24.7, @babel/plugin-transform-unicode-regex@npm:^7.27.1": +"@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.24.7, @babel/plugin-transform-unicode-regex@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-unicode-regex@npm:7.27.1" dependencies: @@ -1695,7 +1966,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:^7.17.12, @babel/preset-react@npm:^7.22.15": +"@babel/preset-react@npm:^7.17.12": version: 7.27.1 resolution: "@babel/preset-react@npm:7.27.1" dependencies: @@ -1711,7 +1982,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:7.27.1, @babel/preset-typescript@npm:^7.13.0, @babel/preset-typescript@npm:^7.17.12, @babel/preset-typescript@npm:^7.23.0": +"@babel/preset-typescript@npm:^7.13.0, @babel/preset-typescript@npm:^7.17.12, @babel/preset-typescript@npm:^7.23.0": version: 7.27.1 resolution: "@babel/preset-typescript@npm:7.27.1" dependencies: @@ -1726,6 +1997,21 @@ __metadata: languageName: node linkType: hard +"@babel/preset-typescript@npm:^7.28.5": + version: 7.29.7 + resolution: "@babel/preset-typescript@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.29.7" + "@babel/helper-validator-option": "npm:^7.29.7" + "@babel/plugin-syntax-jsx": "npm:^7.29.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.29.7" + "@babel/plugin-transform-typescript": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/40746a23a7ab46c0beb1c02d69883d9ffbe3043685cb3ae5363644391376b9261189fdad317191349e322c2c9bc550b031daa42470a1f8987362e4c56e492194 + languageName: node + linkType: hard + "@babel/register@npm:^7.13.16": version: 7.28.3 resolution: "@babel/register@npm:7.28.3" @@ -1777,6 +2063,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 + languageName: node + linkType: hard + "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.27.1": version: 7.27.1 resolution: "@babel/traverse@npm:7.27.1" @@ -1822,6 +2119,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.29.7": + version: 7.29.8 + resolution: "@babel/traverse@npm:7.29.8" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.8" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.8" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.8" + debug: "npm:^4.3.1" + checksum: 10c0/87a28989c434add26d787776ac6d30f749b89cb030f2a605c89f671a516a6fa165ac0476f07e2b69feed70128b2734cae1cbbf41dfe95ccf22081bd8f8b91923 + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.2, @babel/types@npm:^7.27.1, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.27.1 resolution: "@babel/types@npm:7.27.1" @@ -1842,7 +2154,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.27.3, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": +"@babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": version: 7.29.0 resolution: "@babel/types@npm:7.29.0" dependencies: @@ -1852,6 +2164,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.29.7, @babel/types@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/be7c279f0abf2a086c633e21b49c7ca80275d05283cc5a268b67a708c9914bd0c944f1422b3eb3cb37682a2af5d560abf520ccf9b01b53ecbfe6b71fbc3fdde6 + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -1979,34 +2301,36 @@ __metadata: languageName: node linkType: hard -"@expo/cli@npm:55.0.27": - version: 55.0.27 - resolution: "@expo/cli@npm:55.0.27" +"@expo/cli@npm:^57.0.20": + version: 57.0.20 + resolution: "@expo/cli@npm:57.0.20" dependencies: "@expo/code-signing-certificates": "npm:^0.0.6" - "@expo/config": "npm:~55.0.15" - "@expo/config-plugins": "npm:~55.0.8" + "@expo/config": "npm:~57.0.9" + "@expo/config-plugins": "npm:~57.0.9" "@expo/devcert": "npm:^1.2.1" - "@expo/env": "npm:~2.1.1" - "@expo/image-utils": "npm:^0.8.13" - "@expo/json-file": "npm:^10.0.13" - "@expo/log-box": "npm:55.0.11" - "@expo/metro": "npm:~55.1.1" - "@expo/metro-config": "npm:~55.0.18" - "@expo/osascript": "npm:^2.4.2" - "@expo/package-manager": "npm:^1.10.4" - "@expo/plist": "npm:^0.5.2" - "@expo/prebuild-config": "npm:^55.0.16" - "@expo/require-utils": "npm:^55.0.4" - "@expo/router-server": "npm:^55.0.15" - "@expo/schema-utils": "npm:^55.0.3" - "@expo/spawn-async": "npm:^1.7.2" - "@expo/ws-tunnel": "npm:^1.0.1" - "@expo/xcpretty": "npm:^4.4.0" - "@react-native/dev-middleware": "npm:0.83.6" + "@expo/env": "npm:~2.4.3" + "@expo/image-utils": "npm:^0.11.5" + "@expo/inline-modules": "npm:^0.1.7" + "@expo/json-file": "npm:^11.0.1" + "@expo/log-box": "npm:^57.0.4" + "@expo/metro": "npm:~56.0.2" + "@expo/metro-config": "npm:~57.0.12" + "@expo/metro-file-map": "npm:^57.0.2" + "@expo/osascript": "npm:^2.7.1" + "@expo/package-manager": "npm:^1.13.1" + "@expo/plist": "npm:^0.8.1" + "@expo/prebuild-config": "npm:^57.0.15" + "@expo/require-utils": "npm:^57.0.5" + "@expo/router-server": "npm:^57.0.8" + "@expo/schema-utils": "npm:^57.0.2" + "@expo/spawn-async": "npm:^1.8.0" + "@expo/ws-tunnel": "npm:^2.0.0" + "@expo/xcpretty": "npm:^4.4.4" + "@react-native/dev-middleware": "npm:0.86.3" accepts: "npm:^1.3.8" + agent-cli-detector: "npm:0.1.6" arg: "npm:^5.0.2" - better-opn: "npm:~3.0.2" bplist-creator: "npm:0.1.0" bplist-parser: "npm:^0.3.1" chalk: "npm:^4.0.0" @@ -2015,24 +2339,24 @@ __metadata: connect: "npm:^3.7.0" debug: "npm:^4.3.4" dnssd-advertise: "npm:^1.1.4" - expo-server: "npm:^55.0.8" + expo-server: "npm:^57.0.3" fetch-nodeshim: "npm:^0.4.10" getenv: "npm:^2.0.0" glob: "npm:^13.0.0" lan-network: "npm:^0.2.1" - multitars: "npm:^1.0.0" + multitars: "npm:^1.0.2" node-forge: "npm:^1.3.3" npm-package-arg: "npm:^11.0.0" ora: "npm:^3.4.0" - picomatch: "npm:^4.0.3" + picomatch: "npm:^4.0.4" pretty-format: "npm:^29.7.0" progress: "npm:^2.0.3" prompts: "npm:^2.3.2" resolve-from: "npm:^5.0.0" + sandbox-cli-detector: "npm:^0.2.0" semver: "npm:^7.6.0" send: "npm:^0.19.0" slugify: "npm:^1.3.4" - source-map-support: "npm:~0.5.21" stacktrace-parser: "npm:^0.1.10" structured-headers: "npm:^0.4.1" terminal-link: "npm:^2.1.1" @@ -2050,8 +2374,8 @@ __metadata: react-native: optional: true bin: - expo-internal: build/bin/cli - checksum: 10c0/5d4ec33050ee6d5e2a010f8462fa0e59b4d143c6f12f165b9dee03232316b6d8c9e5fc7b672456a8c58a2f17bded4aeacaa2e113f067d497039f701741dee774 + expo-internal: main.js + checksum: 10c0/aa76d20848de6ca1d8c0f2cc7795284d72e3a9183a3a74d68ec24478e87f179d76cd7fd75e8d4b93a811d4d2815a1bf385b257398249afa6d6ffd218b5790b2f languageName: node linkType: hard @@ -2064,49 +2388,49 @@ __metadata: languageName: node linkType: hard -"@expo/config-plugins@npm:~55.0.8": - version: 55.0.8 - resolution: "@expo/config-plugins@npm:55.0.8" +"@expo/config-plugins@npm:~57.0.9": + version: 57.0.9 + resolution: "@expo/config-plugins@npm:57.0.9" dependencies: - "@expo/config-types": "npm:^55.0.5" - "@expo/json-file": "npm:~10.0.13" - "@expo/plist": "npm:^0.5.2" + "@expo/config-types": "npm:^57.0.2" + "@expo/json-file": "npm:~11.0.1" + "@expo/plist": "npm:^0.8.1" + "@expo/require-utils": "npm:^57.0.5" "@expo/sdk-runtime-versions": "npm:^1.0.0" chalk: "npm:^4.1.2" debug: "npm:^4.3.5" getenv: "npm:^2.0.0" glob: "npm:^13.0.0" - resolve-from: "npm:^5.0.0" semver: "npm:^7.5.4" slugify: "npm:^1.6.6" xcode: "npm:^3.0.1" xml2js: "npm:0.6.0" - checksum: 10c0/14f15495ea885c295dbb4df6d0c88753099601b109489d8bdb7fd75caa52a47349edde11265fc5af26e48680cf5b54ccccffe7305cacf22542f24519bb13de58 + checksum: 10c0/a980ceea445860797dd8d5f446b9dd1710b4101a0d9ac362157c9af2f5777ed7365c09c13f2fd42c00252060105e92e44a390841c294d52dbcb0362cac21c5c0 languageName: node linkType: hard -"@expo/config-types@npm:^55.0.5": - version: 55.0.5 - resolution: "@expo/config-types@npm:55.0.5" - checksum: 10c0/24ce0481cc465ddd3b53cfdde099ef4e899b1f8fff224a0f249b88c93e6c98930e99a55f3929eb53d08138b1b66102ece7b76e16f4e5fadcdf5bbac26c9c3d7e +"@expo/config-types@npm:^57.0.2": + version: 57.0.2 + resolution: "@expo/config-types@npm:57.0.2" + checksum: 10c0/3e3a8abcab03791dfccc758bde5ce85f0fa4b14c22df072e6cc72c670945fc65433239bf2ce39f86bbda422b338d19323612d61eabb2442f988713214837a6dc languageName: node linkType: hard -"@expo/config@npm:~55.0.15": - version: 55.0.15 - resolution: "@expo/config@npm:55.0.15" +"@expo/config@npm:~57.0.9": + version: 57.0.9 + resolution: "@expo/config@npm:57.0.9" dependencies: - "@expo/config-plugins": "npm:~55.0.8" - "@expo/config-types": "npm:^55.0.5" - "@expo/json-file": "npm:^10.0.13" - "@expo/require-utils": "npm:^55.0.4" + "@expo/config-plugins": "npm:~57.0.9" + "@expo/config-types": "npm:^57.0.2" + "@expo/json-file": "npm:^11.0.1" + "@expo/require-utils": "npm:^57.0.5" deepmerge: "npm:^4.3.1" getenv: "npm:^2.0.0" glob: "npm:^13.0.0" resolve-workspace-root: "npm:^2.0.0" semver: "npm:^7.6.0" slugify: "npm:^1.3.4" - checksum: 10c0/8b8f03f375f3a1f1528eda9ba99ccd0ef7d0929034e52f8f6e607e679b56cb53911b78c19b7d57f5ad8fabab4ec38e2e2cd423d97c818c6086e49473d07b187b + checksum: 10c0/b70700a5bdf405c7b71878f5c4549f034a0080ba427069089326017e5b777cbdddaf9abf094aa0de83c80202fea479078fe452ae3ac11c3b75ca7922376ac409 languageName: node linkType: hard @@ -2120,9 +2444,9 @@ __metadata: languageName: node linkType: hard -"@expo/devtools@npm:55.0.2": - version: 55.0.2 - resolution: "@expo/devtools@npm:55.0.2" +"@expo/devtools@npm:~57.0.1": + version: 57.0.1 + resolution: "@expo/devtools@npm:57.0.1" dependencies: chalk: "npm:^4.1.2" peerDependencies: @@ -2133,38 +2457,45 @@ __metadata: optional: true react-native: optional: true - checksum: 10c0/f247e2a5d2c3129d8b0dbee6daa4a5bca5103d5e3a177257522a89661deb598d84af9805d302cc0af166635604a57fa73b38ff1304b5921b17d0bd372b459686 + checksum: 10c0/bf3a24db47725ebeb79adbb95c3cddb71ea26e0040e80ef9aeb50b538417581765617a8fa6c5c63b78cb1d633548da8fc926d892331515e8fb180e99831f0156 languageName: node linkType: hard -"@expo/dom-webview@npm:^55.0.5": - version: 55.0.5 - resolution: "@expo/dom-webview@npm:55.0.5" +"@expo/dom-webview@npm:^57.0.1, @expo/dom-webview@npm:~57.0.1": + version: 57.0.1 + resolution: "@expo/dom-webview@npm:57.0.1" peerDependencies: expo: "*" react: "*" react-native: "*" - checksum: 10c0/d5904a55bbc1a5489e64e754e4161d230b674cc1a3c3e73a604b52c7f9be0c9d6bcd26caf79106118f9d50576b97ff0121545017a5b6547d0046207075feee84 + checksum: 10c0/22c2868e9c6c2eea2f607b1f9b4afc966b4fba3a03a6b05cabe9c8c51540fae4252f60ff171ce406ebcccca8b3e5931d07d0dd04c777020669a1dd11d5cca8a6 languageName: node linkType: hard -"@expo/env@npm:^2.0.11, @expo/env@npm:~2.1.1": - version: 2.1.1 - resolution: "@expo/env@npm:2.1.1" +"@expo/env@npm:^2.4.3, @expo/env@npm:~2.4.3": + version: 2.4.3 + resolution: "@expo/env@npm:2.4.3" dependencies: chalk: "npm:^4.0.0" debug: "npm:^4.3.4" getenv: "npm:^2.0.0" - checksum: 10c0/c863fb05f16e0ffaac10ba0e5f632472c94ff755e5bfea1ce31820a17efc21dc932ccf8d307793187c752e85e151fe0579cc9038db5abc12f4b650174b182cbe + checksum: 10c0/36eb2dacda0765eca69b3df7aaa3e5a74fb05561f5e223ca19a376a70ed18934edb1782345299fb9b026f0d1e4773f047835f1c16035151c1a76a1e373b29412 + languageName: node + linkType: hard + +"@expo/expo-modules-macros-plugin@npm:0.6.1": + version: 0.6.1 + resolution: "@expo/expo-modules-macros-plugin@npm:0.6.1" + checksum: 10c0/1cad524b3468650f1504c09f300b2be2f469b2e997788d7d6a443a1d13944bae679f6976fc1d13d4ffa485ccc5ddb562919dca79aa1d3b46e6adaa78cb787842 languageName: node linkType: hard -"@expo/fingerprint@npm:0.16.6": - version: 0.16.6 - resolution: "@expo/fingerprint@npm:0.16.6" +"@expo/fingerprint@npm:^0.20.11": + version: 0.20.11 + resolution: "@expo/fingerprint@npm:0.20.11" dependencies: - "@expo/env": "npm:^2.0.11" - "@expo/spawn-async": "npm:^1.7.2" + "@expo/env": "npm:^2.4.3" + "@expo/spawn-async": "npm:^1.8.0" arg: "npm:^5.0.2" chalk: "npm:^4.1.2" debug: "npm:^4.3.4" @@ -2176,197 +2507,222 @@ __metadata: semver: "npm:^7.6.0" bin: fingerprint: bin/cli.js - checksum: 10c0/18f597e71aa2fa75ca72c59f81a825df5a0262b27d82b948b2d2a6edd11fa7a3a7f6daccef9d4d92224577631a1a15afd02ce7e0568a8a6db4738704706906fe + checksum: 10c0/f7cb53fb208069d13041f6c1dc9f40eda25bec60b535ff88d9f2d531c3f752da96a24cb925d53f70f7a22b4f90b11fcc2560f4f7c2b6b654e6860a6f8bb91218 languageName: node linkType: hard -"@expo/image-utils@npm:^0.8.13": - version: 0.8.13 - resolution: "@expo/image-utils@npm:0.8.13" +"@expo/image-utils@npm:^0.11.5": + version: 0.11.5 + resolution: "@expo/image-utils@npm:0.11.5" dependencies: - "@expo/require-utils": "npm:^55.0.4" - "@expo/spawn-async": "npm:^1.7.2" + "@expo/require-utils": "npm:^57.0.5" + "@expo/spawn-async": "npm:^1.8.0" chalk: "npm:^4.0.0" getenv: "npm:^2.0.0" jimp-compact: "npm:0.16.1" parse-png: "npm:^2.1.0" semver: "npm:^7.6.0" - checksum: 10c0/eabd8b1578b11a6ba5a0c310a770e16611eebda932a1ad7f4dc4f37c0710c7029df927e309272c896d8f23b509b31cbd3bca5fee19f05dd169b44fabd3c764c5 + checksum: 10c0/bca05d61164c88eafcbb01e82eea92679b9fcbc435ad50f0b8f7b0c74259ea50a7e7535d5403ea747be241c9560b04be66df8f3e0f926a6da1e5a1954a41a45d + languageName: node + linkType: hard + +"@expo/inline-modules@npm:^0.1.7": + version: 0.1.7 + resolution: "@expo/inline-modules@npm:0.1.7" + dependencies: + "@expo/config-plugins": "npm:~57.0.9" + checksum: 10c0/3dff95ce7d61915a20687cf4d3112acfa1c5865d2ca6fa814783355fb4220f074344718c81ec319849f3d9a8e2b942ca540ba7ed1a4377af844b7e6637da2191 languageName: node linkType: hard -"@expo/json-file@npm:^10.0.13, @expo/json-file@npm:~10.0.13": - version: 10.0.13 - resolution: "@expo/json-file@npm:10.0.13" +"@expo/json-file@npm:^11.0.1, @expo/json-file@npm:~11.0.1": + version: 11.0.1 + resolution: "@expo/json-file@npm:11.0.1" dependencies: "@babel/code-frame": "npm:^7.20.0" json5: "npm:^2.2.3" - checksum: 10c0/81f2073aea613264c0c802b13cfe6b5fdda2469ceabdff105a69e2ba3bc72c1b82f8b3c1c7eb6c9e5a7c4322239e6adcbb37cd3ddb305ed2de3e18e962d57887 + checksum: 10c0/b93e8bf0654c7d417bf00a418932a3f676bc24bed75ff63a8c683229e2d1f3b7a48206fc3af7db307ed93a776adab51cb62ebb6a0ed77f86067c7ae85cc2f74d languageName: node linkType: hard -"@expo/local-build-cache-provider@npm:55.0.11": - version: 55.0.11 - resolution: "@expo/local-build-cache-provider@npm:55.0.11" +"@expo/local-build-cache-provider@npm:^57.0.8": + version: 57.0.8 + resolution: "@expo/local-build-cache-provider@npm:57.0.8" dependencies: - "@expo/config": "npm:~55.0.15" + "@expo/config": "npm:~57.0.9" chalk: "npm:^4.1.2" - checksum: 10c0/56b6d0507e17efcff2ce3d4c827e297642e7870f3bb61b16abe1c534f3207121579e00b29d900d18fb94a191649049114779d070b352ba2dac3480afecad8cc1 + checksum: 10c0/9750ae6dbf91a0fc5eb3ffef7f2974c1193a9c212e5cbf80fee78d777c1545cf0445544ea7bc61f0f77767dea7c40b263643caf0d75e54dc84aa9139eaa40795 languageName: node linkType: hard -"@expo/log-box@npm:55.0.11": - version: 55.0.11 - resolution: "@expo/log-box@npm:55.0.11" +"@expo/log-box@npm:^57.0.4": + version: 57.0.4 + resolution: "@expo/log-box@npm:57.0.4" dependencies: - "@expo/dom-webview": "npm:^55.0.5" + "@expo/dom-webview": "npm:^57.0.1" anser: "npm:^1.4.9" stacktrace-parser: "npm:^0.1.10" peerDependencies: - "@expo/dom-webview": ^55.0.5 + "@expo/dom-webview": ^57.0.1 expo: "*" react: "*" react-native: "*" - checksum: 10c0/8a4b53b87def9b4dad0f00821d20f749138431047129c11d5d49217d193745eb080efd51442e97809c2c1f5785ee9282cfc57c09e991a3d02bb1c210b479663f + checksum: 10c0/750b3482d657f450d545130ffe8c3b4e8f860561ad55f75f3058d1e08e0b37c16d512470a52d33cac1d459bbfb695784b199acd15134ec4309bc67287d88709f languageName: node linkType: hard -"@expo/metro-config@npm:55.0.18, @expo/metro-config@npm:~55.0.18": - version: 55.0.18 - resolution: "@expo/metro-config@npm:55.0.18" +"@expo/metro-config@npm:~57.0.12": + version: 57.0.12 + resolution: "@expo/metro-config@npm:57.0.12" dependencies: "@babel/code-frame": "npm:^7.20.0" "@babel/core": "npm:^7.20.0" "@babel/generator": "npm:^7.20.5" - "@expo/config": "npm:~55.0.15" - "@expo/env": "npm:~2.1.1" - "@expo/json-file": "npm:~10.0.13" - "@expo/metro": "npm:~55.1.1" - "@expo/spawn-async": "npm:^1.7.2" + "@expo/config": "npm:~57.0.9" + "@expo/env": "npm:~2.4.3" + "@expo/json-file": "npm:~11.0.1" + "@expo/metro": "npm:~56.0.2" + "@expo/require-utils": "npm:^57.0.5" + "@expo/spawn-async": "npm:^1.8.0" + "@jridgewell/gen-mapping": "npm:^0.3.13" + "@jridgewell/remapping": "npm:^2.3.5" + "@jridgewell/sourcemap-codec": "npm:^1.5.5" browserslist: "npm:^4.25.0" chalk: "npm:^4.1.0" debug: "npm:^4.3.2" getenv: "npm:^2.0.0" glob: "npm:^13.0.0" - hermes-parser: "npm:^0.32.0" + hermes-parser: "npm:^0.36.0" jsc-safe-url: "npm:^0.2.4" lightningcss: "npm:^1.30.1" - picomatch: "npm:^4.0.3" - postcss: "npm:~8.4.32" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.14" resolve-from: "npm:^5.0.0" peerDependencies: expo: "*" peerDependenciesMeta: expo: optional: true - checksum: 10c0/82a043eebf328ef4cd9f8d6c512cac4659decbde16da29c163c5190733ecb80323dc9732698af9823af11f2152757d43f25e5dc62936fb5e0c10c4930778b80f + checksum: 10c0/eeb34426026d7b1188ba528c4e2c3c1f1023d9a2b0af192b120c6bcf0c89aa9d2c9c6af1086a02a05444a21a5eac4b2bdd89e11288abceacd7debd380edd864a + languageName: node + linkType: hard + +"@expo/metro-file-map@npm:^57.0.2": + version: 57.0.2 + resolution: "@expo/metro-file-map@npm:57.0.2" + dependencies: + debug: "npm:^4.3.4" + fb-watchman: "npm:^2.0.2" + invariant: "npm:^2.2.4" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + checksum: 10c0/01d716fb3fb52c75b1a94e51207401cd69568447def7fb962e4dfece5141c528d726f0a2d4ac5e0d4d080f67a9bd7aa3b932cac8111f5a7c91be6e8b2913dc01 languageName: node linkType: hard -"@expo/metro@npm:~55.1.1": - version: 55.1.1 - resolution: "@expo/metro@npm:55.1.1" +"@expo/metro@npm:~56.0.2": + version: 56.0.2 + resolution: "@expo/metro@npm:56.0.2" dependencies: - metro: "npm:0.83.7" - metro-babel-transformer: "npm:0.83.7" - metro-cache: "npm:0.83.7" - metro-cache-key: "npm:0.83.7" - metro-config: "npm:0.83.7" - metro-core: "npm:0.83.7" - metro-file-map: "npm:0.83.7" - metro-minify-terser: "npm:0.83.7" - metro-resolver: "npm:0.83.7" - metro-runtime: "npm:0.83.7" - metro-source-map: "npm:0.83.7" - metro-symbolicate: "npm:0.83.7" - metro-transform-plugins: "npm:0.83.7" - metro-transform-worker: "npm:0.83.7" - checksum: 10c0/325166a4ad7de0af29746575c8c48466a61c74b711f5a7a77965058b8a3ba41f31cdd66aa3e32b66c94d58e1b4958d4b5e72abc284cee6b88753ce4cbe0c7cbf + metro: "npm:0.84.5" + metro-babel-transformer: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-cache-key: "npm:0.84.5" + metro-config: "npm:0.84.5" + metro-core: "npm:0.84.5" + metro-file-map: "npm:0.84.5" + metro-minify-terser: "npm:0.84.5" + metro-resolver: "npm:0.84.5" + metro-runtime: "npm:0.84.5" + metro-source-map: "npm:0.84.5" + metro-symbolicate: "npm:0.84.5" + metro-transform-plugins: "npm:0.84.5" + metro-transform-worker: "npm:0.84.5" + checksum: 10c0/5cf6b2ffe125d6d54138757e8fada0d88515ee0ec2113595ca224babf8baf95b866e12eba51cdb1d89aae4476a7982a653e46ea2091b22e1433984ae608cc380 languageName: node linkType: hard -"@expo/osascript@npm:^2.4.2": - version: 2.4.2 - resolution: "@expo/osascript@npm:2.4.2" +"@expo/osascript@npm:^2.7.1": + version: 2.7.1 + resolution: "@expo/osascript@npm:2.7.1" dependencies: - "@expo/spawn-async": "npm:^1.7.2" - checksum: 10c0/80adc04b4a6f0695d00a88dcfe3336b395d6431fdccb9e8316c2ec1819ae6524a7063d7c8f4da7f1f3718e57637204c62c2383b7488b0008410efeb7108aa00f + "@expo/spawn-async": "npm:^1.8.0" + checksum: 10c0/45223563531d300e4e7d7035278ea1a1ffc7410dbfa5a1f6297847732061ee971f92a89d8f733cdfe306eaddc43f288fe3231a3a9bb9e66a226bc4ebd990bf2c languageName: node linkType: hard -"@expo/package-manager@npm:^1.10.4": - version: 1.10.4 - resolution: "@expo/package-manager@npm:1.10.4" +"@expo/package-manager@npm:^1.13.1": + version: 1.13.1 + resolution: "@expo/package-manager@npm:1.13.1" dependencies: - "@expo/json-file": "npm:^10.0.13" - "@expo/spawn-async": "npm:^1.7.2" + "@expo/json-file": "npm:^11.0.1" + "@expo/spawn-async": "npm:^1.8.0" chalk: "npm:^4.0.0" npm-package-arg: "npm:^11.0.0" ora: "npm:^3.4.0" resolve-workspace-root: "npm:^2.0.0" - checksum: 10c0/a7b2971082a0c0323706da87dc579c252c5ffbdc10eefb08899bd323abb8be3a2482f512dab54de36952be770fb96a9b557a662db73b1dc61fb4f75ca31e7acc + checksum: 10c0/a9e24519c85046ca159d944d1051647159f96165fb423bf1f9d02c32abe95a46a81b7c8a2ce7f012bb84c5baf1a71500f07f6c57422b95d079dfb4f58d7e9e75 languageName: node linkType: hard -"@expo/plist@npm:^0.5.2": - version: 0.5.2 - resolution: "@expo/plist@npm:0.5.2" +"@expo/plist@npm:^0.8.1": + version: 0.8.1 + resolution: "@expo/plist@npm:0.8.1" dependencies: "@xmldom/xmldom": "npm:^0.8.8" base64-js: "npm:^1.5.1" xmlbuilder: "npm:^15.1.1" - checksum: 10c0/19adae2a365ac1a12db93682fb310ff8be03c711f9173bebe5841cbe60cdfb749247bc1a95fa0977b5bac3aa6a078a0fceeafe4ff6c66d1ed67cce496679e310 + checksum: 10c0/45f130f3d5155d46dc415ab61b97449d5cc7bf47e41ed37965f12e50cc9728aa7c4da83fc958a8d7113e31e4342baebc1b2517de7e20186f3e64d53be378a9ce languageName: node linkType: hard -"@expo/prebuild-config@npm:^55.0.16": - version: 55.0.16 - resolution: "@expo/prebuild-config@npm:55.0.16" +"@expo/prebuild-config@npm:^57.0.15": + version: 57.0.15 + resolution: "@expo/prebuild-config@npm:57.0.15" dependencies: - "@expo/config": "npm:~55.0.15" - "@expo/config-plugins": "npm:~55.0.8" - "@expo/config-types": "npm:^55.0.5" - "@expo/image-utils": "npm:^0.8.13" - "@expo/json-file": "npm:^10.0.13" - "@react-native/normalize-colors": "npm:0.83.6" + "@expo/config": "npm:~57.0.9" + "@expo/config-plugins": "npm:~57.0.9" + "@expo/config-types": "npm:^57.0.2" + "@expo/image-utils": "npm:^0.11.5" + "@expo/json-file": "npm:^11.0.1" + "@react-native/normalize-colors": "npm:0.86.3" debug: "npm:^4.3.1" + expo-modules-autolinking: "npm:~57.0.12" resolve-from: "npm:^5.0.0" semver: "npm:^7.6.0" - xml2js: "npm:0.6.0" - peerDependencies: - expo: "*" - checksum: 10c0/85b8f89c2c15c7383d460ff561b1923f0d981e004a1a5edd9d97051e60ea6dd9eac093881e494c82837688f9878e0eb69a7c571235375bcf70db9dbc563dfb72 + checksum: 10c0/a11790363b067159a5b046d45965672a5f3f925ddd1d9004c0536dbd9bc71f20464e9385b79abd12f8475b7d48bf57735a7672e477d99e54758bdc5eb4ecee7a languageName: node linkType: hard -"@expo/require-utils@npm:^55.0.4": - version: 55.0.4 - resolution: "@expo/require-utils@npm:55.0.4" +"@expo/require-utils@npm:^57.0.5": + version: 57.0.5 + resolution: "@expo/require-utils@npm:57.0.5" dependencies: "@babel/code-frame": "npm:^7.20.0" "@babel/core": "npm:^7.25.2" "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" peerDependencies: - typescript: ^5.0.0 || ^5.0.0-0 + typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/2bd6ed23a117ca4538f8972313ff201de6dffc196b65c0322b2f078f98a439eaaa1104a06b2dd8152f88b52abbe741acc8abc20c9820dcf275409f6c387b865d + checksum: 10c0/42841b76d6540098b664bee633e6ef9825ac84041e7c91045bd15a84d6aed4f2ad1f5027da263755a1e2dc2190283e7b7d6c4fb9ec3335c7a87016606b443754 languageName: node linkType: hard -"@expo/router-server@npm:^55.0.15": - version: 55.0.15 - resolution: "@expo/router-server@npm:55.0.15" +"@expo/router-server@npm:^57.0.8": + version: 57.0.8 + resolution: "@expo/router-server@npm:57.0.8" dependencies: debug: "npm:^4.3.4" peerDependencies: - "@expo/metro-runtime": ^55.0.10 + "@expo/metro-runtime": ^57.0.14 expo: "*" - expo-constants: ^55.0.15 - expo-font: ^55.0.6 + expo-constants: ^57.0.15 + expo-font: ^57.0.1 expo-router: "*" - expo-server: ^55.0.8 + expo-server: ^57.0.3 react: "*" react-dom: "*" react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 @@ -2379,14 +2735,14 @@ __metadata: optional: true react-server-dom-webpack: optional: true - checksum: 10c0/cb432c8d561866d71ed167569b2522b070c42cdab9fa47ee4913d5bbef8315a1b9f639885c3b9dcf364bda13eaeb51edba0afe2a2ae2c03a5796f6f2b965cedc + checksum: 10c0/7483da2e525e16e6d34c620a084c464f0c6450d6a3933c5a7539e5898d83c9f95d594ab8018271c549a7826bc74aa7637bf9d56e9d25fca78055a379c9ac4d04 languageName: node linkType: hard -"@expo/schema-utils@npm:^55.0.3": - version: 55.0.3 - resolution: "@expo/schema-utils@npm:55.0.3" - checksum: 10c0/517c35966d8ce94b55ee913d7b6876039f51b52706b59aa10891224c71edab885f02004e174b2986f891e132871e82ad5c73a4a2bfd9412488dce756c6f9b5bc +"@expo/schema-utils@npm:^57.0.2": + version: 57.0.2 + resolution: "@expo/schema-utils@npm:57.0.2" + checksum: 10c0/5438d6dd1cb58c35c1202cefd7bb44f38eb7078da08b706849154bd6d97f0a9db7a41862110e34d7e111edf50ac44ffe132b0af64ebfdcce8203eb8e5ecd51b9 languageName: node linkType: hard @@ -2397,12 +2753,12 @@ __metadata: languageName: node linkType: hard -"@expo/spawn-async@npm:^1.7.2": - version: 1.7.2 - resolution: "@expo/spawn-async@npm:1.7.2" +"@expo/spawn-async@npm:^1.8.0": + version: 1.8.0 + resolution: "@expo/spawn-async@npm:1.8.0" dependencies: - cross-spawn: "npm:^7.0.3" - checksum: 10c0/0548c4e95ee39393c2f3919bc605f21eba4f0a8ba66fa82fbbc4b1b624e0054526918489227b924f03af5bc156a011f39a2472c223c0d2237fb7afd8dedd5357 + cross-spawn: "npm:^7.0.6" + checksum: 10c0/08d3c63f9cc097ce9c8cf6850ca482fd7999a6fddc4cb38a3a9915a1662cb674fe7353de2eb3c693728542bf57db732ae433e82b2d698be141d07cea3092ebf3 languageName: node linkType: hard @@ -2413,7 +2769,7 @@ __metadata: languageName: node linkType: hard -"@expo/vector-icons@npm:^15.0.2, @expo/vector-icons@npm:^15.1.1": +"@expo/vector-icons@npm:^15.1.1": version: 15.1.1 resolution: "@expo/vector-icons@npm:15.1.1" peerDependencies: @@ -2424,23 +2780,25 @@ __metadata: languageName: node linkType: hard -"@expo/ws-tunnel@npm:^1.0.1": - version: 1.0.6 - resolution: "@expo/ws-tunnel@npm:1.0.6" - checksum: 10c0/050eb7fbd54b636c97c818e7ec5402ce616cae655290386a51600b200947e281cdd12d182251c07fab449e11a732135d61429b738cd03945e94757061e652ecd +"@expo/ws-tunnel@npm:^2.0.0": + version: 2.0.0 + resolution: "@expo/ws-tunnel@npm:2.0.0" + peerDependencies: + ws: ^8.0.0 + checksum: 10c0/5668ffcb3525f98339f1eac579267483771788f98a216ecf6d4c4a106c62ed4e4501ab64ded530a14b865f1b014bb7631dd2c243b08b91e425b422cfee9d0fdf languageName: node linkType: hard -"@expo/xcpretty@npm:^4.4.0": - version: 4.4.3 - resolution: "@expo/xcpretty@npm:4.4.3" +"@expo/xcpretty@npm:^4.4.4": + version: 4.4.4 + resolution: "@expo/xcpretty@npm:4.4.4" dependencies: "@babel/code-frame": "npm:^7.20.0" chalk: "npm:^4.1.0" js-yaml: "npm:^4.1.0" bin: excpretty: build/cli.js - checksum: 10c0/dd1af095a22851669a43e40579f7565a69172d7fa6f3987f600c51346dd16b700e03ccc852d11ecf4692a7aff4493a6ece2094d02766a17b6ceb860d6d4f98e3 + checksum: 10c0/cd555ad49438dee2cc3f2950ecbef3048f7169bbdadc8db169cfcddaad13668fee6377c010624ed4079dc439c46d6023d2551da403a2070deec000bc864e8dd8 languageName: node linkType: hard @@ -2822,7 +3180,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.12": +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.13": version: 0.3.13 resolution: "@jridgewell/gen-mapping@npm:0.3.13" dependencies: @@ -2891,6 +3249,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.6.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.6.0" + checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -3368,10 +3733,10 @@ __metadata: languageName: node linkType: hard -"@react-native/assets-registry@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/assets-registry@npm:0.83.6" - checksum: 10c0/bbb13196c8e49516ed48f19cdf48c6172f39e5972dc5eff7b9da5f6a25c45aead8d5458dcd6d5b5f88ee87718c33ef2dbeab307b88cb9e93b8cd800544087b93 +"@react-native/assets-registry@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/assets-registry@npm:0.86.3" + checksum: 10c0/7971ecc2f7ca8037ed9ae2eb36fd0d9c2f7abb08a14e4a6b79f92d5047c232a963001f516a859e284546cd406dd3dc35bb6d2f6a01a03552b790a2586507df54 languageName: node linkType: hard @@ -3392,13 +3757,13 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/babel-plugin-codegen@npm:0.83.6" +"@react-native/babel-plugin-codegen@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/babel-plugin-codegen@npm:0.86.3" dependencies: - "@babel/traverse": "npm:^7.25.3" - "@react-native/codegen": "npm:0.83.6" - checksum: 10c0/f07a93444d4ffdb38316d2b44d4e0978bd7b8864721d0d97bb74f9ca66bc76337908a8c761680c3031437ee3c3b304403355a75ecc7b0beddf932eb4ba4456c5 + "@babel/traverse": "npm:^7.29.0" + "@react-native/codegen": "npm:0.86.3" + checksum: 10c0/e381c2769e05308d85a5705e803387de5a61726c9dd36c48e84e916e790489f65fb8cb0024b257fca54146a03ef62ee96ef14391ea3370f8db77d59b204cb8d2 languageName: node linkType: hard @@ -3457,61 +3822,6 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-preset@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/babel-preset@npm:0.83.6" - dependencies: - "@babel/core": "npm:^7.25.2" - "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" - "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" - "@babel/plugin-transform-block-scoping": "npm:^7.25.0" - "@babel/plugin-transform-class-properties": "npm:^7.25.4" - "@babel/plugin-transform-classes": "npm:^7.25.4" - "@babel/plugin-transform-computed-properties": "npm:^7.24.7" - "@babel/plugin-transform-destructuring": "npm:^7.24.8" - "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" - "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.25.1" - "@babel/plugin-transform-literals": "npm:^7.25.2" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" - "@babel/plugin-transform-parameters": "npm:^7.24.7" - "@babel/plugin-transform-private-methods": "npm:^7.24.7" - "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" - "@babel/plugin-transform-react-display-name": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx": "npm:^7.25.2" - "@babel/plugin-transform-react-jsx-self": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" - "@babel/plugin-transform-regenerator": "npm:^7.24.7" - "@babel/plugin-transform-runtime": "npm:^7.24.7" - "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" - "@babel/plugin-transform-spread": "npm:^7.24.7" - "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" - "@babel/plugin-transform-typescript": "npm:^7.25.2" - "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/template": "npm:^7.25.0" - "@react-native/babel-plugin-codegen": "npm:0.83.6" - babel-plugin-syntax-hermes-parser: "npm:0.32.0" - babel-plugin-transform-flow-enums: "npm:^0.0.2" - react-refresh: "npm:^0.14.0" - peerDependencies: - "@babel/core": "*" - checksum: 10c0/304ef4b2554f290823ce01e226a2051814d304ec59e05cc1cd9adc1cedc07b58a4cc7f5f86940ec57b53af0269b288cf34b7cd5e2c8a05cdffed4719a909375e - languageName: node - linkType: hard - "@react-native/codegen@npm:0.79.2": version: 0.79.2 resolution: "@react-native/codegen@npm:0.79.2" @@ -3527,20 +3837,20 @@ __metadata: languageName: node linkType: hard -"@react-native/codegen@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/codegen@npm:0.83.6" +"@react-native/codegen@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/codegen@npm:0.86.3" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/parser": "npm:^7.25.3" - glob: "npm:^7.1.1" - hermes-parser: "npm:0.32.0" + "@babel/parser": "npm:^7.29.0" + hermes-parser: "npm:0.36.0" invariant: "npm:^2.2.4" nullthrows: "npm:^1.1.1" + tinyglobby: "npm:^0.2.15" yargs: "npm:^17.6.2" peerDependencies: "@babel/core": "*" - checksum: 10c0/7a3034ae8c1b7f5ce30434085e18ff9cc0faba2218d8d5ef20d1e6475e36a54f2b645e4a6760fda54cd8e061e59526fc16e7ed63e93f7cda5b43a90b4129c453 + checksum: 10c0/2dc1f49b342d009c2efd56d6d2839fdffac4afefba2e0f2159c8491ce48aef9eb60a0cec2fa88569f956eabd6915f47a5b27ad1cf43463a55e67b213edb84fe7 languageName: node linkType: hard @@ -3582,26 +3892,26 @@ __metadata: languageName: node linkType: hard -"@react-native/community-cli-plugin@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/community-cli-plugin@npm:0.83.6" +"@react-native/community-cli-plugin@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/community-cli-plugin@npm:0.86.3" dependencies: - "@react-native/dev-middleware": "npm:0.83.6" + "@react-native/dev-middleware": "npm:0.86.3" debug: "npm:^4.4.0" invariant: "npm:^2.2.4" - metro: "npm:^0.83.6" - metro-config: "npm:^0.83.6" - metro-core: "npm:^0.83.6" + metro: "npm:^0.84.3" + metro-config: "npm:^0.84.3" + metro-core: "npm:^0.84.3" semver: "npm:^7.1.3" peerDependencies: "@react-native-community/cli": "*" - "@react-native/metro-config": "*" + "@react-native/metro-config": 0.86.3 peerDependenciesMeta: "@react-native-community/cli": optional: true "@react-native/metro-config": optional: true - checksum: 10c0/efd6aa6e9f92a2da8a48021dffa5b8b2520986d8c8905a9a98ba5397cf101cffa0368a5fe074f279d26168a17de14a9e8dc6db89f5e19627904c23bf2e777d4c + checksum: 10c0/3fe9b5eef5e35a3c89f5659f9fd7c4429844c82c442f252d03195d60db67515a81deb2633711b8b4070cd479685e7dc6d62c4d2a7c4ef130b4ae17e2a00bc22f languageName: node linkType: hard @@ -3612,20 +3922,21 @@ __metadata: languageName: node linkType: hard -"@react-native/debugger-frontend@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/debugger-frontend@npm:0.83.6" - checksum: 10c0/7117317edf310dbded75065224ba55ed4a0939544cbcb5425520f41be52b771c394c01a19acdb077b7a2c4694809cdda6c63d1024fbf4125886f77ebca5428c2 +"@react-native/debugger-frontend@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/debugger-frontend@npm:0.86.3" + checksum: 10c0/377e3cb20097d6f298513f2ab4a98581c212cf7b54e50df85384bc5e6ae73a70f97d4e27047658abe60f39d3ab533e8904033678be732aff52a66355c7c8101d languageName: node linkType: hard -"@react-native/debugger-shell@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/debugger-shell@npm:0.83.6" +"@react-native/debugger-shell@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/debugger-shell@npm:0.86.3" dependencies: cross-spawn: "npm:^7.0.6" + debug: "npm:^4.4.0" fb-dotslash: "npm:0.5.8" - checksum: 10c0/ed0a55eb20ef92682b0a276f8c9f51f9b8a6bca6bcfab62f36c2deae2fa852eb7632c3e0f2d2d8ddb852e442cccdf8b96a1f2fc10075d22d7d1bf65756b6141c + checksum: 10c0/f6c0c7f15e9f9aed247d96be3bca5768f94a65cace549b62bf899c72e0aaff5a564b7898f3f63dc5e987728d4b73db2f786c4373e64272b029a43fef64153395 languageName: node linkType: hard @@ -3648,15 +3959,15 @@ __metadata: languageName: node linkType: hard -"@react-native/dev-middleware@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/dev-middleware@npm:0.83.6" +"@react-native/dev-middleware@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/dev-middleware@npm:0.86.3" dependencies: "@isaacs/ttlcache": "npm:^1.4.1" - "@react-native/debugger-frontend": "npm:0.83.6" - "@react-native/debugger-shell": "npm:0.83.6" + "@react-native/debugger-frontend": "npm:0.86.3" + "@react-native/debugger-shell": "npm:0.86.3" chrome-launcher: "npm:^0.15.2" - chromium-edge-launcher: "npm:^0.2.0" + chromium-edge-launcher: "npm:^0.3.0" connect: "npm:^3.6.5" debug: "npm:^4.4.0" invariant: "npm:^2.2.4" @@ -3664,7 +3975,7 @@ __metadata: open: "npm:^7.0.3" serve-static: "npm:^1.16.2" ws: "npm:^7.5.10" - checksum: 10c0/d2e6cddd66683e2a2bfd8bd8a867cb443d1b1850c0dc68da275d8baf7758e1672581c4a1d2f140f2fe67c5ee126f0afd43724fe5ca118d102feb8e89f6bba05a + checksum: 10c0/d313ed01d6e8e7ddff84fee8285a23138863486af02466fb7431891928cab3c51d90859ac883f64cd25051b90bba609990224e9f8d31e8be474f8355deec631d languageName: node linkType: hard @@ -3705,10 +4016,10 @@ __metadata: languageName: node linkType: hard -"@react-native/gradle-plugin@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/gradle-plugin@npm:0.83.6" - checksum: 10c0/77435dbb82228ad5ec73993727b1887c2184578f96a61282b522f3b2f4461b9442d019bec0b897bf5702ca2222037c088bf0f6d454043afda539048e85ac0727 +"@react-native/gradle-plugin@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/gradle-plugin@npm:0.86.3" + checksum: 10c0/ebbf2959e99990e97eb16892e6e9b7d9115b8710ce7759a33e7fe3b58610e451841ef4fa58b4820230680dd9af4cee6a39916952f2dfc5463396a4aad80f88f6 languageName: node linkType: hard @@ -3726,10 +4037,10 @@ __metadata: languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/js-polyfills@npm:0.83.6" - checksum: 10c0/ec0a1f75cfe28e0a35d320b43a1a9e61f2e035c0f953a3568de4cb9f869b0de7359241a0858e38accf41902db148a10ae08815f1bbb0e3fb797ebf3384502cba +"@react-native/js-polyfills@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/js-polyfills@npm:0.86.3" + checksum: 10c0/8b1fca9f5405cb556d28afb09fc6aa6f3117c1e550269ece8b4f8c61e9f13d278fc92d8d2e055663d5cdda9e7896be8761b3beb420c25fdaffec00e3540cd7b8 languageName: node linkType: hard @@ -3780,10 +4091,10 @@ __metadata: languageName: node linkType: hard -"@react-native/normalize-colors@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/normalize-colors@npm:0.83.6" - checksum: 10c0/51b6fce82aa92c17bb046e703bdec6bc70d26cb89ad57071deef5b2fe617fec288cebbe5e3da6511be1df182f622c3bd6b649a2a0c04071a0e2b07c991b6a276 +"@react-native/normalize-colors@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/normalize-colors@npm:0.86.3" + checksum: 10c0/33893681bdb3235d35dcb9f2fff5e1b2b0996be08c3cef24afed32c3a931298349ad5cbd33c3bb7fbf506f735d3e37995684d26ba9241795bbd3e709bd8d3165 languageName: node linkType: hard @@ -3818,20 +4129,20 @@ __metadata: languageName: node linkType: hard -"@react-native/virtualized-lists@npm:0.83.6": - version: 0.83.6 - resolution: "@react-native/virtualized-lists@npm:0.83.6" +"@react-native/virtualized-lists@npm:0.86.3": + version: 0.86.3 + resolution: "@react-native/virtualized-lists@npm:0.86.3" dependencies: invariant: "npm:^2.2.4" nullthrows: "npm:^1.1.1" peerDependencies: "@types/react": ^19.2.0 react: "*" - react-native: "*" + react-native: 0.86.3 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/ae6e3d861182421e9a08896ed7cf01e5b2dc00151cd93ed8b89ae1ac0af82f02c617e0a2df2572aa3a571f89607506f2b632f7650e4da6e8176edfdd79f9dbc4 + checksum: 10c0/eee86f802cda4e95dc414ff27f7283a505e19d58d2f00d1a577c0d6b33b3d25439d239ea3c24033dd5820cebf970162f458c710695991bfe5794b9f8f1129159 languageName: node linkType: hard @@ -4512,6 +4823,15 @@ __metadata: languageName: node linkType: hard +"agent-cli-detector@npm:0.1.6": + version: 0.1.6 + resolution: "agent-cli-detector@npm:0.1.6" + bin: + agent-cli-detector: dist/cli.js + checksum: 10c0/0653c78987a7382ebd6e19334a2c9362eae700486614354fcf4d10b173202997b7b330545391deab343fe830930ea1b1db90c0d2a5106e6e9a3254abeef6ed6d + languageName: node + linkType: hard + "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -4932,21 +5252,21 @@ __metadata: languageName: node linkType: hard -"babel-plugin-syntax-hermes-parser@npm:0.32.0": - version: 0.32.0 - resolution: "babel-plugin-syntax-hermes-parser@npm:0.32.0" +"babel-plugin-syntax-hermes-parser@npm:0.36.0": + version: 0.36.0 + resolution: "babel-plugin-syntax-hermes-parser@npm:0.36.0" dependencies: - hermes-parser: "npm:0.32.0" - checksum: 10c0/2e5aad897d4abd643d33329814ed7adb301047890a8a4325ef140da86e377a1127f1ce6af4064526e5cb603c16d3d3e15784998df4095f1385e7f4e8ca53f03e + hermes-parser: "npm:0.36.0" + checksum: 10c0/3c20d2b2595e890bf7bf14b48a1dc6eb2ad2625e04f942254f3676440e20489eb8266fba8f8077194d294dfb67552b6b969bb40b93c873b88fdc319528dc2606 languageName: node linkType: hard -"babel-plugin-syntax-hermes-parser@npm:^0.32.0": - version: 0.32.1 - resolution: "babel-plugin-syntax-hermes-parser@npm:0.32.1" +"babel-plugin-syntax-hermes-parser@npm:^0.36.0": + version: 0.36.1 + resolution: "babel-plugin-syntax-hermes-parser@npm:0.36.1" dependencies: - hermes-parser: "npm:0.32.1" - checksum: 10c0/b254a2a324cf823c9ec749de0019cf787d59102e9bdd79fc687937e631574ba44f7d249954e284997f1ada1a2b9a1ffa87bc10b16f7e81869b767f99a978b2cf + hermes-parser: "npm:0.36.1" + checksum: 10c0/d6343ef16032253408cc8f4585c7acc275101f3194c312606e0f0d65ccf768db36b74e907296a93f3844c108ed19776052d5e844cbd21ec18c43a23ce7c2055e languageName: node linkType: hard @@ -4991,37 +5311,56 @@ __metadata: languageName: node linkType: hard -"babel-preset-expo@npm:~55.0.19": - version: 55.0.19 - resolution: "babel-preset-expo@npm:55.0.19" +"babel-preset-expo@npm:~57.0.9": + version: 57.0.9 + resolution: "babel-preset-expo@npm:57.0.9" dependencies: "@babel/generator": "npm:^7.20.5" "@babel/helper-module-imports": "npm:^7.25.9" "@babel/plugin-proposal-decorators": "npm:^7.12.9" "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" + "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" + "@babel/plugin-transform-block-scoping": "npm:^7.25.0" + "@babel/plugin-transform-class-properties": "npm:^7.25.4" "@babel/plugin-transform-class-static-block": "npm:^7.27.1" + "@babel/plugin-transform-classes": "npm:^7.25.4" + "@babel/plugin-transform-destructuring": "npm:^7.24.8" "@babel/plugin-transform-export-namespace-from": "npm:^7.25.9" "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" + "@babel/plugin-transform-for-of": "npm:^7.24.7" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" "@babel/plugin-transform-parameters": "npm:^7.24.7" "@babel/plugin-transform-private-methods": "npm:^7.24.7" "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" + "@babel/plugin-transform-react-display-name": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx": "npm:^7.28.6" + "@babel/plugin-transform-react-jsx-development": "npm:^7.27.1" + "@babel/plugin-transform-react-pure-annotations": "npm:^7.27.1" "@babel/plugin-transform-runtime": "npm:^7.24.7" - "@babel/preset-react": "npm:^7.22.15" + "@babel/plugin-transform-typescript": "npm:^7.25.2" + "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" "@babel/preset-typescript": "npm:^7.23.0" - "@react-native/babel-preset": "npm:0.83.6" + "@react-native/babel-plugin-codegen": "npm:0.86.3" babel-plugin-react-compiler: "npm:^1.0.0" babel-plugin-react-native-web: "npm:~0.21.0" - babel-plugin-syntax-hermes-parser: "npm:^0.32.0" + babel-plugin-syntax-hermes-parser: "npm:^0.36.0" babel-plugin-transform-flow-enums: "npm:^0.0.2" debug: "npm:^4.3.4" - resolve-from: "npm:^5.0.0" peerDependencies: "@babel/runtime": ^7.20.0 expo: "*" - expo-widgets: ^55.0.15 + expo-widgets: ^57.0.13 react-refresh: ">=0.14.0 <1.0.0" peerDependenciesMeta: "@babel/runtime": @@ -5030,7 +5369,7 @@ __metadata: optional: true expo-widgets: optional: true - checksum: 10c0/693d2285709d7ac3aaa7f3f89772cea6c44c01b410245ccb6cc53a16e53efc4ed9088f4a9a4831b882c850068a5cb1aa3e829790cefa947f6a2094da57ba61db + checksum: 10c0/1938da42466b5e76650d1c5d3a45466303afef34081aca0e7bb0ed7d1b348ec76bf59151721db628bd5291a0ea2b705df5f0a2d825b4b1acda4680a3357c9e0d languageName: node linkType: hard @@ -5120,15 +5459,6 @@ __metadata: languageName: node linkType: hard -"better-opn@npm:~3.0.2": - version: 3.0.2 - resolution: "better-opn@npm:3.0.2" - dependencies: - open: "npm:^8.0.4" - checksum: 10c0/911ef25d44da75aabfd2444ce7a4294a8000ebcac73068c04a60298b0f7c7506b60421aa4cd02ac82502fb42baaff7e4892234b51e6923eded44c5a11185f2f5 - languageName: node - linkType: hard - "big-integer@npm:1.6.x": version: 1.6.52 resolution: "big-integer@npm:1.6.52" @@ -5461,6 +5791,19 @@ __metadata: languageName: node linkType: hard +"chromium-edge-launcher@npm:^0.3.0": + version: 0.3.0 + resolution: "chromium-edge-launcher@npm:0.3.0" + dependencies: + "@types/node": "npm:*" + escape-string-regexp: "npm:^4.0.0" + is-wsl: "npm:^2.2.0" + lighthouse-logger: "npm:^1.0.0" + mkdirp: "npm:^1.0.4" + checksum: 10c0/ad04a75bf53ebed0b7adc5bd133587369b0c2e55c92fe460eb6ccec5efe03c161a7466756173969867a2acbe02dd40449186bd74671dd892520492283d4ff43d + languageName: node + linkType: hard + "ci-info@npm:^2.0.0": version: 2.0.0 resolution: "ci-info@npm:2.0.0" @@ -5739,7 +6082,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:2.0.0, convert-source-map@npm:^2.0.0": +"convert-source-map@npm:^2.0.0": version: 2.0.0 resolution: "convert-source-map@npm:2.0.0" checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b @@ -5981,13 +6324,6 @@ __metadata: languageName: node linkType: hard -"define-lazy-prop@npm:^2.0.0": - version: 2.0.0 - resolution: "define-lazy-prop@npm:2.0.0" - checksum: 10c0/db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 - languageName: node - linkType: hard - "define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -6801,15 +7137,16 @@ __metadata: dependencies: "@expo/vector-icons": "npm:^15.1.1" "@react-native-masked-view/masked-view": "npm:0.3.2" - expo: "npm:^55.0.19" - expo-asset: "npm:~55.0.16" - expo-font: "npm:~55.0.6" - expo-haptics: "npm:~55.0.14" - expo-linear-gradient: "npm:~55.0.13" - react: "npm:19.2.0" - react-native: "npm:0.83.6" + expo: "npm:~57.0.18" + expo-asset: "npm:~57.0.15" + expo-font: "npm:~57.0.2" + expo-haptics: "npm:~57.0.2" + expo-linear-gradient: "npm:~57.0.1" + expo-splash-screen: "npm:~57.0.8" + react: "npm:19.2.3" + react-native: "npm:0.86.3" react-native-audio-api: "npm:^0.12.1" - react-native-worklets: "npm:0.7.4" + react-native-worklets: "npm:0.10.1" languageName: unknown linkType: soft @@ -6867,167 +7204,197 @@ __metadata: languageName: node linkType: hard -"expo-asset@npm:~55.0.16": - version: 55.0.16 - resolution: "expo-asset@npm:55.0.16" +"expo-asset@npm:~57.0.15": + version: 57.0.15 + resolution: "expo-asset@npm:57.0.15" dependencies: - "@expo/image-utils": "npm:^0.8.13" - expo-constants: "npm:~55.0.15" + "@expo/image-utils": "npm:^0.11.5" + expo-constants: "npm:~57.0.15" peerDependencies: expo: "*" react: "*" react-native: "*" - checksum: 10c0/3a6ea5f669841962eb5f822ea5b5796da53bbb94f63dd850258834741d5bc97237d55b3ffe19ab9ba7eef9ceb3b0fcf68643995a225013a994947bbef31c051c + checksum: 10c0/22d8580dc732274b2756cb50760ce8ecdf3566c81fb68c07927450dcbe660ef781a7584763b7afd13be1ba7e3d76cd768b0e319579a0a2cb78c01d493ed10327 languageName: node linkType: hard -"expo-constants@npm:~55.0.15": - version: 55.0.15 - resolution: "expo-constants@npm:55.0.15" +"expo-constants@npm:~57.0.15, expo-constants@npm:~57.0.16": + version: 57.0.16 + resolution: "expo-constants@npm:57.0.16" dependencies: - "@expo/env": "npm:~2.1.1" + "@expo/env": "npm:~2.4.3" peerDependencies: expo: "*" react-native: "*" - checksum: 10c0/3fdaa2e492fabf6e12c9a063f2e54f4027bed39a3d68480d66135fe396d951f6527b317e3074e668a3e71afe9806ee0ea3bd459f91b8934cb0ad122e41f4ffd0 + checksum: 10c0/2f6a2c87217b44e696dd2403de16ab9fee215495d9aaaf2e8b0089bd8283b584b663392488b70296e9d495e84700202b0a81937fa841f525d3e406f57aad5e31 languageName: node linkType: hard -"expo-file-system@npm:~55.0.17": - version: 55.0.17 - resolution: "expo-file-system@npm:55.0.17" +"expo-file-system@npm:~57.0.6": + version: 57.0.6 + resolution: "expo-file-system@npm:57.0.6" peerDependencies: expo: "*" react-native: "*" - checksum: 10c0/3a7019562460d23f07053439664fdbca32db333beeaa2fdb7fd9fd852e8126b253faa650d1a9d212b8841b79b821f597c66ccca63545b38adf2c6b956e892b66 + checksum: 10c0/7d1526333a39e9c3c9f6b7fb7f8e0974e22efc6110cc5da48ec517893511677e0836ea808979a2f56d0f2988036c39d0b3686a9e6a9188f7ad15c7c732974675 languageName: node linkType: hard -"expo-font@npm:~55.0.6": - version: 55.0.6 - resolution: "expo-font@npm:55.0.6" +"expo-font@npm:~57.0.2": + version: 57.0.2 + resolution: "expo-font@npm:57.0.2" dependencies: fontfaceobserver: "npm:^2.1.0" peerDependencies: expo: "*" react: "*" react-native: "*" - checksum: 10c0/accd823ca5ab4fb8ab3faf665414a22a0e991119ee2f99a35acf06b55b0dc0436d426515edf889114d1b7ebe5cd79094ef711ec052fc5f176e0abe56453d175d + checksum: 10c0/4d668f584282a7643903d7f486ea098436d25525c8ba7d9470c49bb31ce605dd290dc72fc5a03205939869c21a5435f1aeb522f125823f3f227691b8a8099b2a languageName: node linkType: hard -"expo-haptics@npm:~55.0.14": - version: 55.0.14 - resolution: "expo-haptics@npm:55.0.14" +"expo-haptics@npm:~57.0.2": + version: 57.0.2 + resolution: "expo-haptics@npm:57.0.2" peerDependencies: expo: "*" - checksum: 10c0/512f9c367f262bfa5f8646c41235b1b6a7b2745a522a583bf96f9576afb7ec04a4399850d783060d85695a03008050ca8dec2cefbd44b44a79b9b8a8ee07721a + checksum: 10c0/11ed5deb61a7e3791ce1161375f2a95f487578788fd8ebf84ef0ca42ea6d3efe6d315aa6c7d2f16bee0639f6583eb9407154e9e1afe4030e60d6e512993a5fcc languageName: node linkType: hard -"expo-keep-awake@npm:~55.0.7": - version: 55.0.7 - resolution: "expo-keep-awake@npm:55.0.7" +"expo-keep-awake@npm:~57.0.1": + version: 57.0.1 + resolution: "expo-keep-awake@npm:57.0.1" peerDependencies: expo: "*" react: "*" - checksum: 10c0/061f682d927b204d239e65b2b91e699fc7945cf1d5b6707413b061794573ade62560b55c2869a4cbd34fb96a7278bb9275583311ea3be4316ecf4b8470289fb3 + checksum: 10c0/bb95a181c3a808c3ee41df93909baae913e2226bbc9382c6712549d734cb684a8bb28026d3411c01ab25251798ab77617ce2a6b2ea98f986c0ddae2c74b2f89e languageName: node linkType: hard -"expo-linear-gradient@npm:~55.0.13": - version: 55.0.13 - resolution: "expo-linear-gradient@npm:55.0.13" +"expo-linear-gradient@npm:~57.0.1": + version: 57.0.1 + resolution: "expo-linear-gradient@npm:57.0.1" peerDependencies: expo: "*" react: "*" react-native: "*" - checksum: 10c0/e86517604ccef7aa71c2a72f8c055fe1bbc0393c4d14ae76b91fc7a6bd0a76f51980b19efb002f9190916ed809c8267f60aacd992b9a944634aba8acd070ed39 + checksum: 10c0/d52735f8d1e28142525032361a7881dd77455fe402941f5b35007f4f7c6bf6f3fe675b07e6499d3c52e10cf87c7a496405fb6353974f6575939e2beb02fed6ae languageName: node linkType: hard -"expo-modules-autolinking@npm:55.0.19": - version: 55.0.19 - resolution: "expo-modules-autolinking@npm:55.0.19" +"expo-modules-autolinking@npm:~57.0.12": + version: 57.0.12 + resolution: "expo-modules-autolinking@npm:57.0.12" dependencies: - "@expo/require-utils": "npm:^55.0.4" - "@expo/spawn-async": "npm:^1.7.2" + "@expo/require-utils": "npm:^57.0.5" + "@expo/spawn-async": "npm:^1.8.0" chalk: "npm:^4.1.0" commander: "npm:^7.2.0" bin: expo-modules-autolinking: bin/expo-modules-autolinking.js - checksum: 10c0/0056bfcfcf4d5ac450dfeb2da0d4931aa7f14a3da3566dee63af8265f58f80082fe09ebfbb3a60a4757ddf17bcddf14fddc090fa65d54720299fdc8b318bdfa4 + checksum: 10c0/9afa2687f7cfaa8206b3c66cb6ad210f7c723efcbd9a0d46fc9633cfcc33727869ddf96aa3bdb7d813a4357977abb6fdd5c27e083cfe203a5bb2677e480a1868 languageName: node linkType: hard -"expo-modules-core@npm:55.0.24": - version: 55.0.24 - resolution: "expo-modules-core@npm:55.0.24" +"expo-modules-core@npm:~57.0.14": + version: 57.0.14 + resolution: "expo-modules-core@npm:57.0.14" dependencies: + "@expo/expo-modules-macros-plugin": "npm:0.6.1" + expo-modules-jsi: "npm:~57.0.6" invariant: "npm:^2.2.4" peerDependencies: react: "*" react-native: "*" - react-native-worklets: ^0.7.4 || ^0.8.0 + react-native-worklets: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 peerDependenciesMeta: react-native-worklets: optional: true - checksum: 10c0/118f4edb578194f0a949162fd5c3ba3c58647d8bc36f26fc0447c43e9973aa259b9b5d5f1e1f1884f3085f26e898f2fbabf57c1441cd4ed7a817cf4faa864145 + checksum: 10c0/4a884257c3d83145e02aa242f167fae825f708423383934c6989dd3ca225f82b3f00a67087e73169d2314672d4a6df337fa0addb3b86cff055929452ca088978 + languageName: node + linkType: hard + +"expo-modules-jsi@npm:~57.0.6": + version: 57.0.6 + resolution: "expo-modules-jsi@npm:57.0.6" + peerDependencies: + react-native: "*" + checksum: 10c0/8816212b685f4c81b7ccd2775339cea914b4c18b357e55c70bff5a307a8510ef61860034c9b02e9db0f73991cbf1af1c1a7536cf82562cd47258a0b48a61f8ec + languageName: node + linkType: hard + +"expo-server@npm:^57.0.3": + version: 57.0.3 + resolution: "expo-server@npm:57.0.3" + checksum: 10c0/541d9b319ba513df718868c87a8d57e30161c3879b7d5b43f308923290ce5ee349a1b3469901c2f382984b6595f4cf6beaee9d870f5eb874c45ddc0a42a13e56 languageName: node linkType: hard -"expo-server@npm:^55.0.8": - version: 55.0.8 - resolution: "expo-server@npm:55.0.8" - checksum: 10c0/41b9c09ea7cdafcf2a82d81f471900c91c2826b4df798bd23b981ecf725a8aea9846ccd675ab5456c14cf617acc398c854c85df84abfc78a785be650154d5413 +"expo-splash-screen@npm:~57.0.8": + version: 57.0.8 + resolution: "expo-splash-screen@npm:57.0.8" + dependencies: + "@expo/config-plugins": "npm:~57.0.9" + "@expo/image-utils": "npm:^0.11.5" + xml2js: "npm:0.6.0" + peerDependencies: + expo: "*" + checksum: 10c0/70d7e785a10b9229158fd7cf9d2d6a0dfd74e2fb87d1025191cfcb62838981823e7222f0652fe1e1ff5e151e9bf2ecb1a2f33b53f1924bf028ee745e3fe0876d languageName: node linkType: hard -"expo@npm:^55.0.19": - version: 55.0.19 - resolution: "expo@npm:55.0.19" +"expo@npm:~57.0.18": + version: 57.0.18 + resolution: "expo@npm:57.0.18" dependencies: "@babel/runtime": "npm:^7.20.0" - "@expo/cli": "npm:55.0.27" - "@expo/config": "npm:~55.0.15" - "@expo/config-plugins": "npm:~55.0.8" - "@expo/devtools": "npm:55.0.2" - "@expo/fingerprint": "npm:0.16.6" - "@expo/local-build-cache-provider": "npm:55.0.11" - "@expo/log-box": "npm:55.0.11" - "@expo/metro": "npm:~55.1.1" - "@expo/metro-config": "npm:55.0.18" - "@expo/vector-icons": "npm:^15.0.2" + "@expo/cli": "npm:^57.0.20" + "@expo/config": "npm:~57.0.9" + "@expo/config-plugins": "npm:~57.0.9" + "@expo/devtools": "npm:~57.0.1" + "@expo/dom-webview": "npm:~57.0.1" + "@expo/fingerprint": "npm:^0.20.11" + "@expo/local-build-cache-provider": "npm:^57.0.8" + "@expo/log-box": "npm:^57.0.4" + "@expo/metro": "npm:~56.0.2" + "@expo/metro-config": "npm:~57.0.12" "@ungap/structured-clone": "npm:^1.3.0" - babel-preset-expo: "npm:~55.0.19" - expo-asset: "npm:~55.0.16" - expo-constants: "npm:~55.0.15" - expo-file-system: "npm:~55.0.17" - expo-font: "npm:~55.0.6" - expo-keep-awake: "npm:~55.0.7" - expo-modules-autolinking: "npm:55.0.19" - expo-modules-core: "npm:55.0.24" + babel-preset-expo: "npm:~57.0.9" + expo-asset: "npm:~57.0.15" + expo-constants: "npm:~57.0.16" + expo-file-system: "npm:~57.0.6" + expo-font: "npm:~57.0.2" + expo-keep-awake: "npm:~57.0.1" + expo-modules-autolinking: "npm:~57.0.12" + expo-modules-core: "npm:~57.0.14" pretty-format: "npm:^29.7.0" react-refresh: "npm:^0.14.2" - whatwg-url-minimum: "npm:^0.1.1" + whatwg-url-minimum: "npm:^0.1.2" peerDependencies: "@expo/dom-webview": "*" "@expo/metro-runtime": "*" react: "*" + react-dom: "*" react-native: "*" + react-native-web: "*" react-native-webview: "*" peerDependenciesMeta: "@expo/dom-webview": optional: true "@expo/metro-runtime": optional: true + react-dom: + optional: true + react-native-web: + optional: true react-native-webview: optional: true bin: expo: bin/cli expo-modules-autolinking: bin/autolinking fingerprint: bin/fingerprint - checksum: 10c0/bf28c00f3cc7399cb1c3d6b16b234ac0ada01c48e2ef0b9ce8f405ffbfe90fd53a74753fab89c4c73ceb4a6c5ca6a9c9747cee50432ba6324c9239fa74534b90 + checksum: 10c0/6bb2a3cca52ddca5975ad0ca31be951f3ef7776fda993c00b2dc6b8c6136f5ec067c68f36bc57bf97ed05d901c9d1687e3067e5e7dc7d01f6acd0f758360311b languageName: node linkType: hard @@ -7108,7 +7475,7 @@ __metadata: languageName: node linkType: hard -"fb-watchman@npm:^2.0.0": +"fb-watchman@npm:^2.0.0, fb-watchman@npm:^2.0.2": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" dependencies: @@ -7669,10 +8036,10 @@ __metadata: languageName: node linkType: hard -"hermes-compiler@npm:0.14.1": - version: 0.14.1 - resolution: "hermes-compiler@npm:0.14.1" - checksum: 10c0/223dc2c58fe7ad89a16519a3179fa924f577b1464fa89e2db7a375ce967d668595a50251e4e5756c7b274b062db7c697fcd601ba5f2e021e2e3557eee536f618 +"hermes-compiler@npm:250829098.0.17": + version: 250829098.0.17 + resolution: "hermes-compiler@npm:250829098.0.17" + checksum: 10c0/1bcfcb9268b18593e19e7d4e071dd644cbcff380535a74ea0318b417e30ba190b33e70086f8576c3b28deb2572ba01f57b1a9e5ae10f6841dedaff281b037200 languageName: node linkType: hard @@ -7690,24 +8057,24 @@ __metadata: languageName: node linkType: hard -"hermes-estree@npm:0.32.0": - version: 0.32.0 - resolution: "hermes-estree@npm:0.32.0" - checksum: 10c0/3b67d1fe44336240ef7f9c40ecbf363279ba263d51efe120570c3862cc109e652fc09aebddfe6b73d0f0246610bee130e4064c359f1f4cbf002bdb1d99717ef2 +"hermes-estree@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-estree@npm:0.35.0" + checksum: 10c0/a88c9dc63b8b3679b1aeb43e72e977597096c1bd7d59978c952f1d6df6d1a517c4a817c70b1b701854996b485adfa66c2fc7f80871029a7f0c04306f6717b59a languageName: node linkType: hard -"hermes-estree@npm:0.32.1": - version: 0.32.1 - resolution: "hermes-estree@npm:0.32.1" - checksum: 10c0/750d1e26c0df4aae15707765368352c6a34934939df09d96e6d260ee1e1500e753f7a18adac56647ef8ca2057e8f0e5d21ae07b97103b0d9c94d68afee154c5e +"hermes-estree@npm:0.36.0": + version: 0.36.0 + resolution: "hermes-estree@npm:0.36.0" + checksum: 10c0/fe27f57b0f8c3921e9dc48c517e25f4399609ca386353f90e04d3f859bacbee93184af80a6b930db5e981e8953a5c4083147556b21b19519917e131e7533b27c languageName: node linkType: hard -"hermes-estree@npm:0.35.0": - version: 0.35.0 - resolution: "hermes-estree@npm:0.35.0" - checksum: 10c0/a88c9dc63b8b3679b1aeb43e72e977597096c1bd7d59978c952f1d6df6d1a517c4a817c70b1b701854996b485adfa66c2fc7f80871029a7f0c04306f6717b59a +"hermes-estree@npm:0.36.1": + version: 0.36.1 + resolution: "hermes-estree@npm:0.36.1" + checksum: 10c0/9f552e1f809e05a4ddc5dcc10c5d1d5e375eade985026c3f8876a227db76c33059d25f93f92628a0e43d29a6da6977fbb643fafc8830babad62c1b88b2b56739 languageName: node linkType: hard @@ -7736,30 +8103,30 @@ __metadata: languageName: node linkType: hard -"hermes-parser@npm:0.32.0": - version: 0.32.0 - resolution: "hermes-parser@npm:0.32.0" +"hermes-parser@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-parser@npm:0.35.0" dependencies: - hermes-estree: "npm:0.32.0" - checksum: 10c0/5902d2c5d347c0629fba07a47eaad5569590ac69bc8bfb2e454e08d2dfbe1ebd989d88518dca2cba64061689b5eac5960ae6bd15a4a66600bbf377498a3234b7 + hermes-estree: "npm:0.35.0" + checksum: 10c0/49d98093a2094758db5b536627c6cf5146b140f66e63143acf471c62f1d3fd8bd6ae10a33f2372f72e3653deda5d4615c6dae89d01248849440916209901fc4a languageName: node linkType: hard -"hermes-parser@npm:0.32.1, hermes-parser@npm:^0.32.0": - version: 0.32.1 - resolution: "hermes-parser@npm:0.32.1" +"hermes-parser@npm:0.36.0": + version: 0.36.0 + resolution: "hermes-parser@npm:0.36.0" dependencies: - hermes-estree: "npm:0.32.1" - checksum: 10c0/77dc8b116c51d1b30ba9942629d4965301f2c7fa6a751a1842828d110ce33410daed5755ce8943a110dbfc6a5cafc704ddbfb7559e76b5c3170d2173c513047c + hermes-estree: "npm:0.36.0" + checksum: 10c0/76e726366ac2ea91e9464853f439d582ddee9c07ccb84cac35297ce1a5d197bf96b84ab030423a22940b55a0fa5bb191bc0ebeebd95f8e34116900decdccdb86 languageName: node linkType: hard -"hermes-parser@npm:0.35.0": - version: 0.35.0 - resolution: "hermes-parser@npm:0.35.0" +"hermes-parser@npm:0.36.1, hermes-parser@npm:^0.36.0": + version: 0.36.1 + resolution: "hermes-parser@npm:0.36.1" dependencies: - hermes-estree: "npm:0.35.0" - checksum: 10c0/49d98093a2094758db5b536627c6cf5146b140f66e63143acf471c62f1d3fd8bd6ae10a33f2372f72e3653deda5d4615c6dae89d01248849440916209901fc4a + hermes-estree: "npm:0.36.1" + checksum: 10c0/31f2b0a383e15a3cdd8f34bc11498a35504771b58f24db5694ea5013faf6dbe6b27a47f7825bae5494dd57afac4de54c05f750d4ad646ff6787bbba97dfbd8eb languageName: node linkType: hard @@ -8105,7 +8472,7 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": +"is-docker@npm:^2.0.0": version: 2.2.1 resolution: "is-docker@npm:2.2.1" bin: @@ -9697,16 +10064,16 @@ __metadata: languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.7": - version: 0.83.7 - resolution: "metro-babel-transformer@npm:0.83.7" +"metro-babel-transformer@npm:0.84.5": + version: 0.84.5 + resolution: "metro-babel-transformer@npm:0.84.5" dependencies: "@babel/core": "npm:^7.25.2" flow-enums-runtime: "npm:^0.0.6" hermes-parser: "npm:0.35.0" - metro-cache-key: "npm:0.83.7" + metro-cache-key: "npm:0.84.5" nullthrows: "npm:^1.1.1" - checksum: 10c0/c50c2fe9fa3d3fbe382d8a482ebcc77d7dba0ef4673cee4904a55b9ea28dea4b86d3b80e9b9ac0cd2b094b9ecd0368c953bba0f1df1cfd36c3a27c666352fd86 + checksum: 10c0/d105413dbe7887f05bf46b2c187e17abc7432bbabb6ac50caff2bf7460e5fa029654dd7478d502eb39987e0a1a0ec1d7660f1786cea53c7738da909ac319c59d languageName: node linkType: hard @@ -9726,12 +10093,12 @@ __metadata: languageName: node linkType: hard -"metro-cache-key@npm:0.83.7": - version: 0.83.7 - resolution: "metro-cache-key@npm:0.83.7" +"metro-cache-key@npm:0.84.5": + version: 0.84.5 + resolution: "metro-cache-key@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/a2f20b86b173cd2be710552a49e6389a2c9ef994ac35873616bd4e5c34588beb8c93279bcf525295ca73b6d206ab0d9942f35cbd4a76e642d359bdab48924d7a + checksum: 10c0/2e3507b31b2d21f833a5cf78fadbb3d7042921212c418ffc870aeef05759d954e20d347e9b2c1953847ca43c64e85cedfa7e0e2b7ad5dcb685826b2dc6346bb9 languageName: node linkType: hard @@ -9757,15 +10124,15 @@ __metadata: languageName: node linkType: hard -"metro-cache@npm:0.83.7": - version: 0.83.7 - resolution: "metro-cache@npm:0.83.7" +"metro-cache@npm:0.84.5": + version: 0.84.5 + resolution: "metro-cache@npm:0.84.5" dependencies: exponential-backoff: "npm:^3.1.1" flow-enums-runtime: "npm:^0.0.6" https-proxy-agent: "npm:^7.0.5" - metro-core: "npm:0.83.7" - checksum: 10c0/81ae4b2d0d389d25b816bb2d59eb8baed5e1f03a727aa9bcecadb5547e949525f90af888c956fbd5ff4a732cf4dc48b6d903bab648d23dc1830135d820040825 + metro-core: "npm:0.84.5" + checksum: 10c0/fa590e17d8e16274c09b874e34df5d6cbf8912aec329d563a1d751a4ebc8c8cfa7c6133df1a240be1e2ebdada1f1d65e30ea97dbf58549fbeec81d632c073be8 languageName: node linkType: hard @@ -9799,19 +10166,19 @@ __metadata: languageName: node linkType: hard -"metro-config@npm:0.83.7, metro-config@npm:^0.83.6": - version: 0.83.7 - resolution: "metro-config@npm:0.83.7" +"metro-config@npm:0.84.5, metro-config@npm:^0.84.3": + version: 0.84.5 + resolution: "metro-config@npm:0.84.5" dependencies: connect: "npm:^3.6.5" flow-enums-runtime: "npm:^0.0.6" jest-validate: "npm:^29.7.0" - metro: "npm:0.83.7" - metro-cache: "npm:0.83.7" - metro-core: "npm:0.83.7" - metro-runtime: "npm:0.83.7" + metro: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-core: "npm:0.84.5" + metro-runtime: "npm:0.84.5" yaml: "npm:^2.6.1" - checksum: 10c0/04f9ae008a4972d8399ed90dc03286afefabc2fc362fef8b8f362ec9c09f2edfe3f0f9e0e5aebf42de8b253f202629cc8e86ef0b07e205adf257cd609ac386fb + checksum: 10c0/ba946982432e442a8187ca955a65dca18903c087e6f279433a9173a9283b72e32bb2bdd2ca0ccda491e5c068b9378725c6a6ba73dbe3e180cf0f089cd5523bdc languageName: node linkType: hard @@ -9836,14 +10203,14 @@ __metadata: languageName: node linkType: hard -"metro-core@npm:0.83.7, metro-core@npm:^0.83.6": - version: 0.83.7 - resolution: "metro-core@npm:0.83.7" +"metro-core@npm:0.84.5, metro-core@npm:^0.84.3": + version: 0.84.5 + resolution: "metro-core@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" lodash.throttle: "npm:^4.1.1" - metro-resolver: "npm:0.83.7" - checksum: 10c0/cd267b2516ccce0cc152d12ff1f08073abec0a9b210779357a43f1a279acb3c3be0ff625de030da9a110aea45b70e36f14e13b846cb4fee8419fae67db2a53b4 + metro-resolver: "npm:0.84.5" + checksum: 10c0/f351c27f84f83bd3ec1db4e06566dfbfb01c6ebe193c4aa59850661374208b548e5738091525fe642a63c6117c64b053489f529e0aa29c311ff871cf290c00c3 languageName: node linkType: hard @@ -9888,9 +10255,9 @@ __metadata: languageName: node linkType: hard -"metro-file-map@npm:0.83.7": - version: 0.83.7 - resolution: "metro-file-map@npm:0.83.7" +"metro-file-map@npm:0.84.5": + version: 0.84.5 + resolution: "metro-file-map@npm:0.84.5" dependencies: debug: "npm:^4.4.0" fb-watchman: "npm:^2.0.0" @@ -9901,7 +10268,7 @@ __metadata: micromatch: "npm:^4.0.4" nullthrows: "npm:^1.1.1" walker: "npm:^1.0.7" - checksum: 10c0/467729e9f607552504d9e1b9a8d81e5ea5a27f979f19a40360215773b38e1a3b563789047f06a5502cf5983aae922b3c52f5a7e0be8edc0b0bb747556e7d5af8 + checksum: 10c0/16c0fbdc0e50c1785048e707f3b70813d89c72388ef024fcd16a78d18705e335c14649e842f594d5146d6f592616b4cc13953e6abbf0769006b5a6219d4c22d4 languageName: node linkType: hard @@ -9939,13 +10306,13 @@ __metadata: languageName: node linkType: hard -"metro-minify-terser@npm:0.83.7": - version: 0.83.7 - resolution: "metro-minify-terser@npm:0.83.7" +"metro-minify-terser@npm:0.84.5": + version: 0.84.5 + resolution: "metro-minify-terser@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" terser: "npm:^5.15.0" - checksum: 10c0/bf59f9b5a51c3cfff313f64daab78673fa17d3c9d0ec34823d295cd454daa146daa23706f4cae36104bc41f10ad7b5fa2802246891a6f5eb4d0736ef891c9c7a + checksum: 10c0/0fb7f2882d66698110027565a8a4ccd099d28df660ac3853a328575b457b24e2eb0a5ab06a000464fc86f25da34502e60cc1314c5decb90c98098378f90386d2 languageName: node linkType: hard @@ -10089,12 +10456,12 @@ __metadata: languageName: node linkType: hard -"metro-resolver@npm:0.83.7": - version: 0.83.7 - resolution: "metro-resolver@npm:0.83.7" +"metro-resolver@npm:0.84.5": + version: 0.84.5 + resolution: "metro-resolver@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/be5ba43302362f3918ef4a1f91f96d63c04469cc9fed076a2846785f60f7f53193278a36d249b92a4678fcf95f7a6e31da87a13df82ddc384f010c80fd3b074d + checksum: 10c0/44822149fbd52f680461e94ac7f8bf0855461607ed8d286a5ad615b8bd4a6b113cf0f3c2c403285514c6e1f46cfe7c95e52d9b59633506701dee735b6e788599 languageName: node linkType: hard @@ -10118,13 +10485,13 @@ __metadata: languageName: node linkType: hard -"metro-runtime@npm:0.83.7, metro-runtime@npm:^0.83.6": - version: 0.83.7 - resolution: "metro-runtime@npm:0.83.7" +"metro-runtime@npm:0.84.5, metro-runtime@npm:^0.84.3": + version: 0.84.5 + resolution: "metro-runtime@npm:0.84.5" dependencies: "@babel/runtime": "npm:^7.25.0" flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/b9feb9cffdf4df086cea91d803529d706c1a212d0c6b9d138ef4985905cb9bd22f9861a77061364a397917b772d2ef722e465a5b198d6405f574481dfa35a9a7 + checksum: 10c0/8f4de088ed0946b153d4afb51bb0e1dc7a36d276821ad7ee81fdbddcc72a7a8391f49d044c351c377387dbb955654078843a8fd11d3a3e41be4017d5d59a0848 languageName: node linkType: hard @@ -10162,20 +10529,20 @@ __metadata: languageName: node linkType: hard -"metro-source-map@npm:0.83.7, metro-source-map@npm:^0.83.6": - version: 0.83.7 - resolution: "metro-source-map@npm:0.83.7" +"metro-source-map@npm:0.84.5, metro-source-map@npm:^0.84.3": + version: 0.84.5 + resolution: "metro-source-map@npm:0.84.5" dependencies: "@babel/traverse": "npm:^7.29.0" "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-symbolicate: "npm:0.83.7" + metro-symbolicate: "npm:0.84.5" nullthrows: "npm:^1.1.1" - ob1: "npm:0.83.7" + ob1: "npm:0.84.5" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" - checksum: 10c0/6c424f6258a2128bb4ae6e7591fb7ed1f79b042978397c24a1e35d8bdfcd8960655b3cdd0c4d8ff90f3055ce5b00eca5cb61b327720e7029b3f334fd8ae1776d + checksum: 10c0/9aedfc28c2f04b41bf68844b6daa4141455e9b748b5651384d5eea22b3c65457aaa3470a845f2b464cb3a2d9c16e1a8fd5b04a2347ea6241d5c9912b3b9755bd languageName: node linkType: hard @@ -10211,19 +10578,19 @@ __metadata: languageName: node linkType: hard -"metro-symbolicate@npm:0.83.7": - version: 0.83.7 - resolution: "metro-symbolicate@npm:0.83.7" +"metro-symbolicate@npm:0.84.5": + version: 0.84.5 + resolution: "metro-symbolicate@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-source-map: "npm:0.83.7" + metro-source-map: "npm:0.84.5" nullthrows: "npm:^1.1.1" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" bin: metro-symbolicate: src/index.js - checksum: 10c0/4968a89d9bdd040e0cb6328852f14a8555e912875a1661e639566879e548fbfb849134b8f55076f875c062d202c713be1c9f47b4e68c627a4f8b367d7e208213 + checksum: 10c0/75defd592eea1a4b6ef5e0e1610bb21188764b9d56ccf52b16031524377ac9127d7af66dbc259bc85b1fe8afa9cd51b677c97a4c1d0fcc37d4494a08656f5fe6 languageName: node linkType: hard @@ -10254,9 +10621,9 @@ __metadata: languageName: node linkType: hard -"metro-transform-plugins@npm:0.83.7": - version: 0.83.7 - resolution: "metro-transform-plugins@npm:0.83.7" +"metro-transform-plugins@npm:0.84.5": + version: 0.84.5 + resolution: "metro-transform-plugins@npm:0.84.5" dependencies: "@babel/core": "npm:^7.25.2" "@babel/generator": "npm:^7.29.1" @@ -10264,7 +10631,7 @@ __metadata: "@babel/traverse": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" nullthrows: "npm:^1.1.1" - checksum: 10c0/2fce17ba1013e96278bd6800141ff050af0331a60c4c0dbc24f83c6b524806b6ff4089b36e3268c2315375211d16e875d644b7cbf2b23d43dc050047ec019556 + checksum: 10c0/3ef1e0c487c6b91b1f9eacfffb2f152094d67fb1b0d536030dd7a02d95da881acb19f0d03c0c4cffc7bca6e3d57538559e1d6fea9e610fe88946c2c0d9c1b9d7 languageName: node linkType: hard @@ -10309,24 +10676,24 @@ __metadata: languageName: node linkType: hard -"metro-transform-worker@npm:0.83.7": - version: 0.83.7 - resolution: "metro-transform-worker@npm:0.83.7" +"metro-transform-worker@npm:0.84.5": + version: 0.84.5 + resolution: "metro-transform-worker@npm:0.84.5" dependencies: "@babel/core": "npm:^7.25.2" "@babel/generator": "npm:^7.29.1" "@babel/parser": "npm:^7.29.0" "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" - metro: "npm:0.83.7" - metro-babel-transformer: "npm:0.83.7" - metro-cache: "npm:0.83.7" - metro-cache-key: "npm:0.83.7" - metro-minify-terser: "npm:0.83.7" - metro-source-map: "npm:0.83.7" - metro-transform-plugins: "npm:0.83.7" + metro: "npm:0.84.5" + metro-babel-transformer: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-cache-key: "npm:0.84.5" + metro-minify-terser: "npm:0.84.5" + metro-source-map: "npm:0.84.5" + metro-transform-plugins: "npm:0.84.5" nullthrows: "npm:^1.1.1" - checksum: 10c0/a84e3bae6679826059d66bc9438f00292d93569bdefb4193bcbe4c9a72f5423d88d8d3da94389f95674674118af1ee233ac8a2b5b8279ff568c8becf4c609f1c + checksum: 10c0/b4f147f97555feb0fe91114b7a49e8345a8065d88739ff217d26d17bc56fe707b2b1ed0533432dd27aa7169bd7542709ffee5edcfee7dc999c276cc891fbee7d languageName: node linkType: hard @@ -10438,9 +10805,9 @@ __metadata: languageName: node linkType: hard -"metro@npm:0.83.7, metro@npm:^0.83.6": - version: 0.83.7 - resolution: "metro@npm:0.83.7" +"metro@npm:0.84.5, metro@npm:^0.84.3": + version: 0.84.5 + resolution: "metro@npm:0.84.5" dependencies: "@babel/code-frame": "npm:^7.29.0" "@babel/core": "npm:^7.25.2" @@ -10457,23 +10824,22 @@ __metadata: flow-enums-runtime: "npm:^0.0.6" graceful-fs: "npm:^4.2.4" hermes-parser: "npm:0.35.0" - image-size: "npm:^1.0.2" invariant: "npm:^2.2.4" jest-worker: "npm:^29.7.0" jsc-safe-url: "npm:^0.2.2" lodash.throttle: "npm:^4.1.1" - metro-babel-transformer: "npm:0.83.7" - metro-cache: "npm:0.83.7" - metro-cache-key: "npm:0.83.7" - metro-config: "npm:0.83.7" - metro-core: "npm:0.83.7" - metro-file-map: "npm:0.83.7" - metro-resolver: "npm:0.83.7" - metro-runtime: "npm:0.83.7" - metro-source-map: "npm:0.83.7" - metro-symbolicate: "npm:0.83.7" - metro-transform-plugins: "npm:0.83.7" - metro-transform-worker: "npm:0.83.7" + metro-babel-transformer: "npm:0.84.5" + metro-cache: "npm:0.84.5" + metro-cache-key: "npm:0.84.5" + metro-config: "npm:0.84.5" + metro-core: "npm:0.84.5" + metro-file-map: "npm:0.84.5" + metro-resolver: "npm:0.84.5" + metro-runtime: "npm:0.84.5" + metro-source-map: "npm:0.84.5" + metro-symbolicate: "npm:0.84.5" + metro-transform-plugins: "npm:0.84.5" + metro-transform-worker: "npm:0.84.5" mime-types: "npm:^3.0.1" nullthrows: "npm:^1.1.1" serialize-error: "npm:^2.1.0" @@ -10483,7 +10849,7 @@ __metadata: yargs: "npm:^17.6.2" bin: metro: src/cli.js - checksum: 10c0/f759631778ce2e8cd7e1338ec886ffa0e3d4cd8456d5a506c57e5212206519435a345bcac6334dac22b3adf188b34854688007ddb1669c65afbadf0a0eef431a + checksum: 10c0/be9981927748f56c2f50f10d1de06e11917421718f3107dad95ee149892a8e0fb91bddc0b17f091984259c1b41bb01a193dc6280785ae6e4a5ad804fbd61c6d2 languageName: node linkType: hard @@ -10746,10 +11112,10 @@ __metadata: languageName: node linkType: hard -"multitars@npm:^1.0.0": - version: 1.0.0 - resolution: "multitars@npm:1.0.0" - checksum: 10c0/c3c432ae6c76f802270bfaff63de66ba740d8599503e78cca7c489d43968869509330f68329abd5d3dfc495790c47598fd3994a2cccb68b738d76eba66ae4b04 +"multitars@npm:^1.0.2": + version: 1.0.2 + resolution: "multitars@npm:1.0.2" + checksum: 10c0/3ba8805a66bcdb95a9841e13b59370bd737777a0435fed661e321af89e6f235ef286ca5b5d7992c4f079795826e8734e9aa7db368e389bc7a008550b50d928d2 languageName: node linkType: hard @@ -10760,12 +11126,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.7": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" +"nanoid@npm:^3.3.17": + version: 3.3.18 + resolution: "nanoid@npm:3.3.18" bin: nanoid: bin/nanoid.cjs - checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + checksum: 10c0/b994b4e396730f8be2520923284e2040d61eaee55cc6d4935ef6d38d34bafdc46133eda4d3faea5073bda545aa6079d82b886caeac5c731cf9ac18bcc1301425 languageName: node linkType: hard @@ -10965,12 +11331,12 @@ __metadata: languageName: node linkType: hard -"ob1@npm:0.83.7": - version: 0.83.7 - resolution: "ob1@npm:0.83.7" +"ob1@npm:0.84.5": + version: 0.84.5 + resolution: "ob1@npm:0.84.5" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/b009d7a79b7ccf8db520acdc713a544a1ec379ae804438328080715c66944aa871551b8d7b1cf7cfb89cbe53bbab29f3345097c455259d7b7fe76b0891860187 + checksum: 10c0/f00c3bdb8c8b53de03a3352a062e170e1ba1cf1977b3dfab492882a0f6fa9eccb5503dde012b1ef7d24c5060f5a6439457e850b98bd4148e2578ffa650b414b8 languageName: node linkType: hard @@ -11125,17 +11491,6 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.4": - version: 8.4.2 - resolution: "open@npm:8.4.2" - dependencies: - define-lazy-prop: "npm:^2.0.0" - is-docker: "npm:^2.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -11404,6 +11759,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.4": + version: 4.0.7 + resolution: "picomatch@npm:4.0.7" + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f + languageName: node + linkType: hard + "pidtree@npm:^0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" @@ -11470,14 +11832,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:~8.4.32": - version: 8.4.49 - resolution: "postcss@npm:8.4.49" +"postcss@npm:^8.5.14": + version: 8.5.26 + resolution: "postcss@npm:8.5.26" dependencies: - nanoid: "npm:^3.3.7" + nanoid: "npm:^3.3.17" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3 + checksum: 10c0/2bdafc00d96bd57b6649a52e458864a4bf58ee56cfdbe4aea1472b5cccc127e6c1ad653bd0bec50d211e650eb0b9270c80e1e72aff2e2fa40d9e7363234d6e43 languageName: node linkType: hard @@ -11850,26 +12212,28 @@ __metadata: languageName: unknown linkType: soft -"react-native-worklets@npm:0.7.4": - version: 0.7.4 - resolution: "react-native-worklets@npm:0.7.4" - dependencies: - "@babel/plugin-transform-arrow-functions": "npm:7.27.1" - "@babel/plugin-transform-class-properties": "npm:7.27.1" - "@babel/plugin-transform-classes": "npm:7.28.4" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:7.27.1" - "@babel/plugin-transform-optional-chaining": "npm:7.27.1" - "@babel/plugin-transform-shorthand-properties": "npm:7.27.1" - "@babel/plugin-transform-template-literals": "npm:7.27.1" - "@babel/plugin-transform-unicode-regex": "npm:7.27.1" - "@babel/preset-typescript": "npm:7.27.1" - convert-source-map: "npm:2.0.0" - semver: "npm:7.7.3" +"react-native-worklets@npm:0.10.1": + version: 0.10.1 + resolution: "react-native-worklets@npm:0.10.1" + dependencies: + "@babel/plugin-transform-arrow-functions": "npm:^7.27.1" + "@babel/plugin-transform-class-properties": "npm:^7.28.6" + "@babel/plugin-transform-classes": "npm:^7.28.6" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.28.6" + "@babel/plugin-transform-optional-chaining": "npm:^7.28.6" + "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" + "@babel/plugin-transform-template-literals": "npm:^7.27.1" + "@babel/plugin-transform-unicode-regex": "npm:^7.27.1" + "@babel/preset-typescript": "npm:^7.28.5" + "@babel/types": "npm:^7.27.1" + convert-source-map: "npm:^2.0.0" + semver: "npm:^7.7.4" peerDependencies: "@babel/core": "*" + "@react-native/metro-config": "*" react: "*" - react-native: "*" - checksum: 10c0/19683baa69b6f7458cdfdb2af27644d6fd14168c8c658e1c0f20fb3ea96e612742ed5ea9e189fc54959e2dc617b6864bbbb6dc3242b7a6a2d70bb49da09d033b + react-native: 0.83 - 0.86 + checksum: 10c0/31712cf1b311e3dc96544a665da269cdd6be8cab286234cea968c22509398924020f168b1e9e13a532acc23086048c16f5167aa3737e756711239b6f7521fe5a languageName: node linkType: hard @@ -11973,33 +12337,29 @@ __metadata: languageName: node linkType: hard -"react-native@npm:0.83.6": - version: 0.83.6 - resolution: "react-native@npm:0.83.6" +"react-native@npm:0.86.3": + version: 0.86.3 + resolution: "react-native@npm:0.86.3" dependencies: - "@jest/create-cache-key-function": "npm:^29.7.0" - "@react-native/assets-registry": "npm:0.83.6" - "@react-native/codegen": "npm:0.83.6" - "@react-native/community-cli-plugin": "npm:0.83.6" - "@react-native/gradle-plugin": "npm:0.83.6" - "@react-native/js-polyfills": "npm:0.83.6" - "@react-native/normalize-colors": "npm:0.83.6" - "@react-native/virtualized-lists": "npm:0.83.6" + "@react-native/assets-registry": "npm:0.86.3" + "@react-native/codegen": "npm:0.86.3" + "@react-native/community-cli-plugin": "npm:0.86.3" + "@react-native/gradle-plugin": "npm:0.86.3" + "@react-native/js-polyfills": "npm:0.86.3" + "@react-native/normalize-colors": "npm:0.86.3" + "@react-native/virtualized-lists": "npm:0.86.3" abort-controller: "npm:^3.0.0" anser: "npm:^1.4.9" ansi-regex: "npm:^5.0.0" - babel-jest: "npm:^29.7.0" - babel-plugin-syntax-hermes-parser: "npm:0.32.0" + babel-plugin-syntax-hermes-parser: "npm:0.36.0" base64-js: "npm:^1.5.1" commander: "npm:^12.0.0" flow-enums-runtime: "npm:^0.0.6" - glob: "npm:^7.1.1" - hermes-compiler: "npm:0.14.1" + hermes-compiler: "npm:250829098.0.17" invariant: "npm:^2.2.4" - jest-environment-node: "npm:^29.7.0" memoize-one: "npm:^5.0.0" - metro-runtime: "npm:^0.83.6" - metro-source-map: "npm:^0.83.6" + metro-runtime: "npm:^0.84.3" + metro-source-map: "npm:^0.84.3" nullthrows: "npm:^1.1.1" pretty-format: "npm:^29.7.0" promise: "npm:^8.3.0" @@ -12009,18 +12369,22 @@ __metadata: scheduler: "npm:0.27.0" semver: "npm:^7.1.3" stacktrace-parser: "npm:^0.1.10" + tinyglobby: "npm:^0.2.15" whatwg-fetch: "npm:^3.0.0" ws: "npm:^7.5.10" yargs: "npm:^17.6.2" peerDependencies: + "@react-native/jest-preset": 0.86.3 "@types/react": ^19.1.1 - react: ^19.2.0 + react: ^19.2.3 peerDependenciesMeta: + "@react-native/jest-preset": + optional: true "@types/react": optional: true bin: react-native: cli.js - checksum: 10c0/9f30b4a35a2eae4e8397d743bc6d16cce95a730837af8ff80fbaf3f29e80a999f1448ac1261b374b72679ba2039318dbaaa7230d4c13f423e69c3df9ac3ff77d + checksum: 10c0/a8354c58be9ca1509c48d4c06194f4be4b902c17ef6f4fc5bdfb05960b079b156b453998c69ed08ee0a4d60b00aeac7eaf8a9cfc428d24bec46304c06063b4c0 languageName: node linkType: hard @@ -12079,10 +12443,10 @@ __metadata: languageName: node linkType: hard -"react@npm:19.2.0": - version: 19.2.0 - resolution: "react@npm:19.2.0" - checksum: 10c0/1b6d64eacb9324725bfe1e7860cb7a6b8a34bc89a482920765ebff5c10578eb487e6b46b2f0df263bd27a25edbdae2c45e5ea5d81ae61404301c1a7192c38330 +"react@npm:19.2.3": + version: 19.2.3 + resolution: "react@npm:19.2.3" + checksum: 10c0/094220b3ba3a76c1b668f972ace1dd15509b157aead1b40391d1c8e657e720c201d9719537375eff08f5e0514748c0319063392a6f000e31303aafc4471f1436 languageName: node linkType: hard @@ -12473,6 +12837,15 @@ __metadata: languageName: node linkType: hard +"sandbox-cli-detector@npm:^0.2.0": + version: 0.2.0 + resolution: "sandbox-cli-detector@npm:0.2.0" + bin: + sandbox-cli-detector: dist/cli.js + checksum: 10c0/e70964918150079a3183948a8adadcde736823da2ad1135b4ccead2baa8d24d6487ddd222c030f94c9ef54e84bc41aa525a5452ef4cb9239f6f64b2ac03337bc + languageName: node + linkType: hard + "sax@npm:>=0.6.0": version: 1.4.1 resolution: "sax@npm:1.4.1" @@ -12512,15 +12885,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.7.3": - version: 7.7.3 - resolution: "semver@npm:7.7.3" - bin: - semver: bin/semver.js - checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e - languageName: node - linkType: hard - "semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -12557,6 +12921,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.7.4": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + "send@npm:0.19.0": version: 0.19.0 resolution: "send@npm:0.19.0" @@ -12877,7 +13250,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21": +"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -13808,10 +14181,10 @@ __metadata: languageName: node linkType: hard -"whatwg-url-minimum@npm:^0.1.1": - version: 0.1.1 - resolution: "whatwg-url-minimum@npm:0.1.1" - checksum: 10c0/0e10fa110a3f7292d3fe0192ac0d823ab83601c5e2c1817a6371df038a9e1790cabdb866b0e1e67967e11d9b0d0b8ad1c61511fea62771e9f5fbb48c54b71319 +"whatwg-url-minimum@npm:^0.1.2": + version: 0.1.2 + resolution: "whatwg-url-minimum@npm:0.1.2" + checksum: 10c0/5437bc4e1f49d89ff58b7565214db4640c4ad5d90de6c61207e0dc766dae9b9894a0ea7b7883b8576524601586705c5dc359681c388d76c18eddb51f50de558f languageName: node linkType: hard From b6c7d1e6e6e198df1ccaf4724e4293d0b74883c9 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 31 Aug 2026 22:02:05 +0100 Subject: [PATCH 8/8] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20react-native?= =?UTF-8?q?-audio-api=20to=200.13.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.13.0 broke the queue source node and AVAudioSessionCategoryOptions. The example touches neither: its audio demo is commented out and uses only core Web Audio (AudioContext, decodeAudioData, createBufferSource). Peer range wants worklets >= 0.6.0, satisfied by the 0.10.1 the SDK 57 bump brought in. Kept separate from the SDK bump so it can be reverted on its own. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014JJ2weaFu4jhu2XirMkbWe --- examples/example-expo/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/example-expo/package.json b/examples/example-expo/package.json index b35ef95..764f3f5 100644 --- a/examples/example-expo/package.json +++ b/examples/example-expo/package.json @@ -21,7 +21,7 @@ "expo-splash-screen": "~57.0.8", "react": "19.2.3", "react-native": "0.86.3", - "react-native-audio-api": "^0.12.1", + "react-native-audio-api": "^0.13.3", "react-native-worklets": "0.10.1" }, "private": true diff --git a/yarn.lock b/yarn.lock index 06b1116..ac06bb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7145,7 +7145,7 @@ __metadata: expo-splash-screen: "npm:~57.0.8" react: "npm:19.2.3" react-native: "npm:0.86.3" - react-native-audio-api: "npm:^0.12.1" + react-native-audio-api: "npm:^0.13.3" react-native-worklets: "npm:0.10.1" languageName: unknown linkType: soft @@ -12090,9 +12090,9 @@ __metadata: languageName: node linkType: hard -"react-native-audio-api@npm:^0.12.1": - version: 0.12.1 - resolution: "react-native-audio-api@npm:0.12.1" +"react-native-audio-api@npm:^0.13.3": + version: 0.13.3 + resolution: "react-native-audio-api@npm:0.13.3" dependencies: semver: "npm:^7.7.3" peerDependencies: @@ -12104,7 +12104,7 @@ __metadata: optional: true bin: setup-rn-audio-api-web: scripts/setup-rn-audio-api-web.js - checksum: 10c0/f9e23b474ba7115cc390e5383f778960e405ae7d1eb6c3f128e594f947bdfa33f68421d4b634b45eb2e3a9b15a0e29e1647c7c155156f9686bd7013232dc04e7 + checksum: 10c0/a2bd1f7fd1f7cd2a8efc6706a89c7c5bf64a6a420e90193ae7224311cd626ecca61a340217485ce107bf159f3eec0606680d84ebf385bf2a2a32d538e2e26b1c languageName: node linkType: hard