diff --git a/.github/Examples/Slider-Android-Example.gif b/.github/Examples/Slider-Android-Example.gif deleted file mode 100644 index 5bb1398c..00000000 Binary files a/.github/Examples/Slider-Android-Example.gif and /dev/null differ diff --git a/.github/Examples/Slider-Web-Example.gif b/.github/Examples/Slider-Web-Example.gif deleted file mode 100644 index 21223750..00000000 Binary files a/.github/Examples/Slider-Web-Example.gif and /dev/null differ diff --git a/.github/Examples/Slider-Windows-Example.gif b/.github/Examples/Slider-Windows-Example.gif deleted file mode 100644 index 2a968e2a..00000000 Binary files a/.github/Examples/Slider-Windows-Example.gif and /dev/null differ diff --git a/.github/Examples/Slider-iOS-Example.gif b/.github/Examples/Slider-iOS-Example.gif deleted file mode 100644 index c0efcaaa..00000000 Binary files a/.github/Examples/Slider-iOS-Example.gif and /dev/null differ diff --git a/.github/Examples/Slider6-Demo-Android.gif b/.github/Examples/Slider6-Demo-Android.gif new file mode 100644 index 00000000..0c1dff52 Binary files /dev/null and b/.github/Examples/Slider6-Demo-Android.gif differ diff --git a/.github/Examples/Slider6-Demo-iOS.gif b/.github/Examples/Slider6-Demo-iOS.gif new file mode 100644 index 00000000..56d56d21 Binary files /dev/null and b/.github/Examples/Slider6-Demo-iOS.gif differ diff --git a/.github/Examples/resources/CustomStepMarker-Component.png b/.github/Examples/resources/CustomStepMarker-Component.png deleted file mode 100644 index 4a1d5d11..00000000 Binary files a/.github/Examples/resources/CustomStepMarker-Component.png and /dev/null differ diff --git a/.github/Examples/resources/CustomStepMarker-Default.png b/.github/Examples/resources/CustomStepMarker-Default.png deleted file mode 100644 index d8f67716..00000000 Binary files a/.github/Examples/resources/CustomStepMarker-Default.png and /dev/null differ diff --git a/.github/Examples/resources/CustomStepMarker-StepnNumber.png b/.github/Examples/resources/CustomStepMarker-StepnNumber.png deleted file mode 100644 index 30341cb8..00000000 Binary files a/.github/Examples/resources/CustomStepMarker-StepnNumber.png and /dev/null differ diff --git a/README.md b/README.md index 4d42aa5d..73dd7736 100644 --- a/README.md +++ b/README.md @@ -3,51 +3,51 @@ React Native Slider

- - Latest version released on npmjs + + Latest version released on npmjs Build on GitHub Actions - Supported platforms + Supported platforms - License + License

- React Native component used to select a single value from a range of values. + `@callstack/Slider` component makes native Slider implementations of SwiftUI and Material Design 3 available in React Native.
Currently supported on following platforms:

-|iOS|Android|Windows|Web| -|:-:|:-:|:-:|:-:| -|![iOS Screenshot](.github/Examples/Slider-iOS-Example.gif)|![Android Screenshot](.github/Examples/Slider-Android-Example.gif)|![Windows Screenshot](.github/Examples/Slider-Windows-Example.gif)|![Web Screenshot](.github/Examples/Slider-Web-Example.gif)| +|iOS|Android| +|:-:|:-:| +|![iOS Screenshot](.github/Examples/Slider6-Demo-iOS.gif)|![Android Screenshot](.github/Examples/Slider6-Demo-Android.gif)| ## Installation & Usage -To install this module `cd` to your project directory and enter the following command: +To install `cd` to your project and: ``` -yarn add @react-native-community/slider +yarn add @callstack/slider ``` or ``` -npm install @react-native-community/slider --save +npm install @callstack/slider --save ``` If using iOS please remember to install cocoapods by running: `npx pod-install` The following code presents the basic usage scenario of this library: ```javascript -import Slider from '@react-native-community/slider'; +import Slider from '@callstack/slider'; +// Slider with initial value of 5, taking 80% of container's width ``` @@ -58,142 +58,35 @@ Check out the [example project](example) for more examples. If you have any questions, issues or any other topic that you need an advise in, reach out to us on our channel on Discord under this ๐Ÿ‘‰ [link](https://discord.com/channels/426714625279524876/1339942689524678737).
See you there! -## React Native Compatibility -To use this library you need to ensure you are using the correct version of React Native. - -| `@react-native-community/slider` version | Required React Native Version | -| ---------------------------------------- | ---------------------------- | -| `5.0.0` | `>=0.76` | -| `4.5.1` | `>=0.69` | -| `4.3.0` | `>=0.64` | -| `4.x.x` | `>=0.60`; `>=0.62` (on Windows); | -| `3.1.x` | `>=0.60` | -| `2.x.x` | `>= 0.60` | -| [`1.x.x`](https://github.com/react-native-community/react-native-slider/tree/937f0942f1fffc6ed88b5cf7c88d73b7878f00f0) | `<= 0.59` | - - ## Properties -| Property | Description | Type | Platform | -| -------- | ----------- | ---- | -------- | -| `style` | Used to style and layout the `Slider`. See `StyleSheet.js` and `ViewStylePropTypes.js` for more info. | View.style | | -| `disabled`| If true the user won't be able to move the slider.
Default value is false. | bool | | -| `maximumValue` | Initial maximum value of the slider.
Default value is 1. | number | | -| `minimumTrackTintColor` | The color used for the track to the left of the button.
Overrides the default blue gradient image on iOS. | [color](https://reactnative.dev/docs/colors) | | -| `minimumValue` | Initial minimum value of the slider.
Default value is 0. | number | | -| `lowerLimit` | Slide lower limit. The user won't be able to slide below this limit. | number | Android, iOS, Web | -| `upperLimit` | Slide upper limit. The user won't be able to slide above this limit. | number | Android, iOS, Web | -| `onSlidingStart` | Callback that is called when the user picks up the slider.
The initial value is passed as an argument to the callback handler. | function | | -| `onSlidingComplete` | Callback that is called when the user releases the slider, regardless if the value has changed.
The current value is passed as an argument to the callback handler. | function | | -| `onValueChange` | Callback continuously called while the user is dragging the slider. | function | | -| `step` | Step value of the slider. The value should be between 0 and (maximumValue - minimumValue). Default value is 0.
On Windows OS the default value is 1% of slider's range (from `minimumValue` to `maximumValue`). | number | | -| `maximumTrackTintColor` | The color used for the track to the right of the button.
Overrides the default gray gradient image on iOS. | [color](https://reactnative.dev/docs/colors) | | -| `testID` | Used to locate this view in UI automation tests. | string | | -| `value` | Write-only property representing the value of the slider. Can be used to programmatically control the position of the thumb. Entered once at the beginning still acts as an initial value. Changing the value programmatically does not trigger any event.
The value should be between minimumValue and maximumValue, which default to 0 and 1 respectively. Default value is 0.
_This is not a controlled component_, you don't need to update the value during dragging. | number | | -| `tapToSeek` | Permits tapping on the slider track to set the thumb position.
Defaults to false on iOS. No effect on Android or Windows. | bool | iOS | -| `inverted` | Reverses the direction of the slider.
Default value is false. | bool | | -| `vertical` | Changes the orientation of the slider to vertical, if set to `true`.
Default value is false. | bool | Windows | -| `thumbTintColor` | Color of the foreground switch grip.
**NOTE:** This prop will override the `thumbImage` prop set, meaning that if both `thumbImage` and `thumbTintColor` will be set, image used for the thumb may not be displayed correctly! | [color](https://reactnative.dev/docs/colors) | Android | -| `thumbSize` | Sets the size (width and height) of the thumb.
If `thumbImage` is provided, it will be scaled to this size.
Units: points on iOS, dp on Android. | number | Android, iOS, Web | -| `maximumTrackImage` | Assigns a maximum track image. Only static images are supported. The leftmost pixel of the image will be stretched to fill the track. | Image
.propTypes
.source | iOS | -| `minimumTrackImage` | Assigns a minimum track image. Only static images are supported. The rightmost pixel of the image will be stretched to fill the track. | Image
.propTypes
.source | iOS | -| `thumbImage` | Sets an image for the thumb. Only static images are supported. Needs to be a URI of a local or network image; base64-encoded SVG is not supported. | Image
.propTypes
.source | | -| `trackImage` | Assigns a single image for the track. Only static images are supported. The center pixel of the image will be stretched to fill the track. | Image
.propTypes
.source | iOS | -| [`StepMarker`](#stepmarker) | Component to be rendered for each step on the track,
with the possibility to change the styling, when thumb is at that given step | `FC` | iOS, Android, Windows | -| [`renderStepNumber`](#renderstepnumber) | Turns on the displaying of numbers of steps.
Numbers of steps are displayed under the track | bool | iOS, Android, Windows | -| `ref` | Reference object. | MutableRefObject | web | +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| `style` | Used to style and layout the Slider. See `StyleSheet.js` and `ViewStylePropTypes.js` for more info. | View.style | | +| `minimumValue` | Initial minimum value of the slider. | number | `0` | +| `maximumValue` | Initial maximum value of the slider. | number | `1` | +| `value` | Value of the slider. Can be used to programmatically control the position of the thumb, and entered once at the beginning it acts as the initial value.
_This is not a controlled component_, you don't need to update the value while the user is dragging.
Ignored by a ranged slider, which is positioned by `valueLeft` and `valueRight` instead. | number | `0` | +| `ranged` | Whether the slider selects a span of its range with two thumbs, rather than a single value with one. | bool | `false` | +| `valueLeft` | Value of the thumb bounding the selected span from below.
Behaves like `value`, and is only used by a ranged slider. The thumb cannot be dragged past `valueRight`. | number | `minimumValue` | +| `valueRight` | Value of the thumb bounding the selected span from above.
Behaves like `value`, and is only used by a ranged slider. The thumb cannot be dragged past `valueLeft`. | number | `maximumValue` | +| `lowerLimit` | Limits the available range of the slider to the lower value. The user won't be able to slide below this limit.
The track is still drawn from `minimumValue`. | number | `minimumValue` | +| `upperLimit` | Limits the available range of the slider to the upper value. The user won't be able to slide above this limit.
The track is still drawn up to `maximumValue`. | number | `maximumValue` | +| `step` | Step defines the granularity of the slider. The value of the slider will always be a multiple of the step value.
This works for both single and ranged sliders in the same manner.
A `step` of `0` leaves the value continuous. | number | `0` | +| `orientation` | The orientation of the Slider.
Switches the way the Slider is displayed. A vertical Slider holds its lowest value at the bottom, and its highest at the top. | `"horizontal"` \| `"vertical"` | `"horizontal"` | +| `disabled` | Switches the Slider off. It is drawn in the platform's own disabled look, and takes no taps, clicks, drags or keyboard input, so none of the callbacks are called.
The values given through the props still apply, so a disabled Slider keeps showing wherever they put its thumbs. It stays readable for screen readers, but cannot be reached by them.
A custom `track`, `thumb` or `stepMarkers` component is dimmed along with the rest of the Slider, and is passed `disabled` so that it can draw the state its own way instead. | bool | `false` | +| `onValueChange` | Callback continuously called while the user is dragging the thumb of a single Slider. A ranged Slider reports its thumbs through `onLeftValueChange` and `onRightValueChange` instead. | (value: number) => void | | +| `onLeftValueChange` | Callback continuously called while the user is dragging the lower thumb of a ranged Slider. | (value: number) => void | | +| `onRightValueChange` | Callback continuously called while the user is dragging the upper thumb of a ranged Slider. | (value: number) => void | | +| `onSlidingStart` | Callback called each time the user starts dragging.
The event is fired as soon as the user holds (long press) any thumb (left or right), regardless if the drag changed the value or not.
A ranged Slider reports whichever of its thumbs was grabbed first, and does not report the second one being grabbed on top of it. | () => void | | +| `onSlidingComplete` | Callback called each time the user ends dragging.
The event is fired as soon as the user releases any thumb (left or right) after long press, regardless if the drag changed the value or not.
A ranged Slider reports the last of its thumbs being released, so that every start is answered by exactly one complete. | () => void | | +| `thumb` | Used to pass a custom component rendered as a thumb of the Slider, in place of the built-in one.
A ranged Slider renders the given component twice, once for each of its thumbs. | `FC` | | +| `minimumTrackColor` | Defines the color the track from `minimumValue` up to the thumb, or up to the left thumb (when `ranged`), will be. | [color](https://reactnative.dev/docs/colors) | | +| `middleRangeTrackColor` | Defines the color the track from the left thumb to the right thumb will be.
Only used by a ranged Slider. | [color](https://reactnative.dev/docs/colors) | | +| `maximumTrackColor` | Defines the color the track from the thumb, or from the right thumb (when `ranged`), up to `maximumValue` will be. | [color](https://reactnative.dev/docs/colors) | | +| `track` | Replaces the default (native) track with the given component.
The component will be spread along the whole track by its X-axis, meaning that:
* In a horizontal Slider, the width of the given component will be increased to the width of the track,
* In a vertical Slider, the width of the given component will be increased as well, but the component will be transformed in the same 90 degrees as a vertical Slider is.
**NOTE**, that the custom track component takes the priority against the `minimumTrackColor`, `middleRangeTrackColor` and `maximumTrackColor`. | `FC` | | +| `stepMarkers` | Renders steps below the track.
**NOTE**: Is effective only when the `step` prop is set.
`true` - switches on rendering of default, built-in steps indicators
`false` - switches the steps rendering off.
component - if a custom component is passed, switches the steps rendering on with the given component used to render each step. | boolean \| `FC` | `false` | | `View` | [Inherited `View` props...](https://github.com/facebook/react-native-website/blob/master/docs/view.md#props) | | | -## Custom step marker and step numbers - -It is possible to render default step numbers under your slider and to render custom step component and step marker. -
This can be achieved by using: - -### `renderStepNumber` - -Turns on the displaying of numbers of steps.
Numbers of steps are displayed under the track. -
Two font sizes are available and they will be selected automatically depending on the overall number of steps. - -### `StepMarker` - -Your custom component rendered for every step on the Slider, both the thumb and the rest of steps along the Slider's whole length. -
This `StepMarker` prop accepts your custom component and provides it with the following parameters: - - - - - - - - - - - - - - - - - - - - - - -
- -```typescript - stepMarked: boolean; -``` - - - -Indicates whether that custom step is the one that the thum is currently on. -
If user drags or clicks on that step, thumb will be moved there and the `stepMarked` parameter will be set to `true`. -
Use it, to differentiate between rendering your custom thumb component, or your custom step marker. - -
- -```typescript - currentValue: number; -``` - - -Contains the `number` type saying at which value of the Slider the thumb currently is. -
Use it, for example, to render the Slider value on every step marker, or to render different step marker's variant depending on the Thumb position. - -
- -```typescript - index: number; -``` - - - -Contains the index at which this exact instantiation of your custom step marker was rendered at. -
Use it if you would like to render step number within the step marker, or, for example, if you want to render many variants of step marker depending on their positions along the Slider's width. - -
- -```typescript - min: number; -``` - - - -Minimum value of the Slider. It is equal to `minimumValue` and has the same default if not set. - -
- -```typescript - max: number; -``` - - - -Maximum value of the Slider. It is equal to `maximumValue` and has the same default if not set. - -
## Roadmap and Progress @@ -234,10 +127,6 @@ You can also do this manually by: - [Bartosz Klonowski](https://github.com/BartoszKlonowski) - [Callstack](https://callstack.com/) - [Maciej ลodygowski](https://github.com/draggie) - [Callstack](https://callstack.com/) -## Contributors - -This module was extracted from `react-native` core. Please, refer to [contributors graph](https://github.com/react-native-community/react-native-slider/graphs/contributors) for the complete list of contributors. - --- ## Made with โค๏ธ at Callstack diff --git a/example-web/.gitignore b/example-web/.gitignore deleted file mode 100644 index 4d29575d..00000000 --- a/example-web/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/example-web/README.md b/example-web/README.md deleted file mode 100644 index e7a69a73..00000000 --- a/example-web/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Web Example - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Setup - -This example project can be used to test/demo this project in the context of -`react-native-web`. Here's how to use it: - -1. Build the library: - ```sh - # go to the `package` folder - cd ../package - # build the library - npm run prepare - # go back to the example - cd ../example-web - ``` -2. Run the dev server: - ```sh - npm start - ``` - - -:warning: **NOTE**: each time you make a change to the underlying lib you'll -need to run `npm run prepare` and `npm start` again. diff --git a/example-web/craco.config.js b/example-web/craco.config.js deleted file mode 100644 index 90051fbb..00000000 --- a/example-web/craco.config.js +++ /dev/null @@ -1,38 +0,0 @@ -const path = require('path'); -const babelInclude = require('@dealmore/craco-plugin-babel-include'); -const webpack = require('webpack'); - -const LIB_PATH = `../package/dist/Slider.js`; - -module.exports = { - webpack: { - alias: { - 'react-native$': 'react-native-web', - '@react-native-community/slider': path.resolve(__dirname, LIB_PATH), - // make sure we don't include multiple versions of react - 'react': path.resolve(__dirname, './node_modules/react'), - }, - babel: { - presets: [ - '@babel/preset-react', - '@babel/preset-typescript', - ], - plugins: [ - '@babel/plugin-proposal-private-methods', - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-private-property-in-object', - ], - }, - }, - - plugins: [ - { - plugin: babelInclude, - options: { - include: [ - path.resolve(__dirname, LIB_PATH), - ], - }, - }, - ], -}; diff --git a/example-web/package.json b/example-web/package.json deleted file mode 100644 index 7df4a7eb..00000000 --- a/example-web/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "example-web", - "version": "0.1.0", - "private": true, - "dependencies": { - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.3.0", - "@testing-library/user-event": "^14.6.1", - "@types/jest": "^29.5.14", - "@types/node": "^22.15.3", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.3", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-native-web": "^0.20.0", - "react-scripts": "5.0.1", - "typescript": "5.8.3", - "web-vitals": "^2.1.4" - }, - "scripts": { - "start": "craco start", - "build": "craco build", - "test": "craco test", - "eject": "craco eject", - "refresh-package": "rimraf ./node_modules/@react-native-community/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@react-native-community/slider && rimraf ./node_modules/@react-native-community/slider/node_modules" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "@craco/craco": "^5.9.0", - "@dealmore/craco-plugin-babel-include": "^1.0.0", - "copyfiles": "^2.4.1", - "rimraf": "^6.0.1" - }, - "overrides": { - "react-scripts": { - "typescript": "5.8.3" - } - } -} diff --git a/example-web/public/favicon.ico b/example-web/public/favicon.ico deleted file mode 100644 index a11777cc..00000000 Binary files a/example-web/public/favicon.ico and /dev/null differ diff --git a/example-web/public/index.html b/example-web/public/index.html deleted file mode 100644 index aa069f27..00000000 --- a/example-web/public/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - React App - - - -
- - - diff --git a/example-web/public/logo192.png b/example-web/public/logo192.png deleted file mode 100644 index fc44b0a3..00000000 Binary files a/example-web/public/logo192.png and /dev/null differ diff --git a/example-web/public/logo512.png b/example-web/public/logo512.png deleted file mode 100644 index a4e47a65..00000000 Binary files a/example-web/public/logo512.png and /dev/null differ diff --git a/example-web/public/manifest.json b/example-web/public/manifest.json deleted file mode 100644 index 080d6c77..00000000 --- a/example-web/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/example-web/public/robots.txt b/example-web/public/robots.txt deleted file mode 100644 index e9e57dc4..00000000 --- a/example-web/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/example-web/src/App.tsx b/example-web/src/App.tsx deleted file mode 100644 index a096bec4..00000000 --- a/example-web/src/App.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import { - Text, - View, - ScrollView, - Platform, - StyleSheet, -// @ts-ignore -} from 'react-native'; - -import {examples, Props as ExamplesTabProperties} from './Examples'; -import {propsExamples, Props as PropsTabProperties} from './Props'; - -function App() { - const renderExampleTab = ( - sliders: PropsTabProperties[] | ExamplesTabProperties[], - filtered?: boolean, - ) => { - return ( - - - {(filtered - ? (sliders as ExamplesTabProperties[]).filter( - e => !e.platform || e.platform === Platform.OS, - ) - : sliders - ).map((e, i) => ( - - {e.title} - {e.render()} - - ))} - - - ); - }; - - return ( -
-
- - {renderExampleTab(examples, true)} - {renderExampleTab(propsExamples)} - -
-
- ); -} - -export default App; - -const pageViewPositionSlider = { - trackColor: '#ABABAB', - thumbColor: '#1411AB', - style: { - width: '100%', - }, -}; - -const styles = StyleSheet.create({ - pagerViewContainer: { - flex: 1, - }, - homeScreenContainer: { - flex: 1, - }, - scrollView: { - backgroundColor: '#F5FCFF', - }, - container: { - justifyContent: 'center', - alignItems: 'center', - paddingVertical: 20, - }, - title: { - fontSize: 30, - color: pageViewPositionSlider.thumbColor, - textAlign: 'center', - width: '100%', - marginVertical: 20, - }, - instructions: { - textAlign: 'center', - color: '#333333', - marginBottom: 5, - fontSize: 20, - }, - sliderWidget: { - marginVertical: 30, - }, -}); diff --git a/example-web/src/Examples.tsx b/example-web/src/Examples.tsx deleted file mode 100644 index b91f4307..00000000 --- a/example-web/src/Examples.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import React, {useState} from 'react'; -// @ts-ignore -import {Text, View, StyleSheet, ScrollView} from 'react-native'; -// @ts-ignore -import Slider, {SliderProps} from '@react-native-community/slider'; - -export interface Props { - title: string; - render(): React.ReactElement; - platform?: string; -} - -const SliderExample = (props: SliderProps) => { - const [value, setValue] = useState(0); - return ( - - {value && +value.toFixed(3)} - - - ); -}; - -const SlidingStartExample = (props: SliderProps) => { - const [slideStartingValue, setSlideStartingValue] = useState(0); - const [slideStartingCount, setSlideStartingCount] = useState(0); - return ( - - { - setSlideStartingValue(value); - setSlideStartingCount(prev => prev + 1); - }} - /> - - Starts: {slideStartingCount} Value: {slideStartingValue} - - - ); -}; - -const SlidingCompleteExample = (props: SliderProps) => { - const [slideCompletionValue, setSlideCompletionValue] = useState(0); - const [slideCompletionCount, setSlideCompletionCount] = useState(0); - return ( - - { - setSlideCompletionValue(value); - setSlideCompletionCount(prev => prev + 1); - }} - /> - - Completions: {slideCompletionCount} Value: {slideCompletionValue} - - - ); -}; - -export default SliderExample; - -const styles = StyleSheet.create({ - slider: { - width: 300, - opacity: 1, - height: 50, - marginTop: 10, - }, - text: { - fontSize: 14, - textAlign: 'center', - fontWeight: '500', - margin: 0, - }, -}); - -export const examples: Props[] = [ - { - title: 'Default settings', - render() { - return ; - }, - }, - { - title: 'Initial value: 0.5', - render() { - return ; - }, - }, - { - title: 'minimumValue: -1, maximumValue: 2', - render(): React.ReactElement { - return ; - }, - }, - { - title: 'lowerLimit: 2, upperLimit: 7', - render(): React.ReactElement { - return ; - }, - }, - { - title: 'step: 0.25, tap to seek on iOS', - render(): React.ReactElement { - return ; - }, - }, - { - title: 'onSlidingStart', - render(): React.ReactElement { - return ; - }, - }, - { - title: 'onSlidingComplete', - render() { - return ; - }, - }, - { - title: 'Custom min/max track tint color', - render() { - return ( - - ); - }, - }, - { - title: 'Custom thumb tint color', - render() { - return ; - }, - }, - { - title: 'Custom thumb image', - render() { - return ( - - ); - }, - }, - { - title: 'Custom thumb (network image)', - platform: 'windows', - render() { - return ( - - ); - }, - }, - { - title: 'Custom track image', - platform: 'ios', - render() { - return ; - }, - }, - { - title: 'Custom min/max track image', - platform: 'ios', - render() { - return ( - - ); - }, - }, - { - title: 'Inverted slider direction', - render() { - return ; - }, - }, - { - title: 'Vertical slider', - platform: 'windows', - render() { - return ; - }, - }, - { - title: 'Disabled slider', - render() { - return ; - }, - }, - { - title: 'Slider with accessibilityState disabled', - platform: 'android', - render() { - return ; - }, - }, - { - title: 'Slider in horizontal scroll view', - render() { - return ( - - - - - Scroll right, then slide โž” - - - - ); - }, - }, - // Check the fix for the issue #743 - { - title: 'With step numbers', - render() { - return ( - - ); - }, - }, -]; diff --git a/example-web/src/Props.tsx b/example-web/src/Props.tsx deleted file mode 100644 index 715e8e40..00000000 --- a/example-web/src/Props.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React, {useState} from 'react'; -// @ts-ignore -import {Text, View, StyleSheet} from 'react-native'; -// @ts-ignore -import Slider, {SliderProps} from '@react-native-community/slider'; - -export interface Props { - title: string; - render(): React.ReactElement; -} - -const SliderExample = (props: SliderProps) => { - const [value, setValue] = useState(0); - return ( - - {value && +value.toFixed(3)} - - - ); -}; - -const SlidingStartExample = (props: SliderProps) => { - const [slideStartingValue, setSlideStartingValue] = useState(0); - const [slideStartingCount, setSlideStartingCount] = useState(0); - return ( - - { - setSlideStartingValue(value); - setSlideStartingCount(prev => prev + 1); - }} - /> - - Starts: {slideStartingCount} Value: {slideStartingValue} - - - ); -}; - -const SlidingCompleteExample = () => { - const [slideCompletionValue, setSlideCompletionValue] = useState(0); - const [slideCompletionCount, setSlideCompletionCount] = useState(0); - return ( - - { - setSlideCompletionValue(value); - setSlideCompletionCount(prev => prev + 1); - }} - /> - - Completions: {slideCompletionCount} Value: {slideCompletionValue} - - - ); -}; - -export default SliderExample; - -const styles = StyleSheet.create({ - slider: { - width: 300, - opacity: 1, - height: 50, - marginTop: 10, - }, - text: { - fontSize: 14, - textAlign: 'center', - fontWeight: '500', - margin: 0, - }, -}); - -export const propsExamples: Props[] = [ - { - title: 'Default settings', - render() { - return ; - }, - }, - { - title: 'disabled', - render() { - return ; - }, - }, - { - title: 'maximumValue', - render() { - return ; - }, - }, - { - title: 'minimumTrackTintColor', - render() { - return ; - }, - }, - { - title: 'minimumValue', - render() { - return ; - }, - }, - { - title: 'onSlidingStart', - render() { - return ; - }, - }, - { - title: 'onSlidingComplete', - render() { - return ; - }, - }, - { - title: 'onValueChange', - render() { - return ; - }, - }, - { - title: 'step', - render() { - return ; - }, - }, - { - title: 'maximumTrackTintColor', - render() { - return ; - }, - }, - { - title: 'value', - render() { - return ; - }, - }, - { - title: 'tapToSeek', - render(): React.ReactElement { - return ; - }, - }, - { - title: 'inverted', - render() { - return ; - }, - }, - { - title: 'vertical', - render() { - return ; - }, - }, - { - title: 'thumbTintColor', - render() { - return ; - }, - }, - { - title: 'maximumTrackImage', - render() { - return ( - - ); - }, - }, - { - title: 'minimumTrackImage', - render() { - return ( - - ); - }, - }, - { - title: 'thumbImage', - render() { - return ( - - ); - }, - }, - { - title: 'trackImage', - render() { - return ; - }, - }, -]; diff --git a/example-web/src/index.css b/example-web/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/example-web/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/example-web/src/index.tsx b/example-web/src/index.tsx deleted file mode 100644 index 032464fb..00000000 --- a/example-web/src/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; - -const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement -); -root.render( - - - -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/example-web/src/logo.svg b/example-web/src/logo.svg deleted file mode 100644 index 9dfc1c05..00000000 --- a/example-web/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/example-web/src/react-app-env.d.ts b/example-web/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5f..00000000 --- a/example-web/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/example-web/src/reportWebVitals.ts b/example-web/src/reportWebVitals.ts deleted file mode 100644 index 49a2a16e..00000000 --- a/example-web/src/reportWebVitals.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReportHandler } from 'web-vitals'; - -const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/example-web/src/resources/slider-left.png b/example-web/src/resources/slider-left.png deleted file mode 100644 index dff4e11e..00000000 Binary files a/example-web/src/resources/slider-left.png and /dev/null differ diff --git a/example-web/src/resources/slider-right.png b/example-web/src/resources/slider-right.png deleted file mode 100644 index 57e8c7d3..00000000 Binary files a/example-web/src/resources/slider-right.png and /dev/null differ diff --git a/example-web/src/resources/slider.png b/example-web/src/resources/slider.png deleted file mode 100644 index 91419910..00000000 Binary files a/example-web/src/resources/slider.png and /dev/null differ diff --git a/example-web/src/resources/twitter.png b/example-web/src/resources/twitter.png deleted file mode 100644 index eb2cb32f..00000000 Binary files a/example-web/src/resources/twitter.png and /dev/null differ diff --git a/example-web/src/resources/uie_thumb_big.png b/example-web/src/resources/uie_thumb_big.png deleted file mode 100644 index dbfdb1b9..00000000 Binary files a/example-web/src/resources/uie_thumb_big.png and /dev/null differ diff --git a/example-web/src/setupTests.ts b/example-web/src/setupTests.ts deleted file mode 100644 index 8f2609b7..00000000 --- a/example-web/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/example-web/tsconfig.json b/example-web/tsconfig.json deleted file mode 100644 index a273b0cf..00000000 --- a/example-web/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": [ - "src" - ] -} diff --git a/example/Gemfile b/example/Gemfile index e5ed8b9f..0f0c3b8f 100644 --- a/example/Gemfile +++ b/example/Gemfile @@ -7,7 +7,7 @@ ruby ">= 2.6.10" gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' gem 'xcodeproj', '< 1.26.0' -gem 'concurrent-ruby', '< 1.3.8' +gem 'concurrent-ruby', '< 1.3.4' # Ruby 3.4.0 has removed some libraries from the standard library. gem 'bigdecimal' diff --git a/example/Gemfile.lock b/example/Gemfile.lock index d0f269fe..dc35b54e 100644 --- a/example/Gemfile.lock +++ b/example/Gemfile.lock @@ -110,8 +110,6 @@ PLATFORMS DEPENDENCIES activesupport (>= 6.1.7.5, != 7.1.0) - benchmark - bigdecimal cocoapods (>= 1.13, != 1.15.1, != 1.15.0) concurrent-ruby (< 1.3.8) logger diff --git a/example/README.md b/example/README.md new file mode 100644 index 00000000..485e630d --- /dev/null +++ b/example/README.md @@ -0,0 +1,587 @@ +# Example app + +A small React Native application that exercises `@callstack/slider` on both platforms it supports. +It renders one Slider per feature, each under its own title, in a single scrolling list โ€” so it +doubles as the manual check you run before opening a pull request. + +The sources live in [src/](src/): + +| File | Role | +| ---- | ---- | +| [src/App.tsx](src/App.tsx) | Draws the list: a title, a `ScrollView`, and one block per example. | +| [src/Examples.tsx](src/Examples.tsx) | The examples themselves, plus the custom thumb, track and step marker they share. | + +## Running the app + +### From the repository root + +```sh +npm install # installs the dependencies of package/ and of example/ +npm run example-ios # pod-install + run-ios +npm run example-android # run-android +``` + +### From this directory + +```sh +npm install +npm start # Metro +npm run ios # in another terminal +npm run android +``` + +The example consumes the library through `"@callstack/slider": "file:../package"`, and its +`postinstall` script **copies** `package/` into `example/node_modules/@callstack/slider`. Because of +that copy, changes you make in `package/` are not picked up by Metro on their own โ€” refresh the +copy after editing the library: + +```sh +npm run refresh-package +``` + +Other scripts available here: + +| Script | What it does | +| ------ | ------------ | +| `npm run refresh-package` | Re-copies `package/` into the example's `node_modules`. Run it after any change to the library sources. | +| `npm run lint` | ESLint over the example sources. | +| `npx tsc --noEmit` | Type-checks the example sources. Both of the above run in CI. | +| `npm test` | Jest, using `jest.config.js`. | +| `npm run clean-android` | Removes `android/app/build`. | +| `npm run clean-ios` | Removes `ios/build/generated/ios`, `ios/Pods` and `ios/Podfile.lock`. | + +Requirements are the usual React Native ones for [0.87](https://reactnative.dev/docs/set-up-your-environment): +Node `^22.13.0 || ^24.3.0 || >= 26.0.0`, Xcode and CocoaPods for iOS, Android SDK and JDK for Android. + +There is a `npm run windows` script and a `windows/` project too, but iOS and Android are the +platforms Slider supports today, and those are the only two the CI builds the example for. + +## How the examples are written + +Almost every example is a `SliderExample`, which stands in front of a Slider and holds the state an +app around a Slider would: where each thumb stands, and whether one of them is being dragged. The +readout above each Slider is that state, printed. + +```tsx +const rounded = (value: number) => Math.round(value * 100) / 100; + +const SliderExample = (props: SliderProps) => { + const {ranged, minimumValue = 0, maximumValue = 1} = props; + + const [value, setValue] = useState(props.value ?? minimumValue); + const [left, setLeft] = useState(props.valueLeft ?? minimumValue); + const [right, setRight] = useState(props.valueRight ?? maximumValue); + const [dragging, setDragging] = useState(false); + + // A ranged Slider reports its thumbs one by one, and never through + // `onValueChange`, which is what a single Slider is reported by. + const readout = ranged + ? `${rounded(left)} โ€“ ${rounded(right)}` + : `${rounded(value)}`; + + return ( + + + {readout} + {dragging ? ' ยท dragging' : ''} + + setDragging(true)} + onSlidingComplete={() => setDragging(false)} + /> + + ); +}; +``` + +The Slider is **not a controlled component**, so this only mirrors what its events report โ€” a drag +moves the thumb on its own, without `SliderExample` writing the value back. + +The snippets under each example below hold a plain `Slider` with every prop spelled out, so that you +can copy one into your own code as it stands. Each carries `style={{width: '90%'}}`, since a Slider +sizes itself to its container and needs some width to be given โ€” and one of them a height too, for the +reason given there. None of them passes a handler โ€” the +Slider is not controlled, so a drag moves the thumb whether or not you are listening; add +`onValueChange` (or `onLeftValueChange` / `onRightValueChange` when `ranged`) to hear about it. + +Every snippet assumes this import: + +```tsx +import Slider, {StepsProps, ThumbProps} from '@callstack/slider'; +import {StyleSheet, Text, View} from 'react-native'; +``` + +Most of the examples build on three pieces, given here in full โ€” styles included โ€” so that those +examples can be pasted as a whole: `SteppedThumb` is a thumb wearing the number of the step it +stands on, `DashedTrack` is a track of dashes in place of the solid rail, and `SteppedMarker` is a +step drawn as a tick that fills in once a thumb stands on it. `DASHES` is how many dashes to draw: +one per step position of a `0`โ€“`100` range taken by `5`. + +```tsx +/** Material 3's baseline primary, which is what Android tints its Slider with. */ +const ACCENT = '#6750A4'; + +/** One dash per step position of a 0-100 range taken by 5. */ +const DASHES = 21; + +const SteppedThumb = ({index}: ThumbProps) => ( + + {index} + +); + +const DashedTrack = () => ( + + {Array.from({length: DASHES}, (_, index) => ( + + ))} + +); + +const SteppedMarker = ({value, marked}: StepsProps) => ( + + + {value % 20 === 0 && ( + + {value} + + )} + +); + +const styles = StyleSheet.create({ + thumb: { + width: 26, + height: 26, + borderRadius: 13, + borderWidth: 2, + borderColor: ACCENT, + backgroundColor: '#FFFFFF', + justifyContent: 'center', + alignItems: 'center', + // The lift, taken the way each platform asks for it. + elevation: 3, + shadowColor: '#000000', + shadowOpacity: 0.2, + shadowRadius: 3, + shadowOffset: {width: 0, height: 1}, + }, + thumbLabel: { + fontSize: 10, + fontWeight: '600', + color: ACCENT, + }, + dashedTrack: { + height: 10, + borderRadius: 5, + backgroundColor: '#ECE6F5', + flexDirection: 'row', + }, + dash: { + flex: 1, + marginHorizontal: 2, + borderRadius: 5, + backgroundColor: ACCENT, + }, + mutedDash: { + opacity: 0.35, + }, + marker: { + alignItems: 'center', + }, + // The tick only changes its width and its colour when marked: a marker is hung + // from the point below its step, so one that grew any taller would push the + // label under it down every time a thumb came to rest on it. + tick: { + width: 2, + height: 12, + borderRadius: 1, + backgroundColor: '#CAC4D0', // M3 outline variant + }, + markedTick: { + width: 4, + borderRadius: 2, + backgroundColor: ACCENT, + }, + markerLabel: { + marginTop: 2, + fontSize: 9, + fontWeight: '500', + color: '#79747E', // M3 outline + }, + markedLabel: { + color: ACCENT, + fontWeight: '600', + }, + // A Slider grows itself to fit the markers it draws, but not the ones JS + // renders, so the room those hang into has to be given from here - a marker + // hangs from a point below the thumb, and nothing clips it. + markedSlider: { + height: 112, + }, +}); +``` + +The app itself keeps this support code in [src/Examples.tsx](src/Examples.tsx) and additionally folds +what the examples share into two constants โ€” the bounds every example but the first runs on, and the +three track tints โ€” which is what lets each of its entries stay to a few lines. So the app's +sources read differently from the snippets here, while rendering the same Sliders: + +```tsx +const BOUNDS = { + minimumValue: 0, + maximumValue: 100, + step: 5, +} as const; + +/** One dash per step of `BOUNDS`, so that the dashes fall where the steps do. */ +const DASHES = (BOUNDS.maximumValue - BOUNDS.minimumValue) / BOUNDS.step + 1; + +/** The three tints, stepped through Material 3's own baseline purple. */ +const TRACK_COLORS = { + minimumTrackColor: ACCENT, // tone 40 + middleRangeTrackColor: '#D0BCFF', // tone 80 + maximumTrackColor: '#EADDFF', // tone 90 +} as const; +``` + +--- + +## Examples + +In the order the app renders them. + +### Default Slider + +The smallest Slider there is: `minimumValue`, `maximumValue` and `step` are all left out, so the +defaults take over โ€” a range of `0` to `1` โ€” and the thumb travels across it without snapping +anywhere. Started at `0.5`, it sits in the middle of the track. + +```tsx +; +``` + +### minimumValue, maximumValue and step + +The three props that define what the Slider selects. The range runs `0` to `100`, and `step: 5` makes +the thumb land on multiples of the step instead of anywhere in between โ€” whatever you receive from +`onValueChange` is only ever `0`, `5`, `10`, and so on. + +```tsx +; +``` + +### The same, limited from top and bottom + +`lowerLimit` and `upperLimit` cut off the ends of the range: the thumb stops at `20` from below and +at `80` from above, while the track is still drawn across the whole `0`โ€“`100` span. That leftover +track at both ends is the point โ€” a limit is not a new minimum or maximum. + +```tsx +; +``` + +### The same, ranged + +`ranged` turns the single thumb into two bounding a span, positioned by `valueLeft` and `valueRight` +rather than `value`. Both thumbs snap to the very same `step`, and neither can be dragged past the +other. Each of them reports through `onLeftValueChange` / `onRightValueChange`, and a ranged Slider +sends no `onValueChange` at all. + +```tsx +; +``` + +### The three track colours + +The track of a Slider is drawn in three parts, each with its own tint. A single Slider has only two +of them โ€” up to the thumb, and from it to the maximum โ€” so this example is ranged to show all three: +`minimumTrackColor` up to the left thumb, `middleRangeTrackColor` across the span the two bound, and +`maximumTrackColor` from the right thumb to `maximumValue`. + +```tsx +; +``` + +The tints are the ones the example app reuses everywhere else: Material 3's baseline purple at tones +40, 80 and 90, darkest where the value has been reached and palest ahead of it. Leave any of them out +and the platform fills that part in with its own โ€” Android keeps Material 3's colours, iOS tints the +selected part with its accent and draws the rest in system fill. The tints reach the built-in step markers too: a marked step takes the middle +tint (the thumb tint on a single Slider) and an unmarked one the maximum tint. + +### Custom thumb + +`thumb` replaces the built-in thumb with a component of yours. `SteppedThumb` โ€” the one given above โ€” +receives `index`, the number of the step the thumb stands on counted from `minimumValue`, and prints +it, so the thumb itself says how far along the range it is. Standing on `25` of a `step: 5` range +starting at `0`, it reads `5`. + +```tsx +; +``` + +### Default step markers + +`stepMarkers: true` draws a dot below every step the Slider has. A dot takes the tint of the selected +part of the track once a thumb stands on that step, and of the rest of it otherwise โ€” so the row of +dots reads as how far the value has travelled, without the track being touched at all. + +```tsx +; +``` + +The dots hang below the thumb, so a Slider drawing its own markers measures itself taller than one +without them and needs no style of its own to hold them. The prop is effective only when `step` is +set โ€” with no step there are no steps to mark. + +### Custom step markers + +Given a component instead of `true`, `stepMarkers` renders each step with it. The Slider hands each +marker its `value` and whether a thumb currently stands on it as `marked`, and hangs it from the +point below its step โ€” which is what lets `SteppedMarker` draw a scale: a tick that thickens and takes +the accent once a thumb rests on it, labelled every `20`. + +```tsx +; +``` + +Unlike the built-in dots, markers JS renders do not make the Slider any taller: they hang out of it +into whatever room the layout gives, unscaled and unclipped, so the labels come out readable. Nothing +gives them that room for you, though, so this is the one example that has to give its Slider a height +as well as a width โ€” the `height` above is the `markedSlider` style the app's own entry for this +example passes. Too little of it and the labels are cut off at the bottom edge of the Slider. + +### Custom track + +`track` replaces the native rail. The Slider stretches whatever the component renders along its own +length, so the dashes of `DashedTrack` spread to fill the track, and `DASHES` gives them exactly one +dash per step position โ€” which is why every second one is muted, and they still read as separate dashes +rather than one solid bar. + +```tsx +; +``` + +A custom track takes priority over `minimumTrackColor`, `middleRangeTrackColor` and +`maximumTrackColor`: the native track it replaces is left undrawn, tints and all. It is given no value +of its own, so it cannot show how far the thumb has travelled โ€” a track you render is a decoration, +not a readout. In the app, the readout above each Slider is what reports the value. The step markers +are a separate layer, so `stepMarkers` still draws its dots under a custom track. + +### Custom thumb and track + +Both customizations at once, which is where the two layers have to cooperate: the JS-rendered thumb +rides on top of the JS-rendered track, and both are laid out by the Slider against the same value. + +```tsx +; +``` + +### Custom thumb and track, ranged + +The same pair with two thumbs. A ranged Slider renders the `thumb` component twice, once for each +thumb, and each copy is given the index of the thumb it stands for โ€” so the two labels read `4` and +`12` here, for the thumbs sitting on `20` and `60`. + +```tsx +; +``` + +### Limited from top and bottom, disabled + +`disabled` switches the Slider off. It keeps showing wherever the props put the thumb and keeps +drawing it the way the platform draws its own disabled state, but it takes no touches and reports no +`onSlidingStart`, `onValueChange` or `onSlidingComplete` of its own. The limits are still there, just +unreachable. + +```tsx +; +``` + +A Slider whose props change while it is switched off still moves its thumbs to where the new props +put them โ€” it is the touching that is switched off, not the drawing. + +### Custom thumb and track, disabled + +A thumb or a track that JS renders is a view of its own, mounted over the one the Slider draws, so the +platform's own disabled look does not reach it. Rather than leave those pieces at full brightness over +a faded Slider, the Slider dims the containers it hangs them in to the faintness of a control nobody +can reach โ€” so the whole of a switched-off Slider goes faint together, whatever JS drew into it. + +```tsx +; +``` + +Each custom component is also passed `disabled` itself, which is how it can draw the state its own way +instead of only being dimmed. The two apply on top of each other, so a component that dims itself as +well ends up dimmed twice over โ€” dim it yourself only instead of the dimming, not on top of it. + +### Vertical orientation + +`orientation: "vertical"` lays the very same Slider out along the other axis. It grows **upwards**: +`minimumValue` is at the bottom of the track and `maximumValue` at the top. The step, the limits, the +values and the events are all unchanged โ€” only the axis the thumb travels along has moved. + +`stepMarkers` is on, and the built-in dots come along for the turn โ€” one per step, to the **right** of +the track, at the step each one stands for, tinted once the thumb rests on it. + +```tsx +; +``` + +The `style` is the one thing that has to be written differently than for a horizontal Slider. A Slider +takes its **length** from whichever axis the orientation runs along and its breadth from the other, +so a vertical one needs its height to be the generous side of the pair and its width the tight one โ€” +the reverse of the `width: '90%'` every other snippet here carries. What a vertical Slider is given is +also what it is drawn inside of: the track and thumbs stay centred in the box, so a Slider given a wide +one shows its track down the middle of a wide gap rather than against an edge. + +That swap is also what the markers pay for. The length is what they are spread along, but each one is +hung **across** the Slider โ€” half a thumb and a gap past the middle of the track โ€” so on a vertical +Slider they are spending the axis that a horizontal one spends its height on, and that axis is now the +narrow one. The `60` above is how much of it they need: the width a marked Slider measures itself to +when given none, Android's wider thumb setting the distance (a thumb of 44 with a 4 marker and its 4 gap +on either side of it). Give a Slider a width of its own and it is never asked to measure itself, so the +room is not grown into โ€” it has to be handed over, in either orientation. + +### Custom thumb and track, vertical + +The same pair on the vertical Slider, this time with `SteppedMarker` marking the steps, and the point +of the example is what does *not* change: these are the very same `SteppedThumb`, `DashedTrack` and +`SteppedMarker` the horizontal examples pass, with no orientation-aware code in any of them. + +```tsx +; +``` + +A custom track is written as a row of dashes and stretched along the Slider's **length**, and the +Slider then turns the whole thing a quarter turn with itself โ€” which is what carries the first of the +dashes down to the bottom, where the minimum is. A custom thumb is never turned at all: it is centred +on the thumb it stands for, and that thumb only ever moves along the Slider. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a794bfc6..5e52d35f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1268,7 +1268,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-slider (5.2.2): + - react-native-slider (6.0.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1281,7 +1281,7 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-slider/common (= 5.2.2) + - react-native-slider/common (= 6.0.0) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1291,7 +1291,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-slider/common (5.2.2): + - react-native-slider/common (6.0.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1726,7 +1726,7 @@ DEPENDENCIES: - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - React-mutationobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver`) - react-native-pager-view (from `../node_modules/react-native-pager-view`) - - "react-native-slider (from `../node_modules/@react-native-community/slider`)" + - "react-native-slider (from `../node_modules/@callstack/slider`)" - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`) - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) @@ -1865,7 +1865,7 @@ EXTERNAL SOURCES: react-native-pager-view: :path: "../node_modules/react-native-pager-view" react-native-slider: - :path: "../node_modules/@react-native-community/slider" + :path: "../node_modules/@callstack/slider" React-NativeModulesApple: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-networking: @@ -1992,7 +1992,7 @@ SPEC CHECKSUMS: React-microtasksnativemodule: 512d9f7c6b66574d90832a6b10b6102d4a8fdf30 React-mutationobservernativemodule: 2c5abeb9a4ae316ba6d9420ef8d9baa81faa9d56 react-native-pager-view: e05e9fb5bfc04445320204b33fa6ee30d84efc2a - react-native-slider: 8ec501a2b7aa69970f879aca7aba59bfba845c27 + react-native-slider: 85858c5080bafe8160ff0ab405cb44c7f53161c2 React-NativeModulesApple: 22b2f3e1538ceb1aab1ab182ccb0666ea2ec36ad React-networking: b58b3098d4a626f88bbaeaf6bf5e67b18ff39089 React-oscompat: 72573ceb65b6c6c6bc34e51e0df8f44c91081a6d diff --git a/example/package-lock.json b/example/package-lock.json index 74d7f5a8..031be13c 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "hasInstallScript": true, "dependencies": { - "@react-native-community/slider": "file:../package", + "@callstack/slider": "file:../package", "babel-preset-expo": "^9.2.0", "react": "^19.2.3", "react-native": "^0.87.0", @@ -50,40 +50,41 @@ } }, "../package": { - "name": "@react-native-community/slider", - "version": "5.2.2", + "name": "@callstack/slider", + "version": "6.0.0", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.25.2", - "@babel/preset-flow": "^7.9.0", - "@babel/preset-typescript": "^7.28.5", - "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "^20.0.2", - "@react-native-community/cli-platform-android": "^20.0.2", - "@react-native-community/cli-platform-ios": "^20.0.2", - "@react-native-community/eslint-config": "^3.2.0", - "@react-native/babel-preset": "^0.81.1", - "@react-native/eslint-config": "^0.81.1", - "@react-native/metro-config": "^0.81.1", - "@react-native/typescript-config": "^0.81.1", - "@testing-library/react-native": "^13.3.3", + "@babel/cli": "^7.29.7", + "@babel/core": "^7.29.7", + "@babel/preset-flow": "^7.29.7", + "@babel/preset-typescript": "^7.29.7", + "@babel/runtime": "^7.29.10", + "@react-native-community/cli": "^20.2.0", + "@react-native-community/cli-platform-android": "^20.2.0", + "@react-native-community/cli-platform-ios": "^20.2.0", + "@react-native/babel-preset": "^0.87.1", + "@react-native/eslint-config": "^0.87.1", + "@react-native/jest-preset": "^0.87.1", + "@react-native/metro-config": "^0.87.1", + "@react-native/typescript-config": "^0.87.1", + "@testing-library/react-native": "^14.1.0", "@types/jest": "^30.0.0", - "@types/react": "^19.2.0", - "babel-jest": "^30.2.0", - "babel-plugin-module-resolver": "5.0.0", + "@types/react": "^19.2.18", + "babel-jest": "^30.5.2", + "babel-plugin-module-resolver": "^5.0.3", "copyfiles": "^2.4.1", - "eslint": "^8.19.0", - "eslint-plugin-prettier": "4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-native": "^4.0.0", - "flow-bin": "^0.163.0", - "jest": "^30.2.0", - "prettier": "2.8.8", - "react": "^19.2.0", - "react-native": "^0.81.4", - "react-test-renderer": "^19.2.0", - "typescript": "^5.0.4" + "eslint": "^9.39.5", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.6", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-native": "^5.0.0", + "hermes-eslint": "^0.37.0", + "jest": "^30.5.2", + "prettier": "^3.9.9", + "react": "19.2.3", + "react-native": "^0.87.1", + "test-renderer": "~1.2.0", + "typescript": "^6.0.3" } }, "../package/node_modules/@babel/cli": { @@ -2428,35 +2429,6 @@ "node": ">=10" } }, - "../package/node_modules/@react-native-community/eslint-config": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/eslint-parser": "^7.18.2", - "@react-native-community/eslint-plugin": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.30.5", - "@typescript-eslint/parser": "^5.30.5", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-ft-flow": "^2.0.1", - "eslint-plugin-jest": "^26.5.3", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-native": "^4.0.0" - }, - "peerDependencies": { - "eslint": ">=8", - "prettier": ">=2" - } - }, - "../package/node_modules/@react-native-community/eslint-plugin": { - "version": "1.3.0", - "dev": true, - "license": "MIT" - }, "../package/node_modules/@react-native/assets-registry": { "version": "0.81.6", "dev": true, @@ -3271,76 +3243,6 @@ "dev": true, "license": "MIT" }, - "../package/node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "../package/node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.7.4", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "../package/node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "../package/node_modules/@typescript-eslint/scope-manager": { "version": "5.62.0", "dev": true, @@ -3357,32 +3259,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "../package/node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "../package/node_modules/@typescript-eslint/types": { "version": "5.62.0", "dev": true, @@ -5379,29 +5255,6 @@ "eslint": "^8.1.0" } }, - "../package/node_modules/eslint-plugin-jest": { - "version": "26.9.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^5.10.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, "../package/node_modules/eslint-plugin-prettier": { "version": "4.2.1", "dev": true, @@ -5453,17 +5306,6 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "../package/node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, "../package/node_modules/eslint-plugin-react-native": { "version": "4.1.0", "dev": true, @@ -5940,17 +5782,6 @@ "dev": true, "license": "ISC" }, - "../package/node_modules/flow-bin": { - "version": "0.163.0", - "dev": true, - "license": "MIT", - "bin": { - "flow": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "../package/node_modules/flow-enums-runtime": { "version": "0.0.6", "dev": true, @@ -9301,11 +9132,6 @@ "dev": true, "license": "MIT" }, - "../package/node_modules/natural-compare-lite": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, "../package/node_modules/negotiator": { "version": "0.6.4", "dev": true, @@ -10600,6 +10426,7 @@ "version": "19.2.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "react-is": "^19.2.4", "scheduler": "^0.27.0" @@ -10611,12 +10438,14 @@ "../package/node_modules/react-test-renderer/node_modules/react-is": { "version": "19.2.4", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "../package/node_modules/react-test-renderer/node_modules/scheduler": { "version": "0.27.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "../package/node_modules/readable-stream": { "version": "1.0.34", @@ -14206,6 +14035,10 @@ "dev": true, "license": "MIT" }, + "node_modules/@callstack/slider": { + "resolved": "../package", + "link": true + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "dev": true, @@ -15186,10 +15019,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@react-native-community/slider": { - "resolved": "../package", - "link": true - }, "node_modules/@react-native/asset-utils": { "version": "0.87.0", "resolved": "https://registry.npmjs.org/@react-native/asset-utils/-/asset-utils-0.87.0.tgz", diff --git a/example/package.json b/example/package.json index 2544574d..162b791e 100644 --- a/example/package.json +++ b/example/package.json @@ -8,14 +8,14 @@ "lint": "eslint .", "start": "react-native start", "test": "jest", - "windows": "npx @react-native-community/cli run-windows", - "postinstall": "rimraf ./node_modules/@react-native-community/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@react-native-community/slider && rimraf ./node_modules/@react-native-community/slider/node_modules", - "refresh-package": "rimraf ./node_modules/@react-native-community/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@react-native-community/slider && rimraf ./node_modules/@react-native-community/slider/node_modules", + "windows": "npx @callstack/cli run-windows", + "postinstall": "rimraf ./node_modules/@callstack/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@callstack/slider && rimraf ./node_modules/@callstack/slider/node_modules", + "refresh-package": "rimraf ./node_modules/@callstack/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@callstack/slider && rimraf ./node_modules/@callstack/slider/node_modules", "clean-android": "rm -rf android/app/build", "clean-ios": "rm -rf ios/build/generated/ios && rm -rf ios/Pods && rm ios/Podfile.lock" }, "dependencies": { - "@react-native-community/slider": "file:../package", + "@callstack/slider": "file:../package", "babel-preset-expo": "^9.2.0", "react": "^19.2.3", "react-native": "^0.87.0", diff --git a/example/src/App.tsx b/example/src/App.tsx index 6f6cf618..8ceaf62c 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,74 +1,22 @@ -import React, {useState} from 'react'; -import { - Platform, - SafeAreaView, - ScrollView, - StyleSheet, - Text, - View, -} from 'react-native'; -import {examples, Props as ExamplesTabProperties} from './Examples'; -import {propsExamples, Props as PropsTabProperties} from './Props'; -import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view'; -import Slider from '@react-native-community/slider'; +import React from 'react'; +import {ScrollView, StyleSheet, Text, View} from 'react-native'; +import {examples} from './Examples'; const App = () => { - const [currentPage, setCurrentPage] = useState(0); - const titles = ['Examples', 'Props']; - - const renderExampleTab = ( - sliders: PropsTabProperties[] | ExamplesTabProperties[], - filtered?: boolean, - ) => { - return ( - - - {(filtered - ? (sliders as ExamplesTabProperties[]).filter( - e => !e.platform || e.platform === Platform.OS, - ) - : sliders - ).map((e, i) => ( - - {e.title} - {e.render()} - - ))} - - - ); - }; - return ( - - - - - {titles[currentPage]} - - - { - setCurrentPage(e.nativeEvent.position); - }}> - {renderExampleTab(examples, true)} - {renderExampleTab(propsExamples)} - - + + + {'Examples'} + + + {examples.map((e, i) => ( + + {e.title} + {e.render()} + + ))} + + ); }; @@ -83,22 +31,11 @@ const pageViewPositionSlider = { }; const styles = StyleSheet.create({ - slider: { - width: '100%' as const, - }, - pagerViewContainer: { - flex: 1, - }, - homeScreenContainer: { - flex: 1, - }, scrollView: { backgroundColor: '#F5FCFF', }, container: { - justifyContent: 'center', - alignItems: 'center', - paddingVertical: 20, + flex: 1, }, title: { fontSize: 30, diff --git a/example/src/Examples.tsx b/example/src/Examples.tsx index e7736c08..85df8888 100644 --- a/example/src/Examples.tsx +++ b/example/src/Examples.tsx @@ -1,777 +1,456 @@ -import React, {FC, JSX, useCallback, useState} from 'react'; -import {Text, View, StyleSheet, Image} from 'react-native'; -import Slider, {MarkerProps, SliderProps} from '@react-native-community/slider'; +import React, {JSX, useState} from 'react'; +import {StyleSheet, Text, View} from 'react-native'; +import Slider, {SliderProps, StepsProps, ThumbProps} from '@callstack/slider'; export interface Props { title: string; render(): JSX.Element; - platform?: string; } -const CONSTANTS = { - MAX_VALUE: 100, - MIN_VALUE: 10, - STEP: 10, - DEFAULT_STEP_RESOLUTION: 100, -} as const; - -const SliderExample = (props: SliderProps) => { - const [value, setValue] = useState(props.value ?? 0); - return ( - - {value && +value.toFixed(3)} - - - ); -}; - -const SlidingStartExample = (props: SliderProps) => { - const [slideStartingValue, setSlideStartingValue] = useState(0); - const [slideStartingCount, setSlideStartingCount] = useState(0); - return ( - - { - setSlideStartingValue(value); - setSlideStartingCount(prev => prev + 1); - }} - /> - - Starts: {slideStartingCount} Value: {slideStartingValue} - - - ); -}; - -const SlidingCompleteExample = (props: SliderProps) => { - const [slideCompletionValue, setSlideCompletionValue] = useState(0); - const [slideCompletionCount, setSlideCompletionCount] = useState(0); - return ( - - { - setSlideCompletionValue(value); - setSlideCompletionCount(prev => prev + 1); - }} - /> - - Completions: {slideCompletionCount} Value: {slideCompletionValue} - - - ); -}; - -const SlidingStepsExample = (props: SliderProps) => { - const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }, []); - - return ( - - - - ); -}; +/** Material 3's baseline primary, which is what Android tints its Slider with. */ +const ACCENT = '#6750A4'; -const SlidingStepsNumbersExample = (props: SliderProps) => { - const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }, []); +/** + * The three tints of the track, stepped through Material 3's own baseline + * purple - tones 40, 80 and 90, the darkest where the value has been reached + * and the palest ahead of it. The middle is what a single Slider has none of. + */ +const TRACK_COLORS = { + minimumTrackColor: ACCENT, // tone 40 + middleRangeTrackColor: '#D0BCFF', // tone 80 + maximumTrackColor: '#EADDFF', // tone 90 +} as const; - return ( - - - - ); -}; +/** + * The bounds shared by every example but the default one, so that each of them + * only has to say what it adds on top of them. + */ +const BOUNDS = { + minimumValue: 0, + maximumValue: 100, + step: 5, +} as const; -const SlidingStepsSmallNumbersExample = (props: SliderProps) => { - const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }, []); +/** One dash per step of `BOUNDS`, so that the dashes fall where the steps do. */ +const DASHES = (BOUNDS.maximumValue - BOUNDS.minimumValue) / BOUNDS.step + 1; - return ( - - - - ); -}; +const rounded = (value: number) => Math.round(value * 100) / 100; -const SlidingCustomStepsThumbImageNumbersExample = (props: SliderProps) => { - const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }, []); - return ( - - - - ); -}; - -const SlidingCustomStepsAnotherThumbImageNumbersExample = ( - props: SliderProps, -) => { - const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }, []); +/** + * A thumb wearing the number of the step it stands on. `index` counts the steps + * from `minimumValue`, so the thumb says how far along the range it is - and a + * ranged Slider renders the very same component once for each of its thumbs. + */ +const SteppedThumb = ({index}: ThumbProps) => ( + + {index} + +); - return ( - - ( + + {Array.from({length: DASHES}, (_, index) => ( + - - ); -}; - -const InvertedSliderWithStepMarker = (props: SliderProps) => { - const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }, []); + ))} + +); - return ( - - - - ); -}; - -const SlidingCustomStepsThumbImageWithNumbersAndDifferentWidth = ( - props: SliderProps, -) => { - const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { - return stepMarked ? ( - - ) : ( - - ); - }, []); - - return ( - - - - ); -}; +/** + * A step drawn as a tick that fills in once a thumb stands on it. `value` is the + * value the step stands for, so the ticks read as the scale the thumb travels + * over - and every fourth of them says what it is worth. + */ +const SteppedMarker = ({value, marked}: StepsProps) => ( + + + {value % 20 === 0 && ( + + {value} + + )} + +); -const MyStepMarker: FC = ({stepMarked, currentValue}) => { - return stepMarked ? ( - - - - {currentValue !== undefined ? ( - - {currentValue % 1 === 0 ? currentValue : currentValue.toFixed(2)} - - ) : ( - {'-'} - )} - - - - ) : ( - - ); -}; +/** + * Stands in front of a Slider of the given props, holding the state an app + * around a Slider would: where each of its thumbs stands, and whether one of + * them is being dragged. The Slider is not a controlled component, so this only + * mirrors what its events report - a drag moves the thumb on its own. + */ +const SliderExample = (props: SliderProps) => { + const {ranged, minimumValue = 0, maximumValue = 1} = props; -const CustomComponent: FC = ({ - stepMarked, - currentValue, - index, - max, -}) => { - if (stepMarked) { - return ( - - - {index} - - - {max} - - - / - - - ); - } - return currentValue > index ? ( - - ) : ( - - ); -}; + const [value, setValue] = useState(props.value ?? minimumValue); + const [left, setLeft] = useState(props.valueLeft ?? minimumValue); + const [right, setRight] = useState(props.valueRight ?? maximumValue); + const [dragging, setDragging] = useState(false); -const SliderExampleWithCustomMarker = (props: SliderProps) => { - const [value, setValue] = useState(props.value ?? CONSTANTS.MIN_VALUE); + // A ranged Slider reports its thumbs one by one, and never through + // `onValueChange`, which is what a single Slider is reported by. + const readout = ranged + ? `${rounded(left)} โ€“ ${rounded(right)}` + : `${rounded(value)}`; return ( - {value && +value.toFixed(3)} + + {readout} + {dragging ? ' ยท dragging' : ''} + - - ); -}; -const SliderExampleWithCustomComponentAndFilledSteps = (props: SliderProps) => { - const [value, setValue] = useState(props.value || 50); - - return ( - - {value && +value.toFixed(3)} - setDragging(true)} + onSlidingComplete={() => setDragging(false)} /> ); }; -export default SliderExample; - -const styles = StyleSheet.create({ - text: { - fontSize: 14, - textAlign: 'center', - fontWeight: '500', - margin: 0, - }, - trackText: { - color: '#FFFFFF', - fontSize: 10, - justifyContent: 'center', - alignSelf: 'center', - top: 12, - }, - trackDot: { - width: 10, - height: 10, - borderRadius: 10, - top: 4, - }, - empty: { - backgroundColor: '#B3BFC9', - }, - filled: { - backgroundColor: '#00629A', - }, - customComponentFrame: { - flex: 1, - flexDirection: 'row', - top: -10, - opacity: 0.95, - }, - customComponentLeftFrame: { - height: 40, - width: 20, - borderTopLeftRadius: 40, - borderBottomLeftRadius: 40, - }, - customComponentRightFrame: { - height: 40, - width: 20, - borderTopRightRadius: 40, - borderBottomRightRadius: 40, - }, - divider: { - width: 2, - height: 20, - backgroundColor: '#ffffff', - justifyContent: 'center', - alignItems: 'center', - }, - separator: { - width: 2, - height: 20, - backgroundColor: '#00629A', - justifyContent: 'center', - alignItems: 'center', - }, - label: { - marginTop: 10, - width: 55, - paddingVertical: 5, - paddingHorizontal: 10, - backgroundColor: '#ffffff', - shadowColor: '#000000', - shadowOffset: { - width: 0, - height: 1, - }, - shadowOpacity: 0.4, - shadowRadius: 4, - justifyContent: 'center', - alignItems: 'center', - }, - background: { - justifyContent: 'center', - alignItems: 'center', - }, - tinyLogo: { - marginVertical: 2, - aspectRatio: 1, - flex: 1, - height: '100%', - width: '100%', - }, - minMaxLabel: { - flexDirection: 'row', - zIndex: -1, - }, - slider: { - width: 300, - opacity: 1, - marginTop: 10, - }, - outer: { - width: 20, - height: 20, - borderRadius: 10, - backgroundColor: '#11FF11', - justifyContent: 'center', - alignItems: 'center', - }, - outerTrue: { - width: 20, - height: 20, - borderRadius: 10, - backgroundColor: '#0F0FFF', - justifyContent: 'center', - alignItems: 'center', - }, - inner: { - width: 10, - height: 10, - borderRadius: 5, - backgroundColor: '#111111', - }, - innerTrue: { - width: 10, - height: 10, - borderRadius: 5, - backgroundColor: '#0F0FFF', - }, - container: { - alignItems: 'center', - }, - outerSmall: { - width: 4, - height: 4, - top: 6, - borderRadius: 2, - backgroundColor: '#003366', - justifyContent: 'center', - alignItems: 'center', - }, - outerTrueSmall: { - width: 8, - height: 8, - borderRadius: 2, - backgroundColor: '#ABCDEF', - justifyContent: 'center', - alignItems: 'center', - }, - innerSmall: { - width: 7, - height: 7, - borderRadius: 1, - backgroundColor: '#223366', - }, - innerTrueSmall: { - width: 7, - height: 7, - borderRadius: 1, - backgroundColor: '#334488', - }, -}); - export const examples: Props[] = [ { - title: 'Default settings', - render() { - return ; - }, - }, - { - title: 'Initial value: 0.5', + title: 'Default Slider', render() { + // Only started off centre: with `minimumValue`, `maximumValue` and `step` + // left out the thumb travels from 0 to 1 without snapping anywhere. return ; }, }, { - title: 'minimumValue: -1, maximumValue: 2', - render(): React.ReactElement { - return ; + title: 'minimumValue, maximumValue and step', + render() { + return ; }, }, { - title: 'minimumValue: -10, maximumValue: 0', - render(): React.ReactElement { + title: 'The same, limited from top and bottom', + render() { + // The thumbs stop at the limits, while the track still spans the whole + // range from `minimumValue` to `maximumValue`. return ( - + ); }, }, { - title: 'step: 0.25, tap to seek on iOS', - render(): React.ReactElement { - return ; - }, - }, - { - title: 'Limit on positive values [30, 80]', + title: 'The same, ranged', render() { + // Two thumbs bounding a span, snapping to the very same step. return ( - + ); }, }, { - title: 'Limit on negative values [-70, -20]', + title: 'The three track colours', render() { + // Ranged, because the track only splits into three parts between two + // thumbs: up to the left one, the span they bound, and from the right + // one to the maximum. A single Slider has no middle to colour. return ( ); }, }, { - title: 'onSlidingStart', - render(): React.ReactElement { - return ; + title: 'Custom thumb', + render() { + return ; }, }, { - title: 'onSlidingComplete', + title: 'Default step markers', render() { - return ; + // The same Slider, with the markers the Slider draws for its own steps + // switched on: a dot below each step, tinted once a thumb stands on it. + return ( + + ); }, }, { - title: 'Custom min/max track tint color', + title: 'Custom step markers', render() { + // A marker hangs below the step it stands for, and says so through + // `marked` - the Slider hands each one the value of its step, and whether + // a thumb currently stands on it. return ( ); }, }, { - title: 'Custom thumb tint color', + title: 'Custom track', render() { - return ; + return ; }, }, { - title: 'Custom thumb image', + title: 'Custom thumb and track', render() { return ( - + ); }, }, { - title: 'Custom thumb (network image)', - platform: 'windows', + title: 'Custom thumb and track, ranged', render() { + // The thumb is rendered twice, once holding the index of each thumb. return ( ); }, }, { - title: 'Custom track image', - platform: 'ios', + title: 'Limited from top and bottom, disabled', render() { - return ; + // Switched off, the Slider keeps showing wherever the props put the + // thumb, but takes no touches and reports no events of its own. + return ( + + ); }, }, { - title: 'Custom min/max track image', - platform: 'ios', + title: 'Custom thumb and track, disabled', render() { + // A thumb or a track JS renders is a view of its own, outside the one + // the Slider draws, so it cannot be told to look switched off - instead + // the Slider dims the room it hangs the view in, and everything goes + // faint together. return ( ); }, }, { - title: 'Slider with customized indicator and no numbers', + title: 'Vertical orientation', render() { - return ; + // `orientation` lays the very same Slider out along the other axis: it + // grows upwards, so the minimum is at the bottom and the maximum at the + // top. The step, the values and everything the Slider reports are as they + // were - only the axis the thumb travels along has changed. + // + // The built-in markers come along for the turn, and with them the axis + // they are paid for out of: they are laid out *across* the Slider, to the + // right of the track, so it is now the width that has to be deep enough to + // hold them, where a horizontal Slider spends its height. That height is + // what a marked Slider is measured to grow to, and the turn moves that + // growth onto the width - which has to be written out here rather than + // grown into, as a Slider whose style gives it a size is never asked to + // measure itself. See `verticalSlider`. + return ( + + ); }, }, { - title: 'Slider with customized indicator and default numbers', + title: 'Custom thumb and track, vertical', render() { - return ; + // The track is turned a quarter turn along with the Slider, which is what + // carries the first of its dashes down to the bottom, where the minimum + // is - so this is the very same `DashedTrack`, written as a row, that the + // horizontal examples use. A thumb only ever moves along the Slider, so + // it needs nothing at all for the turn. + // + // `SteppedMarker` is the same component too, and stays the right way up: + // the markers hang beside the track instead of turning round with it, + // which keeps the numbers under the ticks readable. They hang out to its + // right now rather than down out of its bottom, so the room this example + // had to give its Slider has moved sides with them - from the height of + // `markedSlider` to the width of `verticalMarkedSlider`. + return ( + + ); }, }, - { - title: 'Slider with smaller customized indicator and default numbers', - render() { - return ; - }, +]; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', }, - { - title: 'Slider with custom steps, thumbImage and steps numbers', - render() { - return ; - }, + slider: { + width: '90%', + marginTop: 10, }, - { - title: 'Slider with custom steps, different thumbImage and steps numbers', - render() { - return ; - }, + // A vertical Slider takes its breadth from its width and its length from its + // height, so the two have to be given the other way round than `slider` has + // them: room to grow upwards, and only as much across as the thumb, the touch + // area around it and whatever hangs beside it need. The Slider is centred in + // whatever room JS gives it, so a wide one of these only puts the track in the + // middle of a wide gap. + // + // The across side is the one a vertical Slider has to think about once it + // marks its steps: the markers hang to the right of the track, half a thumb and + // a gap past its middle, which is the room a horizontal Slider spends out of its + // height without noticing. Android's thumb is the wider of the two, so it sets + // how far out the dots land - and 60 is what a marked Slider measures itself to + // there, a Slider being asked to measure itself only when its style gives it no + // size to take. + verticalSlider: { + width: 60, + height: 220, + }, + // Roomier across again, for the markers `SteppedMarker` renders. Those start + // from the very point a built-in dot starts from, and from the same edge of it, + // but a marker is as wide as it makes itself - a tick with a label under it, + // against a dot four across - so the whole of it reaches a good deal further + // right from that start. Nothing widens the Slider for them either: they hang + // out of it, unclipped, exactly as this marker hangs out of the bottom of the + // horizontal example. What this width buys is therefore not room the markers + // are cut to fit, but room for them to stand beside the track without running + // past the Slider into the edge of the page. + verticalMarkedSlider: { + width: 96, + height: 220, + }, + readout: { + fontSize: 14, + fontWeight: '500', + textAlign: 'center', + color: '#333333', + }, + thumb: { + width: 26, + height: 26, + borderRadius: 13, + borderWidth: 2, + borderColor: ACCENT, + backgroundColor: '#FFFFFF', + justifyContent: 'center', + alignItems: 'center', + // The lift, taken the way each platform asks for it. + elevation: 3, + shadowColor: '#000000', + shadowOpacity: 0.2, + shadowRadius: 3, + shadowOffset: {width: 0, height: 1}, }, - { - title: 'Slider with custom steps, different width and thumbImage', - render() { - return ; - }, + thumbLabel: { + fontSize: 10, + fontWeight: '600', + color: ACCENT, }, - { - title: 'Inverted slider direction with steps number and thumbImage', - render() { - return ; - }, + dashedTrack: { + height: 10, + borderRadius: 5, + backgroundColor: '#ECE6F5', + flexDirection: 'row', }, - { - title: 'Custom step marker settings', - render() { - return ; - }, + dash: { + flex: 1, + marginHorizontal: 2, + borderRadius: 5, + backgroundColor: ACCENT, }, - { - title: 'Custom component with steps filled when passed', - render() { - return ; - }, + mutedDash: { + opacity: 0.35, }, - { - title: 'Inverted slider direction', - render() { - return ; - }, + // A Slider grows itself to fit the markers it draws, but not the ones JS + // renders, so the room those hang into has to be given from here - a marker + // hangs from a point below the thumb, and nothing clips it. + markedSlider: { + height: 112, }, - { - title: 'Vertical slider', - platform: 'windows', - render() { - return ; - }, + marker: { + alignItems: 'center', }, - { - title: 'Disabled slider', - render() { - return ; - }, + // The tick only changes its width and its colour when marked: a marker is hung + // from the point below its step, so one that grew any taller would push the + // label under it down every time a thumb came to rest on it. + tick: { + width: 2, + height: 12, + borderRadius: 1, + backgroundColor: '#CAC4D0', // M3 outline variant }, - { - title: 'Slider with accessibilityState disabled', - platform: 'android', - render() { - return ; - }, + markedTick: { + width: 4, + borderRadius: 2, + backgroundColor: ACCENT, }, - { - title: 'Custom thumb size (no image)', - render() { - return ; - }, + markerLabel: { + marginTop: 2, + fontSize: 9, + fontWeight: '500', + color: '#79747E', // M3 outline }, - { - title: 'Custom thumb size (scaled image)', - render() { - return ( - - ); - }, + markedLabel: { + color: ACCENT, + fontWeight: '600', }, -]; +}); + +export default SliderExample; diff --git a/example/src/Props.tsx b/example/src/Props.tsx deleted file mode 100644 index 22498bd0..00000000 --- a/example/src/Props.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import React, {JSX, useState} from 'react'; -import {Text, View, StyleSheet} from 'react-native'; -import Slider, {SliderProps} from '@react-native-community/slider'; - -export interface Props { - title: string; - render(): JSX.Element; -} - -const SliderExample = (props: SliderProps) => { - const [value, setValue] = useState(0); - return ( - - {value && +value.toFixed(3)} - - - ); -}; - -const SlidingStartExample = (props: SliderProps) => { - const [slideStartingValue, setSlideStartingValue] = useState(0); - const [slideStartingCount, setSlideStartingCount] = useState(0); - return ( - - { - setSlideStartingValue(value); - setSlideStartingCount(prev => prev + 1); - }} - /> - - Starts: {slideStartingCount} Value: {slideStartingValue} - - - ); -}; - -const SlidingCompleteExample = () => { - const [slideCompletionValue, setSlideCompletionValue] = useState(0); - const [slideCompletionCount, setSlideCompletionCount] = useState(0); - return ( - - { - setSlideCompletionValue(value); - setSlideCompletionCount(prev => prev + 1); - }} - /> - - Completions: {slideCompletionCount} Value: {slideCompletionValue} - - - ); -}; - -export default SliderExample; - -const styles = StyleSheet.create({ - slider: { - width: 300, - opacity: 1, - height: 50, - marginTop: 10, - }, - text: { - fontSize: 14, - textAlign: 'center', - fontWeight: '500', - margin: 0, - }, -}); - -export const propsExamples: Props[] = [ - { - title: 'Default settings', - render() { - return ; - }, - }, - { - title: 'disabled', - render() { - return ; - }, - }, - { - title: 'maximumValue', - render() { - return ; - }, - }, - { - title: 'minimumTrackTintColor', - render() { - return ; - }, - }, - { - title: 'minimumValue', - render() { - return ; - }, - }, - { - title: 'onSlidingStart', - render() { - return ; - }, - }, - { - title: 'onSlidingComplete', - render() { - return ; - }, - }, - { - title: 'onValueChange', - render() { - return ; - }, - }, - { - title: 'step', - render() { - return ; - }, - }, - { - title: 'maximumTrackTintColor', - render() { - return ; - }, - }, - { - title: 'value', - render() { - return ; - }, - }, - { - title: 'tapToSeek', - render(): React.ReactElement { - return ; - }, - }, - { - title: 'inverted', - render() { - return ; - }, - }, - { - title: 'vertical', - render() { - return ; - }, - }, - { - title: 'thumbTintColor', - render() { - return ; - }, - }, - { - title: 'maximumTrackImage', - render() { - return ( - - ); - }, - }, - { - title: 'minimumTrackImage', - render() { - return ( - - ); - }, - }, - { - title: 'thumbImage', - render() { - return ( - - ); - }, - }, - { - title: 'trackImage', - render() { - return ; - }, - }, -]; diff --git a/package/.eslintrc.json b/package/.eslintrc.json deleted file mode 100644 index 812d655a..00000000 --- a/package/.eslintrc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@react-native-community", - "env": { - "es6": true, - "jest": true - }, - "globals": { - "it": true, - "expect": true, - "element": true, - "describe": true, - "by": true, - "device": true, - "beforeAll": true, - "beforeEach": true, - "afterAll": true, - "jest": true, - "jasmine": true - } -} \ No newline at end of file diff --git a/package/__test__/Slider.test.tsx b/package/__test__/Slider.test.tsx index f2073126..93cf8af5 100644 --- a/package/__test__/Slider.test.tsx +++ b/package/__test__/Slider.test.tsx @@ -1,80 +1,708 @@ -import React from 'react'; -import {fireEvent, render} from '@testing-library/react-native'; -import Slider from '../src/Slider'; +import React from "react"; +import {StyleSheet, Text, ViewProps} from "react-native"; +import {act, fireEvent, render, within} from "@testing-library/react-native"; +import Slider from "../src/Slider"; +import type {StepsProps, ThumbProps, TrackProps} from "../typings"; -describe('Slider', () => { - it('Calls the given onValueChange when native event is emitted', () => { +const StepThumb = ({index}: ThumbProps) => {`step ${index}`}; + +const StepMarker = ({index, value, marked}: StepsProps) => ( + {`marker ${index}: ${value}${marked ? " marked" : ""}`} +); + +const TrackThatReports = ({disabled}: TrackProps) => ( + {`track${disabled ? " disabled" : ""}`} +); + +const ThumbThatReports = ({index, disabled}: ThumbProps) => ( + {`step ${index}${disabled ? " disabled" : ""}`} +); + +const MarkerThatReports = ({index, disabled}: StepsProps) => ( + {`marker ${index}${disabled ? " disabled" : ""}`} +); + +const markerTexts = (texts: {props: {children?: unknown}}[]) => + texts.map(({props}) => props.children); + +describe("Slider", () => { + it("Calls the given onValueChange when the native event is emitted", async () => { const onValueChange = jest.fn(); - const {getByTestId} = render( + const {getByTestId} = await render( , ); - const slider = getByTestId('slider'); - fireEvent(slider, 'change', {nativeEvent: {value: 2}}); + + await fireEvent(getByTestId("slider"), "valueChange", { + nativeEvent: {value: 2}, + }); + expect(onValueChange).toHaveBeenCalledWith(2); }); - it('Handles provided events when sliding starts is emitted', () => { - const onSlidingStart = jest.fn(); - const {getByTestId} = render( - , - ); - const slider = getByTestId('slider'); + it("Does not pass a handler down to the native component when none is given", async () => { + const {getByTestId} = await render(); - fireEvent(slider, 'onRNCSliderSlidingStart', {nativeEvent: {value: 2}}); - expect(onSlidingStart).toHaveBeenCalledWith(2); + expect(getByTestId("slider").props.onValueChange).toBeUndefined(); }); - it('Handles provided events when sliding end is emitted', () => { - const onSlidingComplete = jest.fn(); - const {getByTestId} = render( - , + it("Forwards the range and the value to the native component", async () => { + const {getByTestId} = await render( + , ); - const slider = getByTestId('slider'); + const slider = getByTestId("slider"); - fireEvent(slider, 'onRNCSliderSlidingComplete', {nativeEvent: {value: 2}}); - expect(onSlidingComplete).toHaveBeenCalledWith(2); + expect(slider.props.minimumValue).toBe(5); + expect(slider.props.maximumValue).toBe(25); + expect(slider.props.value).toBe(10); }); - it('Calls the accessibility handler when accessibility action is triggered', () => { - const mockedAccessibilityHandler = jest.fn(); - const {getByTestId} = render( - , - ); - const slider = getByTestId('slider'); + it("Falls back to the default range and value", async () => { + const {getByTestId} = await render(); + const slider = getByTestId("slider"); + + expect(slider.props.minimumValue).toBe(0); + expect(slider.props.maximumValue).toBe(1); + expect(slider.props.value).toBe(0); + }); + + it("Forwards the step to the native component", async () => { + const {getByTestId} = await render(); + + expect(getByTestId("slider").props.step).toBe(0.25); + }); + + it("Falls back to a slider with no step", async () => { + const {getByTestId} = await render(); + + expect(getByTestId("slider").props.step).toBe(0); + }); - fireEvent(slider, 'onRNCSliderAccessibilityAction', { - actionName: 'mocked-action', + describe("Disabled", () => { + it("Forwards being switched off to the native component", async () => { + const {getByTestId} = await render(); + expect(getByTestId("slider").props.disabled).toBe(true); }); - expect(mockedAccessibilityHandler).toHaveBeenCalledWith({ - actionName: 'mocked-action', + + it("Leaves a slider given no decision to make switched on", async () => { + const {getByTestId} = await render(); + expect(getByTestId("slider").props.disabled).toBeUndefined(); + }); + + it("Keeps forwarding the values a switched-off slider is to be shown at", async () => { + const {getByTestId} = await render( + , + ); + const slider = getByTestId("slider"); + + expect(slider.props.value).toBe(20); + expect(slider.props.valueLeft).toBe(10); + expect(slider.props.valueRight).toBe(40); + }); + + it("Tells a custom track and thumb that the Slider is switched off", async () => { + const {getByText} = await render( + , + ); + + expect(getByText("track disabled")).toBeTruthy(); + expect(getByText("step 0 disabled")).toBeTruthy(); + }); + + it("Tells every custom step marker that the Slider is switched off", async () => { + const {getAllByText} = await render( + , + ); + + expect(markerTexts(getAllByText(/^marker/))).toEqual([ + "marker 0 disabled", + "marker 1 disabled", + "marker 2 disabled", + ]); + }); + + it("Tells both thumbs of a switched-off ranged Slider", async () => { + const {getAllByText} = await render( + , + ); + + expect(markerTexts(getAllByText(/^step/))).toEqual([ + "step 0 disabled", + "step 1 disabled", + ]); + }); + + it("Leaves a switched-on Slider's custom elements uninformed", async () => { + const {queryByText, getByText} = await render( + , + ); + + expect(queryByText("track disabled")).toBeNull(); + expect(queryByText("step 0 disabled")).toBeNull(); + expect(queryByText("marker 0 disabled")).toBeNull(); + expect(getByText("track")).toBeTruthy(); + }); + + it("Switches the custom elements off and back on with the Slider", async () => { + const {queryByText, getByText, rerender} = await render( + , + ); + + expect(getByText("step 0")).toBeTruthy(); + + await act(async () => { + await rerender( + , + ); + }); + + expect(getByText("step 0 disabled")).toBeTruthy(); + + await act(async () => { + await rerender( + , + ); + }); + + expect(queryByText("step 0 disabled")).toBeNull(); + expect(getByText("step 0")).toBeTruthy(); }); }); - it('Emitts a warning in the dev console if lower and upper limits are switched', () => { - const mockedWarn = jest.fn(); - console.warn = mockedWarn; - render(); - expect(mockedWarn).toHaveBeenCalled(); + describe("Custom thumb", () => { + it("Renders nothing inside the slider when no thumb is given", async () => { + const {getByTestId} = await render(); + + expect(getByTestId("slider").children).toHaveLength(0); + }); + + it("Renders the thumb inside the slider, told the step it stands on", async () => { + const {getByTestId, getByText} = await render( + , + ); + + expect(getByTestId("slider").children).toHaveLength(1); + expect(getByText("step 20")).toBeTruthy(); + }); + + it("Hosts each thumb in a view of its own that leaves touches to the slider", async () => { + const {getByTestId} = await render( + , + ); + const thumbHosts = getByTestId("slider").children; + + expect(thumbHosts).toHaveLength(2); + thumbHosts.forEach((thumbHost) => { + expect(typeof thumbHost).not.toBe("string"); + const {props} = thumbHost as {props: ViewProps}; + expect(props.collapsable).toBe(false); + expect(props.pointerEvents).toBe("none"); + }); + }); + + it("Counts the steps from the minimum value", async () => { + const {getByText} = await render( + , + ); + + expect(getByText("step 3")).toBeTruthy(); + }); + + it("Counts a step the value does not land on exactly as the nearest one", async () => { + const {getByText} = await render( + , + ); + + expect(getByText("step 3")).toBeTruthy(); + }); + + it("Places a value outside the range at the nearest end of it", async () => { + const {getByText, rerender} = await render( + , + ); + + expect(getByText("step 5")).toBeTruthy(); + + await rerender( + , + ); + + expect(getByText("step 0")).toBeTruthy(); + }); + + it("Reports step 0 on a slider with no step", async () => { + const {getByText} = await render( + , + ); + + expect(getByText("step 0")).toBeTruthy(); + }); + + it("Follows the value the user drags the thumb to", async () => { + const {getByTestId, getByText} = await render( + , + ); + + await fireEvent(getByTestId("slider"), "valueChange", { + nativeEvent: {value: 7}, + }); + + expect(getByText("step 7")).toBeTruthy(); + }); + + it("Still calls the given onValueChange", async () => { + const onValueChange = jest.fn(); + const {getByTestId} = await render( + , + ); + + await fireEvent(getByTestId("slider"), "valueChange", { + nativeEvent: {value: 7}, + }); + + expect(onValueChange).toHaveBeenCalledWith(7); + }); + + it("Listens for the value even when no handler is given for it", async () => { + const {getByTestId} = await render( + , + ); + const slider = getByTestId("slider"); + + expect(slider.props.onValueChange).toBeDefined(); + expect(slider.props.onLeftValueChange).toBeDefined(); + expect(slider.props.onRightValueChange).toBeDefined(); + }); + + it("Moves back to a value given in props after a drag", async () => { + const {getByTestId, getByText, rerender} = await render( + , + ); + + await fireEvent(getByTestId("slider"), "valueChange", { + nativeEvent: {value: 7}, + }); + await rerender( + , + ); + + expect(getByText("step 30")).toBeTruthy(); + }); + + it("Renders a thumb at each end of a ranged slider, left one first", async () => { + const {getByTestId} = await render( + , + ); + + const [left, right] = getByTestId("slider").children; + + expect(typeof left).not.toBe("string"); + expect(typeof right).not.toBe("string"); + if (typeof left !== "string" && typeof right !== "string") { + expect(within(left).getByText("step 2")).toBeTruthy(); + expect(within(right).getByText("step 8")).toBeTruthy(); + } + }); + + it("Follows each thumb of a ranged slider on its own", async () => { + const onLeftValueChange = jest.fn(); + const onRightValueChange = jest.fn(); + const {getByTestId, getByText, queryByText} = await render( + , + ); + const slider = getByTestId("slider"); + + await fireEvent(slider, "leftValueChange", {nativeEvent: {value: 40}}); + expect(getByText("step 4")).toBeTruthy(); + expect(getByText("step 8")).toBeTruthy(); + expect(queryByText("step 2")).toBeNull(); + + await fireEvent(slider, "rightValueChange", {nativeEvent: {value: 60}}); + expect(getByText("step 4")).toBeTruthy(); + expect(getByText("step 6")).toBeTruthy(); + expect(queryByText("step 8")).toBeNull(); + + expect(onLeftValueChange).toHaveBeenCalledWith(40); + expect(onRightValueChange).toHaveBeenCalledWith(60); + }); + + it("Drops the second thumb when the slider stops being ranged", async () => { + const {getByTestId, rerender} = await render( + , + ); + + expect(getByTestId("slider").children).toHaveLength(2); + + await act(async () => { + await rerender(); + }); + + expect(getByTestId("slider").children).toHaveLength(1); + }); }); - it('Provides the onLayout with the measured width', () => { - const {getByTestId} = render(); - const slider = getByTestId('slider'); - fireEvent(slider, 'onLayout', {nativeEvent: {layout: {width: 200}}}); - expect(slider).toHaveStyle({width: 200}); + describe("Step markers", () => { + it("Leaves the steps unmarked by default", async () => { + const {getByTestId} = await render(); + const slider = getByTestId("slider"); + + expect(slider.props.stepMarkers).toBe("none"); + expect(slider.children).toHaveLength(0); + }); + + it("Has the native slider mark the steps when asked to", async () => { + const {getByTestId} = await render( + , + ); + const slider = getByTestId("slider"); + + expect(slider.props.stepMarkers).toBe("native"); + expect(slider.children).toHaveLength(0); + }); + + it("Leaves the steps unmarked when asked not to", async () => { + const {getByTestId} = await render( + , + ); + + expect(getByTestId("slider").props.stepMarkers).toBe("none"); + }); + + it("Renders the given step marker for every step", async () => { + const {getByTestId, getAllByText} = await render( + , + ); + const slider = getByTestId("slider"); + + expect(slider.props.stepMarkers).toBe("custom"); + expect(markerTexts(getAllByText(/^marker/))).toEqual([ + "marker 0: 10", + "marker 1: 15 marked", + "marker 2: 20", + ]); + }); + + it("Hosts each step marker in a view of its own that leaves touches to the slider", async () => { + const {getByTestId} = await render( + , + ); + const markerHosts = getByTestId("slider").children; + + expect(markerHosts).toHaveLength(3); + markerHosts.forEach((markerHost) => { + expect(typeof markerHost).not.toBe("string"); + const {props} = markerHost as {props: ViewProps}; + expect(props.collapsable).toBe(false); + expect(props.pointerEvents).toBe("none"); + }); + }); + + it("Renders no step markers on a slider with no step", async () => { + const {getByTestId} = await render( + , + ); + + expect(getByTestId("slider").children).toHaveLength(0); + }); + + it("Ends on the maximum value when the step does not divide the range", async () => { + const {getAllByText} = await render( + , + ); + + expect(markerTexts(getAllByText(/^marker/))).toEqual([ + "marker 0: 0 marked", + "marker 1: 3", + "marker 2: 6", + "marker 3: 7", + ]); + }); + + it("Reports each step the way its step is written", async () => { + const {getAllByText} = await render( + , + ); + + expect(markerTexts(getAllByText(/^marker/))).toEqual([ + "marker 0: 0 marked", + "marker 1: 0.1", + "marker 2: 0.2", + "marker 3: 0.3", + ]); + }); + + it("Marks the step the user drags the thumb to", async () => { + const {getByTestId, getByText, queryByText} = await render( + , + ); + + await fireEvent(getByTestId("slider"), "valueChange", { + nativeEvent: {value: 3}, + }); + + expect(getByText("marker 3: 3 marked")).toBeTruthy(); + expect(queryByText("marker 0: 0 marked")).toBeNull(); + }); + + it("Marks the step under each thumb of a ranged slider", async () => { + const {getAllByText} = await render( + , + ); + + expect(markerTexts(getAllByText(/marked$/))).toEqual([ + "marker 1: 1 marked", + "marker 3: 3 marked", + ]); + }); + + it("Renders the step markers after the custom track and thumb", async () => { + const Track = () => track; + const {getByTestId} = await render( + , + ); + const slider = getByTestId("slider"); + const [track, thumb, ...markers] = slider.children; + + expect(slider.props.customTrack).toBe(true); + expect(slider.props.customThumb).toBe(true); + expect(typeof track).not.toBe("string"); + expect(typeof thumb).not.toBe("string"); + if (typeof track !== "string" && typeof thumb !== "string") { + expect(within(track).getByText("track")).toBeTruthy(); + expect(within(thumb).getByText("step 0")).toBeTruthy(); + } + expect(markers).toHaveLength(2); + }); + + it("Does not tell the native slider about a custom thumb when there is none", async () => { + const {getByTestId} = await render( + , + ); + + expect(getByTestId("slider").props.customThumb).toBe(false); + }); }); - it('Prevents the gesture control from being released externally', () => { - const mockedRelease = jest.fn(); - jest.mock('../src/index', () => ({ - ...jest.requireActual('../src/index'), - onResponderRelease: mockedRelease, - })); - const {getByTestId} = render(); - fireEvent(getByTestId('slider'), 'onResponderTerminationRequest'); - expect(mockedRelease).not.toHaveBeenCalled(); + // The direction is applied entirely on the native side. A value reversed here + // would be reversed a second time there, and land back where it started. + describe("Reading direction", () => { + const VALUE_PROPS = [ + "minimumValue", + "maximumValue", + "value", + "ranged", + "valueLeft", + "valueRight", + "step", + "lowerLimit", + "upperLimit", + ] as const; + + const valuesOf = (slider: {props: Record}) => + VALUE_PROPS.reduce( + (values, prop) => ({...values, [prop]: slider.props[prop]}), + {} as Record, + ); + + it("Carries the direction of its subtree down to the native component", async () => { + const {getByTestId} = await render( + , + ); + + expect(StyleSheet.flatten(getByTestId("slider").props.style)).toEqual( + expect.objectContaining({direction: "rtl"}), + ); + }); + + it("Forwards the same values whichever way the slider reads", async () => { + const props = { + minimumValue: 10, + maximumValue: 90, + step: 5, + lowerLimit: 20, + upperLimit: 80, + ranged: true, + valueLeft: 30, + valueRight: 70, + }; + const {getByTestId: readingLeftToRight} = await render( + , + ); + const {getByTestId: readingRightToLeft} = await render( + , + ); + + expect(valuesOf(readingRightToLeft("slider"))).toEqual( + valuesOf(readingLeftToRight("slider")), + ); + }); + + it("Counts a custom thumb's step the same whichever way the slider reads", async () => { + const thumbForDirection = async (direction?: "rtl") => { + const {getByText} = await render( + , + ); + + return getByText(/^step /).props.children; + }; + + expect(await thumbForDirection("rtl")).toBe(await thumbForDirection()); + }); + + it("Renders the same step markers, in the same order, whichever way the slider reads", async () => { + const markersForDirection = async (direction?: "rtl") => { + const {getAllByText} = await render( + , + ); + + return markerTexts(getAllByText(/^marker/)); + }; + + const leftToRight = await markersForDirection(); + + expect(leftToRight).toEqual([ + "marker 0: 0", + "marker 1: 1", + "marker 2: 2", + "marker 3: 3 marked", + "marker 4: 4", + ]); + expect(await markersForDirection("rtl")).toEqual(leftToRight); + }); + + it("Reports a dragged value to the handlers it was given, reading right-to-left", async () => { + const onValueChange = jest.fn(); + const {getByTestId} = await render( + , + ); + + await fireEvent(getByTestId("slider"), "valueChange", { + nativeEvent: {value: 2}, + }); + + expect(onValueChange).toHaveBeenCalledWith(2); + }); }); }); diff --git a/package/__test__/components/StepNumber.test.tsx b/package/__test__/components/StepNumber.test.tsx deleted file mode 100644 index 92828c72..00000000 --- a/package/__test__/components/StepNumber.test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import {render} from '@testing-library/react-native'; -import {StepNumber} from '../../src/components/StepNumber'; - -describe('StepNumber', () => { - it('Displays number of step according to given index', () => { - const {getByText} = render( - , - ); - expect(getByText('0')).toBeDefined(); - }); -}); diff --git a/package/__test__/components/StepsIndicator.test.tsx b/package/__test__/components/StepsIndicator.test.tsx deleted file mode 100644 index 5f49a93f..00000000 --- a/package/__test__/components/StepsIndicator.test.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import {render} from '@testing-library/react-native'; -import {StepsIndicator} from '../../src/components/StepsIndicator'; -import {constants} from '../../src/utils/constants'; -import {Platform} from 'react-native'; -import {styles} from '../../src/utils/styles'; - -const defaultOptions = [0, 1, 2, 3, 4, 5]; -const longerOptions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - -describe('StepsIndicator', () => { - it('Renders every step provided in options', () => { - const {getByText} = render( - , - ); - for (const step of defaultOptions) { - expect(getByText(step.toString())).toBeDefined(); - } - }); - - it('Applies the big font size to the lower number of steps', () => { - const {getByTestId} = render( - , - ); - expect(getByTestId('0th-step')).toHaveStyle({ - fontSize: constants.STEP_NUMBER_TEXT_FONT_BIG, - }); - }); - - it('Applies the small font size to the number of steps higher than 9', () => { - const {getByTestId} = render( - , - ); - expect(getByTestId('0th-step')).toHaveStyle({ - fontSize: constants.STEP_NUMBER_TEXT_FONT_SMALL, - }); - }); - - it('Applies platform-dependent styles for web', () => { - Platform.OS = 'web'; - const {getByTestId} = render( - , - ); - expect(getByTestId('StepsIndicator-Container')).toHaveStyle( - styles.stepsIndicator, - ); - }); - - it('Reverts given options when isLTR is set', () => { - const {getByTestId} = render( - , - ); - expect(getByTestId('0th-step')).toHaveTextContent('5'); - expect(getByTestId('2th-step')).toHaveTextContent('3'); - }); - - it('Does not display any step numbers if prop is not set', () => { - const {queryByTestId} = render( - , - ); - expect(queryByTestId('0th-step')).toBeFalsy(); - }); -}); diff --git a/package/__test__/components/TrackMark.test.tsx b/package/__test__/components/TrackMark.test.tsx deleted file mode 100644 index b2356879..00000000 --- a/package/__test__/components/TrackMark.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import {render} from '@testing-library/react-native'; -import {MarkerProps, SliderTrackMark} from '../../src/components/TrackMark'; -import {View} from 'react-native'; - -const MockedStepMarker = ({}: MarkerProps) => ( - -); - -const MockedThumbImage = 1; - -describe('TrackMark', () => { - it('Renders the StepMarker if custom component is given', () => { - const {getByTestId} = render( - , - ); - expect(getByTestId('mockedStepMarker')).toBeDefined(); - }); - - it('Renders the StepMarker with thumbImage if provided', () => { - const {getByTestId} = render( - , - ); - expect(getByTestId('sliderTrackMark-thumbImage')).toBeDefined(); - }); -}); diff --git a/package/android/build.gradle b/package/android/build.gradle index 362ad4a5..8bbd0d73 100644 --- a/package/android/build.gradle +++ b/package/android/build.gradle @@ -1,52 +1,65 @@ - buildscript { + ext.safeExtGet = { name, fallback -> + rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['ReactNativeSlider_' + name] ?: fallback) + } + + ext.kotlinVersion = safeExtGet('kotlinVersion', '2.2.0') + repositories { mavenCentral() google() } dependencies { - classpath("com.android.tools.build:gradle:7.1.1") + classpath("com.android.tools.build:gradle:8.6.0") classpath("com.facebook.react:react-native-gradle-plugin") - classpath("de.undercouch:gradle-download-task:5.0.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + // Bundled with Kotlin 2.x - this is what turns @Composable into Compose code. + classpath("org.jetbrains.kotlin:compose-compiler-gradle-plugin:$kotlinVersion") } } apply plugin: 'com.android.library' +apply plugin: 'org.jetbrains.kotlin.android' +apply plugin: 'org.jetbrains.kotlin.plugin.compose' apply plugin: 'com.facebook.react' -def getExtOrDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeSlider_' + name] +def getExtOrDefault(name, fallback) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['ReactNativeSlider_' + name] ?: fallback) } -def getExtOrIntegerDefault(name) { - return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['ReactNativeSlider_' + name]).toInteger() +def getExtOrIntegerDefault(name, fallback) { + return getExtOrDefault(name, fallback).toString().toInteger() } android { + namespace "callstack.slider" - def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') - // Check AGP version for backward compatibility w/react-native versions still on gradle plugin 6 - def major = agpVersion[0].toInteger() - def minor = agpVersion[1].toInteger() - if ((major == 7 && minor >= 3) || major >= 8) { - namespace "com.reactnativecommunity.slider" - buildFeatures { - buildConfig true - } + compileSdkVersion getExtOrIntegerDefault('compileSdkVersion', 35) + buildToolsVersion getExtOrDefault('buildToolsVersion', '35.0.0') + + defaultConfig { + minSdkVersion getExtOrIntegerDefault('minSdkVersion', 24) + targetSdkVersion getExtOrIntegerDefault('targetSdkVersion', 35) } - compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') - buildToolsVersion getExtOrDefault('buildToolsVersion') + buildFeatures { + buildConfig true + compose true + } - defaultConfig { - minSdkVersion getExtOrIntegerDefault('minSdkVersion') - targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = '17' } sourceSets { main { - java.srcDirs += ['src', "${project.buildDir}/generated/source/codegen/java"] + java.srcDirs += ["${project.buildDir}/generated/source/codegen/java"] } } } @@ -59,5 +72,11 @@ repositories { dependencies { //noinspection GradleDynamicVersion api 'com.facebook.react:react-native:+' -} + def composeBom = platform("androidx.compose:compose-bom:${getExtOrDefault('composeBomVersion', '2024.09.00')}") + implementation composeBom + implementation 'androidx.compose.runtime:runtime' + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.foundation:foundation' + implementation 'androidx.compose.material3:material3' +} diff --git a/package/android/gradle.properties b/package/android/gradle.properties index cdf09fb3..307df7d3 100644 --- a/package/android/gradle.properties +++ b/package/android/gradle.properties @@ -1,4 +1,8 @@ -ReactNativeSlider_compileSdkVersion=30 -ReactNativeSlider_buildToolsVersion=30.0.0 -ReactNativeSlider_targetSdkVersion=30 -ReactNativeSlider_minSdkVersion=21 +ReactNativeSlider_compileSdkVersion=35 +ReactNativeSlider_buildToolsVersion=35.0.0 +ReactNativeSlider_targetSdkVersion=35 +ReactNativeSlider_minSdkVersion=24 +ReactNativeSlider_kotlinVersion=2.2.0 +ReactNativeSlider_composeBomVersion=2024.09.00 + +android.useAndroidX=true diff --git a/package/android/src/main/AndroidManifest.xml b/package/android/src/main/AndroidManifest.xml index f8c7bb80..a2f47b60 100644 --- a/package/android/src/main/AndroidManifest.xml +++ b/package/android/src/main/AndroidManifest.xml @@ -1,3 +1,2 @@ - - \ No newline at end of file + + diff --git a/package/android/src/main/java/callstack/slider/SliderPackage.kt b/package/android/src/main/java/callstack/slider/SliderPackage.kt new file mode 100644 index 00000000..b52766eb --- /dev/null +++ b/package/android/src/main/java/callstack/slider/SliderPackage.kt @@ -0,0 +1,18 @@ +package callstack.slider + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class SliderPackage : ReactPackage { + + // This package ships no native modules, so the deprecated eager API is still the + // cheapest way to satisfy ReactPackage across the supported React Native range. + @Suppress("OVERRIDE_DEPRECATION") + override fun createNativeModules(reactContext: ReactApplicationContext): List = + emptyList() + + override fun createViewManagers(reactContext: ReactApplicationContext): List> = + listOf(SliderViewManager()) +} diff --git a/package/android/src/main/java/callstack/slider/SliderSlidingEvent.kt b/package/android/src/main/java/callstack/slider/SliderSlidingEvent.kt new file mode 100644 index 00000000..0818fbf9 --- /dev/null +++ b/package/android/src/main/java/callstack/slider/SliderSlidingEvent.kt @@ -0,0 +1,39 @@ +package callstack.slider + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event + +/** + * Emitted when the user takes hold of a thumb of a [SliderView], and again + * when they let go of it. + * + * It carries no value: it says that a drag began or ended, not where the thumb + * stands - which is what [SliderValueChangeEvent] is for. + */ +class SliderSlidingEvent( + surfaceId: Int, + viewId: Int, + private val phase: Phase, +) : Event(surfaceId, viewId) { + + override fun getEventName(): String = phase.eventName + + /** + * Each of these marks a moment rather than a position, so none of them may be + * dropped in favour of a later one the way a value change may. + */ + override fun canCoalesce(): Boolean = false + + override fun getEventData(): WritableMap = + Arguments.createMap().apply { putInt("target", viewTag) } + + /** Which end of a drag happened, and the event JS hears about it through. */ + enum class Phase(val eventName: String, val registrationName: String) { + /** The user has grabbed a thumb, which has not moved yet. */ + START("topSlidingStart", "onSlidingStart"), + + /** The user has let go of the thumb they were dragging. */ + COMPLETE("topSlidingComplete", "onSlidingComplete"), + } +} diff --git a/package/android/src/main/java/callstack/slider/SliderValueChangeEvent.kt b/package/android/src/main/java/callstack/slider/SliderValueChangeEvent.kt new file mode 100644 index 00000000..69439e6a --- /dev/null +++ b/package/android/src/main/java/callstack/slider/SliderValueChangeEvent.kt @@ -0,0 +1,36 @@ +package callstack.slider + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event + +/** Emitted while the user drags the thumb of a [SliderView]. */ +class SliderValueChangeEvent( + surfaceId: Int, + viewId: Int, + private val thumb: Thumb, + private val value: Double, +) : Event(surfaceId, viewId) { + + override fun getEventName(): String = thumb.eventName + + override fun getCoalescingKey(): Short = 0 + + override fun getEventData(): WritableMap = + Arguments.createMap().apply { + putInt("target", viewTag) + putDouble("value", value) + } + + /** Which thumb moved, and the event JS hears about it through. */ + enum class Thumb(val eventName: String, val registrationName: String) { + /** The only thumb of a plain slider. */ + SINGLE("topValueChange", "onValueChange"), + + /** The thumb bounding a ranged slider's selected span from below. */ + LEFT("topLeftValueChange", "onLeftValueChange"), + + /** The thumb bounding a ranged slider's selected span from above. */ + RIGHT("topRightValueChange", "onRightValueChange"), + } +} diff --git a/package/android/src/main/java/callstack/slider/SliderView.kt b/package/android/src/main/java/callstack/slider/SliderView.kt new file mode 100644 index 00000000..338d3b5e --- /dev/null +++ b/package/android/src/main/java/callstack/slider/SliderView.kt @@ -0,0 +1,1214 @@ +package callstack.slider + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.RangeSlider +import androidx.compose.material3.RangeSliderState +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableDoubleStateOf +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.abs +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.pow +import kotlin.math.round + +/** + * Hosts the Jetpack Compose slider. Every prop coming from JS is kept in Compose + * state, so a prop update recomposes just the slider instead of touching the view + * hierarchy. + * + * The Compose content lives in a child [ComposeView] rather than in an + * `AbstractComposeView` subclass: Fabric measures a view as soon as it mounts it, + * which happens before the surface is attached to a window, and Compose can only + * find a recomposer once it is attached. Measuring the child is therefore deferred + * until this view has a window - see [onMeasure] and [onAttachedToWindow]. + */ +class SliderView(context: Context) : FrameLayout(context) { + + private var minimumValue by mutableFloatStateOf(DEFAULT_MINIMUM_VALUE) + private var maximumValue by mutableFloatStateOf(DEFAULT_MAXIMUM_VALUE) + private var sliderValue by mutableFloatStateOf(DEFAULT_VALUE) + + /** + * The range exactly as JS gave it, for the steps to be worked out from - see + * [stepMarkerValues], as a range rounded to a [Float] can come to a different + * number of them than JS came to. + */ + private var exactMinimumValue by mutableDoubleStateOf(DEFAULT_MINIMUM_VALUE.toDouble()) + private var exactMaximumValue by mutableDoubleStateOf(DEFAULT_MAXIMUM_VALUE.toDouble()) + + private var selectsRange by mutableStateOf(DEFAULT_RANGED) + private var valueLeft by mutableFloatStateOf(DEFAULT_VALUE_LEFT) + private var valueRight by mutableFloatStateOf(DEFAULT_VALUE_RIGHT) + + /** + * Granularity of the values the slider reports: every one of them is a multiple of + * this. Zero means no granularity. + * + * Kept as a [Double] where the rest of the state is a [Float], because it is the + * reported values that are snapped to it rather than the thumb position - see + * [snapped]. Named as it is so that its setter does not clash with [setStep]. + */ + private var stepValue by mutableDoubleStateOf(DEFAULT_STEP) + + /** + * Bounds the thumbs can be dragged between. The track is drawn across the whole range + * regardless: a limit takes away the values a thumb can reach, not the ones the slider + * shows. + */ + private var lowerLimitValue by mutableFloatStateOf(DEFAULT_LOWER_LIMIT) + private var upperLimitValue by mutableFloatStateOf(DEFAULT_UPPER_LIMIT) + + /** + * Whether the slider is laid out along the view's height rather than its width, with + * the larger values at the top - see [sliderAxis]. Held as the one thing the + * orientation decides, so that anything other than "vertical" reads as the horizontal + * slider it defaults to. + */ + private var isVertical by mutableStateOf(DEFAULT_VERTICAL) + + /** + * Whether the slider is switched off: drawn in the disabled look Material 3 gives a + * slider nobody can reach, deaf to touches, taps and keyboard, and reporting no + * events of its own. + * + * The values the slider holds are no part of this - a slider switched off still shows + * wherever the props put its thumbs. + */ + private var disabledValue by mutableStateOf(DEFAULT_DISABLED) + + /** + * Tints of the three parts the thumbs cut the track into: below the thumb (or the + * left thumb), between the two thumbs of a ranged slider, and above the thumb (or + * the right thumb). [Color.Unspecified] leaves a part the colour Material 3 gives + * it - see [trackColors]. + */ + private var minimumTrackColor by mutableStateOf(Color.Unspecified) + private var middleRangeTrackColor by mutableStateOf(Color.Unspecified) + private var maximumTrackColor by mutableStateOf(Color.Unspecified) + + /** + * Whether the user has hold of the slider, from the touch going down to the last + * finger coming off - so it covers a drag that never moved a thumb as well as one + * that did, see [slidingGestures]. + * + * While the user drags, the thumb position is owned by this view and value updates + * from JS are ignored. A ranged slider stops listening for both of its values at + * once: Compose's `RangeSlider` is driven by the span as a whole, so a value pushed + * for the thumb standing still would disturb the one being dragged just the same. + */ + private var isSliding = false + + /** + * How many of the thumbs JS has replaced with views of its own, counted from the + * left one - which the single thumb counts as. A replaced thumb is still there to + * be dragged, it is just no longer drawn; the view JS gave is laid over it + * instead - see [thumbContainers]. + */ + private var customThumbCount by mutableIntStateOf(0) + + /** + * Whether JS has replaced the track with a view of its own. The slider then draws + * no track at all, whatever its tints - the view JS gave lies underneath it + * instead, see [trackContainer]. + */ + private var hasCustomTrack by mutableStateOf(DEFAULT_CUSTOM_TRACK) + + /** + * Whether JS has replaced the thumbs with views of its own, which is what tells + * the thumbs apart from the step markers that follow them - see [childViews]. + */ + private var hasCustomThumb = DEFAULT_CUSTOM_THUMB + + private var showsStepMarkers by mutableStateOf(false) + + /** + * Whether JS has replaced the step markers with views of its own, one for each of + * [stepMarkerValues]. The slider then draws none of its own - the views JS gave + * hang below the steps instead, see [stepMarkerContainers]. + */ + private var hasCustomStepMarkers by mutableStateOf(false) + + /** + * How thick the thumb Material 3 draws is across the track, which the step + * markers keep clear of. Read off the thumb itself rather than written down here, + * as the thumb is a different shape from one version of Material 3 to the next. + */ + private var thumbThickness by mutableIntStateOf(0) + + /** + * Where the track lies, and which way round, as last laid out - which is where + * the custom step markers are hung from, see [placeStepMarkerContainers]. + */ + private var stepMarkerTrack: LayoutCoordinates? = null + private var stepMarkerLayoutDirection = LayoutDirection.Ltr + + /** + * The views JS rendered as the slider's children, in the order it rendered them: + * the track, when [hasCustomTrack] says JS replaced it, then the single thumb, or + * the left and then the right one, when [hasCustomThumb] says JS replaced them, + * and then a step marker for each step, when [hasCustomStepMarkers] says JS + * replaced those. + */ + private val childViews = mutableListOf() + + /** + * One for each thumb there can be, holding the view that replaces it. Each is + * moved so that its origin stays on the centre of its thumb: the shadow node + * centres the view it holds on that origin, so the view ends up centred on the + * thumb too. + * + * They handle no touches, and neither does a view JS renders as a thumb, so every + * touch falls through them to the slider underneath. + */ + private val thumbContainers = List(MAXIMUM_THUMB_COUNT) { ChildContainer(context) } + + /** + * Holds the view that replaces the track, the way [thumbContainers] hold the + * thumbs: its origin stays on the middle of the slider, and it is turned a quarter + * turn anticlockwise about it on a vertical slider, which carries the start of the + * track down to the bottom, where the minimum is - see [placeTrackContainer]. + * + * It lies underneath the slider rather than over it, so that the thumbs the slider + * draws stay on top of the track. + */ + private val trackContainer = + ChildContainer(context).apply { + pivotX = 0f + pivotY = 0f + } + + /** + * One for each step JS renders a marker for, holding that marker the way + * [thumbContainers] hold the thumbs - except that the origin of each is kept at the + * point its marker hangs from, see [placeStepMarkerContainers]. + * + * There are as many of them as there are markers, so they are made as the markers + * come, and lie between the slider and the thumbs. + */ + private val stepMarkerContainers = mutableListOf() + + /** + * Invoked continuously while the user drags a thumb, and once each when the user + * takes hold of one and lets go of it - whether or not the drag in between moved it + * anywhere. A single slider reports through [onValueChange], a ranged one through + * its two sided handlers. + */ + var onValueChange: ((Double) -> Unit)? = null + var onLeftValueChange: ((Double) -> Unit)? = null + var onRightValueChange: ((Double) -> Unit)? = null + var onSlidingStart: (() -> Unit)? = null + var onSlidingComplete: (() -> Unit)? = null + + private val composeView = + ComposeView(context).apply { + setViewCompositionStrategy( + ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool + ) + setContent { SliderContent() } + // The step markers hang below the thumbs, which takes them past the bottom of + // a slider laid out at its own height. + clipChildren = false + } + + init { + addView(composeView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) + + // A custom thumb is centred on the origin of its container, and so hangs off it + // - and off the slider too, at either end of the track. So does the custom track, + // which is centred the same way. + clipChildren = false + addView(trackContainer, 0, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) + thumbContainers.forEach { + addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) + } + } + + /** + * Takes a view JS rendered in place of the built-in track or of a built-in thumb - + * see [childViews]. + */ + fun addChildView(view: View, index: Int) { + childViews.add(index.coerceIn(0, childViews.size), view) + onChildViewsChanged() + } + + fun removeChildViewAt(index: Int) { + val view = childViews.removeAt(index) + (view.parent as? ViewGroup)?.removeView(view) + onChildViewsChanged() + } + + fun getChildViewCount(): Int = childViews.size + + fun getChildViewAt(index: Int): View? = childViews.getOrNull(index) + + /** + * Hands every child to the container it belongs in. Which one that is depends on + * which of the track, the thumbs and the step markers JS replaced, which can change + * before or after the children it concerns are added - so it is worked out afresh + * whenever either does. + */ + private fun onChildViewsChanged() { + val trackView = if (hasCustomTrack) childViews.firstOrNull() else null + val remainingViews = if (trackView != null) childViews.drop(1) else childViews + val thumbCount = if (hasCustomThumb) (if (selectsRange) MAXIMUM_THUMB_COUNT else 1) else 0 + val thumbViews = remainingViews.take(thumbCount) + val stepMarkerViews = if (hasCustomStepMarkers) remainingViews.drop(thumbCount) else emptyList() + + setStepMarkerContainerCount(stepMarkerViews.size) + + val placements = + thumbViews.zip(thumbContainers) + + stepMarkerViews.zip(stepMarkerContainers) + + listOfNotNull(trackView?.let { it to trackContainer }) + + // A view left without a container is not shown anywhere. + childViews + .filter { view -> placements.none { (placed, _) -> placed === view } } + .forEach { view -> (view.parent as? ViewGroup)?.removeView(view) } + placements.forEach { (view, container) -> + if (view.parent !== container) { + (view.parent as? ViewGroup)?.removeView(view) + container.addView(view) + } + } + + customThumbCount = minOf(thumbViews.size, MAXIMUM_THUMB_COUNT) + placeStepMarkerContainers() + applyDisabledLook() + } + + /** + * Makes or drops step marker containers until there is one for each of the given + * number of markers. A dropped container takes the marker it held with it. + */ + private fun setStepMarkerContainerCount(count: Int) { + while (stepMarkerContainers.size > count) { + removeView(stepMarkerContainers.removeAt(stepMarkerContainers.lastIndex)) + } + while (stepMarkerContainers.size < count) { + val container = ChildContainer(context) + addView( + container, + indexOfChild(thumbContainers.first()), + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT), + ) + stepMarkerContainers.add(container) + } + } + + /** + * Moves the origin of every step marker container onto the point its marker hangs + * from: right under where the thumb stands on that step and clear of it, so that + * the marker of the step a thumb is on stays in sight. + * + * The point is found along the track as Material 3 lays it out, and turned with it + * on a vertical slider - which puts the markers to the right of the track. The + * markers themselves are not turned, which keeps whatever they read the right way + * up. + * + * A marker JS renders for a step the slider does not have - the two being + * momentarily out of step while props and children arrive - is left hidden rather + * than piled up somewhere. + */ + private fun placeStepMarkerContainers() { + val track = stepMarkerTrack?.takeIf { it.isAttached } ?: return + val values = stepMarkerValues() + val below = stepMarkerOffset(track.size.height.toFloat(), resources.displayMetrics.density) + + stepMarkerContainers.forEachIndexed { index, container -> + val value = values.getOrNull(index) + container.visibility = if (value == null) INVISIBLE else VISIBLE + if (value != null) { + val anchor = + track.localToRoot( + Offset(alongTrack(value, track.size.width.toFloat(), stepMarkerLayoutDirection), below) + ) + container.translationX = anchor.x + container.translationY = anchor.y + } + } + } + + fun setCustomThumb(value: Boolean) { + if (value == hasCustomThumb) { + return + } + + hasCustomThumb = value + onChildViewsChanged() + } + + /** + * How the steps are marked: not at all, by markers drawn here, or by the views JS + * adds last. + */ + fun setStepMarkers(value: String?) { + showsStepMarkers = value == STEP_MARKERS_NATIVE || value == STEP_MARKERS_CUSTOM + hasCustomStepMarkers = value == STEP_MARKERS_CUSTOM + onChildViewsChanged() + } + + fun setCustomTrack(value: Boolean) { + if (value == hasCustomTrack) { + return + } + + hasCustomTrack = value + onChildViewsChanged() + } + + fun setMinimumValue(value: Double) { + minimumValue = value.toFloat() + exactMinimumValue = value + placeStepMarkerContainers() + } + + fun setMaximumValue(value: Double) { + maximumValue = value.toFloat() + exactMaximumValue = value + placeStepMarkerContainers() + } + + fun setValue(value: Double) { + if (!isSliding) { + sliderValue = value.toFloat() + } + } + + fun setRanged(value: Boolean) { + selectsRange = value + onChildViewsChanged() + } + + fun setValueLeft(value: Double) { + if (!isSliding) { + valueLeft = value.toFloat() + } + } + + fun setValueRight(value: Double) { + if (!isSliding) { + valueRight = value.toFloat() + } + } + + /** A value pushed from JS is taken as it comes, never snapped to the step. */ + fun setStep(value: Double) { + stepValue = value + placeStepMarkerContainers() + } + + fun setLowerLimit(value: Double) { + lowerLimitValue = value.toFloat() + } + + fun setUpperLimit(value: Double) { + upperLimitValue = value.toFloat() + } + + fun setOrientation(value: String?) { + isVertical = value == ORIENTATION_VERTICAL + placeTrackContainer() + } + + /** + * Switches the slider off, or back on. + * + * A drag in progress is answered the way a drag coming to an end is, rather than + * abandoned along with the touches that were driving it: JS is promised one complete + * for every start, and a drag left hanging would keep the values JS pushes from + * reaching the thumbs - see [isSliding]. + * + * The views JS renders in place of the track, the thumbs and the step markers are + * switched off along with the rest of the slider - see [applyDisabledLook]. + */ + fun setDisabled(value: Boolean) { + disabledValue = value + + if (value) { + finishSliding() + } + + applyDisabledLook() + } + + /** + * Draws the views JS renders in place of the track, of a thumb and of the step markers + * at the faintness of a control nobody can reach. + * + * The slider draws itself in Material 3's own disabled colours once it is switched off, + * but a view JS renders is JS's own drawing, mounted over it, and nothing here reaches + * it as a state to draw itself by. So the views JS gave are dimmed here instead - over + * whatever JS drew into them, which is why one that dims itself for being switched off + * ends up dimmed twice over. + */ + private fun applyDisabledLook() { + val alpha = if (disabledValue) DISABLED_CHILD_ALPHA else 1f + + trackContainer.alpha = alpha + thumbContainers.forEach { container -> container.alpha = alpha } + stepMarkerContainers.forEach { container -> container.alpha = alpha } + } + + fun setMinimumTrackColor(value: Int?) { + minimumTrackColor = value.toTrackColor() + } + + fun setMiddleRangeTrackColor(value: Int?) { + middleRangeTrackColor = value.toTrackColor() + } + + fun setMaximumTrackColor(value: Int?) { + maximumTrackColor = value.toTrackColor() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if (!isAttachedToWindow) { + // Measuring the child here would make Compose look for a window recomposer + // that does not exist yet. Take the size Fabric asked for and stop there. + setMeasuredDimension( + getDefaultSize(suggestedMinimumWidth, widthMeasureSpec), + getDefaultSize(suggestedMinimumHeight, heightMeasureSpec), + ) + return + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + placeTrackContainer() + } + + /** + * Moves the origin of [trackContainer] onto the middle of the slider, where the + * shadow node centres the track on - laid out along the slider's length, which is + * its height on a vertical one. + */ + private fun placeTrackContainer() { + trackContainer.translationX = width / 2f + trackContainer.translationY = height / 2f + trackContainer.rotation = if (isVertical) -90f else 0f + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + // Fabric already measured and laid this view out while it was detached, at which + // point the child was skipped. Redo that pass now that Compose can compose. + post { + if (width > 0 && height > 0) { + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), + ) + layout(left, top, right, bottom) + } + } + } + + /** + * Compose requires a non-empty, ascending range. Props arrive one at a time, so a + * momentarily inverted range is expected rather than exceptional. + */ + private fun valueRange(): ClosedFloatingPointRange = + if (maximumValue > minimumValue) { + minimumValue..maximumValue + } else { + minimumValue..minimumValue + 1f + } + + /** + * The range narrowed down to the part of it the thumbs can be dragged across. + * + * A limit that cannot be honoured is dropped: limits that have crossed leave the + * slider unlimited, and one reaching out of the range limits only as far as the + * range itself goes. + */ + private fun limits(range: ClosedFloatingPointRange): ClosedFloatingPointRange = + if (lowerLimitValue > upperLimitValue) { + range + } else { + lowerLimitValue.coerceIn(range)..upperLimitValue.coerceIn(range) + } + + private fun selectedRange(range: ClosedFloatingPointRange): ClosedFloatingPointRange { + val left = valueLeft.coerceIn(range) + val right = valueRight.coerceIn(range) + + return minOf(left, right)..maxOf(left, right) + } + + /** + * The values the steps stand at: every step from the minimum, and the maximum + * itself when the step does not divide the range, which is how a thumb can still + * reach it. A slider with no step has no steps to mark. + * + * JS works out the very same values to render its own step markers by, and has to + * come to as many of them as this does - so the two are worked out the same way, + * down to the tolerance. + */ + private fun stepMarkerValues(): List { + val span = exactMaximumValue - exactMinimumValue + if (stepValue <= 0 || span <= 0) { + return emptyList() + } + + val wholeSteps = floor(span / stepValue + STEP_TOLERANCE).toInt() + val values = List(wholeSteps + 1) { exactMinimumValue + it * stepValue } + + return if (span - wholeSteps * stepValue > stepValue * STEP_TOLERANCE) { + values + exactMaximumValue + } else { + values + } + } + + /** + * Whether a thumb stands on the step at the given value - which is what `marked` + * tells a step marker JS renders, too. The thumbs are held as a [Float], so a + * thumb is let off by as much as that rounds it by. + */ + private fun isMarked(value: Double): Boolean { + val thumbValues = if (selectsRange) listOf(valueLeft, valueRight) else listOf(sliderValue) + + return thumbValues.any { thumbValue -> + val tolerance = max(stepValue * STEP_TOLERANCE, Math.ulp(thumbValue).toDouble()) + abs(thumbValue - value) <= tolerance + } + } + + /** + * Takes the value a drag has moved the thumb to and reports it snapped to the step + * and held to the limits. A stepped or limited slider hears from Compose far more + * often than it moves, so a value that came back as the one already showing is + * dropped rather than reported to JS again - which is also how a drag carrying on + * beyond a limit stays unheard of. + */ + private fun onSliderValueChange(value: Float, limits: ClosedFloatingPointRange) { + // Asked for of the values as much as of the events: Material stops reporting them + // once `enabled` goes, but only from the recomposition that carries it on. + if (disabledValue) { + return + } + + val stepped = snapped(value, limits) + + if (stepped.toFloat() == sliderValue) { + return + } + + sliderValue = stepped.toFloat() + onValueChange?.invoke(stepped) + } + + /** + * Takes the span a drag has moved the thumbs to and reports the ends that actually + * moved, snapped to the step, so that JS hears about the thumb under the finger and + * not about the one standing still. + */ + private fun onSelectedRangeChange( + from: ClosedFloatingPointRange, + to: ClosedFloatingPointRange, + limits: ClosedFloatingPointRange, + ) { + if (disabledValue) { + return + } + + val left = snapped(to.start, limits) + if (left.toFloat() != from.start) { + valueLeft = left.toFloat() + onLeftValueChange?.invoke(left) + } + + val right = snapped(to.endInclusive, limits) + if (right.toFloat() != from.endInclusive) { + valueRight = right.toFloat() + onRightValueChange?.invoke(right) + } + } + + /** + * Rounds a value to the nearest multiple of the step and back into the part of + * the range the thumbs are limited to, which is the granularity JS is promised. + * A step of zero leaves the value where it is. + * + * Compose has a step of its own, but it counts ticks spread evenly across the range + * and lands the value a whole number of steps away from `minimumValue` rather than + * on a multiple of the step, and it needs the step to divide the range evenly to do + * even that. Snapping here answers for both, and answers the same way iOS does. + */ + private fun snapped(value: Float, limits: ClosedFloatingPointRange): Double { + val stepped = + if (stepValue > 0) value.toDouble().roundedToMultipleOf(stepValue) else value.toDouble() + + return stepped.coerceIn(limits.start.toDouble(), limits.endInclusive.toDouble()) + } + + /** + * How far along a track of the given width the step at the given value is, + * counted from its left edge - which the minimum value is at the other end of in a + * right-to-left layout. + */ + private fun alongTrack(value: Double, width: Float, layoutDirection: LayoutDirection): Float { + val span = exactMaximumValue - exactMinimumValue + val fraction = if (span > 0) ((value - exactMinimumValue) / span).coerceIn(0.0, 1.0).toFloat() else 0f + + return width * if (layoutDirection == LayoutDirection.Rtl) 1f - fraction else fraction + } + + /** + * How far below the top of a track of the given height a step marker begins: past + * the thumb, which is centred on the track, and a gap after it. + */ + private fun stepMarkerOffset(trackHeight: Float, density: Float): Float = + trackHeight / 2f + thumbThickness / 2f + STEP_MARKER_GAP_DP * density + + @Composable + private fun SliderContent() { + // The slider is only ever as thick as the control itself, and a vertical one is + // far narrower than the view is wide, so it sits in the middle of whatever room + // JS has given it rather than against one edge. + // + // A vertical slider is composed as if it read left-to-right, whatever way the app + // reads: Material 3 mirrors its slider whenever the direction asks for it, and a + // vertical one here is that horizontal slider turned a quarter turn, so the mirror + // would land inside the turn and carry the minimum up to the top, where the maximum + // belongs. The turn is what puts the minimum at the bottom, so it is the only turn + // a vertical slider needs. + // + // Held to the whole composed slider: the step markers hang off the track inside the + // same turn, so they have to be measured against the direction it was drawn by - + // see [stepMarkerLayoutDirection]. + val compositionDirection = if (isVertical) LayoutDirection.Ltr else LocalLayoutDirection.current + + CompositionLocalProvider(LocalLayoutDirection provides compositionDirection) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + if (selectsRange) { + RangedSliderContent() + } else { + SingleSliderContent() + } + } + } + } + + /** + * Lays the slider out along the axis the orientation asks for, stretched across the + * view. + * + * Material 3 has no vertical slider, so a vertical one here is its horizontal + * slider turned a quarter turn anticlockwise, which puts the larger values at the + * top. Everything the rotation encloses is the same control as ever - the range and + * the step, both thumbs of a ranged slider, the gestures [slidingGestures] listens + * to and the accessibility the platform's own slider comes with - it is only + * measured and drawn the other way round. + */ + private fun Modifier.sliderAxis(): Modifier = + if (!isVertical) { + fillMaxWidth() + } else { + layout { measurable, constraints -> + // Inside the rotation the slider is still a horizontal control, so it is + // offered the height it has to span as its width, and the other way round. + val placeable = + measurable.measure( + Constraints( + minWidth = constraints.minHeight, + maxWidth = constraints.maxHeight, + minHeight = constraints.minWidth, + maxHeight = constraints.maxWidth, + ) + ) + + // Turning anticlockwise about its top left corner carries the slider + // straight up out of the view, so it is placed one width further down to + // come back into place. + // + // The rotation is applied here, on the slider inside the box, rather than on + // the box itself: touches are hit-tested against the bounds of every node, + // with each one's rotation undone. Rotating the box would leave the slider + // laid out off to the side of it, outside those bounds. + layout(placeable.height, placeable.width) { + placeable.placeWithLayer(0, placeable.width) { + rotationZ = -90f + transformOrigin = TransformOrigin(0f, 0f) + } + } + } + .fillMaxWidth() + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun SingleSliderContent() { + val range = valueRange() + val limits = limits(range) + val interactionSource = remember { MutableInteractionSource() } + + Slider( + value = sliderValue.coerceIn(range.start, range.endInclusive), + enabled = !disabledValue, + valueRange = range, + onValueChange = { moved -> onSliderValueChange(moved, limits) }, + modifier = Modifier.sliderAxis().slidingGestures(), + interactionSource = interactionSource, + thumb = { Thumb(interactionSource, index = 0) }, + track = { state -> + val colors = trackColors(active = minimumTrackColor, inactive = maximumTrackColor) + StepMarkedTrack(colors) { + if (customThumbCount > 0) { + SliderDefaults.Track( + sliderState = state, + modifier = Modifier.trackVisibility(), + colors = colors, + enabled = !disabledValue, + thumbTrackGapSize = 0.dp, + ) + } else { + SliderDefaults.Track( + sliderState = state, + modifier = Modifier.trackVisibility(), + colors = colors, + enabled = !disabledValue, + ) + } + } + }, + ) + } + + /** + * Material 3 has a two-thumb slider of its own, so a ranged slider is that rather + * than a pair of thumbs assembled here - it comes with the thumbs that cannot swap + * places, the tinted span between them and the per-thumb accessibility that the + * platform's own control has. + * + * What it does not come with is any notion of the two thumbs being separate: a drag + * reports the whole span at once. Splitting that back into a thumb apiece is left + * to [onSelectedRangeChange]. + */ + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun RangedSliderContent() { + val range = valueRange() + val selected = selectedRange(range) + val limits = limits(range) + val startInteractionSource = remember { MutableInteractionSource() } + val endInteractionSource = remember { MutableInteractionSource() } + + RangeSlider( + value = selected, + enabled = !disabledValue, + valueRange = range, + onValueChange = { moved -> onSelectedRangeChange(from = selected, to = moved, limits = limits) }, + modifier = Modifier.sliderAxis().slidingGestures(), + startInteractionSource = startInteractionSource, + endInteractionSource = endInteractionSource, + startThumb = { Thumb(startInteractionSource, index = 0) }, + endThumb = { Thumb(endInteractionSource, index = 1) }, + track = { state -> RangedTrack(state) }, + ) + } + + /** + * The slider's own colours, with the parts of the track JS has tinted swapped for + * the tints it gave. Material 3 fills in whatever is left [Color.Unspecified]. + */ + @Composable + private fun trackColors(active: Color, inactive: Color): SliderColors = + SliderDefaults.colors(activeTrackColor = active, inactiveTrackColor = inactive) + + /** + * The built-in thumb, or - once JS has replaced it - the same thumb left undrawn, so + * that it keeps its place in the slider's geometry and the view that replaces it has + * a centre to follow. + * + * The centre is read off the thumb itself rather than worked out from the value, + * which leaves the geometry to Material 3 and takes the rotation of a vertical + * slider along with it. + */ + @Composable + private fun Thumb(interactionSource: MutableInteractionSource, index: Int) { + val measured = + Modifier.onSizeChanged { size -> + thumbThickness = size.height + placeStepMarkerContainers() + } + + if (index >= customThumbCount) { + SliderDefaults.Thumb( + interactionSource = interactionSource, + modifier = measured, + enabled = !disabledValue, + ) + return + } + + SliderDefaults.Thumb( + interactionSource = interactionSource, + enabled = !disabledValue, + modifier = + measured.alpha(0f).onGloballyPositioned { coordinates -> + val center = + coordinates.localToRoot( + Offset(coordinates.size.width / 2f, coordinates.size.height / 2f) + ) + thumbContainers[index].translationX = center.x + thumbContainers[index].translationY = center.y + }, + ) + } + + /** + * Leaves the built-in track undrawn once JS has replaced it, the way a replaced + * thumb is - so that it keeps its place in the slider's geometry, and the thumbs + * travel along the custom track exactly as far as they would along this one. + */ + private fun Modifier.trackVisibility(): Modifier = if (hasCustomTrack) alpha(0f) else this + + /** + * Reports the beginning and the end of a drag, which JS is promised to hear about + * whether or not the drag moved a thumb anywhere. + * + * Neither Compose slider offers that: `onValueChangeFinished` does fire for a press + * that moved nothing, but there is no callback for the press itself, and the drag + * interactions the slider publishes only begin once the finger has travelled past + * the touch slop - by which point the thumb has already moved. So the touches are + * watched here instead, on the initial pass, where they can be heard before the + * slider claims them rather than taken away from it. + * + * A slider that is switched off watches nothing. Material 3 keeps the touches away + * from the control itself, but these are watched one modifier further out, where + * they would still be heard - and a start reported there would have to be answered + * with a complete, on a slider that had moved nowhere and was asked not to report + * anything at all. + */ + private fun Modifier.slidingGestures(): Modifier = + pointerInput(disabledValue) { + if (disabledValue) { + return@pointerInput + } + + awaitEachGesture { + awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial) + if (isVertical) { + claimGesture() + } + beginSliding() + + try { + // The gesture is over once the last finger comes off: a second finger landing + // on the other thumb of a ranged slider joins the drag already in progress. + do { + val event = awaitPointerEvent(PointerEventPass.Initial) + } while (event.changes.any { it.pressed }) + } finally { + finishSliding() + } + } + } + + /** + * Keeps the views this slider sits in from taking the gesture away from it. + * + * A vertical slider is dragged along the same axis a vertical scroll view scrolls + * along, and the scroll view gets to intercept every move before the slider sees it. + * Both wait for the finger to travel past the touch slop, so the scroll view would + * win that race and cancel the drag - and a ranged slider, which only picks the + * thumb to drag once the slop is crossed, would hardly ever get hold of one. So the + * gesture is claimed as soon as it lands on the slider instead. + * + * A horizontal slider is left to share its touches: it moves across the way a page + * scrolls, so the two tell each other apart on their own, and claiming the press + * would stop the page scrolling from any touch that happens to start on it. + * + * The claim lasts until the finger comes off, when Android lets it go by itself. + */ + private fun claimGesture() { + parent?.requestDisallowInterceptTouchEvent(true) + } + + private fun beginSliding() { + if (isSliding || disabledValue) { + return + } + + isSliding = true + onSlidingStart?.invoke() + } + + private fun finishSliding() { + if (!isSliding) { + return + } + + isSliding = false + onSlidingComplete?.invoke() + } + + /** + * The track of a ranged slider, tinted in three parts - see [minimumTrackColor]. + * + * Material 3 draws both parts outside the span in the one inactive colour, so the + * track is drawn twice over, once in the colours of each end, and each copy is cut + * down to its own side of the left thumb. The cut runs through the centre of that + * thumb, where the track is hidden under it or under the gap it leaves around it, so + * the two copies meet unseen. + */ + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun RangedTrack(state: RangeSliderState) { + val span = state.valueRange.endInclusive - state.valueRange.start + val cut = if (span > 0f) ((state.activeRangeStart - state.valueRange.start) / span).coerceIn(0f, 1f) else 0f + + val upperColors = trackColors(active = middleRangeTrackColor, inactive = maximumTrackColor) + + StepMarkedTrack(upperColors) { + Box(modifier = Modifier.trackVisibility()) { + RangedTrackPart( + state, + trackColors(active = middleRangeTrackColor, inactive = minimumTrackColor), + Modifier.clipAlongTrack(from = 0f, to = cut), + ) + RangedTrackPart(state, upperColors, Modifier.clipAlongTrack(from = cut, to = 1f)) + } + } + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun RangedTrackPart(state: RangeSliderState, colors: SliderColors, modifier: Modifier) { + if (customThumbCount > 0) { + SliderDefaults.Track( + rangeSliderState = state, + modifier = modifier, + colors = colors, + enabled = !disabledValue, + thumbTrackGapSize = 0.dp, + ) + } else { + SliderDefaults.Track( + rangeSliderState = state, + modifier = modifier, + colors = colors, + enabled = !disabledValue, + ) + } + } + + /** + * What the markers of the steps look like, marked and unmarked. + * + * Material 3 tints the track itself once `enabled` is given to [SliderDefaults.Track], + * but the markers are dots drawn here, so their colours are looked up by hand - from + * the same colours, and asked for the same state. + */ + private fun stepMarkerColors(colors: SliderColors): Pair = + if (disabledValue) { + colors.disabledActiveTickColor to colors.disabledInactiveTickColor + } else { + colors.activeTrackColor to colors.inactiveTrackColor + } + + /** + * The track, with the markers of the steps drawn below it as dots hanging clear of + * the thumbs - in the colour of the selected part of the track for a step a thumb is + * on, and in that of the rest of it otherwise. + * + * The markers are drawn around the track rather than over the slider, as the track + * spans exactly the distance between the centres of the thumbs at either end of the + * range - so a fraction of its width is exactly where a thumb at that fraction of + * the range is centred, see [alongTrack]. They are drawn here even when the track is + * replaced, which is only ever left undrawn. + * + * The track is where custom step markers are hung from too, once it is laid out. + */ + @Composable + private fun StepMarkedTrack(colors: SliderColors, track: @Composable () -> Unit) { + val layoutDirection = LocalLayoutDirection.current + + Box( + modifier = + Modifier.onGloballyPositioned { coordinates -> + stepMarkerTrack = coordinates + stepMarkerLayoutDirection = layoutDirection + placeStepMarkerContainers() + } + .drawBehind { + if (!showsStepMarkers || hasCustomStepMarkers) { + return@drawBehind + } + + val (markedColor, unmarkedColor) = stepMarkerColors(colors) + val radius = STEP_MARKER_SIZE_DP * density / 2f + val below = stepMarkerOffset(size.height, density) + radius + stepMarkerValues().forEach { value -> + drawCircle( + color = if (isMarked(value)) markedColor else unmarkedColor, + radius = radius, + center = Offset(alongTrack(value, size.width, layoutDirection), below), + ) + } + } + ) { + track() + } + } + + /** + * Draws only the stretch of the track between the two given fractions of it, + * counted from the end its minimum value is at - which is the right-hand one in a + * right-to-left layout, the way Material 3 lays the track out too. + */ + private fun Modifier.clipAlongTrack(from: Float, to: Float): Modifier = drawWithContent { + val (left, right) = + if (layoutDirection == LayoutDirection.Rtl) (1f - to) to (1f - from) else from to to + clipRect(left = left * size.width, right = right * size.width) { + this@drawWithContent.drawContent() + } + } + + companion object { + const val DEFAULT_MINIMUM_VALUE = 0f + const val DEFAULT_MAXIMUM_VALUE = 1f + const val DEFAULT_VALUE = 0f + const val DEFAULT_RANGED = false + const val DEFAULT_VALUE_LEFT = 0f + const val DEFAULT_VALUE_RIGHT = 1f + const val DEFAULT_STEP = 0.0 + const val DEFAULT_LOWER_LIMIT = 0f + const val DEFAULT_UPPER_LIMIT = 1f + const val DEFAULT_VERTICAL = false + const val DEFAULT_DISABLED = false + const val DEFAULT_CUSTOM_TRACK = false + const val DEFAULT_CUSTOM_THUMB = false + + /** The one orientation that is not the default. */ + private const val ORIENTATION_VERTICAL = "vertical" + + /** The two thumbs of a ranged slider. */ + private const val MAXIMUM_THUMB_COUNT = 2 + + /** The ways of marking the steps that mark them at all. */ + private const val STEP_MARKERS_NATIVE = "native" + private const val STEP_MARKERS_CUSTOM = "custom" + + /** Leeway, as a fraction of a step, for a value to count as landing on one. */ + private const val STEP_TOLERANCE = 1e-9 + + /** The diameter of a built-in step marker. */ + const val STEP_MARKER_SIZE_DP = 4f + + /** How far a step marker keeps from the thumb. */ + const val STEP_MARKER_GAP_DP = 4f + + /** + * How faint a view JS renders in place of the track, a thumb or a step marker is + * drawn once the slider is switched off - the alpha Material 3 dims a switched-off + * control's own content to, so that the views JS gave go faint along with the + * slider they sit on rather than against it. + */ + const val DISABLED_CHILD_ALPHA = 0.38f + } +} + +/** + * Holds the view JS renders in place of a thumb or of the track - see + * `SliderView.thumbContainers` and `SliderView.trackContainer`. + * + * Fabric lays that view out itself, so this lays out nothing, and leaves the view + * drawn wherever it hangs off it. + */ +private class ChildContainer(context: Context) : ViewGroup(context) { + init { + clipChildren = false + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) = Unit + + override fun hasOverlappingRendering(): Boolean = false +} + +/** A colour coming from JS, where `null` means JS gave none. */ +private fun Int?.toTrackColor(): Color = this?.let { Color(it) } ?: Color.Unspecified + +/** + * Rounds this value to the nearest multiple of [step]. + * + * Multiplying the step back out misses the mark by a fraction whenever the step is one + * that binary floating point cannot hold - three tenths arriving as + * 0.30000000000000004 - so the product is rounded to the decimals the step itself is + * written with, and reaches JS reading the way the step was written. + */ +private fun Double.roundedToMultipleOf(step: Double): Double = + (round(this / step) * step).roundedTo(step.decimals()) + +/** + * How many decimals it takes to write this value out, capped at the digits a [Double] + * can be trusted with. + */ +private fun Double.decimals(): Int { + var decimals = 0 + var scaled = abs(this) + + while (decimals < MAXIMUM_DECIMALS && scaled != floor(scaled)) { + scaled *= 10 + decimals++ + } + + return decimals +} + +private fun Double.roundedTo(decimals: Int): Double { + val scale = 10.0.pow(decimals) + + return round(this * scale) / scale +} + +/** The decimals a [Double] can be trusted with. */ +private const val MAXIMUM_DECIMALS = 15 diff --git a/package/android/src/main/java/callstack/slider/SliderViewManager.kt b/package/android/src/main/java/callstack/slider/SliderViewManager.kt new file mode 100644 index 00000000..12034935 --- /dev/null +++ b/package/android/src/main/java/callstack/slider/SliderViewManager.kt @@ -0,0 +1,229 @@ +package callstack.slider + +import android.content.Context +import android.view.View +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.CallstackSliderManagerDelegate +import com.facebook.react.viewmanagers.CallstackSliderManagerInterface +import com.facebook.yoga.YogaMeasureMode +import com.facebook.yoga.YogaMeasureOutput + +/** + * Manages instances of [SliderView]. + * + * The only children a slider has are the custom track, thumbs and step markers JS + * renders in place of the built-in ones, which the view keeps apart from the slider + * it hosts - so its children are those, and not the views it happens to hold. + */ +@ReactModule(name = SliderViewManager.REACT_CLASS) +class SliderViewManager : + ViewGroupManager(), CallstackSliderManagerInterface { + + private val delegate: ViewManagerDelegate = CallstackSliderManagerDelegate(this) + + override fun getDelegate(): ViewManagerDelegate = delegate + + override fun getName(): String = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext): SliderView = + SliderView(reactContext) + + @ReactProp(name = "minimumValue", defaultDouble = 0.0) + override fun setMinimumValue(view: SliderView, value: Double) { + view.setMinimumValue(value) + } + + @ReactProp(name = "maximumValue", defaultDouble = 1.0) + override fun setMaximumValue(view: SliderView, value: Double) { + view.setMaximumValue(value) + } + + @ReactProp(name = "value", defaultDouble = 0.0) + override fun setValue(view: SliderView, value: Double) { + view.setValue(value) + } + + @ReactProp(name = "ranged", defaultBoolean = false) + override fun setRanged(view: SliderView, value: Boolean) { + view.setRanged(value) + } + + @ReactProp(name = "valueLeft", defaultDouble = 0.0) + override fun setValueLeft(view: SliderView, value: Double) { + view.setValueLeft(value) + } + + @ReactProp(name = "valueRight", defaultDouble = 1.0) + override fun setValueRight(view: SliderView, value: Double) { + view.setValueRight(value) + } + + @ReactProp(name = "step", defaultDouble = 0.0) + override fun setStep(view: SliderView, value: Double) { + view.setStep(value) + } + + @ReactProp(name = "lowerLimit", defaultDouble = 0.0) + override fun setLowerLimit(view: SliderView, value: Double) { + view.setLowerLimit(value) + } + + @ReactProp(name = "upperLimit", defaultDouble = 1.0) + override fun setUpperLimit(view: SliderView, value: Double) { + view.setUpperLimit(value) + } + + @ReactProp(name = "orientation") + override fun setOrientation(view: SliderView, value: String?) { + view.setOrientation(value) + } + + @ReactProp(name = "disabled", defaultBoolean = false) + override fun setDisabled(view: SliderView, value: Boolean) { + view.setDisabled(value) + } + + @ReactProp(name = "minimumTrackColor", customType = "Color") + override fun setMinimumTrackColor(view: SliderView, value: Int?) { + view.setMinimumTrackColor(value) + } + + @ReactProp(name = "middleRangeTrackColor", customType = "Color") + override fun setMiddleRangeTrackColor(view: SliderView, value: Int?) { + view.setMiddleRangeTrackColor(value) + } + + @ReactProp(name = "maximumTrackColor", customType = "Color") + override fun setMaximumTrackColor(view: SliderView, value: Int?) { + view.setMaximumTrackColor(value) + } + + @ReactProp(name = "customTrack", defaultBoolean = false) + override fun setCustomTrack(view: SliderView, value: Boolean) { + view.setCustomTrack(value) + } + + @ReactProp(name = "customThumb", defaultBoolean = false) + override fun setCustomThumb(view: SliderView, value: Boolean) { + view.setCustomThumb(value) + } + + @ReactProp(name = "stepMarkers") + override fun setStepMarkers(view: SliderView, value: String?) { + view.setStepMarkers(value) + } + + override fun addView(parent: SliderView, child: View, index: Int) { + parent.addChildView(child, index) + } + + override fun getChildCount(parent: SliderView): Int = parent.getChildViewCount() + + override fun getChildAt(parent: SliderView, index: Int): View? = parent.getChildViewAt(index) + + override fun removeViewAt(parent: SliderView, index: Int) { + parent.removeChildViewAt(index) + } + + override fun addEventEmitters(reactContext: ThemedReactContext, view: SliderView) { + @Suppress("DEPRECATION") + val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id) + + // The tag and the surface are read at dispatch time, so a recycled view keeps + // reporting under the identity it currently has. + fun emit(thumb: SliderValueChangeEvent.Thumb): (Double) -> Unit = { value -> + eventDispatcher?.dispatchEvent( + SliderValueChangeEvent(UIManagerHelper.getSurfaceId(view), view.id, thumb, value) + ) + } + + fun emitSliding(phase: SliderSlidingEvent.Phase): () -> Unit = { + eventDispatcher?.dispatchEvent( + SliderSlidingEvent(UIManagerHelper.getSurfaceId(view), view.id, phase) + ) + } + + view.onValueChange = emit(SliderValueChangeEvent.Thumb.SINGLE) + view.onLeftValueChange = emit(SliderValueChangeEvent.Thumb.LEFT) + view.onRightValueChange = emit(SliderValueChangeEvent.Thumb.RIGHT) + view.onSlidingStart = emitSliding(SliderSlidingEvent.Phase.START) + view.onSlidingComplete = emitSliding(SliderSlidingEvent.Phase.COMPLETE) + } + + override fun onDropViewInstance(view: SliderView) { + view.onValueChange = null + view.onLeftValueChange = null + view.onRightValueChange = null + view.onSlidingStart = null + view.onSlidingComplete = null + super.onDropViewInstance(view) + } + + override fun getExportedCustomDirectEventTypeConstants(): Map = + SliderValueChangeEvent.Thumb.entries.associate { thumb -> + thumb.eventName to registrationOf(thumb.registrationName) + } + + SliderSlidingEvent.Phase.entries.associate { phase -> + phase.eventName to registrationOf(phase.registrationName) + } + + private fun registrationOf(registrationName: String): Map = + mapOf("registrationName" to registrationName) + + /** + * Fabric asks for the intrinsic size once per surface and reuses the answer for + * every slider on it, so it must not depend on the incoming constraints. The width + * is only a hint - flexbox is what actually stretches the slider - and the answer + * describes a horizontal one, which its shadow node turns round for a vertical one. + * + * The one prop that bears on the size is whether the slider draws step markers of its + * own, which need it thick enough across to hold them - see [STEP_MARKED_HEIGHT_DIP]. + */ + override fun measure( + context: Context, + localData: ReadableMap?, + props: ReadableMap?, + state: ReadableMap?, + width: Float, + widthMode: YogaMeasureMode, + height: Float, + heightMode: YogaMeasureMode, + attachmentsPositions: FloatArray?, + ): Long = + YogaMeasureOutput.make( + INTRINSIC_WIDTH_DIP, + if (props?.getString("stepMarkers") == "native") STEP_MARKED_HEIGHT_DIP else INTRINSIC_HEIGHT_DIP, + ) + + companion object { + const val REACT_CLASS = "CallstackSlider" + + /** Matches the height Material 3 reserves for a slider's touch target. */ + private const val INTRINSIC_HEIGHT_DIP = 48f + + private const val INTRINSIC_WIDTH_DIP = 200f + + /** + * The height of the thumb Material 3 draws - its handle. The view reads the + * thumb's actual thickness to hang the step markers below it, but a slider is + * measured before anything is composed, so the size it is measured at has to + * take the handle at its word. + */ + private const val MATERIAL_THUMB_HEIGHT_DIP = 44f + + /** + * How tall a slider drawing its own step markers has to be for them to fit + * inside it: a full thumb, with a marker and its gap on either side of it, as the + * track stays in the middle of the slider. + */ + private const val STEP_MARKED_HEIGHT_DIP = + MATERIAL_THUMB_HEIGHT_DIP + + (SliderView.STEP_MARKER_GAP_DP + SliderView.STEP_MARKER_SIZE_DP) * 2 + } +} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java deleted file mode 100644 index 19808941..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java +++ /dev/null @@ -1,398 +0,0 @@ -package com.reactnativecommunity.slider; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.drawable.BitmapDrawable; -import android.os.Build; -import android.util.Log; -import android.util.AttributeSet; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import androidx.appcompat.widget.AppCompatSeekBar; - -import java.net.URL; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import javax.annotation.Nullable; -import com.facebook.react.modules.i18nmanager.I18nUtil; -/** - * Slider that behaves more like the iOS one, for consistency. - * - *

On iOS, the value is 0..1. Android SeekBar only supports integer values. For consistency, we - * pretend in JS that the value is 0..1 but set the SeekBar value to 0..100. - * - *

Note that the slider is _not_ a controlled component (setValue isn't called during dragging). - */ -public class ReactSlider extends AppCompatSeekBar { - - /** - * If step is 0 (unset) we default to this total number of steps. Don't use 100 which leads to - * rounding errors (0.200000000001). - */ - private static int DEFAULT_TOTAL_STEPS = 128; - - /** - * We want custom min..max range. Android only supports 0..max range so we implement this - * ourselves. - */ - private double mMinValue = 0; - - private double mMaxValue = 0; - - /** - * Value sent from JS (setState). Doesn't get updated during drag (slider is not a controlled - * component). - */ - private double mValue = 0; - - private boolean isSliding = false; - - /** If zero it's determined automatically. */ - private double mStep = 0; - - private double mStepCalculated = 0; - - private String mAccessibilityUnits; - - private List mAccessibilityIncrements; - - /** Real limit value based on min and max values. This comes from props */ - private double mRealLowerLimit = Long.MIN_VALUE; - - /** Lower limit based on the SeekBar progress 0..total steps */ - private int mLowerLimit; - - /** Real limit value based on min and max values. This comes from props */ - private double mRealUpperLimit = Long.MAX_VALUE; - - /** Upper limit based on the SeekBar progress 0..total steps */ - private int mUpperLimit; - - /** Thumb size in pixels (0 = default) */ - private int mThumbSizePx = 0; - - /** Original thumb drawable URI */ - @Nullable private String mThumbImageUri = null; - - /** Cached thumb tint color */ - @Nullable private Integer mThumbTintColor = null; - - public ReactSlider(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); - super.setLayoutDirection(sharedI18nUtilInstance.isRTL(context) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR); - disableStateListAnimatorIfNeeded(); - } - - private void disableStateListAnimatorIfNeeded() { - // We disable the state list animator for Android 6 and 7; this is a hack to prevent T37452851 - // and https://github.com/facebook/react-native/issues/9979 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - super.setStateListAnimator(null); - } - } - - /* package */ void setMaxValue(double max) { - mMaxValue = max; - updateAll(); - } - - /* package */ void setMinValue(double min) { - mMinValue = min; - updateAll(); - } - - /*package*/ int getValidProgressValue(int progress) { - if (progress < getLowerLimit()) { - progress = getLowerLimit(); - } else if (progress > getUpperLimit()) { - progress = getUpperLimit(); - } - return progress; - } - - /* package */ void setValue(double value) { - mValue = value; - updateValue(); - } - - /* package */ void setStep(double step) { - mStep = step; - updateAll(); - } - - /* package */ void setLowerLimit(double value) { - mRealLowerLimit = value; - updateLowerLimit(); - } - - /* package */ void setUpperLimit(double value) { - mRealUpperLimit = value; - updateUpperLimit(); - } - - int getLowerLimit() { - return this.mLowerLimit; - } - - int getUpperLimit() { - return this.mUpperLimit; - } - - boolean isSliding() { - return isSliding; - } - - void isSliding(boolean isSliding) { - this.isSliding = isSliding; - } - - void setAccessibilityUnits(String accessibilityUnits) { - mAccessibilityUnits = accessibilityUnits; - } - - void setAccessibilityIncrements(List accessibilityIncrements) { - mAccessibilityIncrements = accessibilityIncrements; - } - - @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED || - (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SELECTED && this.isAccessibilityFocused())) { - this.setupAccessibility((int)mValue); - } - } - } - - @Override - public void announceForAccessibility(CharSequence text) { - Context ctx = this.getContext(); - final AccessibilityManager manager = (AccessibilityManager) ctx.getSystemService(Context.ACCESSIBILITY_SERVICE); - - if (manager.isEnabled()) { - final AccessibilityEvent e = AccessibilityEvent.obtain(); - e.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT); - e.setClassName(this.getClass().getName()); - e.setPackageName(ctx.getPackageName()); - e.getText().add(text); - - TimerTask task = new TimerTask() { - @Override - public void run() { - manager.sendAccessibilityEvent(e); - } - }; - - Timer timer = new Timer(); - timer.schedule(task, 1000); - } - } - - public void setupAccessibility(int index) { - if (mAccessibilityUnits != null && mAccessibilityIncrements != null && mAccessibilityIncrements.size() - 1 == (int)mMaxValue) { - String sliderValue = mAccessibilityIncrements.get(index); - int stringLength = mAccessibilityUnits.length(); - - String spokenUnits = mAccessibilityUnits; - if (sliderValue != null && Integer.parseInt(sliderValue) == 1) { - spokenUnits = spokenUnits.substring(0, stringLength - 1); - } - - this.announceForAccessibility(String.format("%s %s", sliderValue, spokenUnits)); - } - } - - - - /** - * Convert SeekBar's native progress value (e.g. 0..100) to a value passed to JS (e.g. -1.0..2.5). - */ - public double toRealProgress(int seekBarProgress) { - if (seekBarProgress == getMax()) { - return mMaxValue; - } - return seekBarProgress * getStepValue() + mMinValue; - } - - /** Update underlying native SeekBar's values. */ - private void updateAll() { - if (mStep == 0) { - mStepCalculated = (mMaxValue - mMinValue) / (double) DEFAULT_TOTAL_STEPS; - } - setMax(getTotalSteps()); - setKeyProgressIncrement(1); - updateLowerLimit(); - updateUpperLimit(); - updateValue(); - } - - /** Update limit based on props limit, max and min - * Fallback to upper limit if invalid configuration provided - */ - private void updateLowerLimit() { - double limit = Math.max(mRealLowerLimit, mMinValue); - int lowerLimit = (int) Math.round((limit - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps()); - if(lowerLimit > mUpperLimit) { - Log.d("Invalid configuration", "upperLimit < lowerLimit; lowerLimit not set"); - }else { - mLowerLimit = Math.min(lowerLimit, mUpperLimit); - } - } - - /** Update limit based on props limit, max and min - */ - private void updateUpperLimit() { - double limit = Math.min(mRealUpperLimit, mMaxValue); - int upperLimit = (int) Math.round((limit - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps()); - if (mLowerLimit > upperLimit) { - Log.d("Invalid configuration", "upperLimit < lowerLimit; upperLimit not set"); - } else { - mUpperLimit = upperLimit; - } - } - - /** Update value only (optimization in case only value is set). */ - private void updateValue() { - setProgress((int) Math.round((mValue - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps())); - } - - private int getTotalSteps() { - return (int) Math.ceil((mMaxValue - mMinValue) / getStepValue()); - } - - private double getStepValue() { - return mStep > 0 ? mStep : mStepCalculated; - } - - private BitmapDrawable getBitmapDrawable(final String uri) { - BitmapDrawable bitmapDrawable = null; - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Future future = executorService.submit(new Callable() { - @Override - public BitmapDrawable call() { - BitmapDrawable bitmapDrawable = null; - try { - Bitmap bitmap = null; - if (uri.startsWith("http://") || uri.startsWith("https://") || - uri.startsWith("file://") || uri.startsWith("asset://") || uri.startsWith("data:")) { - bitmap = BitmapFactory.decodeStream(new URL(uri).openStream()); - } else { - int drawableId = getResources() - .getIdentifier(uri, "drawable", getContext() - .getPackageName()); - bitmap = BitmapFactory.decodeResource(getResources(), drawableId); - } - - bitmapDrawable = new BitmapDrawable(getResources(), bitmap); - } catch (Exception e) { - e.printStackTrace(); - } - return bitmapDrawable; - } - }); - try { - bitmapDrawable = future.get(); - } catch (Exception e) { - e.printStackTrace(); - } - return bitmapDrawable; - } - - public void setThumbImage(@Nullable final String uri) { - mThumbImageUri = uri; - refreshThumb(); - } - - public void setThumbSize(final float size) { - float density = getResources().getDisplayMetrics().density; - mThumbSizePx = size > 0 ? Math.round(size * density) : 0; - refreshThumb(); - } - - public void setThumbTintColor(@Nullable final Integer color) { - mThumbTintColor = color; - if (mThumbImageUri != null || mThumbSizePx > 0) { - refreshThumb(); - } else { - applyThumbTintColorFilter(); - } - } - - private void applyThumbTintColorFilter() { - if (getThumb() == null) { - return; - } - - if (mThumbTintColor != null) { - getThumb().setColorFilter(mThumbTintColor, PorterDuff.Mode.SRC_IN); - } else { - getThumb().clearColorFilter(); - } - } - - private void refreshThumb() { - if (mThumbImageUri != null) { - BitmapDrawable drawable = getBitmapDrawable(mThumbImageUri); - if (drawable != null) { - if (mThumbSizePx > 0) { - Bitmap originalBitmap = drawable.getBitmap(); - Bitmap scaledBitmap = - Bitmap.createScaledBitmap(originalBitmap, mThumbSizePx, mThumbSizePx, true); - setThumb(new BitmapDrawable(getResources(), scaledBitmap)); - } else { - setThumb(drawable); - } - applyThumbTintColorFilter(); - // Enable alpha channel for the thumbImage - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setSplitTrack(false); - } - return; - } - } - - if (mThumbSizePx > 0) { - Bitmap bitmap = Bitmap.createBitmap(mThumbSizePx, mThumbSizePx, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - - int fillColor = - mThumbTintColor != null - ? mThumbTintColor - : (getThumbTintList() != null ? getThumbTintList().getDefaultColor() : 0xFFFFFFFF); - - Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - fillPaint.setStyle(Paint.Style.FILL); - fillPaint.setColor(fillColor); - float radius = mThumbSizePx / 2f; - canvas.drawCircle(radius, radius, radius, fillPaint); - - Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - strokePaint.setStyle(Paint.Style.STROKE); - strokePaint.setStrokeWidth(1); - strokePaint.setColor(0x1A000000); - canvas.drawCircle(radius, radius, radius - 0.5f, strokePaint); - - setThumb(new BitmapDrawable(getResources(), bitmap)); - applyThumbTintColorFilter(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setSplitTrack(false); - } - } else { - // No special sizing; keep existing thumb, only apply tint if needed. - applyThumbTintColorFilter(); - } - } -} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderEvent.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderEvent.java deleted file mode 100644 index 1fe33f6c..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderEvent.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.reactnativecommunity.slider; - -import androidx.annotation.Nullable; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; - -/** - * Event emitted by a ReactSliderManager when user changes slider position. - */ -public class ReactSliderEvent extends Event { - - public static final String EVENT_NAME = "topChange"; - - private final double mValue; - private final boolean mFromUser; - - public ReactSliderEvent(int viewId, double value, boolean fromUser) { - super(viewId); - mValue = value; - mFromUser = fromUser; - } - - public double getValue() { - return mValue; - } - - public boolean isFromUser() { - return mFromUser; - } - - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public short getCoalescingKey() { - return 0; - } - - @Nullable - @Override - protected WritableMap getEventData() { - return serializeEventData(); - } - - private WritableMap serializeEventData() { - WritableMap eventData = Arguments.createMap(); - eventData.putInt("target", getViewTag()); - eventData.putDouble("value", getValue()); - eventData.putBoolean("fromUser", isFromUser()); - return eventData; - } -} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java deleted file mode 100644 index bdd08f18..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java +++ /dev/null @@ -1,243 +0,0 @@ -package com.reactnativecommunity.slider; - -import android.content.Context; -import android.view.View; -import android.widget.SeekBar; -import androidx.annotation.Nullable; - -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.PixelUtil; -import com.facebook.react.uimanager.SimpleViewManager; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.UIManagerHelper; -import com.facebook.react.uimanager.ViewManagerDelegate; -import com.facebook.react.uimanager.ViewProps; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.events.EventDispatcher; -import java.util.Map; -import com.facebook.react.viewmanagers.RNCSliderManagerInterface; -import com.facebook.react.viewmanagers.RNCSliderManagerDelegate; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.yoga.YogaMeasureMode; -import com.facebook.yoga.YogaMeasureOutput; - -/** - * Manages instances of {@code ReactSlider}. - */ -@ReactModule(name = ReactSliderManagerImpl.REACT_CLASS) -public class ReactSliderManager extends SimpleViewManager implements RNCSliderManagerInterface { - - private final ViewManagerDelegate mDelegate; - - public ReactSliderManager() { - mDelegate = new RNCSliderManagerDelegate<>(this); - } - - @Nullable - @Override - protected ViewManagerDelegate getDelegate() { - return mDelegate; - } - - private static final SeekBar.OnSeekBarChangeListener ON_CHANGE_LISTENER = - new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { - ReactSlider slider = (ReactSlider)seekbar; - - progress = slider.getValidProgressValue(progress); - seekbar.setProgress(progress); - - ReactContext reactContext = (ReactContext) seekbar.getContext(); - if (fromUser) { - int reactTag = seekbar.getId(); - UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag) - .dispatchEvent(new ReactSliderEvent(reactTag, slider.toRealProgress(progress), true)); - } - } - - @Override - public void onStartTrackingTouch(SeekBar seekbar) { - ReactContext reactContext = (ReactContext) seekbar.getContext(); - int reactTag = seekbar.getId(); - ((ReactSlider)seekbar).isSliding(true); - UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag) - .dispatchEvent(new ReactSlidingStartEvent( - reactTag, - ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); - } - - @Override - public void onStopTrackingTouch(SeekBar seekbar) { - ReactContext reactContext = (ReactContext) seekbar.getContext(); - ((ReactSlider)seekbar).isSliding(false); - int reactTag = seekbar.getId(); - - EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag); - - eventDispatcher.dispatchEvent( - new ReactSlidingCompleteEvent( - reactTag, - ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress())) - ); - } - }; - - @Override - public String getName() { - return ReactSliderManagerImpl.REACT_CLASS; - } - - @Override - protected ReactSlider createViewInstance(ThemedReactContext context) { - return ReactSliderManagerImpl.createViewInstance(context); - } - - @Override - @ReactProp(name = "disabled", defaultBoolean = false) - public void setDisabled(ReactSlider view, boolean disabled) { - ReactSliderManagerImpl.setDisabled(view, disabled); - } - - @Override - @ReactProp(name = "value", defaultFloat = 0f) - public void setValue(ReactSlider view, float value) { - ReactSliderManagerImpl.setValue(view, value); - } - - @Override - @ReactProp(name = "minimumValue", defaultFloat = 0f) - public void setMinimumValue(ReactSlider view, double value) { - ReactSliderManagerImpl.setMinimumValue(view, value); - } - - @Override - @ReactProp(name = "maximumValue", defaultFloat = 0f) - public void setMaximumValue(ReactSlider view, double value) { - ReactSliderManagerImpl.setMaximumValue(view, value); - } - - @Override - @ReactProp(name = "step", defaultFloat = 0f) - public void setStep(ReactSlider view, double value) { - ReactSliderManagerImpl.setStep(view, value); - } - - @Override - @ReactProp(name = "thumbTintColor", customType = "Color") - public void setThumbTintColor(ReactSlider view, Integer color) { - ReactSliderManagerImpl.setThumbTintColor(view, color); - } - - @Override - @ReactProp(name = "thumbSize", defaultFloat = 0f) - public void setThumbSize(ReactSlider view, float size) { - ReactSliderManagerImpl.setThumbSize(view, size); - } - - @Override - @ReactProp(name = "minimumTrackTintColor", customType = "Color") - public void setMinimumTrackTintColor(ReactSlider view, Integer color) { - ReactSliderManagerImpl.setMinimumTrackTintColor(view, color); - } - - @Override - @ReactProp(name = "maximumTrackTintColor", customType = "Color") - public void setMaximumTrackTintColor(ReactSlider view, Integer color) { - ReactSliderManagerImpl.setMaximumTrackTintColor(view, color); - } - - @Override - @ReactProp(name = "inverted", defaultBoolean = false) - public void setInverted(ReactSlider view, boolean inverted) { - ReactSliderManagerImpl.setInverted(view, inverted); - } - - @Override - @ReactProp(name = "accessibilityUnits") - public void setAccessibilityUnits(ReactSlider view, String accessibilityUnits) { - ReactSliderManagerImpl.setAccessibilityUnits(view, accessibilityUnits); - } - - @Override - @ReactProp(name = "accessibilityIncrements") - public void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibilityIncrements) { - ReactSliderManagerImpl.setAccessibilityIncrements(view, accessibilityIncrements); - } - - @ReactProp(name = "lowerLimit") - public void setLowerLimit(ReactSlider view, float value) { - ReactSliderManagerImpl.setLowerLimit(view, value); - } - - @ReactProp(name = "upperLimit") - public void setUpperLimit(ReactSlider view, float value) { - ReactSliderManagerImpl.setUpperLimit(view, value); - } - - @Override - @ReactProp(name = "thumbImage") - public void setThumbImage(ReactSlider view, @androidx.annotation.Nullable ReadableMap source) { - ReactSliderManagerImpl.setThumbImage(view, source); - } - - @Override - public void setTestID(ReactSlider view, @Nullable String value) { - super.setTestId(view, value); - } - - @Override - protected void addEventEmitters(final ThemedReactContext reactContext, final ReactSlider view) { - view.setOnSeekBarChangeListener(ON_CHANGE_LISTENER); - } - - // these props are not available on Android, however we must override their setters - @Override - public void setMinimumTrackImage(ReactSlider view, @Nullable ReadableMap readableMap) {} - - @Override - public void setMaximumTrackImage(ReactSlider view, @Nullable ReadableMap readableMap) {} - - @Override - public void setTrackImage(ReactSlider view, @Nullable ReadableMap value) {} - - @Override - public void setTapToSeek(ReactSlider view, boolean value) {} - - @Override - public void setVertical(ReactSlider view, boolean value) {} - - @Override - public long measure( - Context context, - ReadableMap localData, - ReadableMap props, - ReadableMap state, - float width, - YogaMeasureMode widthMode, - float height, - YogaMeasureMode heightMode, - @Nullable float[] attachmentsPositions) { - ReactSlider view = new ReactSlider(context, null); - int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - view.measure(measureSpec, measureSpec); - return YogaMeasureOutput.make( - PixelUtil.toDIPFromPixel(view.getMeasuredWidth()), - PixelUtil.toDIPFromPixel(view.getMeasuredHeight())); - } - - @Nullable - @Override - public Map getExportedCustomBubblingEventTypeConstants() { - return ReactSliderManagerImpl.getExportedCustomBubblingEventTypeConstants(); - } - - @Nullable - @Override - public Map getExportedCustomDirectEventTypeConstants() { - return ReactSliderManagerImpl.getExportedCustomDirectEventTypeConstants(); - } -} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java deleted file mode 100644 index 4d76b983..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.reactnativecommunity.slider; - -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.os.Build; - -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.ThemedReactContext; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -public class ReactSliderManagerImpl { - - public static final String REACT_CLASS = "RNCSlider"; - - public static ReactSlider createViewInstance(ThemedReactContext context) { - ReactSlider slider = new ReactSlider(context, null); - - if (Build.VERSION.SDK_INT >= 21) { - /** - * The "splitTrack" parameter should have "false" value, - * otherwise the SeekBar progress line doesn't appear when it is rotated. - */ - slider.setSplitTrack(false); - } - - return slider; - } - - public static void setValue(ReactSlider view, double value) { - if (view.isSliding() == false) { - view.setValue(value); - if (view.isAccessibilityFocused() && Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { - view.setupAccessibility((int)value); - } - } - } - - public static void setMinimumValue(ReactSlider view, double value) { - view.setMinValue(value); - } - - public static void setMaximumValue(ReactSlider view, double value) { - view.setMaxValue(value); - } - - public static void setLowerLimit(ReactSlider view, double value) { - view.setLowerLimit(value); - } - - public static void setUpperLimit(ReactSlider view, double value) { - view.setUpperLimit(value); - } - - public static void setStep(ReactSlider view, double value) { - view.setStep(value); - } - - public static void setDisabled(ReactSlider view, boolean disabled) { - view.setEnabled(!disabled); - } - - public static void setThumbTintColor(ReactSlider view, Integer color) { - view.setThumbTintColor(color); - } - - public static void setMinimumTrackTintColor(ReactSlider view, Integer color) { - LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent(); - Drawable progress = drawable.findDrawableByLayerId(android.R.id.progress); - if (color == null) { - progress.clearColorFilter(); - } else { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - progress.setColorFilter(new PorterDuffColorFilter((int)color, PorterDuff.Mode.SRC_IN)); - } - else { - progress.setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - } - } - - public static void setThumbImage(ReactSlider view, @Nullable ReadableMap source) { - String uri = null; - if (source != null) { - uri = source.getString("uri"); - } - view.setThumbImage(uri); - } - - public static void setThumbSize(ReactSlider view, float size) { - view.setThumbSize(size); - } - - public static void setMaximumTrackTintColor(ReactSlider view, Integer color) { - LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent(); - Drawable background = drawable.findDrawableByLayerId(android.R.id.background); - if (color == null) { - background.clearColorFilter(); - } else { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - background.setColorFilter(new PorterDuffColorFilter((int)color, PorterDuff.Mode.SRC_IN)); - } - else { - background.setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - } - } - - public static void setInverted(ReactSlider view, boolean inverted) { - if (inverted) view.setScaleX(-1f); - else view.setScaleX(1f); - } - - public static void setAccessibilityUnits(ReactSlider view, String accessibilityUnits) { - view.setAccessibilityUnits(accessibilityUnits); - } - - public static void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibilityIncrements) { - List objectList = accessibilityIncrements.toArrayList(); - List stringList = new ArrayList<>(); - for(Object item: objectList) { - stringList.add((String)item); - } - view.setAccessibilityIncrements(stringList); - } - - public static Map getExportedCustomBubblingEventTypeConstants() { - return MapBuilder.of( - ReactSliderEvent.EVENT_NAME, MapBuilder.of("registrationName", ReactSliderEvent.EVENT_NAME) - ); - } - - public static Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.of( - ReactSlidingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", ReactSlidingStartEvent.EVENT_NAME), - ReactSlidingCompleteEvent.EVENT_NAME, MapBuilder.of("registrationName", ReactSlidingCompleteEvent.EVENT_NAME) - ); - } -} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderPackage.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderPackage.java deleted file mode 100644 index 38a3f1f7..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderPackage.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.reactnativecommunity.slider; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; - -public class ReactSliderPackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } - - // Deprecated from RN 0.47 - public List> createJSModules() { - return Collections.emptyList(); - } - - @Override - @SuppressWarnings("rawtypes") - public List createViewManagers(ReactApplicationContext reactContext) { - return Arrays.asList(new ReactSliderManager()); - } -} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlidingCompleteEvent.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlidingCompleteEvent.java deleted file mode 100644 index b6e2a0e3..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlidingCompleteEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.reactnativecommunity.slider; - -import androidx.annotation.Nullable; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; - -/** - * Event emitted when the user finishes dragging the slider. - */ -public class ReactSlidingCompleteEvent extends Event { - - public static final String EVENT_NAME = "onRNCSliderSlidingComplete"; - - private final double mValue; - - public ReactSlidingCompleteEvent(int viewId, double value) { - super(viewId); - mValue = value; - } - - public double getValue() { - return mValue; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public short getCoalescingKey() { - return 0; - } - - @Override - public boolean canCoalesce() { - return false; - } - - @Nullable - @Override - protected WritableMap getEventData() { - return serializeEventData(); - } - - private WritableMap serializeEventData() { - WritableMap eventData = Arguments.createMap(); - eventData.putInt("target", getViewTag()); - eventData.putDouble("value", getValue()); - return eventData; - } - -} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlidingStartEvent.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlidingStartEvent.java deleted file mode 100644 index fe07d8b2..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlidingStartEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.reactnativecommunity.slider; - -import androidx.annotation.Nullable; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; - -/** - * Event emitted when the user starts dragging the slider. - */ - -public class ReactSlidingStartEvent extends Event { - public static final String EVENT_NAME = "onRNCSliderSlidingStart"; - - private final double mValue; - - public ReactSlidingStartEvent(int viewId, double value) { - super(viewId); - mValue = value; - } - - public double getValue() { - return mValue; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public short getCoalescingKey() { - return 0; - } - - @Override - public boolean canCoalesce() { - return false; - } - - @Nullable - @Override - protected WritableMap getEventData() { - return serializeEventData(); - } - - private WritableMap serializeEventData() { - WritableMap eventData = Arguments.createMap(); - eventData.putInt("target", getViewTag()); - eventData.putDouble("value", getValue()); - return eventData; - } - -} diff --git a/package/android/src/main/jni/CMakeLists.txt b/package/android/src/main/jni/CMakeLists.txt index 6fe63910..d573ae38 100644 --- a/package/android/src/main/jni/CMakeLists.txt +++ b/package/android/src/main/jni/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE ON) -set(LIB_LITERAL RNCSlider) +set(LIB_LITERAL CallstackSlider) set(LIB_TARGET_NAME react_codegen_${LIB_LITERAL}) set(LIB_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) diff --git a/package/android/src/main/jni/RNCSlider.h b/package/android/src/main/jni/CallstackSlider.h similarity index 66% rename from package/android/src/main/jni/RNCSlider.h rename to package/android/src/main/jni/CallstackSlider.h index f0c0e9f5..d20a994e 100644 --- a/package/android/src/main/jni/RNCSlider.h +++ b/package/android/src/main/jni/CallstackSlider.h @@ -3,13 +3,13 @@ #include #include #include -#include +#include namespace facebook { namespace react { JSI_EXPORT -std::shared_ptr RNCSlider_ModuleProvider( +std::shared_ptr CallstackSlider_ModuleProvider( const std::string &moduleName, const JavaTurboModule::InitParams ¶ms); diff --git a/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderComponentDescriptor.h b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderComponentDescriptor.h new file mode 100644 index 00000000..6d18bb2a --- /dev/null +++ b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderComponentDescriptor.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include "CallstackSliderMeasurementsManager.h" + +namespace facebook { + namespace react { + + class CallstackSliderComponentDescriptor final + : public ConcreteComponentDescriptor { +#ifdef ANDROID + public: + CallstackSliderComponentDescriptor(const ComponentDescriptorParameters ¶meters) + : ConcreteComponentDescriptor(parameters), measurementsManager_( + std::make_shared(contextContainer_)) {} + + void adopt(ShadowNode &shadowNode) const override { + ConcreteComponentDescriptor::adopt(shadowNode); + + auto &sliderShadowNode = + static_cast(shadowNode); + + sliderShadowNode.setSliderMeasurementsManager( + measurementsManager_); + } + private: + const std::shared_ptr measurementsManager_; +#else + public: + CallstackSliderComponentDescriptor(const ComponentDescriptorParameters ¶meters) + : ConcreteComponentDescriptor(parameters) {} +#endif + }; + + } +} diff --git a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderMeasurementsManager.cpp b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderMeasurementsManager.cpp similarity index 58% rename from package/common/cpp/react/renderer/components/RNCSlider/RNCSliderMeasurementsManager.cpp rename to package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderMeasurementsManager.cpp index fada95c9..d89a5913 100644 --- a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderMeasurementsManager.cpp +++ b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderMeasurementsManager.cpp @@ -1,5 +1,5 @@ #ifdef ANDROID -#include "RNCSliderMeasurementsManager.h" +#include "CallstackSliderMeasurementsManager.h" #include #include @@ -9,13 +9,15 @@ using namespace facebook::jni; namespace facebook::react { -Size RNCSliderMeasurementsManager::measure( +Size CallstackSliderMeasurementsManager::measure( SurfaceId surfaceId, - LayoutConstraints layoutConstraints) const { + LayoutConstraints layoutConstraints, + bool drawsStepMarkers) const { + auto &cached = drawsStepMarkers ? cachedStepMarkedMeasurement_ : cachedMeasurement_; { std::scoped_lock lock(mutex_); - if (hasBeenMeasured_) { - return cachedMeasurement_; + if (cached) { + return *cached; } } @@ -38,14 +40,23 @@ Size RNCSliderMeasurementsManager::measure( auto minimumSize = layoutConstraints.minimumSize; auto maximumSize = layoutConstraints.maximumSize; - local_ref componentName = make_jstring("RNCSlider"); + local_ref componentName = make_jstring("CallstackSlider"); + + // The view manager is only told what bears on the size: whether the slider + // draws step markers, which `SliderViewManager.measure` makes room for. + local_ref props; + if (drawsStepMarkers) { + auto propsRNM = ReadableNativeMap::newObjectCxxArgs( + folly::dynamic::object("stepMarkers", "native")); + props = make_local(reinterpret_cast(propsRNM.get())); + } auto measurement = yogaMeassureToSize(measure( fabricUIManager, surfaceId, componentName.get(), nullptr, - nullptr, + props.get(), nullptr, minimumSize.width, maximumSize.width, @@ -53,8 +64,7 @@ Size RNCSliderMeasurementsManager::measure( maximumSize.height)); std::scoped_lock lock(mutex_); - cachedMeasurement_ = measurement; - hasBeenMeasured_ = true; + cached = measurement; return measurement; } diff --git a/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderMeasurementsManager.h b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderMeasurementsManager.h new file mode 100644 index 00000000..d716c155 --- /dev/null +++ b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderMeasurementsManager.h @@ -0,0 +1,37 @@ +#ifdef ANDROID +#pragma once + +#include +#include +#include + +#include +#include + +namespace facebook::react { + + class CallstackSliderMeasurementsManager { + public: + CallstackSliderMeasurementsManager( + const std::shared_ptr &contextContainer) + : contextContainer_(contextContainer) {} + + /* + * The size of a slider, which every slider on a surface shares - apart + * from those drawing step markers of their own, which are measured to + * fit them instead. Each of the two is asked for once and kept. + */ + Size measure( + SurfaceId surfaceId, + LayoutConstraints layoutConstraints, + bool drawsStepMarkers) const; + + private: + const std::shared_ptr contextContainer_; + mutable std::mutex mutex_; + mutable std::optional cachedMeasurement_; + mutable std::optional cachedStepMarkedMeasurement_; + + }; +} +#endif diff --git a/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderShadowNode.cpp b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderShadowNode.cpp new file mode 100644 index 00000000..ec0ecdf9 --- /dev/null +++ b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderShadowNode.cpp @@ -0,0 +1,176 @@ +#include "CallstackSliderShadowNode.h" +#include "CallstackSliderMeasurementsManager.h" +#include +#include +#include +#include +#include + +namespace facebook { + namespace react { + extern const char CallstackSliderComponentName[] = "CallstackSlider"; + +#pragma mark - LayoutableShadowNode + + void CallstackSliderShadowNode::layout(LayoutContext layoutContext) { + ensureUnsealed(); + + if (getChildren().empty()) { + return; + } + + const auto &props = getConcreteProps(); + + // A thumb is as big as it makes itself: the slider has no say in it, + // and a thumb bigger than the slider simply spills out of it. + auto thumbConstraints = LayoutConstraints{ + .minimumSize = {0, 0}, + .maximumSize = { + std::numeric_limits::infinity(), + std::numeric_limits::infinity()}, + .layoutDirection = getLayoutMetrics().layoutDirection}; + + // The children are shared with the tree this slider was cloned from, + // so each one is cloned before being laid out - the way + // `ParagraphShadowNode` lays out the views inlined in its text. + auto children = std::make_shared>>(); + children->reserve(getChildren().size()); + + const auto trackCount = props.customTrack ? 1 : 0; + const auto thumbCount = props.customThumb ? (props.ranged ? 2 : 1) : 0; + const auto vertical = props.orientation == CallstackSliderOrientation::Vertical; + + for (const auto &child : getChildren()) { + const auto index = static_cast(children->size()); + + if (index < trackCount) { + children->push_back(layoutTrack(*child, layoutContext)); + continue; + } + + const auto isStepMarker = index >= trackCount + thumbCount && + props.stepMarkers == CallstackSliderStepMarkers::Custom; + + auto laidOutChild = child->clone({}); + + if (auto layoutableChild = dynamic_cast(laidOutChild.get())) { + // A step marker is as big as it makes itself, the way a thumb is. + layoutableChild->layoutTree(layoutContext, thumbConstraints); + + // Placed against the slider's origin rather than where it shows: + // a thumb moves with every drag, and a step marker with the + // geometry of the native slider, and the native view follows + // both without asking JS for a new layout. + auto childMetrics = layoutableChild->getLayoutMetrics(); + childMetrics.frame.origin = isStepMarker + ? hungFromOrigin(childMetrics.frame.size, vertical, layoutContext) + : centredOnOrigin(childMetrics.frame.size, layoutContext); + layoutableChild->setLayoutMetrics(childMetrics); + } + + children->push_back(std::move(laidOutChild)); + } + + children_ = std::move(children); + } + + std::shared_ptr CallstackSliderShadowNode::layoutTrack( + const ShadowNode &trackHost, + LayoutContext layoutContext) const { + // A track lies along the slider, so it is as long as the slider is, + // whichever way round that is: a vertical slider turns its track round + // along with everything else. + const auto sliderSize = getLayoutMetrics().frame.size; + const auto length = getConcreteProps().orientation == CallstackSliderOrientation::Vertical + ? sliderSize.height + : sliderSize.width; + + // The length is forced on each view the track renders, rather than on + // the host JS wraps them in, so that a track sized by its own style is + // stretched along the slider all the same. Across the slider it is as + // thick as it makes itself. + auto partConstraints = LayoutConstraints{ + .minimumSize = {length, 0}, + .maximumSize = {length, std::numeric_limits::infinity()}, + .layoutDirection = getLayoutMetrics().layoutDirection}; + + auto parts = std::make_shared>>(); + parts->reserve(trackHost.getChildren().size()); + auto trackSize = Size{length, 0}; + + for (const auto &part : trackHost.getChildren()) { + auto laidOutPart = part->clone({}); + + if (auto layoutablePart = dynamic_cast(laidOutPart.get())) { + layoutablePart->layoutTree(layoutContext, partConstraints); + + // A track rendering more than one view has them lie on top of + // each other, each one spanning the whole of it. + auto partMetrics = layoutablePart->getLayoutMetrics(); + partMetrics.frame.origin = {0, 0}; + layoutablePart->setLayoutMetrics(partMetrics); + trackSize.height = std::max(trackSize.height, partMetrics.frame.size.height); + } + + parts->push_back(std::move(laidOutPart)); + } + + // The host is only there to keep the track in one piece, so it takes + // the size of what it holds instead of being laid out by Yoga. It is + // centred on the slider's origin the way a thumb is, and the native + // view moves that centre onto the middle of the slider - turning it + // round on a vertical one, which no frame can describe. + auto track = trackHost.clone({.children = parts}); + + if (auto layoutableTrack = dynamic_cast(track.get())) { + auto trackMetrics = layoutableTrack->getLayoutMetrics(); + trackMetrics.frame.size = trackSize; + trackMetrics.frame.origin = centredOnOrigin(trackSize, layoutContext); + trackMetrics.pointScaleFactor = layoutContext.pointScaleFactor; + trackMetrics.layoutDirection = getLayoutMetrics().layoutDirection; + layoutableTrack->setLayoutMetrics(trackMetrics); + } + + return track; + } + + Point CallstackSliderShadowNode::centredOnOrigin(Size size, LayoutContext layoutContext) { + return roundToPixel<&std::round>( + Point{-size.width / 2, -size.height / 2}, + layoutContext.pointScaleFactor); + } + + Point CallstackSliderShadowNode::hungFromOrigin(Size size, bool vertical, LayoutContext layoutContext) { + return roundToPixel<&std::round>( + vertical ? Point{0, -size.height / 2} : Point{-size.width / 2, 0}, + layoutContext.pointScaleFactor); + } + +#ifdef ANDROID + void CallstackSliderShadowNode::setSliderMeasurementsManager( + const std::shared_ptr & + measurementsManager) { + ensureUnsealed(); + measurementsManager_ = measurementsManager; + } + +#pragma mark - LayoutableShadowNode + + Size CallstackSliderShadowNode::measureContent( + const LayoutContext & /*layoutContext*/, + const LayoutConstraints &layoutConstraints) const { + // The step-marked measurement is only the room the markers need, which + // a slider measured any taller already has. + auto horizontalSize = measurementsManager_->measure(getSurfaceId(), layoutConstraints, false); + if (drawsStepMarkers()) { + horizontalSize = sizeForStepMarkers( + horizontalSize, + measurementsManager_->measure(getSurfaceId(), layoutConstraints, true).height); + } + + return sizeForOrientation(horizontalSize); + } +#endif + + } +} diff --git a/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderShadowNode.h b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderShadowNode.h new file mode 100644 index 00000000..9f0570e1 --- /dev/null +++ b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderShadowNode.h @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "CallstackSliderMeasurementsManager.h" + +namespace facebook { + namespace react { + + JSI_EXPORT extern const char CallstackSliderComponentName[]; + +/* + * `ShadowNode` for component. + */ + class JSI_EXPORT CallstackSliderShadowNode final + : public ConcreteViewShadowNode< + CallstackSliderComponentName, + CallstackSliderProps, + CallstackSliderEventEmitter, + CallstackSliderState> { + public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + + /* + * The size this slider takes, given the size measured for a horizontal one. + * A vertical slider is the same control turned a quarter turn, + * so it stands as tall as a horizontal one is wide and as wide as one is tall, + * which is why neither platform measures it a second time. + */ + Size sizeForOrientation(Size horizontalSize) const { + if (getConcreteProps().orientation != CallstackSliderOrientation::Vertical) { + return horizontalSize; + } + + return {horizontalSize.height, horizontalSize.width}; + } + + /* + * Whether the slider draws step markers of its own, which it does for a + * slider that has steps, unless JS replaced the markers. + */ + bool drawsStepMarkers() const { + const auto &props = getConcreteProps(); + + return props.stepMarkers == CallstackSliderStepMarkers::Native && + props.step > 0 && + props.maximumValue > props.minimumValue; + } + + /* + * The size a horizontal slider of the given size takes for the step + * markers it draws to fit inside it: at least the given thickness + * across, which each platform works out from its own thumb. + * + * The track stays in the middle of the slider, so a slider grown to + * fit the markers below it grows above it just as much. A slider + * whose style gives it a size of its own is never asked to measure + * itself, so a height JS gives it still wins. + */ + Size sizeForStepMarkers(Size horizontalSize, Float stepMarkedThickness) const { + if (!drawsStepMarkers()) { + return horizontalSize; + } + + return {horizontalSize.width, std::max(horizontalSize.height, stepMarkedThickness)}; + } + + /* + * A slider is a leaf that knows its own size, so Yoga has to be told to ask for it. + * Being a leaf also keeps its children, the custom thumbs, + * out of Yoga altogether, which Yoga insists on for a node that measures itself: + * they are laid out by `layout` below instead. + */ + static ShadowNodeTraits BaseTraits() { + auto traits = ConcreteViewShadowNode::BaseTraits(); + traits.set(ShadowNodeTraits::Trait::LeafYogaNode); + traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode); + return traits; + } + +#pragma mark - LayoutableShadowNode + + /* + * The children of a slider are the custom track, thumbs and step + * markers JS renders in place of the built-in ones - in that order, + * each of them only when JS replaced it. A slider is a leaf to Yoga, + * so they take no part in laying it out, and are laid out here + * instead: each thumb and step marker at the size it asks for, and + * the track along the whole slider. + * + * The track and the thumbs are centred on the slider's origin, and + * the native slider moves that centre onto its thumb, which is the + * one thing JS cannot know the position of, or onto the middle of + * the slider for the track. A step marker hangs from the origin + * instead, which the native slider moves to just past the thumbs, + * below the step - see `hungFromOrigin`. + */ + void layout(LayoutContext layoutContext) override; + + private: + /* + * Lays out the host JS wraps the custom track in, stretching every + * view of the track along the slider - see `layout`. + */ + std::shared_ptr layoutTrack( + const ShadowNode &trackHost, + LayoutContext layoutContext) const; + + static Point centredOnOrigin(Size size, LayoutContext layoutContext); + + /* + * Where a step marker of the given size goes for the origin to sit + * in the middle of its top edge - or of its left edge on a vertical + * slider, which has its step markers to the right of the track + * rather than below it, still the right way up. + */ + static Point hungFromOrigin(Size size, bool vertical, LayoutContext layoutContext); + + public: +#ifdef ANDROID + void setSliderMeasurementsManager( + const std::shared_ptr &measurementsManager); + +#pragma mark - LayoutableShadowNode + + Size measureContent( + const LayoutContext &layoutContext, + const LayoutConstraints &layoutConstraints) const override; + + private: + std::shared_ptr measurementsManager_; +#else +#pragma mark - LayoutableShadowNode + + /* + * The answer comes from SwiftUI, which C++ cannot ask on its own. + */ + Size measureContent( + const LayoutContext &layoutContext, + const LayoutConstraints &layoutConstraints) const override; +#endif + + }; + + } +} diff --git a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderState.h b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderState.h similarity index 71% rename from package/common/cpp/react/renderer/components/RNCSlider/RNCSliderState.h rename to package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderState.h index 13cdf8e5..6477e2ec 100644 --- a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderState.h +++ b/package/common/cpp/react/renderer/components/CallstackSlider/CallstackSliderState.h @@ -9,12 +9,12 @@ namespace facebook { namespace react { -class RNCSliderState { +class CallstackSliderState { public: - RNCSliderState() = default; + CallstackSliderState() = default; #ifdef ANDROID - RNCSliderState(RNCSliderState const &previousState, folly::dynamic data){}; + CallstackSliderState(CallstackSliderState const &previousState, folly::dynamic data){}; folly::dynamic getDynamic() const { return {}; }; diff --git a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderComponentDescriptor.h b/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderComponentDescriptor.h deleted file mode 100644 index 8bc649ad..00000000 --- a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderComponentDescriptor.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include -#include "RNCSliderMeasurementsManager.h" - -namespace facebook { - namespace react { - - class RNCSliderComponentDescriptor final - : public ConcreteComponentDescriptor { -#ifdef ANDROID - public: - RNCSliderComponentDescriptor(const ComponentDescriptorParameters ¶meters) - : ConcreteComponentDescriptor(parameters), measurementsManager_( - std::make_shared(contextContainer_)) {} - - void adopt(ShadowNode &shadowNode) const override { - ConcreteComponentDescriptor::adopt(shadowNode); - - - auto &rncSliderShadowNode = - static_cast(shadowNode); - - // `RNCSliderShadowNode` uses `RNCSliderMeasurementsManager` to - // provide measurements to Yoga. - rncSliderShadowNode.setSliderMeasurementsManager( - measurementsManager_); - - // All `RNCSliderShadowNode`s must have leaf Yoga nodes with properly - // setup measure function. - rncSliderShadowNode.enableMeasurement(); - } - private: - const std::shared_ptr measurementsManager_; -#else - public: - RNCSliderComponentDescriptor(const ComponentDescriptorParameters ¶meters) - : ConcreteComponentDescriptor(parameters) {} -#endif - }; - - } -} diff --git a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderMeasurementsManager.h b/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderMeasurementsManager.h deleted file mode 100644 index adcb27b5..00000000 --- a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderMeasurementsManager.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifdef ANDROID -#pragma once - -#include -#include -#include - -namespace facebook::react { - - class RNCSliderMeasurementsManager { - public: - RNCSliderMeasurementsManager( - const std::shared_ptr &contextContainer) - : contextContainer_(contextContainer) {} - - Size measure(SurfaceId surfaceId, LayoutConstraints layoutConstraints) const; - - private: - const std::shared_ptr contextContainer_; - mutable std::mutex mutex_; - mutable bool hasBeenMeasured_ = false; - mutable Size cachedMeasurement_{}; - - }; -} -#endif diff --git a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderShadowNode.cpp b/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderShadowNode.cpp deleted file mode 100644 index d10cb078..00000000 --- a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderShadowNode.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "RNCSliderShadowNode.h" -#include "RNCSliderMeasurementsManager.h" - -namespace facebook { - namespace react { - - extern const char RNCSliderComponentName[] = "RNCSlider"; - -#ifdef ANDROID - void RNCSliderShadowNode::setSliderMeasurementsManager( - const std::shared_ptr & - measurementsManager) { - ensureUnsealed(); - measurementsManager_ = measurementsManager; - } - -#pragma mark - LayoutableShadowNode - - Size RNCSliderShadowNode::measureContent( - const LayoutContext & /*layoutContext*/, - const LayoutConstraints &layoutConstraints) const { - return measurementsManager_->measure(getSurfaceId(), layoutConstraints); - } -#endif - - } -} diff --git a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderShadowNode.h b/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderShadowNode.h deleted file mode 100644 index b30099fe..00000000 --- a/package/common/cpp/react/renderer/components/RNCSlider/RNCSliderShadowNode.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "RNCSliderMeasurementsManager.h" - -namespace facebook { - namespace react { - - JSI_EXPORT extern const char RNCSliderComponentName[]; - -/* - * `ShadowNode` for component. - */ - class JSI_EXPORT RNCSliderShadowNode final - : public ConcreteViewShadowNode< - RNCSliderComponentName, - RNCSliderProps, - RNCSliderEventEmitter, - RNCSliderState> { - public: - using ConcreteViewShadowNode::ConcreteViewShadowNode; - -#ifdef ANDROID - void setSliderMeasurementsManager( - const std::shared_ptr &measurementsManager); - -#pragma mark - LayoutableShadowNode - - Size measureContent( - const LayoutContext &layoutContext, - const LayoutConstraints &layoutConstraints) const override; - - private: - std::shared_ptr measurementsManager_; -#endif - - }; - - } -} diff --git a/package/eslint.config.js b/package/eslint.config.js new file mode 100644 index 00000000..1a096ee9 --- /dev/null +++ b/package/eslint.config.js @@ -0,0 +1,20 @@ +const reactNativeConfig = require("@react-native/eslint-config/flat"); +const prettierRecommended = require("eslint-plugin-prettier/recommended"); + +module.exports = [ + { + ignores: ["dist/", "node_modules/"], + }, + ...reactNativeConfig, + prettierRecommended, + { + languageOptions: { + globals: { + element: "writable", + by: "writable", + device: "writable", + jasmine: "writable", + }, + }, + }, +]; diff --git a/package/ios/RNCSlider.h b/package/ios/RNCSlider.h deleted file mode 100644 index aa9d1855..00000000 --- a/package/ios/RNCSlider.h +++ /dev/null @@ -1,31 +0,0 @@ -#import - -#import - -@interface RNCSlider : UISlider - -@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderValueChange; -@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderSlidingStart; -@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderSlidingComplete; - -@property (nonatomic, assign) float step; -@property (nonatomic, assign) float lastValue; -@property (nonatomic, assign) bool isSliding; - -@property (nonatomic, assign) float lowerLimit; -@property (nonatomic, assign) float upperLimit; - -@property (nonatomic, strong) UIImage *trackImage; -@property (nonatomic, strong) UIImage *minimumTrackImage; -@property (nonatomic, strong) UIImage *maximumTrackImage; -@property (nonatomic, strong) UIImage *thumbImage; -@property (nonatomic, assign) CGFloat thumbSize; -@property (nonatomic, assign) bool tapToSeek; -@property (nonatomic, strong) NSString *accessibilityUnits; -@property (nonatomic, strong) NSArray *accessibilityIncrements; - -- (float) discreteValue:(float)value; -- (void) setDisabled:(bool)disabled; -- (void) refreshThumb; - -@end diff --git a/package/ios/RNCSlider.m b/package/ios/RNCSlider.m deleted file mode 100644 index 8bc1c248..00000000 --- a/package/ios/RNCSlider.m +++ /dev/null @@ -1,256 +0,0 @@ -#import "RNCSlider.h" - -@implementation RNCSlider -{ - float _unclippedValue; - bool _minimumTrackImageSet; - bool _maximumTrackImageSet; - UIImage *_thumbImage; - CGFloat _thumbSize; - UIColor *_thumbTintColor; -} - -- (instancetype)init { - if (self = [super init]) { - _upperLimit = FLT_MAX; - _lowerLimit = FLT_MIN; - } - return self; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - return [super initWithFrame:frame]; -} - -- (void)setValue:(float)value -{ - value = [self discreteValue:value]; - _unclippedValue = value; - super.value = value; - [self setupAccessibility:value]; -} - -- (void)setValue:(float)value animated:(BOOL)animated -{ - value = [self discreteValue:value]; - _unclippedValue = value; - [super setValue:value animated:animated]; - [self setupAccessibility:value]; -} - -- (void)setupAccessibility:(float)value -{ - if (self.accessibilityUnits && self.accessibilityIncrements && [self.accessibilityIncrements count] - 1 == (int)self.maximumValue) { - int index = (int)value; - NSString *sliderValue = (NSString *)[self.accessibilityIncrements objectAtIndex:index]; - NSUInteger stringLength = [self.accessibilityUnits length]; - - NSString *spokenUnits = [NSString stringWithString:self.accessibilityUnits]; - if (sliderValue && [sliderValue intValue] == 1) { - spokenUnits = [spokenUnits substringToIndex:stringLength-1]; - } - - self.accessibilityValue = [NSString stringWithFormat:@"%@ %@", sliderValue, spokenUnits]; - } -} - -- (void)setMinimumValue:(float)minimumValue -{ - super.minimumValue = minimumValue; - super.value = _unclippedValue; -} - -- (void)setMaximumValue:(float)maximumValue -{ - super.maximumValue = maximumValue; - super.value = _unclippedValue; -} - -- (void)setTrackImage:(UIImage *)trackImage -{ - if (trackImage != _trackImage) { - _trackImage = trackImage; - CGFloat width = trackImage.size.width / 2; - if (!_minimumTrackImageSet) { - UIImage *minimumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){ - 0, width, 0, width - } resizingMode:UIImageResizingModeStretch]; - [self setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal]; - } - if (!_maximumTrackImageSet) { - UIImage *maximumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){ - 0, width, 0, width - } resizingMode:UIImageResizingModeStretch]; - [self setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal]; - } - } -} - -- (void)setMinimumTrackImage:(UIImage *)minimumTrackImage -{ - _trackImage = nil; - _minimumTrackImageSet = true; - minimumTrackImage = [minimumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){ - 0, minimumTrackImage.size.width, 0, 0 - } resizingMode:UIImageResizingModeStretch]; - [self setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal]; -} - -- (UIImage *)minimumTrackImage -{ - return [self thumbImageForState:UIControlStateNormal]; -} - -- (void)setMaximumTrackImage:(UIImage *)maximumTrackImage -{ - _trackImage = nil; - _maximumTrackImageSet = true; - maximumTrackImage = [maximumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){ - 0, 0, 0, maximumTrackImage.size.width - } resizingMode:UIImageResizingModeStretch]; - [self setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal]; -} - -- (UIImage *)maximumTrackImage -{ - return [self thumbImageForState:UIControlStateNormal]; -} - -- (void)setThumbImage:(UIImage *)thumbImage -{ - _thumbImage = thumbImage; - [self refreshThumb]; -} - -- (UIImage *)thumbImage -{ - return [self thumbImageForState:UIControlStateNormal]; -} - -- (void)setThumbSize:(CGFloat)thumbSize -{ - _thumbSize = thumbSize; - [self refreshThumb]; -} - -- (void)setThumbTintColor:(UIColor *)thumbTintColor -{ - _thumbTintColor = thumbTintColor; - [super setThumbTintColor:thumbTintColor]; - - [self refreshThumb]; -} - -- (void)refreshThumb -{ - if (![NSThread isMainThread]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self refreshThumb]; - }); - return; - } - - UIImage *imageToSet = nil; - - if (_thumbSize > 0) { - CGSize newSize = CGSizeMake(_thumbSize, _thumbSize); - UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); - CGContextRef context = UIGraphicsGetCurrentContext(); - - if (_thumbImage) { - [_thumbImage drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; - } else { - UIColor *fillColor = _thumbTintColor ?: self.thumbTintColor ?: [UIColor whiteColor]; - CGContextSetFillColorWithColor(context, fillColor.CGColor); - CGContextFillEllipseInRect(context, CGRectMake(0, 0, newSize.width, newSize.height)); - - CGContextSetStrokeColorWithColor(context, [[UIColor colorWithWhite:0.0 alpha:0.1] CGColor]); - CGContextSetLineWidth(context, 0.5); - CGContextStrokeEllipseInRect( - context, - CGRectMake(0.25, 0.25, newSize.width - 0.5, newSize.height - 0.5)); - } - - imageToSet = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - } else if (_thumbImage) { - imageToSet = _thumbImage; - } - - if (imageToSet) { - [self setThumbImage:imageToSet forState:UIControlStateNormal]; - [self setThumbImage:imageToSet forState:UIControlStateHighlighted]; - [self setThumbImage:imageToSet forState:UIControlStateSelected]; - } - - [UIView performWithoutAnimation:^{ - float currentValue = super.value; - float minimumValue = super.minimumValue; - float maximumValue = super.maximumValue; - - float eps = (maximumValue - minimumValue) / 1000.0f; - if (eps <= 0) { - eps = 0.0001f; - } - - float nudgedValue = currentValue + eps; - if (nudgedValue > maximumValue) { - nudgedValue = currentValue - eps; - } - if (nudgedValue < minimumValue) { - nudgedValue = minimumValue; - } - - if (nudgedValue != currentValue) { - [super setValue:nudgedValue animated:NO]; - } - [super setValue:currentValue animated:NO]; - - [self setNeedsLayout]; - [self layoutSubviews]; - [self layoutIfNeeded]; - }]; -} - -- (void)setInverted:(BOOL)inverted -{ - if (inverted) { - self.transform = CGAffineTransformMakeScale(-1, 1); - } else { - self.transform = CGAffineTransformMakeScale(1, 1); - } -} - -- (void)setDisabled:(BOOL)disabled -{ - self.enabled = !disabled; - [self layoutSubviews]; -} - -- (float)discreteValue:(float)value -{ - if (self.step > 0 && value >= self.maximumValue) { - return self.maximumValue; - } - - if (self.step > 0 && self.step <= (self.maximumValue - self.minimumValue)) { - double (^_round)(double) = ^(double x) { - if (!UIAccessibilityIsVoiceOverRunning()) { - return round(x); - } else if (self.lastValue > value) { - return floor(x); - } else { - return ceil(x); - } - }; - - return MAX(self.minimumValue, - MIN(self.maximumValue, self.minimumValue + _round((value - self.minimumValue) / self.step) * self.step) - ); - } - - return value; -} - -@end diff --git a/package/ios/RNCSlider.xcodeproj/project.pbxproj b/package/ios/RNCSlider.xcodeproj/project.pbxproj deleted file mode 100644 index 057244e9..00000000 --- a/package/ios/RNCSlider.xcodeproj/project.pbxproj +++ /dev/null @@ -1,278 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 28C79A23220DC7760061DE82 /* RNCSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C79A21220DC7760061DE82 /* RNCSlider.m */; }; - 7682E5172887E3AB00642F2D /* RNCSliderComponentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7682E5162887E3AB00642F2D /* RNCSliderComponentView.mm */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 28C79A07220DC4CC0061DE82 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 28C79A09220DC4CC0061DE82 /* libRNCSlider.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCSlider.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 28C79A1E220DC7760061DE82 /* RNCSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCSlider.h; sourceTree = ""; }; - 28C79A21220DC7760061DE82 /* RNCSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCSlider.m; sourceTree = ""; }; - 7682E5152887E39900642F2D /* RNCSliderComponentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNCSliderComponentView.h; sourceTree = ""; }; - 7682E5162887E3AB00642F2D /* RNCSliderComponentView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNCSliderComponentView.mm; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 28C79A06220DC4CC0061DE82 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 28C79A00220DC4CC0061DE82 = { - isa = PBXGroup; - children = ( - 7682E5162887E3AB00642F2D /* RNCSliderComponentView.mm */, - 7682E5152887E39900642F2D /* RNCSliderComponentView.h */, - 28C79A1E220DC7760061DE82 /* RNCSlider.h */, - 28C79A21220DC7760061DE82 /* RNCSlider.m */, - 28C79A0A220DC4CC0061DE82 /* Products */, - ); - sourceTree = ""; - }; - 28C79A0A220DC4CC0061DE82 /* Products */ = { - isa = PBXGroup; - children = ( - 28C79A09220DC4CC0061DE82 /* libRNCSlider.a */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 28C79A08220DC4CC0061DE82 /* RNCSlider */ = { - isa = PBXNativeTarget; - buildConfigurationList = 28C79A12220DC4CC0061DE82 /* Build configuration list for PBXNativeTarget "RNCSlider" */; - buildPhases = ( - 28C79A05220DC4CC0061DE82 /* Sources */, - 28C79A06220DC4CC0061DE82 /* Frameworks */, - 28C79A07220DC4CC0061DE82 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RNCSlider; - productName = RNCSlider; - productReference = 28C79A09220DC4CC0061DE82 /* libRNCSlider.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 28C79A01220DC4CC0061DE82 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1010; - ORGANIZATIONNAME = "React Native Community"; - TargetAttributes = { - 28C79A08220DC4CC0061DE82 = { - CreatedOnToolsVersion = 10.1; - }; - }; - }; - buildConfigurationList = 28C79A04220DC4CC0061DE82 /* Build configuration list for PBXProject "RNCSlider" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 28C79A00220DC4CC0061DE82; - productRefGroup = 28C79A0A220DC4CC0061DE82 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 28C79A08220DC4CC0061DE82 /* RNCSlider */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 28C79A05220DC4CC0061DE82 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7682E5172887E3AB00642F2D /* RNCSliderComponentView.mm in Sources */, - 28C79A23220DC7760061DE82 /* RNCSlider.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 28C79A10220DC4CC0061DE82 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 28C79A11220DC4CC0061DE82 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 28C79A13220DC4CC0061DE82 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 28C79A14220DC4CC0061DE82 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 28C79A04220DC4CC0061DE82 /* Build configuration list for PBXProject "RNCSlider" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 28C79A10220DC4CC0061DE82 /* Debug */, - 28C79A11220DC4CC0061DE82 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 28C79A12220DC4CC0061DE82 /* Build configuration list for PBXNativeTarget "RNCSlider" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 28C79A13220DC4CC0061DE82 /* Debug */, - 28C79A14220DC4CC0061DE82 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 28C79A01220DC4CC0061DE82 /* Project object */; -} diff --git a/package/ios/RNCSliderComponentView.h b/package/ios/RNCSliderComponentView.h deleted file mode 100644 index 3bb041d7..00000000 --- a/package/ios/RNCSliderComponentView.h +++ /dev/null @@ -1,36 +0,0 @@ -#import -#import -#import "RNCSlider.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef void (^RNCLoadImageCompletionBlock)(NSError * _Nullable error, UIImage * _Nullable image); -typedef void (^RNCLoadImageFailureBlock)(); - -@interface RNCSliderComponentView : RCTViewComponentView - -@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderValueChange; -@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderSlidingStart; -@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderSlidingComplete; - -@property (nonatomic, assign) float step; -@property (nonatomic, assign) float lastValue; -@property (nonatomic, assign) bool isSliding; - -@property (nonatomic, assign) float lowerLimit; -@property (nonatomic, assign) float upperLimit; - -@property (nonatomic, strong) UIImage *trackImage; -@property (nonatomic, strong) UIImage *minimumTrackImage; -@property (nonatomic, strong) UIImage *maximumTrackImage; -@property (nonatomic, strong) UIImage *thumbImage; -@property (nonatomic, assign) CGFloat thumbSize; -@property (nonatomic, assign) bool tapToSeek; -@property (nonatomic, strong) NSString *accessibilityUnits; -@property (nonatomic, strong) NSArray *accessibilityIncrements; - -- (float) discreteValue:(float)value; - -@end - -NS_ASSUME_NONNULL_END diff --git a/package/ios/RNCSliderComponentView.mm b/package/ios/RNCSliderComponentView.mm deleted file mode 100644 index cf14ae6f..00000000 --- a/package/ios/RNCSliderComponentView.mm +++ /dev/null @@ -1,407 +0,0 @@ -#import "RNCSliderComponentView.h" - -#import - -#import -#import -#import -#import -#import -#import -#import "RCTImagePrimitivesConversions.h" -#import -#import -#import "RCTFabricComponentsPlugins.h" -#import "RNCSlider.h" - -using namespace facebook::react; - -@interface RNCSliderComponentView () - -@end - - -@implementation RNCSliderComponentView -{ - RNCSlider *slider; - UIImage *_image; - BOOL _isSliding; -} - -+ (ComponentDescriptorProvider)componentDescriptorProvider -{ - return concreteComponentDescriptorProvider(); -} - -+ (BOOL)shouldBeRecycled { - return NO; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - static const auto defaultProps = std::make_shared(); - _props = defaultProps; - slider = [[RNCSlider alloc] initWithFrame:self.bounds]; - - // Honor app-forced RTL (RCTI18nUtil) instead of device locale - BOOL appIsRTL = [[RCTI18nUtil sharedInstance] isRTL]; - UISemanticContentAttribute attr = appIsRTL - ? UISemanticContentAttributeForceRightToLeft - : UISemanticContentAttributeForceLeftToRight; - slider.semanticContentAttribute = attr; - self.semanticContentAttribute = attr; - - [slider addTarget:self action:@selector(sliderValueChanged:) - forControlEvents:UIControlEventValueChanged]; - [slider addTarget:self action:@selector(sliderTouchStart:) - forControlEvents:UIControlEventTouchDown]; - [slider addTarget:self action:@selector(sliderTouchEnd:) - forControlEvents:(UIControlEventTouchUpInside | - UIControlEventTouchUpOutside | - UIControlEventTouchCancel)]; - - UITapGestureRecognizer *tapGesturer; - tapGesturer = [[UITapGestureRecognizer alloc] initWithTarget: self action:@selector(tapHandler:)]; - [tapGesturer setNumberOfTapsRequired: 1]; - [slider addGestureRecognizer:tapGesturer]; - - UIPanGestureRecognizer *panGesturer; - panGesturer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panHandler:)]; - [slider addGestureRecognizer:panGesturer]; - - slider.value = (float)defaultProps->value; - self.contentView = slider; - } - return self; -} - -- (void)tapHandler:(UITapGestureRecognizer *)gesture { - if ([gesture.view class] != [RNCSlider class]) { - return; - } - RNCSlider *slider = (RNCSlider *)gesture.view; - slider.isSliding = _isSliding; - - // Ignore this tap if in the middle of a slide. - if (_isSliding) { - return; - } - - if (!slider.tapToSeek) { - return; - } - - CGPoint touchPoint = [gesture locationInView:slider]; - float rangeWidth = slider.maximumValue - slider.minimumValue; - - float sliderPercent; - if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:slider.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) { - sliderPercent = 1.0 - (touchPoint.x / slider.bounds.size.width); - } else { - sliderPercent = touchPoint.x / slider.bounds.size.width; - } - - slider.lastValue = slider.value; - float value = slider.minimumValue + (rangeWidth * sliderPercent); - - if (value < slider.lowerLimit) { - value = slider.lowerLimit; - } else if (value > slider.upperLimit) { - value = slider.upperLimit; - } - - [slider setValue:[slider discreteValue:value] animated: YES]; - - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderSlidingStart(RNCSliderEventEmitter::OnRNCSliderSlidingStart{.value = static_cast(slider.lastValue)}); - - // Trigger onValueChange to address https://github.com/react-native-community/react-native-slider/issues/212 - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderValueChange(RNCSliderEventEmitter::OnRNCSliderValueChange{.value = static_cast(slider.value)}); - - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderSlidingComplete(RNCSliderEventEmitter::OnRNCSliderSlidingComplete{.value = static_cast(slider.value)}); -} - -- (void)sliderValueChanged:(RNCSlider *)sender -{ - [self RNCSendSliderEvent:sender withContinuous:YES isSlidingStart:NO]; -} - -- (void)sliderTouchStart:(RNCSlider *)sender -{ - [self RNCSendSliderEvent:sender withContinuous:NO isSlidingStart:YES]; - _isSliding = YES; - sender.isSliding = YES; -} - -- (void)sliderTouchEnd:(RNCSlider *)sender -{ - [self RNCSendSliderEvent:sender withContinuous:NO isSlidingStart:NO]; - sender.isSliding = NO; - _isSliding = NO; -} - -- (void)RNCSendSliderEvent:(RNCSlider *)sender withContinuous:(BOOL)continuous isSlidingStart:(BOOL)isSlidingStart -{ - float value = [sender discreteValue:sender.value]; - - if (value < sender.lowerLimit) { - value = sender.lowerLimit; - [sender setValue:value animated:NO]; - } else if (value > sender.upperLimit) { - value = sender.upperLimit; - [sender setValue:value animated:NO]; - } - - if(!sender.isSliding) { - [sender setValue:value animated:NO]; - } - - if (continuous) { - if (sender.lastValue != value) { - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderValueChange(RNCSliderEventEmitter::OnRNCSliderValueChange{.value = static_cast(value)}); - } - } else { - if (!isSlidingStart) { - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderSlidingComplete(RNCSliderEventEmitter::OnRNCSliderSlidingComplete{.value = static_cast(value)}); - } - if (isSlidingStart) { - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderSlidingStart(RNCSliderEventEmitter::OnRNCSliderSlidingStart{.value = static_cast(value)}); - } - } - - sender.lastValue = value; -} - -- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps -{ - const auto &oldScreenProps = *std::static_pointer_cast(_props); - const auto &newScreenProps = *std::static_pointer_cast(props); - - if (oldScreenProps.value != newScreenProps.value) { - if (!slider.isSliding) { - slider.value = newScreenProps.value; - } - } - if (oldScreenProps.disabled != newScreenProps.disabled) { - [slider setDisabled: newScreenProps.disabled]; - } - if (oldScreenProps.step != newScreenProps.step) { - slider.step = newScreenProps.step; - } - if (oldScreenProps.inverted != newScreenProps.inverted) { - [self setInverted:newScreenProps.inverted]; - } - if (oldScreenProps.maximumValue != newScreenProps.maximumValue) { - [slider setMaximumValue:newScreenProps.maximumValue]; - } - if (slider.lowerLimit != newScreenProps.lowerLimit) { - if(newScreenProps.lowerLimit > slider.upperLimit){ - NSLog(@"Invalid configuration: upperLimit < lowerLimit; lowerLimit not set"); - } else { - slider.lowerLimit = newScreenProps.lowerLimit; - } - } - if (slider.upperLimit != newScreenProps.upperLimit) { - if(newScreenProps.upperLimit < slider.lowerLimit){ - NSLog(@"Invalid configuration: upperLimit < lowerLimit; upperLimit not set"); - } else { - slider.upperLimit = newScreenProps.upperLimit; - } - } - if (oldScreenProps.tapToSeek != newScreenProps.tapToSeek) { - slider.tapToSeek = newScreenProps.tapToSeek; - } - if (oldScreenProps.minimumValue != newScreenProps.minimumValue) { - [slider setMinimumValue:newScreenProps.minimumValue]; - } - if (oldScreenProps.thumbTintColor != newScreenProps.thumbTintColor) { - slider.thumbTintColor = RCTUIColorFromSharedColor(newScreenProps.thumbTintColor); - } - if (oldScreenProps.thumbSize != newScreenProps.thumbSize) { - slider.thumbSize = newScreenProps.thumbSize; - } - if (oldScreenProps.minimumTrackTintColor != newScreenProps.minimumTrackTintColor) { - slider.minimumTrackTintColor = RCTUIColorFromSharedColor(newScreenProps.minimumTrackTintColor); - } - if (oldScreenProps.maximumTrackTintColor != newScreenProps.maximumTrackTintColor) { - slider.maximumTrackTintColor = RCTUIColorFromSharedColor(newScreenProps.maximumTrackTintColor); - } - if (oldScreenProps.accessibilityUnits != newScreenProps.accessibilityUnits) { - NSString *convertedAccessibilityUnits = [NSString stringWithCString:newScreenProps.accessibilityUnits.c_str() - encoding:[NSString defaultCStringEncoding]]; - slider.accessibilityUnits = convertedAccessibilityUnits; - } - if (oldScreenProps.accessibilityIncrements != newScreenProps.accessibilityIncrements) { - id accessibilityIncrements = [NSMutableArray new]; - for (auto str : newScreenProps.accessibilityIncrements) { - [accessibilityIncrements addObject:[NSString stringWithUTF8String:str.c_str()]]; - } - [slider setAccessibilityIncrements:accessibilityIncrements]; - } - if (oldScreenProps.thumbImage != newScreenProps.thumbImage) { - [self loadImageFromImageSource:newScreenProps.thumbImage completionBlock:^(NSError *error, UIImage *image) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->slider setThumbImage:image]; - }); - } - failureBlock:^{ - [self->slider setThumbImage:nil]; - }]; - } - if (oldScreenProps.trackImage != newScreenProps.trackImage) { - [self loadImageFromImageSource:newScreenProps.trackImage completionBlock:^(NSError *error, UIImage *image) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->slider setTrackImage:image]; - }); - } - failureBlock:^{ - [self->slider setTrackImage:nil]; - }]; - } - if (oldScreenProps.minimumTrackImage != newScreenProps.minimumTrackImage) { - [self loadImageFromImageSource:newScreenProps.minimumTrackImage completionBlock:^(NSError *error, UIImage *image) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->slider setMinimumTrackImage:image]; - }); - } - failureBlock:^{ - [self->slider setMinimumTrackImage:nil]; - }]; - } - if (oldScreenProps.maximumTrackImage != newScreenProps.maximumTrackImage) { - [self loadImageFromImageSource:newScreenProps.maximumTrackImage completionBlock:^(NSError *error, UIImage *image) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->slider setMaximumTrackImage:image]; - }); - } - failureBlock:^{ - [self->slider setMaximumTrackImage:nil]; - }]; - } - [super updateProps:props oldProps:oldProps]; -} - - -- (void)loadImageFromImageSource:(ImageSource)source completionBlock:(RNCLoadImageCompletionBlock)completionBlock failureBlock:(RNCLoadImageFailureBlock)failureBlock -{ - NSString *uri = [[NSString alloc] initWithUTF8String:source.uri.c_str()]; - if (!(BOOL)uri.length) { - failureBlock(); - return; - } - - NSURLRequest *request = NSURLRequestFromImageSource(source); - CGFloat scale = source.scale > 0 ? source.scale : RCTScreenScale(); - - void (^loadDirectly)(void) = ^{ - [[[NSURLSession sharedSession] dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - UIImage *image = data ? [UIImage imageWithData:data scale:scale] : nil; - // The session queue is a background queue; callers touch UIKit, so hop to main. - dispatch_async(dispatch_get_main_queue(), ^{ - if (image) { - completionBlock(nil, image); - } else { - failureBlock(); - } - }); - }] resume]; - }; - - id imageLoader = [[RCTBridge currentBridge] moduleForName:@"ImageLoader"]; - if (!imageLoader) { - loadDirectly(); - return; - } - - [imageLoader - loadImageWithURLRequest:request - size:CGSizeMake(source.size.width, source.size.height) - scale:source.scale - clipped:NO - resizeMode:RCTResizeModeCover - progressBlock:nil - partialLoadBlock:nil - completionBlock:^(NSError *error, UIImage *image) { - if (image) { - completionBlock(nil, image); - } else { - loadDirectly(); - } - }]; -} - -- (void)setInverted:(BOOL)inverted -{ - if (inverted) { - self.transform = CGAffineTransformMakeScale(-1, 1); - } else { - self.transform = CGAffineTransformMakeScale(1, 1); - } -} - -- (void)panHandler:(UIPanGestureRecognizer *)gesture { - CGPoint location = [gesture locationInView:slider]; - - switch (gesture.state) { - case UIGestureRecognizerStateBegan: { - [self updateSliderToLocation:location]; - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderSlidingStart(RNCSliderEventEmitter::OnRNCSliderSlidingStart{.value = static_cast(slider.lastValue)}); - break; - } - - case UIGestureRecognizerStateChanged: { - [self updateSliderToLocation:location]; - break; - } - - case UIGestureRecognizerStateEnded: - case UIGestureRecognizerStateCancelled: { - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderSlidingComplete(RNCSliderEventEmitter::OnRNCSliderSlidingComplete{.value = static_cast(slider.value)}); - break; - } - - default: - break; - } -} - -- (void)updateSliderToLocation:(CGPoint)location { - float newValue = [self calculateSliderValueFromLocation:location]; - float discreteValue = [slider discreteValue:newValue]; - - [slider setValue:newValue animated:NO]; - - if (discreteValue != slider.lastValue) { - std::dynamic_pointer_cast(_eventEmitter) - ->onRNCSliderValueChange(RNCSliderEventEmitter::OnRNCSliderValueChange{.value = static_cast(slider.value)}); - } - - slider.lastValue = discreteValue; -} - -- (float)calculateSliderValueFromLocation:(CGPoint)point { - CGFloat sliderWidth = slider.bounds.size.width; - CGFloat percentage = point.x / sliderWidth; - percentage = MIN(1.0, MAX(0.0, percentage)); - - CGFloat range = slider.maximumValue - slider.minimumValue; - float newValue = slider.minimumValue + (percentage * range); - - return newValue; -} - -@end - -Class RNCSliderCls(void) -{ - return RNCSliderComponentView.class; -} diff --git a/package/ios/SliderComponentView.h b/package/ios/SliderComponentView.h new file mode 100644 index 00000000..1e190680 --- /dev/null +++ b/package/ios/SliderComponentView.h @@ -0,0 +1,16 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Fabric component view for . It owns no drawing of its own: the + * slider itself is the SwiftUI view hosted by `SliderView`, and this class + * only translates props and events between it and the renderer - and hands it + * the custom track, thumbs and step markers JS renders as the slider's children. + */ +@interface SliderComponentView : RCTViewComponentView + +@end + +NS_ASSUME_NONNULL_END diff --git a/package/ios/SliderComponentView.mm b/package/ios/SliderComponentView.mm new file mode 100644 index 00000000..7026281a --- /dev/null +++ b/package/ios/SliderComponentView.mm @@ -0,0 +1,254 @@ +#import "SliderComponentView.h" + +#import +#import +#import +#import + +#import + +#import "RCTFabricComponentsPlugins.h" + +// The pod is called `react-native-slider`, so Swift lands in the +// `react_native_slider` module. Which of the two spellings resolves depends on +// whether the app links pods as frameworks. +#if __has_include() +#import +#else +#import "react_native_slider-Swift.h" +#endif + +using namespace facebook::react; + +@interface SliderComponentView () + +@end + +@implementation SliderComponentView { + SliderView *_sliderView; +} + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + _sliderView = [[SliderView alloc] initWithFrame:self.bounds]; + [self applySliderProps:*defaultProps]; + + // Installed once and kept across recycles: the emitter it reaches for is + // reset by `prepareForRecycle`, so it simply goes quiet until the view is + // mounted again. + __weak __typeof(self) weakSelf = self; + _sliderView.onValueChange = ^(double value) { + [weakSelf emitValueChange:value]; + }; + _sliderView.onLeftValueChange = ^(double value) { + [weakSelf emitLeftValueChange:value]; + }; + _sliderView.onRightValueChange = ^(double value) { + [weakSelf emitRightValueChange:value]; + }; + _sliderView.onSlidingStart = ^{ + [weakSelf emitSlidingStart]; + }; + _sliderView.onSlidingComplete = ^{ + [weakSelf emitSlidingComplete]; + }; + + self.contentView = _sliderView; + } + + return self; +} + +- (void)applySliderProps:(const CallstackSliderProps &)props +{ + _sliderView.minimumValue = props.minimumValue; + _sliderView.maximumValue = props.maximumValue; + _sliderView.disabled = props.disabled; + _sliderView.value = props.value; + _sliderView.ranged = props.ranged; + _sliderView.valueLeft = props.valueLeft; + _sliderView.valueRight = props.valueRight; + _sliderView.step = props.step; + _sliderView.lowerLimit = props.lowerLimit; + _sliderView.upperLimit = props.upperLimit; + _sliderView.vertical = props.orientation == CallstackSliderOrientation::Vertical; + _sliderView.minimumTrackColor = RCTUIColorFromSharedColor(props.minimumTrackColor); + _sliderView.middleRangeTrackColor = RCTUIColorFromSharedColor(props.middleRangeTrackColor); + _sliderView.maximumTrackColor = RCTUIColorFromSharedColor(props.maximumTrackColor); + _sliderView.customTrack = props.customTrack; + _sliderView.customThumb = props.customThumb; + _sliderView.stepMarkers = props.stepMarkers != CallstackSliderStepMarkers::None; + _sliderView.customStepMarkers = props.stepMarkers == CallstackSliderStepMarkers::Custom; +} + +- (void)emitValueChange:(double)value +{ + const auto eventEmitter = std::static_pointer_cast(_eventEmitter); + if (!eventEmitter) { + return; + } + + eventEmitter->onValueChange(CallstackSliderEventEmitter::OnValueChange{.value = value}); +} + +- (void)emitLeftValueChange:(double)value +{ + const auto eventEmitter = std::static_pointer_cast(_eventEmitter); + if (!eventEmitter) { + return; + } + + eventEmitter->onLeftValueChange(CallstackSliderEventEmitter::OnLeftValueChange{.value = value}); +} + +- (void)emitRightValueChange:(double)value +{ + const auto eventEmitter = std::static_pointer_cast(_eventEmitter); + if (!eventEmitter) { + return; + } + + eventEmitter->onRightValueChange(CallstackSliderEventEmitter::OnRightValueChange{.value = value}); +} + +- (void)emitSlidingStart +{ + const auto eventEmitter = std::static_pointer_cast(_eventEmitter); + if (!eventEmitter) { + return; + } + + eventEmitter->onSlidingStart(CallstackSliderEventEmitter::OnSlidingStart{}); +} + +- (void)emitSlidingComplete +{ + const auto eventEmitter = std::static_pointer_cast(_eventEmitter); + if (!eventEmitter) { + return; + } + + eventEmitter->onSlidingComplete(CallstackSliderEventEmitter::OnSlidingComplete{}); +} + +#pragma mark - RCTComponentViewProtocol + +// The reading direction comes from the layout metrics rather than from a prop: React +// Native resolves it for the subtree and hands the answer to every component view it lays +// out. The base implementation turns it into `semanticContentAttribute`, which a hosted +// SwiftUI view reads nothing from, so it is handed to the slider directly as well. +- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics + oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics +{ + _sliderView.laysOutRightToLeft = layoutMetrics.layoutDirection == LayoutDirection::RightToLeft; + + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; +} + +- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps +{ + const auto &oldViewProps = *std::static_pointer_cast(_props); + const auto &newViewProps = *std::static_pointer_cast(props); + + if (oldViewProps.minimumValue != newViewProps.minimumValue) { + _sliderView.minimumValue = newViewProps.minimumValue; + } + if (oldViewProps.maximumValue != newViewProps.maximumValue) { + _sliderView.maximumValue = newViewProps.maximumValue; + } + // Applied ahead of the values, as it is in `applySliderProps` and in the spec the + // Android delegate is generated from: switching off ends the drag a slider believes it + // owns, so a value applied first would be kept off the thumbs by that very rule. + if (oldViewProps.disabled != newViewProps.disabled) { + _sliderView.disabled = newViewProps.disabled; + } + if (oldViewProps.value != newViewProps.value) { + _sliderView.value = newViewProps.value; + } + if (oldViewProps.ranged != newViewProps.ranged) { + _sliderView.ranged = newViewProps.ranged; + } + if (oldViewProps.valueLeft != newViewProps.valueLeft) { + _sliderView.valueLeft = newViewProps.valueLeft; + } + if (oldViewProps.valueRight != newViewProps.valueRight) { + _sliderView.valueRight = newViewProps.valueRight; + } + if (oldViewProps.step != newViewProps.step) { + _sliderView.step = newViewProps.step; + } + if (oldViewProps.lowerLimit != newViewProps.lowerLimit) { + _sliderView.lowerLimit = newViewProps.lowerLimit; + } + if (oldViewProps.upperLimit != newViewProps.upperLimit) { + _sliderView.upperLimit = newViewProps.upperLimit; + } + if (oldViewProps.orientation != newViewProps.orientation) { + _sliderView.vertical = newViewProps.orientation == CallstackSliderOrientation::Vertical; + } + if (oldViewProps.minimumTrackColor != newViewProps.minimumTrackColor) { + _sliderView.minimumTrackColor = RCTUIColorFromSharedColor(newViewProps.minimumTrackColor); + } + if (oldViewProps.middleRangeTrackColor != newViewProps.middleRangeTrackColor) { + _sliderView.middleRangeTrackColor = RCTUIColorFromSharedColor(newViewProps.middleRangeTrackColor); + } + if (oldViewProps.maximumTrackColor != newViewProps.maximumTrackColor) { + _sliderView.maximumTrackColor = RCTUIColorFromSharedColor(newViewProps.maximumTrackColor); + } + if (oldViewProps.customTrack != newViewProps.customTrack) { + _sliderView.customTrack = newViewProps.customTrack; + } + if (oldViewProps.customThumb != newViewProps.customThumb) { + _sliderView.customThumb = newViewProps.customThumb; + } + if (oldViewProps.stepMarkers != newViewProps.stepMarkers) { + _sliderView.stepMarkers = newViewProps.stepMarkers != CallstackSliderStepMarkers::None; + _sliderView.customStepMarkers = newViewProps.stepMarkers == CallstackSliderStepMarkers::Custom; + } + + [super updateProps:props oldProps:oldProps]; +} + +// The only children a slider has are the custom track, thumbs and step markers +// JS renders in place of the built-in ones, and the slider view is what keeps +// them on its track, its thumbs and its steps. +- (void)mountChildComponentView:(UIView *)childComponentView index:(NSInteger)index +{ + [_sliderView mountChildView:childComponentView atIndex:index]; +} + +- (void)unmountChildComponentView:(UIView *)childComponentView index:(NSInteger)index +{ + [_sliderView unmountChildView:childComponentView]; +} + +- (void)prepareForRecycle +{ + [super prepareForRecycle]; + + // `updateProps:` diffs against `_props`, so the props and the slider have to + // be walked back to their defaults together. + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + // A view retired mid-drag still believes it owns the thumb it was dragging, + // and would drop the value pushed right after. + [_sliderView cancelSliding]; + [self applySliderProps:*defaultProps]; +} + +@end + +Class CallstackSliderCls(void) +{ + return SliderComponentView.class; +} diff --git a/package/ios/SliderShadowNode.mm b/package/ios/SliderShadowNode.mm new file mode 100644 index 00000000..d308e4c1 --- /dev/null +++ b/package/ios/SliderShadowNode.mm @@ -0,0 +1,53 @@ +#import +#import + +#import + +#import + +// See `SliderComponentView.mm` for why this import has two spellings. +#if __has_include() +#import +#else +#import "react_native_slider-Swift.h" +#endif + +namespace facebook::react { + +#pragma mark - LayoutableShadowNode + +Size CallstackSliderShadowNode::measureContent( + const LayoutContext & /*layoutContext*/, + const LayoutConstraints &layoutConstraints) const +{ + // Layout runs off the main thread, and SwiftUI can only be asked for a size on + // it. Every slider gets the same answer, so pay for the hop once. This mirrors + // `RCTSwitchSize()`, which React Native uses to size the same way. + // + // The answer describes a horizontal slider; a vertical one is the same control + // on its side, and `sizeForOrientation` turns the size round rather than asking + // SwiftUI for a second one. + // + // A slider drawing step markers of its own is grown to fit them, which only + // ever depends on the thumb, so the room they need is read in the same hop. + static CGSize intrinsicSize; + static CGFloat stepMarkedThickness; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + RCTUnsafeExecuteOnMainQueueSync(^{ + intrinsicSize = [SliderView measuredIntrinsicSize]; + stepMarkedThickness = [SliderView stepMarkedThickness]; + }); + }); + + const auto horizontalSize = sizeForStepMarkers( + { + .width = static_cast(intrinsicSize.width), + .height = static_cast(intrinsicSize.height), + }, + static_cast(stepMarkedThickness)); + + return layoutConstraints.clamp(sizeForOrientation(horizontalSize)); +} + +} // namespace facebook::react diff --git a/package/ios/SliderView.swift b/package/ios/SliderView.swift new file mode 100644 index 00000000..6e832d92 --- /dev/null +++ b/package/ios/SliderView.swift @@ -0,0 +1,1579 @@ +import Combine +import SwiftUI +import UIKit + +/// Every prop coming from JS is kept here rather than on the SwiftUI view, so a +/// prop update re-renders just the slider instead of rebuilding the hosted +/// hierarchy. The literal defaults only stand until the component view pushes the +/// real props, which it does as soon as it creates the view. +final class SliderModel: ObservableObject { + @Published var minimumValue: Double = 0 + @Published var maximumValue: Double = 1 + @Published var value: Double = 0 + @Published var ranged: Bool = false + @Published var valueLeft: Double = 0 + @Published var valueRight: Double = 1 + + /// Granularity of the values the slider reports: every one of them is a multiple of + /// this. Zero means no granularity. + @Published var step: Double = 0 + + /// Bounds the thumbs can be dragged between. The track is drawn across the whole + /// range regardless: a limit takes away the values a thumb can reach, not the ones + /// the slider shows. + @Published var lowerLimit: Double = 0 + @Published var upperLimit: Double = 1 + + /// Whether the slider runs up the view rather than across it, with the minimum at + /// the bottom and the maximum at the top. Only the geometry changes with it. + @Published var vertical: Bool = false + + /// The one place the reading direction is kept: which end of the track the minimum + /// value sits at. `SliderComponentView` writes it from the layout direction React + /// Native resolved for the subtree the slider sits in, and both the hosted slider and + /// the `SliderGeometry` the custom elements follow read it back - so the two cannot + /// mirror one another and not the other. + /// + /// A vertical slider is unaffected by it either way: turning a slider a quarter turn + /// takes its reading direction along with it. + @Published var direction: Direction = .leftToRight + + /// Which end of a horizontal track the minimum value sits at. + enum Direction { + case leftToRight + case rightToLeft + } + + /// Whether values run right-to-left across the view, so that everything laid out along the + /// track is measured from its trailing end instead. A vertical slider never reverses. + var reversesAlongTrack: Bool { direction == .rightToLeft && !vertical } + + /// Whether the slider is switched off: washed out to the faintness of a control + /// nobody can reach, taking no touches, taps, drags or VoiceOver adjustments, and so + /// reporting no events of its own. + /// + /// The values are no part of this - a slider switched off still shows wherever the + /// props put its thumbs, which is why the props keep reaching them while it is off. + @Published var disabled: Bool = false + + /// Tints of the three parts the thumbs cut the track into: below the thumb (or the + /// left thumb), between the two thumbs of a ranged slider, and above the thumb (or + /// the right thumb). `nil` leaves a part looking the way `Slider` draws it. + @Published var minimumTrackColor: UIColor? + @Published var middleRangeTrackColor: UIColor? + @Published var maximumTrackColor: UIColor? + + /// How many of the thumbs JS has replaced with views of its own, counted from the + /// left one - which the single thumb counts as. A replaced thumb is still there to + /// be dragged, it is just no longer drawn; `SliderView` lays the view JS gave + /// over it instead. + @Published var customThumbCount: Int = 0 + + /// Whether JS has replaced the track with a view of its own. The slider then draws + /// no track at all, whatever its tints - `SliderView` lays the view JS gave + /// underneath it instead. + @Published var customTrack: Bool = false + + /// Whether the steps are marked below the track - see `stepMarkerValues`. + @Published var stepMarkers: Bool = false + + /// Whether JS has replaced the step markers with views of its own, one for each of + /// `stepMarkerValues`. The slider then draws none of its own - `SliderView` hangs + /// the views JS gave below the steps instead. + @Published var customStepMarkers: Bool = false + + /// The values the steps stand at: every step from the minimum, and the maximum + /// itself when the step does not divide the range, which is how a thumb can still + /// reach it. A slider with no step has no steps to mark. + /// + /// JS works out the very same values to render its own step markers by, and has to + /// come to as many of them as this does - so the two are worked out the same way, + /// down to the tolerance. + var stepMarkerValues: [Double] { + let span = maximumValue - minimumValue + guard step > 0, span > 0 else { return [] } + + let wholeSteps = Int((span / step + Self.stepTolerance).rounded(.down)) + var values = (0...wholeSteps).map { minimumValue + Double($0) * step } + + if span - Double(wholeSteps) * step > step * Self.stepTolerance { + values.append(maximumValue) + } + + return values + } + + /// Whether a thumb stands on the step at the given value - which is what `marked` + /// tells a step marker JS renders, too. + func isMarked(_ stepValue: Double) -> Bool { + let thumbValues = ranged ? [valueLeft, valueRight] : [value] + + return thumbValues.contains { abs($0 - stepValue) <= step * Self.stepTolerance } + } + + /// How faint a slider switched off is drawn, at the faintness a control that cannot be + /// reached is drawn at. Shared by the slider `SliderContent` hosts and by the views + /// JS renders over it, so that the two go faint together - see + /// `SliderView.applyDisabledLook`. + static let disabledAlpha: CGFloat = 0.3 + + /// Leeway, as a fraction of a step, for a value to count as landing on one. + static let stepTolerance = 1e-9 + + /// Which end of the range a thumb drags. The single thumb of a slider that is not + /// ranged counts as the left one, which is how it shares the drag origins and the + /// clamping below with a ranged slider's. + /// + /// The two names stand for the two ends of the *range* - the lower and the upper value - + /// and not for the two ends of the view: on a slider reading right-to-left the left thumb + /// is the one drawn further right. The names line up with `valueLeft` and `valueRight`, + /// which JS gives the same meaning, and hold it whichever way the slider reads. + enum Thumb { + case single + case left + case right + } + + /// The value a thumb holds. + func value(of thumb: Thumb) -> Double { + switch thumb { + case .single: return value + case .left: return valueLeft + case .right: return valueRight + } + } + + /// Takes a thumb to the given value, keeping it inside the limits and on its own + /// side of the other thumb, and reports it to JS. The two thumbs cannot swap places. + /// + /// A thumb held against a limit stops moving, and a value that has not moved is not + /// reported, so JS hears nothing of a drag carrying on beyond it. Answers whether the + /// thumb moved. + /// + /// Both a drag of a thumb and a tap on the track go through here, so the two cannot + /// disagree on where a value lands. + @discardableResult + func set(_ newValue: Double, of thumb: Thumb) -> Bool { + // Drags already in flight when the slider is switched off keep carrying values + // until SwiftUI has been told to stop reading them. + guard !disabled else { return false } + + let stepped = newValue.snapped(to: step) + let limits = self.limits + + switch thumb { + case .single: + let clamped = stepped.clamped(to: limits) + guard clamped != value else { return false } + value = clamped + onValueChange?(clamped) + case .left: + let clamped = stepped.clamped( + to: limits.lowerBound...valueRight.clamped(to: limits) + ) + guard clamped != valueLeft else { return false } + valueLeft = clamped + onLeftValueChange?(clamped) + case .right: + let clamped = stepped.clamped( + to: valueLeft.clamped(to: limits)...limits.upperBound + ) + guard clamped != valueRight else { return false } + valueRight = clamped + onRightValueChange?(clamped) + } + + return true + } + + /// The value a thumb had when the drag in progress began, or `nil` when that thumb is + /// not being dragged. The single thumb keeps its origin in the left one of the two. + /// + /// A drag is tracked by how far it has travelled rather than by where the finger is, + /// so grabbing a thumb off-centre does not snap it under the finger. + var leftDragOrigin: Double? + var rightDragOrigin: Double? + + /// Whether the user has hold of a thumb, which is simply whether either of the + /// origins above is set. + /// + /// While the user drags, the thumb position is owned by this view. Value updates + /// coming from JS in the meantime would fight the gesture, so they are ignored - the + /// slider is uncontrolled for the duration of the drag. + var isSliding: Bool { leftDragOrigin != nil || rightDragOrigin != nil } + + func dragOrigin(of thumb: Thumb) -> Double? { + switch thumb { + case .single, .left: return leftDragOrigin + case .right: return rightDragOrigin + } + } + + /// Takes hold of a thumb, by remembering the value it is held at, or lets go of it. + func setDragOrigin(_ origin: Double?, of thumb: Thumb) { + switch thumb { + case .single, .left: leftDragOrigin = origin + case .right: rightDragOrigin = origin + } + } + + /// The thumb a hold on the track has picked up, and `nil` when the track is not held: + /// one thumb at a time, for as long as the finger is down. + /// + /// The thumbs' own drags report being pressed through `@GestureState`, which resets + /// itself however a gesture ends. This is the track's half of the same look, written + /// by UIKit and so published here for the hosted slider to draw. + @Published var heldThumb: Thumb? + + /// Whether a hold on the track has a thumb in hand. + var isHeld: Bool { heldThumb != nil } + + /// Lets go of every thumb this holds, and answers a drag it was in the middle of with + /// the completion JS was promised for it. + /// + /// Switching a slider off takes the gestures driving its drag away mid-flight, leaving + /// the drag with no end to be read out of it, and leaves the thumb marked as held - + /// which would keep every value pushed from JS off that thumb for good. + func releaseThumbs() { + let wasSliding = isSliding || isHeld + heldThumb = nil + leftDragOrigin = nil + rightDragOrigin = nil + + if wasSliding { + onSlidingComplete?() + } + } + + /// Invoked continuously while the user drags the thumb. + var onValueChange: ((Double) -> Void)? + + /// Invoked continuously while the user drags the respective thumb of a ranged slider. + var onLeftValueChange: ((Double) -> Void)? + var onRightValueChange: ((Double) -> Void)? + + /// Invoked once the user takes hold of a thumb, and again once they let go of it - + /// whether or not the drag in between moved it anywhere. + var onSlidingStart: (() -> Void)? + var onSlidingComplete: (() -> Void)? + + /// SwiftUI traps on an empty or descending range. Props arrive one at a time, so a + /// momentarily inverted range is expected rather than exceptional. + var range: ClosedRange { + maximumValue > minimumValue ? minimumValue...maximumValue : minimumValue...(minimumValue + 1) + } + + /// The range narrowed down to the part of it the thumbs can be dragged across. + /// + /// A limit that cannot be honoured is dropped: limits that have crossed leave the + /// slider unlimited, and one reaching out of the range limits only as far as the + /// range itself goes. + var limits: ClosedRange { + let range = self.range + guard lowerLimit <= upperLimit else { return range } + + return lowerLimit.clamped(to: range)...upperLimit.clamped(to: range) + } +} + +/// Where the thumbs of the slider sit in a view of the given size. +/// +/// The slider draws the thumbs, but a custom thumb from JS is a UIKit view laid over +/// it, so both of them read the geometry from here to agree on where a thumb is. +/// +/// The geometry is written along two axes rather than across the view: values are +/// spread along its *length*, and the thumbs are as thick as its *breadth* allows, with +/// `SliderModel.vertical` deciding which of the view's sides each of those is. +/// +/// The *along* axis is read from the end of the track the minimum value sits at, which +/// `reversed` says which end that is, so that a caller that only ever asks for distances +/// along the track never has to know which way round it runs. +struct SliderGeometry { + let size: CGSize + let vertical: Bool + let ranged: Bool + let range: ClosedRange + let length: CGFloat + let breadth: CGFloat + + /// Whether the minimum value sits at the trailing end of the view rather than the leading + /// one - see `SliderModel.reversesAlongTrack`. + let reversed: Bool + + /// The extent of a thumb along the slider and across it. + /// + /// A thumb is never thicker than the slider itself: JS decides how much room there + /// is across it, and a thumb spilling out would draw over its neighbours. One that + /// has to be thinner is shrunk as a whole, keeping its shape. + let thumbLength: CGFloat + let thumbThickness: CGFloat + + /// The span the near edge of a thumb moves across. The thumbs of a ranged slider + /// stay fully inside the view, so it is short of the length by one thumb there. + let travel: CGFloat + + /// Distance from the end of the view the minimum value sits at to the near edge of + /// the left (or single) and of the right thumb. + let leftOffset: CGFloat + let rightOffset: CGFloat + + init(model: SliderModel, size: CGSize) { + self.size = size + vertical = model.vertical + ranged = model.ranged + range = model.range + reversed = model.reversesAlongTrack + length = vertical ? size.height : size.width + breadth = vertical ? size.width : size.height + let thumbScale = min(breadth / Self.thumbThickness, 1) + thumbLength = Self.thumbLength * thumbScale + thumbThickness = Self.thumbThickness * thumbScale + travel = max(length - thumbLength, 0) + leftOffset = CGFloat(Self.fraction(of: model.ranged ? model.valueLeft : model.value, in: range)) * travel + rightOffset = CGFloat(Self.fraction(of: model.valueRight, in: range)) * travel + } + + /// How far along the slider the centre of the thumb whose near edge sits at the given + /// offset stands. + /// + /// The offsets place a thumb by its near edge, while a thumb covers the track around + /// its *centre* - so that is where the parts of the track have to be cut into. Cutting + /// at the near edge leaves a strip of it on show beside a thumb JS replaces, which + /// draws none of its own. + func thumbCenterOffset(at offset: CGFloat) -> CGFloat { + offset + thumbLength / 2 + } + + /// Centre of the thumb whose near edge sits the given distance along the slider, in + /// the coordinates of the view. The thumbs lie along the middle of the view. + /// + /// The along axis becomes the view's y going upwards on a vertical slider, and its x + /// going towards the end its minimum value sits at on a horizontal one - leftwards when + /// the slider reads right-to-left. + func thumbCenter(atOffset offset: CGFloat) -> CGPoint { + let along = thumbCenterOffset(at: offset) + + if vertical { + return CGPoint(x: size.width / 2, y: size.height - along) + } + + return CGPoint( + x: reversed ? size.width - along : along, + y: size.height / 2 + ) + } + + /// The point the marker of the step at the given value hangs from: the middle of its + /// top edge, right under where the thumb stands on that step and clear of it. On a + /// vertical slider the markers sit to the right of the track instead, at the middle of + /// their left edge - they are not turned with the slider, which keeps whatever they + /// read the right way up. + /// + /// Where along the track a marker stands follows the reading direction, exactly as + /// `thumbCenter(atOffset:)` does for a thumb; how far across it hangs does not. + func stepMarkerAnchor(at value: Double) -> CGPoint { + let nearEdge = CGFloat(Self.fraction(of: value, in: range)) * travel + let along = thumbCenterOffset(at: nearEdge) + let across = breadth / 2 + thumbThickness / 2 + Self.stepMarkerGap + + if vertical { + return CGPoint(x: across, y: size.height - along) + } + + return CGPoint( + x: reversed ? size.width - along : along, + y: across + ) + } + + /// How far a step marker keeps from the thumb. + static let stepMarkerGap: CGFloat = 4 + + /// The diameter of a built-in step marker. + static let stepMarkerSize: CGFloat = 4 + + /// How thick a slider has to be across for the built-in step markers to fit inside + /// it: a full-sized thumb, with a marker and its gap on either side of it, as the + /// track stays in the middle of the slider. + static var stepMarkedThickness: CGFloat { + thumbThickness + (stepMarkerGap + stepMarkerSize) * 2 + } + + static func fraction(of value: Double, in range: ClosedRange) -> Double { + let span = range.upperBound - range.lowerBound + guard span > 0 else { return 0 } + + return ((value - range.lowerBound) / span).clamped(to: 0...1) + } + + /// Match the thumb `UISlider` draws: a capsule lying along the track since iOS 26, + /// and a circle before it. + static var thumbLength: CGFloat { + if #available(iOS 26.0, *) { + return 38 + } + return 28 + } + + static var thumbThickness: CGFloat { + if #available(iOS 26.0, *) { + return 24 + } + return 28 + } + + /// The value asked for by a tap on the given point of the view: the one whose thumb + /// is placed under the finger, rather than pulled up beside it. Where the step + /// leaves that value is no concern of the geometry - the thumb being moved answers + /// for that, exactly as it does for a drag. + func value(tappedAt point: CGPoint) -> Double { + let span = range.upperBound - range.lowerBound + guard travel > 0, span > 0 else { return range.lowerBound } + + let along = min(max(offset(of: point) - thumbLength / 2, 0), travel) + + return range.lowerBound + Double(along / travel) * span + } + + /// How far along the slider the given point of the view is, measured from the end + /// the minimum value sits at - the same measure `leftOffset` and `rightOffset` place + /// the thumbs by, and the inverse of `thumbCenter(atOffset:)`. A vertical slider grows + /// upwards, which the coordinates of the view run backwards along, and a horizontal one + /// reading right-to-left grows leftwards, which they run backwards along too. + func offset(of point: CGPoint) -> CGFloat { + let along = vertical ? size.height - point.y : (reversed ? size.width - point.x : point.x) + + return min(max(along, 0), length) + } + + /// The thumb a tap on the given point of the view should carry to it: the closest + /// one, unless the tap lands on a thumb to begin with. A thumb that has nowhere to + /// travel to has nowhere to be sought to either. + func thumb(tappedAt point: CGPoint) -> SliderModel.Thumb? { + guard travel > 0, !touchesThumb(at: point) else { return nil } + + let offset = offset(of: point) + + return thumbs.min { + abs(thumbCenterOffset(at: $0.offset) - offset) + < abs(thumbCenterOffset(at: $1.offset) - offset) + }?.thumb + } + + /// Whether the given point of the view is one a thumb is drawn to catch. A touch + /// falling there belongs to that thumb rather than to the track, which is exactly + /// what `RNCRangedSliderContent` makes of it too - so that a tap answers either as a + /// thumb or as the track, never as both nor as neither. + func touchesThumb(at point: CGPoint) -> Bool { + thumbs.contains { thumbTouchRect(atOffset: $0.offset).contains(point) } + } + + /// The rectangle a thumb whose near edge sits at the given offset answers touches + /// over: itself grown on either side of each axis to the touch target Apple asks for + /// - the padding `RNCRangedSliderContent` gives a thumb, so that a thumb is answered + /// wherever it catches a drag and nowhere else. + func thumbTouchRect(atOffset offset: CGFloat) -> CGRect { + let center = thumbCenter(atOffset: offset) + let along = Self.touchSlop(around: thumbLength) + let across = Self.touchSlop(around: thumbThickness) + + return vertical + ? CGRect( + x: center.x - thumbThickness / 2 - across, + y: center.y - thumbLength / 2 - along, + width: thumbThickness + across * 2, + height: thumbLength + along * 2 + ) + : CGRect( + x: center.x - thumbLength / 2 - along, + y: center.y - thumbThickness / 2 - across, + width: thumbLength + along * 2, + height: thumbThickness + across * 2 + ) + } + + /// The thumbs there are to be moved, and how far along the slider each of them + /// stands. The single thumb of a slider that is not ranged is the left one, as it is + /// everywhere else. + private var thumbs: [(thumb: SliderModel.Thumb, offset: CGFloat)] { + ranged + ? [(.left, leftOffset), (.right, rightOffset)] + : [(.single, leftOffset)] + } + + /// How much to grow a thumb on either side of the given extent of it to reach the + /// touch target Apple asks for. + static func touchSlop(around extent: CGFloat) -> CGFloat { + max((minimumTouchTarget - extent) / 2, 0) + } + + /// The smallest control Apple's guidelines will accept as a touch target. + static let minimumTouchTarget: CGFloat = 44 +} + +/// The hosted half of the slider. +/// +/// Everything the slider draws is placed by a distance along the track, and that distance +/// is turned into a place in the view by `RNCRangedSliderContent.stackAlignment` and +/// `offsetAlong(_:)` - which is the one place the reading direction is applied. The +/// environment's own direction is therefore pinned to reading left-to-right, so that the +/// named alignments and the padding those two lean on resolve the same way whatever +/// language the app runs in, and cannot turn the track round a second time. +/// +/// Nothing hosted loses out on that pinning: the track, the thumbs and the markers are +/// capsules and a path, and the only text here is the percentage VoiceOver reads out. +/// What JS renders in place of those is hosted outside this view, by React Native, and +/// keeps the direction React Native gives it. +struct SliderContent: View { + @ObservedObject var model: SliderModel + + var body: some View { + RNCRangedSliderContent(model: model) + // A slider switched off is the same slider drawn faint, the way `UISlider` draws + // itself once it is switched off - the track and the thumbs keeping their shapes + // and their places. + // + // Only the slider this view draws is washed out here: what JS rendered is mounted + // over it as a view of its own, and is dimmed to this same faintness there - see + // `SliderView.applyDisabledLook`. + .opacity(model.disabled ? Double(SliderModel.disabledAlpha) : 1) + .environment(\.layoutDirection, .leftToRight) + } +} + +/// Apple's slider, asked for nothing but its height. +/// +/// Only `measuredIntrinsicSize` builds this, to ask SwiftUI how tall its own slider wants +/// to be, and it is thrown away once the answer comes back. Nothing ever shows it: a +/// slider of one value is drawn by `RNCRangedSliderContent` too, holding a single thumb. +struct RNCSingleSliderContent: View { + @ObservedObject var model: SliderModel + + var body: some View { + let range = model.range + + Slider( + value: Binding( + get: { model.value.clamped(to: range) }, + set: { newValue in + model.value = newValue + model.onValueChange?(newValue) + } + ), + in: range + ) + } +} + +/// SwiftUI has no two-thumb slider and no vertical one, so the slider is drawn here: a +/// track, the selected span, and a thumb at each end of it. The shape follows `Slider` - +/// a capsule track with the selected part tinted, and a white capsule thumb that turns +/// to Liquid Glass while it is held on iOS 26 - so that this slider does not look +/// foreign next to a plain one. +/// +/// Where everything goes is worked out by `SliderGeometry` - and on a vertical slider +/// the left thumb is the bottom one, the direction its value grows in being the only +/// thing that changes. +struct RNCRangedSliderContent: View { + @ObservedObject var model: SliderModel + + /// The two shapes a thumb takes are one and the same thing to this view, to the thumb + /// being dragged and to the one a tap on the track jumps. + private typealias Thumb = SliderModel.Thumb + + /// Whether the user has hold of the left (or single) and of the right thumb, which is + /// what turns it to glass. Kept apart from the drag origins in the model, as a + /// gesture state resets itself however the touch ends - a cancelled one included. + @GestureState private var isLeftThumbPressed = false + @GestureState private var isRightThumbPressed = false + + var body: some View { + GeometryReader { proxy in + let geometry = SliderGeometry(model: model, size: proxy.size) + let range = geometry.range + let thumbSize = CGSize( + width: model.vertical ? geometry.thumbThickness : geometry.thumbLength, + height: model.vertical ? geometry.thumbLength : geometry.thumbThickness + ) + + let lowerColor = tint(model.minimumTrackColor, or: model.ranged ? Self.trackColor : .accentColor) + let middleColor = tint(model.middleRangeTrackColor, or: .accentColor) + let upperColor = tint(model.maximumTrackColor, or: Self.trackColor) + // The parts of the track are cut at the centre of the thumbs, not at the near + // edge the offsets give - see `thumbCenterOffset(at:)`. + let lowerEnd = geometry.thumbCenterOffset(at: geometry.leftOffset) + let upperStart = geometry.thumbCenterOffset( + at: model.ranged ? geometry.rightOffset : geometry.leftOffset + ) + + // Everything is placed by how far along the slider it sits, so the stack is + // anchored at the end the minimum value is at: the leading edge across the view, or + // the trailing one when the slider reads right-to-left, and the bottom one up it. + ZStack(alignment: stackAlignment) { + if !model.customTrack { + track(Self.trackColor, length: nil) + + track(lowerColor, length: lowerEnd) + + // Drawn between the two thumb centres, which is where the parts of the track + // are cut - so its length is the gap between the near edges the offsets give, + // and it is inset by half a thumb from the nearer of them. `max` keeps it from + // inverting on values crossed by JS. + if model.ranged { + track(middleColor, length: max(geometry.rightOffset - geometry.leftOffset, 0)) + .offset(offsetAlong(geometry.thumbCenterOffset(at: min(geometry.leftOffset, geometry.rightOffset)))) + } + + track(upperColor, length: geometry.length - upperStart).offset(offsetAlong(upperStart)) + } + if model.ranged { + thumb(.right, size: thumbSize, offset: geometry.rightOffset, range: range, travel: geometry.travel) + thumb(.left, size: thumbSize, offset: geometry.leftOffset, range: range, travel: geometry.travel) + .zIndex(model.valueLeft >= range.upperBound ? 1 : 0) + } else { + thumb(.single, size: thumbSize, offset: geometry.leftOffset, range: range, travel: geometry.travel) + } + } + // Anchored at the same end as the stack, which is only as big as what it holds: + // with the track replaced that is just the thumbs, and a stack centred in the + // view would have them start out from its middle. + .frame( + width: proxy.size.width, + height: proxy.size.height, + alignment: stackAlignment + ) + .background( + stepMarkers( + geometry, + marked: model.ranged ? middleColor : lowerColor, + unmarked: tint(model.maximumTrackColor, or: Self.stepMarkerColor) + ) + ) + } + } + + /// The markers of the steps, drawn as dots hanging below the thumbs, in the tint of + /// the selected part of the track for a step a thumb is on and in that of the rest of + /// it otherwise. + /// + /// Each set of dots is a single path, so that a slider with many steps does not cost a + /// view apiece - and a path draws wherever its points are, so that the dots are not + /// cut off on a slider too thin to hold them. + @ViewBuilder + private func stepMarkers(_ geometry: SliderGeometry, marked: Color, unmarked: Color) -> some View { + if model.stepMarkers, !model.customStepMarkers { + let values = model.stepMarkerValues + + ZStack { + stepMarkerDots(at: values.filter { !model.isMarked($0) }, in: geometry).fill(unmarked) + stepMarkerDots(at: values.filter(model.isMarked), in: geometry).fill(marked) + } + .allowsHitTesting(false) + .accessibilityHidden(true) + } + } + + private func stepMarkerDots(at values: [Double], in geometry: SliderGeometry) -> Path { + let size = SliderGeometry.stepMarkerSize + + return Path { path in + for value in values { + let anchor = geometry.stepMarkerAnchor(at: value) + let origin = model.vertical + ? CGPoint(x: anchor.x, y: anchor.y - size / 2) + : CGPoint(x: anchor.x - size / 2, y: anchor.y) + path.addEllipse(in: CGRect(origin: origin, size: CGSize(width: size, height: size))) + } + } + } + + private func track(_ color: Color, length: CGFloat?) -> some View { + Capsule() + .fill(color) + .frame( + width: model.vertical ? Self.trackThickness : length, + height: model.vertical ? length : Self.trackThickness + ) + } + + private func tint(_ color: UIColor?, or fallback: Color) -> Color { + color.map { Color(uiColor: $0) } ?? fallback + } + + private func thumb( + _ thumb: Thumb, + size: CGSize, + offset: CGFloat, + range: ClosedRange, + travel: CGFloat + ) -> some View { + // A thumb is a small thing to grab, and the two of them can end up right next to + // each other, so the area that answers to a touch is padded out to the 44pt Apple + // asks for, on whichever side of the thumb falls short of it. The padding is then + // shifted back off the offset, leaving the thumb itself where the value puts it. + let horizontalSlop = SliderGeometry.touchSlop(around: size.width) + let verticalSlop = SliderGeometry.touchSlop(around: size.height) + let alongSlop = model.vertical ? verticalSlop : horizontalSlop + + return thumbFace(isReplaced: isCustom(thumb), isPressed: isPressed(thumb)) + .frame(width: size.width, height: size.height) + .padding(.horizontal, horizontalSlop) + .padding(.vertical, verticalSlop) + .contentShape(Rectangle()) + .offset(offsetAlong(offset - alongSlop)) + .gesture( + DragGesture(minimumDistance: 0) + .updating(pressedState(of: thumb)) { _, isPressed, _ in isPressed = true } + .onChanged { gesture in + let origin = model.dragOrigin(of: thumb) ?? beginDrag(of: thumb) + + let travelled = travel > 0 + ? Double(dragDistance(of: gesture) / travel) * span(of: range) + : 0 + model.set(origin + travelled, of: thumb) + } + .onEnded { _ in endDrag(of: thumb) } + ) + // Drawn rather than built from a UIKit control, so VoiceOver has to be told what + // this is by hand. An adjustment moves the thumb by one step, or by 10% of the + // range when the slider has none - which is what `UISlider` does. + .accessibilityElement() + .accessibilityValue(Text(percentage(of: model.value(of: thumb), in: range))) + .outOfReach(model.disabled) + .accessibilityAdjustableAction { direction in + // Guarded for its own sake as well, the rotor being left where it was put on a + // system too old to hear it taken away. + guard !model.disabled else { return } + + let step = model.step > 0 ? model.step : span(of: range) / 10 + switch direction { + case .increment: + model.set(model.value(of: thumb) + step, of: thumb) + case .decrement: + model.set(model.value(of: thumb) - step, of: thumb) + @unknown default: + break + } + } + } + + /// What a thumb looks like. A thumb JS has replaced is still here to be dragged, only + /// undrawn - the clear capsule keeps its place. + /// + /// On iOS 26 a thumb that is held grows and turns to clear glass, the track showing + /// through it, the way the thumb of `Slider` does. The glass only grows the thumb as + /// it is drawn, so neither the touch area nor where the value puts the thumb move + /// with it. + @ViewBuilder + private func thumbFace(isReplaced: Bool, isPressed: Bool) -> some View { + if isReplaced { + Capsule().fill(Color.clear) + } else { + #if compiler(>=6.2) && os(iOS) + if #available(iOS 26.0, *) { + Capsule() + .fill(isPressed ? Color.clear : Color.white) + .shadow(color: .black.opacity(isPressed ? 0 : 0.25), radius: 2, y: 1) + .glassEffect(isPressed ? .clear.interactive() : .identity, in: Capsule()) + .scaleEffect(isPressed ? Self.pressedThumbScale : 1) + .animation(.spring(response: 0.3, dampingFraction: 0.65), value: isPressed) + } else { + Capsule() + .fill(Color.white) + .shadow(color: .black.opacity(0.25), radius: 2, y: 1) + } + #else + Capsule() + .fill(Color.white) + .shadow(color: .black.opacity(0.25), radius: 2, y: 1) + #endif + } + } + + /// Whether the given thumb should be drawn held: while its own gesture has hold of + /// it, or while a hold on the track has picked it up. The latter comes from UIKit, + /// which cannot feed a SwiftUI gesture state. + private func isPressed(_ thumb: Thumb) -> Bool { + if model.heldThumb == thumb { return true } + + switch thumb { + case .single, .left: return isLeftThumbPressed + case .right: return isRightThumbPressed + } + } + + private func pressedState(of thumb: Thumb) -> GestureState { + switch thumb { + case .single, .left: return $isLeftThumbPressed + case .right: return $isRightThumbPressed + } + } + + private func isCustom(_ thumb: Thumb) -> Bool { + switch thumb { + case .single, .left: return model.customThumbCount > 0 + case .right: return model.customThumbCount > 1 + } + } + + /// Takes hold of a thumb, remembering where it stood so that the drag can be tracked + /// by how far it travels, and tells JS that a drag has begun. + /// + /// Only the thumb grabbed first reports a start: a second finger landing on the other + /// thumb of a ranged slider joins the drag already in progress, so that every start is + /// answered by exactly one complete. A slider that is switched off begins nothing. + private func beginDrag(of thumb: Thumb) -> Double { + let origin = model.value(of: thumb) + guard !model.disabled else { return origin } + + let wasSliding = model.isSliding + model.setDragOrigin(origin, of: thumb) + + if !wasSliding { + model.onSlidingStart?() + } + + return origin + } + + /// Lets go of a thumb, and tells JS the drag is over once the last thumb has been let + /// go of - whether or not it moved anywhere in between. + private func endDrag(of thumb: Thumb) { + // A drag that was never begun - a gesture that ended without ever having changed - + // has nothing to report. + guard model.dragOrigin(of: thumb) != nil else { return } + + model.setDragOrigin(nil, of: thumb) + + if !model.isSliding { + model.onSlidingComplete?() + } + } + + private func span(of range: ClosedRange) -> Double { + range.upperBound - range.lowerBound + } + + /// How far a drag has carried a thumb along the slider, towards the maximum value. A + /// vertical slider grows upwards, which SwiftUI's vertical coordinates run backwards + /// along, and a horizontal one reading right-to-left grows leftwards, which they run + /// backwards along too. + private func dragDistance(of gesture: DragGesture.Value) -> CGFloat { + if model.vertical { + return -gesture.translation.height + } + + return model.reversesAlongTrack ? -gesture.translation.width : gesture.translation.width + } + + /// A shift of the given distance along the slider, away from the end its minimum + /// value sits at - the same direction `dragDistance(of:)` measures a drag in, and the + /// same pair of directions `SliderGeometry` measures offsets in. + private func offsetAlong(_ distance: CGFloat) -> CGSize { + if model.vertical { + return CGSize(width: 0, height: -distance) + } + + return CGSize(width: model.reversesAlongTrack ? -distance : distance, height: 0) + } + + /// The corner of the view the track is anchored at: the end its minimum value sits at, + /// laid out along the slider - which is its bottom on a vertical one, and its trailing + /// edge on a horizontal one reading right-to-left. + /// + /// Pairs with `offsetAlong(_:)`, which turns on the same `reversesAlongTrack` and so + /// measures the distance from the same end this anchors at. + private var stackAlignment: Alignment { + if model.vertical { + return .bottom + } + + return model.reversesAlongTrack ? .trailing : .leading + } + + private func percentage(of value: Double, in range: ClosedRange) -> String { + NumberFormatter.localizedString( + from: NSNumber(value: SliderGeometry.fraction(of: value, in: range)), + number: .percent + ) + } + + /// How much bigger a held thumb is drawn, as `Slider` grows its own. + private static let pressedThumbScale: CGFloat = 1.5 + + private static let trackThickness: CGFloat = 4 + + /// What `Slider` leaves the unselected part of its track looking like. + private static let trackColor = Color(uiColor: .systemFill) + + /// What a built-in step marker no thumb is on looks like, which has to stand out + /// against whatever the slider is laid on rather than against the track. + private static let stepMarkerColor = Color(uiColor: .tertiaryLabel) +} + +/// Hosts the SwiftUI slider inside the UIKit view hierarchy Fabric mounts. +/// +/// The hosting controller is parented to the closest view controller once this view +/// reaches a window: SwiftUI needs a controller in the hierarchy for environment +/// propagation, and Fabric hands us a plain view with no controller of its own. +@objc(SliderView) +public final class SliderView: UIView { + + private let model: SliderModel + private let hostingController: UIHostingController + + /// The views JS rendered as the slider's children, in the order it rendered them: the + /// track, when `customTrack` says JS replaced it, then the single thumb, or the left + /// and then the right one, when `customThumb` says JS replaced them, and then a step + /// marker for each step, when `customStepMarkers` says JS replaced those. + private var childViews: [UIView] = [] + + /// Whether JS replaced the thumbs with views of its own, which is what tells the + /// thumbs apart from the step markers that follow them. + private var hasCustomThumb = false + + /// One for each thumb there can be, holding the view that replaces it. Each is kept + /// centred on its thumb and takes up no room of its own: the shadow node centres the + /// view it holds on its origin, so the view ends up centred on the thumb too. + /// + /// They take no touches, which leaves every drag to the slider underneath. + private let thumbContainers: [UIView] = (0..<2).map { _ in + let container = UIView() + container.isUserInteractionEnabled = false + return container + } + + /// Holds the view that replaces the track, the way `thumbContainers` hold the thumbs: + /// centred on the middle of the slider, and turned a quarter turn anticlockwise on a + /// vertical one, which carries the start of the track down to the bottom, where the + /// minimum is. + /// + /// It lies underneath the slider rather than over it, so that the thumbs the slider + /// draws stay on top of the track. + private let trackContainer: UIView = { + let container = UIView() + container.isUserInteractionEnabled = false + return container + }() + + /// One for each step JS renders a marker for, holding that marker the way + /// `thumbContainers` hold the thumbs - except that each is kept at the point its + /// marker hangs from, see `SliderGeometry.stepMarkerAnchor(at:)`. + /// + /// There are as many of them as there are markers, so they are made as the markers + /// come, and lie between the slider and the thumbs. + private var stepMarkerContainers: [UIView] = [] + + /// Keeps the custom thumbs moving with the slider - see `layoutSubviews`. + private var modelObservation: AnyCancellable? + + /// Reads a tap anywhere on the slider as the track being tapped to move the thumb + /// to - the way `UISlider` behaves, which this slider is drawn to stand in for. + /// + /// It sits on this view rather than being a gesture of the hosted slider, as the + /// whole of the track has to answer it while the thumbs keep their own gestures. + /// + /// The touches are left to carry on underneath it, so that the drags of the hosted + /// slider go on seeing the whole of a touch even when a tap ends up being read out of + /// it - a thumb held without ever moving would otherwise never be told it has been + /// let go of. + private lazy var tapToSeek: UITapGestureRecognizer = { + let recognizer = UITapGestureRecognizer(target: self, action: #selector(seekToTap(_:))) + recognizer.cancelsTouchesInView = false + + return recognizer + }() + + /// Takes over a touch held on the track, after a moment, and drags the thumb nearest + /// the finger for as long as it is held. The same thing as the tap above done slowly, + /// so the two share `beginSeek(at:)`, `continueSeek(at:)` and `endSeek()`, and it sits + /// on the very same track for the very same reason. + /// + /// `allowableMovement` is left at what UIKit asks for, so a finger already sliding as + /// it lands is left to whatever the slider sits in, and only a finger that comes to + /// rest on the track picks a thumb up. Once the hold has begun the finger is free to + /// travel the whole track. + private lazy var holdToSeek: UILongPressGestureRecognizer = { + let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(seekToHold(_:))) + recognizer.minimumPressDuration = Self.seekHoldDuration + recognizer.cancelsTouchesInView = false + + return recognizer + }() + + /// How long the track has to be held before a thumb is picked up off it - short + /// enough that picking one up this way feels like it begins at once. + private static let seekHoldDuration: TimeInterval = 0.15 + + /// Installs the two reads of the track, keeping them from answering the same touch + /// twice: a tap recognizes on release however long the finger was down, so without + /// this a press held past the hold duration would be answered by the hold and then + /// again by the tap. + private func installSeekGestures() { + tapToSeek.require(toFail: holdToSeek) + addGestureRecognizer(tapToSeek) + addGestureRecognizer(holdToSeek) + } + + /// Drags the thumb picked at the start of a hold along with the finger. + @objc private func seekToHold(_ recognizer: UILongPressGestureRecognizer) { + let point = recognizer.location(in: hostingController.view) + + switch recognizer.state { + case .began: + beginSeek(at: point) + case .changed: + continueSeek(at: point) + case .ended, .cancelled, .failed: + endSeek() + default: + break + } + } + + /// Takes the thumb nearest a tap on the track to where the tap landed. + /// + /// A tap landing on a thumb, or arriving while a thumb is already being dragged, is + /// left to the hosted slider. + /// + /// A tap is reported the way a drag of the same distance would have been: a start, + /// the value, and a complete. + @objc private func seekToTap(_ recognizer: UITapGestureRecognizer) { + beginSeek(at: recognizer.location(in: hostingController.view)) + endSeek() + } + + /// Takes the thumb nearest the given point of the slider to it, and keeps it there for + /// as long as the finger holds. + /// + /// Unlike a thumb taken hold of directly, which is then dragged by how far the finger + /// travels, a thumb picked up off the track follows where the finger *is*, from the + /// moment it is grabbed: there is nothing to have travelled from when the finger was + /// never on the thumb to begin with. + /// + /// Answers whether the hold began: a thumb already being dragged, or a hold beginning + /// on one, is left to the hosted slider, and a hold beginning where the thumbs cannot + /// travel has no thumb to pick up. + @discardableResult + private func beginSeek(at point: CGPoint) -> Bool { + guard !model.isSliding, !model.isHeld else { return false } + + let geometry = seekGeometry() + guard let thumb = geometry.thumb(tappedAt: point) else { return false } + + // Marks the thumb as held, so that a value pushed from JS is kept off it for as + // long as the finger is down. The seek never reads the value back, as it is the + // finger that decides where the thumb goes. + model.setDragOrigin(model.value(of: thumb), of: thumb) + model.heldThumb = thumb + model.onSlidingStart?() + continueSeek(at: point) + + return true + } + + /// Carries the thumb picked up off the track to the given point of it. + private func continueSeek(at point: CGPoint) { + guard let thumb = model.heldThumb else { return } + + // The value the finger asks for can still be refused: it can be outside the limits + // or on the far side of the other thumb, and a thumb held against one of those does + // not move - exactly as it would not if it were dragged there. + model.set(seekGeometry().value(tappedAt: point), of: thumb) + } + + /// Lets go of a thumb held on the track, and answers the beginning of the hold with + /// its completion. + private func endSeek() { + guard let thumb = model.heldThumb else { return } + model.heldThumb = nil + + // A second finger that took hold of the same thumb, and let go of it first, has + // already reported the completion. A hold can begin once that drag is over, so + // reporting one here too would answer a start that is no longer there. + if model.dragOrigin(of: thumb) != nil { + model.setDragOrigin(nil, of: thumb) + model.onSlidingComplete?() + } + } + + /// The geometry of the slider as the hosted view draws it, which fills this view - so + /// the track is sought where it is drawn. + private func seekGeometry() -> SliderGeometry { + SliderGeometry(model: model, size: hostingController.view.bounds.size) + } + + @objc public var minimumValue: Double { + get { model.minimumValue } + set { model.minimumValue = newValue } + } + + @objc public var maximumValue: Double { + get { model.maximumValue } + set { model.maximumValue = newValue } + } + + /// Setting this while the user is dragging is a no-op - see `SliderModel.isSliding`. + @objc public var value: Double { + get { model.value } + set { + guard !model.isSliding else { return } + model.value = newValue + } + } + + /// Whether the slider selects a span of the range with two thumbs, rather than a + /// single value with one. `value` and `valueLeft`/`valueRight` belong to the two + /// shapes respectively, and the unused pair is simply not drawn. + @objc public var ranged: Bool { + get { model.ranged } + set { + guard newValue != model.ranged else { return } + model.ranged = newValue + childViewsDidChange() + } + } + + /// Setting either of these while the matching thumb is being dragged is a no-op - see + /// `SliderModel.leftDragOrigin`. + @objc public var valueLeft: Double { + get { model.valueLeft } + set { + guard model.leftDragOrigin == nil else { return } + model.valueLeft = newValue + } + } + + @objc public var valueRight: Double { + get { model.valueRight } + set { + guard model.rightDragOrigin == nil else { return } + model.valueRight = newValue + } + } + + /// Granularity of the values the slider reports - see `SliderModel.step`. A value + /// pushed from JS is taken as it comes, so only the values the slider arrives at + /// itself are snapped. + @objc public var step: Double { + get { model.step } + set { model.step = newValue } + } + + /// Bounds the thumbs can be dragged between - see `SliderModel.limits`. A value + /// pushed from JS is taken as it comes, so only the values the slider arrives at + /// itself are held to the limits. + @objc public var lowerLimit: Double { + get { model.lowerLimit } + set { model.lowerLimit = newValue } + } + + @objc public var upperLimit: Double { + get { model.upperLimit } + set { model.upperLimit = newValue } + } + + /// Whether the slider runs up the view rather than across it - see + /// `SliderModel.vertical`. The shadow node measures a vertical slider the other + /// way round to match, so this only decides how it is drawn and dragged. + @objc public var vertical: Bool { + get { model.vertical } + set { model.vertical = newValue } + } + + /// Whether the slider is switched off - see `SliderModel.disabled`. + /// + /// Touches are taken away from this whole view, which leaves the slider nothing to + /// report, and what it is drawn as comes from the same flag. + @objc public var disabled: Bool { + get { model.disabled } + set { + guard newValue != model.disabled else { return } + model.disabled = newValue + + // A drag the slider is in the middle of loses its gesture with the touches, and so + // ends with no `onEnded` to read it to an end. + if newValue { + model.releaseThumbs() + } + + isUserInteractionEnabled = !newValue + applyDisabledLook() + } + } + + /// Draws the views JS rendered in place of the track, of a thumb and of the step + /// markers at the faintness of a control nobody can reach. + /// + /// The hosted slider washes out its own track and thumbs, but a view JS renders is + /// mounted over it as a view of its own, outside that, and nothing reaches it as a + /// state to draw itself by - so the containers holding them are dimmed here instead. + private func applyDisabledLook() { + let alpha: CGFloat = model.disabled ? SliderModel.disabledAlpha : 1 + + trackContainer.alpha = alpha + thumbContainers.forEach { $0.alpha = alpha } + stepMarkerContainers.forEach { $0.alpha = alpha } + } + + /// Which end of the track the minimum value sits at, as React Native laid this slider + /// out - written by `SliderComponentView` from the layout direction of the layout + /// metrics it is given, which is where React Native puts the answer for the subtree + /// the slider sits in. + /// + /// The views JS renders are deliberately left out of this: React Native has already + /// mirrored their layout, so taking the direction down into them would apply it twice. + @objc public var laysOutRightToLeft: Bool { + get { model.direction == .rightToLeft } + set { + let direction: SliderModel.Direction = newValue ? .rightToLeft : .leftToRight + guard direction != model.direction else { return } + + model.direction = direction + // The hosted slider redraws itself off the model; the custom thumbs, track and + // markers are moved by the layout pass this asks for. + setNeedsLayout() + } + } + + /// Tints of the parts of the track - see `SliderModel.minimumTrackColor`. + @objc public var minimumTrackColor: UIColor? { + get { model.minimumTrackColor } + set { model.minimumTrackColor = newValue } + } + + @objc public var middleRangeTrackColor: UIColor? { + get { model.middleRangeTrackColor } + set { model.middleRangeTrackColor = newValue } + } + + @objc public var maximumTrackColor: UIColor? { + get { model.maximumTrackColor } + set { model.maximumTrackColor = newValue } + } + + /// Whether the first of the views JS mounts is a track rather than a thumb - see + /// `SliderModel.customTrack`. + @objc public var customTrack: Bool { + get { model.customTrack } + set { + guard newValue != model.customTrack else { return } + model.customTrack = newValue + childViewsDidChange() + } + } + + /// Whether the views JS mounts after the track include the thumbs - see `childViews`. + @objc public var customThumb: Bool { + get { hasCustomThumb } + set { + guard newValue != hasCustomThumb else { return } + hasCustomThumb = newValue + childViewsDidChange() + } + } + + /// Whether the steps are marked below the track - see `SliderModel.stepMarkers`. + @objc public var stepMarkers: Bool { + get { model.stepMarkers } + set { model.stepMarkers = newValue } + } + + /// Whether the last of the views JS mounts are step markers - see + /// `SliderModel.customStepMarkers`. + @objc public var customStepMarkers: Bool { + get { model.customStepMarkers } + set { + guard newValue != model.customStepMarkers else { return } + model.customStepMarkers = newValue + childViewsDidChange() + } + } + + @objc public var onValueChange: ((Double) -> Void)? { + get { model.onValueChange } + set { model.onValueChange = newValue } + } + + @objc public var onLeftValueChange: ((Double) -> Void)? { + get { model.onLeftValueChange } + set { model.onLeftValueChange = newValue } + } + + @objc public var onRightValueChange: ((Double) -> Void)? { + get { model.onRightValueChange } + set { model.onRightValueChange = newValue } + } + + @objc public var onSlidingStart: (() -> Void)? { + get { model.onSlidingStart } + set { model.onSlidingStart = newValue } + } + + @objc public var onSlidingComplete: (() -> Void)? { + get { model.onSlidingComplete } + set { model.onSlidingComplete = newValue } + } + + public override init(frame: CGRect) { + let model = SliderModel() + self.model = model + hostingController = UIHostingController(rootView: SliderContent(model: model)) + + super.init(frame: frame) + + hostingController.view.backgroundColor = .clear + hostingController.view.frame = bounds + if #available(iOS 16.4, visionOS 1.0, *) { + // Fabric already positions this view inside the safe area; letting the hosting + // controller inset the slider a second time would shift it. + hostingController.safeAreaRegions = [] + } + addSubview(trackContainer) + addSubview(hostingController.view) + thumbContainers.forEach(addSubview) + installSeekGestures() + + // Every change the slider could redraw its thumbs for may have moved them, a drag + // above all. It is only announced before it lands, so the custom thumbs are moved in + // the layout pass that follows it. + modelObservation = model.objectWillChange.sink { [weak self] _ in + self?.setNeedsLayout() + } + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// The size a slider takes when JS gives it none. + /// + /// The height is whatever SwiftUI asks for, so it tracks the platform instead of a + /// number written down here. The width is only a hint - a SwiftUI `Slider` has no + /// width of its own to report, and flexbox is what actually stretches it. + /// + /// A ranged slider is measured the same way, and a vertical one too - it is the same + /// control on its side, so its shadow node turns this size round rather than measuring + /// a second one. + /// + /// Must be called on the main thread. + @objc public static func measuredIntrinsicSize() -> CGSize { + let controller = UIHostingController(rootView: RNCSingleSliderContent(model: SliderModel())) + let fitted = controller.sizeThatFits( + in: CGSize(width: intrinsicWidth, height: .greatestFiniteMagnitude) + ) + + // An unbounded proposal is meant to come back as the ideal height, but a slider laid + // out to an infinite height would disappear rather than look wrong, so don't take + // SwiftUI's word for it unconditionally. + let height = fitted.height.isFinite && fitted.height > 0 ? fitted.height : fallbackHeight + + return CGSize(width: intrinsicWidth, height: height) + } + + private static let intrinsicWidth: CGFloat = 200 + + /// How thick a slider drawing its own step markers has to be across for them to fit + /// inside it - see `SliderGeometry.stepMarkedThickness`. The shadow node grows a + /// slider measured any thinner to it. + @objc public static func stepMarkedThickness() -> CGFloat { + SliderGeometry.stepMarkedThickness + } + + /// The smallest control Apple's guidelines will accept as a touch target. + private static let fallbackHeight: CGFloat = 44 + + /// Drops any drag in progress, so that a value pushed straight after is not swallowed + /// by the rule above. Fabric recycles component views, and one retired mid-drag would + /// otherwise stay deaf to updates for good. + /// + /// The drag is abandoned rather than ended: the JS that would have heard the + /// completion is no longer mounted on this view. + @objc public func cancelSliding() { + model.heldThumb = nil + model.leftDragOrigin = nil + model.rightDragOrigin = nil + } + + /// Takes a view JS rendered in place of the built-in track or of a built-in thumb - + /// see `childViews`. + @objc(mountChildView:atIndex:) + public func mountChildView(_ view: UIView, at index: Int) { + childViews.insert(view, at: min(index, childViews.count)) + childViewsDidChange() + } + + @objc(unmountChildView:) + public func unmountChildView(_ view: UIView) { + childViews.removeAll { $0 === view } + view.removeFromSuperview() + childViewsDidChange() + } + + /// Hands every child to the container it belongs in. Which one that is depends on + /// which of the track, the thumbs and the step markers JS replaced, which can change + /// before or after the children it concerns are mounted - so it is worked out afresh + /// whenever either does. + private func childViewsDidChange() { + let trackView = model.customTrack ? childViews.first : nil + let remainingViews = childViews.dropFirst(trackView == nil ? 0 : 1) + let thumbCount = hasCustomThumb ? (model.ranged ? 2 : 1) : 0 + let thumbViews = Array(remainingViews.prefix(thumbCount)) + let stepMarkerViews = model.customStepMarkers ? Array(remainingViews.dropFirst(thumbCount)) : [] + + setStepMarkerContainerCount(stepMarkerViews.count) + + var placements = zip(thumbViews, thumbContainers).map { ($0, $1) } + placements += zip(stepMarkerViews, stepMarkerContainers).map { ($0, $1) } + if let trackView { + placements.append((trackView, trackContainer)) + } + + // A view left without a container is not shown anywhere. + for view in childViews where !placements.contains(where: { $0.0 === view }) { + view.removeFromSuperview() + } + for (view, container) in placements where view.superview !== container { + container.addSubview(view) + } + + model.customThumbCount = min(thumbViews.count, thumbContainers.count) + applyDisabledLook() + setNeedsLayout() + } + + /// Makes or drops step marker containers until there is one for each of the given + /// number of markers. A dropped container takes the marker it held with it. + private func setStepMarkerContainerCount(_ count: Int) { + while stepMarkerContainers.count > count { + stepMarkerContainers.removeLast().removeFromSuperview() + } + while stepMarkerContainers.count < count { + let container = UIView() + container.isUserInteractionEnabled = false + insertSubview(container, belowSubview: thumbContainers[0]) + stepMarkerContainers.append(container) + } + } + + public override func layoutSubviews() { + super.layoutSubviews() + hostingController.view.frame = bounds + + let geometry = SliderGeometry(model: model, size: bounds.size) + thumbContainers[0].center = geometry.thumbCenter(atOffset: geometry.leftOffset) + thumbContainers[1].center = geometry.thumbCenter(atOffset: geometry.rightOffset) + + // A marker JS renders for a step the slider does not have - the two being + // momentarily out of step while props and children arrive - is left hidden rather + // than piled up somewhere. + let stepValues = model.stepMarkerValues + for (index, container) in stepMarkerContainers.enumerated() { + container.isHidden = index >= stepValues.count + if index < stepValues.count { + container.center = geometry.stepMarkerAnchor(at: stepValues[index]) + } + } + + // The shadow node centres the track on the origin of its container, laid out along + // the slider's length - which is its height on a vertical one. + trackContainer.center = CGPoint(x: bounds.midX, y: bounds.midY) + trackContainer.transform = model.vertical ? CGAffineTransform(rotationAngle: -.pi / 2) : .identity + } + + public override func didMoveToWindow() { + super.didMoveToWindow() + + if window == nil { + detachFromParentViewController() + } else { + attachToParentViewController() + } + } + + private func attachToParentViewController() { + guard hostingController.parent == nil, + let parent = closestViewController() else { + return + } + + // The hosted view is already a subview, so only the controller containment half of + // the usual dance is left to do. + parent.addChild(hostingController) + hostingController.didMove(toParent: parent) + } + + private func detachFromParentViewController() { + guard hostingController.parent != nil else { return } + + hostingController.willMove(toParent: nil) + hostingController.removeFromParent() + } + + private func closestViewController() -> UIViewController? { + var responder: UIResponder? = next + + while let current = responder { + if let viewController = current as? UIViewController { + return viewController + } + responder = current.next + } + + return nil + } +} + +private extension View { + /// Says to VoiceOver that this cannot be reached, leaving it readable all the same. + /// A slider switched off still holds a value, and the person who cannot reach it is + /// the one who most needs that value read out. + func outOfReach(_ disabled: Bool) -> some View { + accessibilityRespondsToUserInteraction(!disabled) + } +} + +private extension Double { + func clamped(to range: ClosedRange) -> Double { + min(max(self, range.lowerBound), range.upperBound) + } + + /// Rounds to the nearest multiple of the given step, which is the granularity JS is + /// promised. A step of zero leaves the value alone. + /// + /// Clamping is left to the caller: a snapped value can land outside the limits or past + /// the other thumb, and each thumb answers for that differently. The ends stay + /// reachable that way, whether or not the step divides the range evenly. + func snapped(to step: Double) -> Double { + guard step > 0 else { return self } + + // Multiplying the step back out misses the mark by a fraction whenever the step is + // one that binary floating point cannot hold - three tenths arriving as + // 0.30000000000000004 - so the product is rounded to the decimals the step itself is + // written with, and reaches JS reading the way the step was written. + return ((self / step).rounded() * step).rounded(toDecimals: step.decimals) + } + + /// How many decimals it takes to write this value out, capped at the digits a `Double` + /// can be trusted with. + var decimals: Int { + var decimals = 0 + var scaled = magnitude + + while decimals < Self.maximumDecimals, scaled != scaled.rounded(.down) { + scaled *= 10 + decimals += 1 + } + + return decimals + } + + func rounded(toDecimals decimals: Int) -> Double { + let scale = pow(10, Double(decimals)) + + return (self * scale).rounded() / scale + } + + private static let maximumDecimals = 15 +} diff --git a/package/package-lock.json b/package/package-lock.json index 928bb181..34ae90ea 100644 --- a/package/package-lock.json +++ b/package/package-lock.json @@ -1,50 +1,51 @@ { - "name": "@react-native-community/slider", - "version": "5.2.2", + "name": "@callstack/slider", + "version": "6.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@react-native-community/slider", - "version": "5.2.2", + "name": "@callstack/slider", + "version": "6.0.0", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.25.2", - "@babel/preset-flow": "^7.9.0", - "@babel/preset-typescript": "^7.28.5", - "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "^20.0.2", - "@react-native-community/cli-platform-android": "^20.0.2", - "@react-native-community/cli-platform-ios": "^20.0.2", - "@react-native-community/eslint-config": "^3.2.0", - "@react-native/babel-preset": "^0.81.1", - "@react-native/eslint-config": "^0.81.1", - "@react-native/metro-config": "^0.81.1", - "@react-native/typescript-config": "^0.81.1", - "@testing-library/react-native": "^13.3.3", + "@babel/cli": "^7.29.7", + "@babel/core": "^7.29.7", + "@babel/preset-flow": "^7.29.7", + "@babel/preset-typescript": "^7.29.7", + "@babel/runtime": "^7.29.10", + "@react-native-community/cli": "^20.2.0", + "@react-native-community/cli-platform-android": "^20.2.0", + "@react-native-community/cli-platform-ios": "^20.2.0", + "@react-native/babel-preset": "^0.87.1", + "@react-native/eslint-config": "^0.87.1", + "@react-native/jest-preset": "^0.87.1", + "@react-native/metro-config": "^0.87.1", + "@react-native/typescript-config": "^0.87.1", + "@testing-library/react-native": "^14.1.0", "@types/jest": "^30.0.0", - "@types/react": "^19.2.0", - "babel-jest": "^30.2.0", - "babel-plugin-module-resolver": "5.0.0", + "@types/react": "^19.2.18", + "babel-jest": "^30.5.2", + "babel-plugin-module-resolver": "^5.0.3", "copyfiles": "^2.4.1", - "eslint": "^8.19.0", - "eslint-plugin-prettier": "4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-native": "^4.0.0", - "flow-bin": "^0.163.0", - "jest": "^30.2.0", - "prettier": "2.8.8", - "react": "^19.2.0", - "react-native": "^0.81.4", - "react-test-renderer": "^19.2.0", - "typescript": "^5.0.4" + "eslint": "^9.39.5", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.6", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-native": "^5.0.0", + "hermes-eslint": "^0.37.0", + "jest": "^30.5.2", + "prettier": "^3.9.9", + "react": "19.2.3", + "react-native": "^0.87.1", + "test-renderer": "~1.2.0", + "typescript": "^6.0.3" } }, "node_modules/@babel/cli": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.6.tgz", - "integrity": "sha512-6EUNcuBbNkj08Oj4gAZ+BUU8yLCgKzgVX4gaTh09Ya2C8ICM4P+G30g4m3akRxSYAp3A/gnWchrNst7px4/nUQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.29.7.tgz", + "integrity": "sha512-/75HwRbAYPqXv/Ax1h7Fg3IZfXgdU98jnA8H93/m/QBaPV3Hp5ICoLqzGYye1yHBCgpmXvtqgSUN8oOKX5tojQ==", "dev": true, "license": "MIT", "dependencies": { @@ -128,9 +129,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz", - "integrity": "sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.29.9.tgz", + "integrity": "sha512-GmrJTAtiRbNip+eKFqeuSvoHMJhKlH36j8U0yE3Gqn45B+SIO7NvsEiHtsmzkBqV1hLSMqYr41dI1aZLTkJ37g==", "dev": true, "license": "MIT", "dependencies": { @@ -147,14 +148,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -164,13 +165,13 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -194,18 +195,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "engines": { @@ -216,14 +217,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.10.tgz", + "integrity": "sha512-/YY9soQuWa8TURtHXHT+GbklpbAA+dZErxFGHIr6qgDg96xuGWrTAs1+MfjgVJPF0YLQsOVsOYs8lhgsPbOinQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.5.3", "semver": "^6.3.1" }, "engines": { @@ -261,14 +262,14 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -307,22 +308,22 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -330,15 +331,15 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -348,15 +349,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -366,14 +367,14 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -410,42 +411,42 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", - "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -455,13 +456,13 @@ } }, "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz", - "integrity": "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.29.7.tgz", + "integrity": "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -539,13 +540,13 @@ } }, "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz", - "integrity": "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.29.7.tgz", + "integrity": "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -555,13 +556,13 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", - "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.29.7.tgz", + "integrity": "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -571,13 +572,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -613,13 +614,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -739,29 +740,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -771,15 +756,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -789,15 +774,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -807,13 +792,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -823,14 +808,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -840,35 +825,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -878,14 +846,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -895,14 +863,14 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", - "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.29.7.tgz", + "integrity": "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-flow": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-flow": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -912,64 +880,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -979,14 +897,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -996,14 +914,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1013,49 +931,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1065,13 +947,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1081,30 +963,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1114,14 +980,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1131,15 +997,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1149,13 +1015,13 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", - "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1165,17 +1031,17 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", - "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-syntax-jsx": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1185,13 +1051,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1201,13 +1067,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1217,13 +1083,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1233,14 +1099,14 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", - "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", @@ -1253,67 +1119,18 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", - "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.9.tgz", + "integrity": "sha512-FFwIwzU+7SCOuxxV4YtJql6T9981ZVTm+FHO5GhVsRqCTdy0WwrEZh3l42ARpXruJUDGOptdduHW6Zr7pNPLNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.28.6" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1323,14 +1140,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1340,15 +1157,15 @@ } }, "node_modules/@babel/preset-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.27.1.tgz", - "integrity": "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.29.7.tgz", + "integrity": "sha512-KYIRV0BuaN68CDdsqFkAD7MU7yipUqQNuNElwATdxaIdpTjhvtY82QvkBJs7zV3Evxj2jFAAZ1iO8nyy0nhjqA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-flow-strip-types": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-flow-strip-types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1358,17 +1175,17 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1378,9 +1195,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", "dev": true, "license": "MIT", "engines": { @@ -1403,18 +1220,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.9", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -1422,9 +1239,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -1454,14 +1271,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/core/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@emnapi/runtime": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", @@ -1473,14 +1282,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/runtime/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -1492,18 +1293,10 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/wasi-threads/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -1542,68 +1335,178 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@hapi/hoek": { @@ -1623,20 +1526,42 @@ "@hapi/hoek": "^9.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1653,13 +1578,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1680,9 +1611,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", "engines": { @@ -1791,6 +1722,16 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1805,6 +1746,20 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1848,9 +1803,9 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -1858,23 +1813,62 @@ } }, "node_modules/@jest/console": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", - "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz", + "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", "slash": "^3.0.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/console/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/console/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -1886,18 +1880,18 @@ } }, "node_modules/@jest/core": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", - "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz", + "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/pattern": "30.4.0", - "@jest/reporters": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.2", + "@jest/pattern": "30.5.0", + "@jest/reporters": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", @@ -1905,20 +1899,20 @@ "exit-x": "^0.2.2", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.4.1", - "jest-config": "30.4.2", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-resolve-dependencies": "30.4.2", - "jest-runner": "30.4.2", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "jest-watcher": "30.4.1", - "pretty-format": "30.4.1", + "jest-changed-files": "30.5.1", + "jest-config": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-resolve-dependencies": "30.5.2", + "jest-runner": "30.5.2", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "jest-watcher": "30.5.2", + "pretty-format": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -1934,9 +1928,9 @@ } }, "node_modules/@jest/core/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -1946,6 +1940,32 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/core/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -1960,16 +1980,16 @@ } }, "node_modules/@jest/core/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1998,182 +2018,277 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@jest/diff-sequences": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", "dev": true, "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@jest/environment": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz", + "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-mock": "30.5.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "node_modules/@jest/environment/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/environment": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", - "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "jest-mock": "30.4.1" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/environment/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.4.1", - "jest-snapshot": "30.4.1" + "expect": "30.5.2", + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", - "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz", + "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" + "@jest/get-type": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", - "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz", + "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/globals": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", - "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/types": "30.4.1", - "jest-mock": "30.4.1" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/pattern": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", - "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "node_modules/@jest/fake-timers/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, - "license": "MIT", - "dependencies": { + "license": "MIT" + }, + "node_modules/@jest/get-type": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz", + "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/types": "30.5.1", + "jest-mock": "30.5.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "jest-regex-util": "30.4.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/pattern": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz", + "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/reporters": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", - "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz", + "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", + "@jest/console": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.5.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -2190,49 +2305,58 @@ } } }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "node_modules/@jest/reporters/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/@jest/reporters/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2249,26 +2373,26 @@ } }, "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/snapshot-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", - "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz", + "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -2277,15 +2401,55 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz", + "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", + "@jridgewell/trace-mapping": "^0.3.31", "callsites": "^3.1.0", + "convert-source-map": "^2.0.0", "graceful-fs": "^4.2.11" }, "engines": { @@ -2293,14 +2457,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", - "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz", + "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.2", + "@jest/types": "30.5.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -2308,16 +2472,55 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/test-result/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/test-sequencer": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", - "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz", + "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", + "@jest/test-result": "30.5.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", + "jest-haste-map": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -2335,23 +2538,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", - "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz", + "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "babel-plugin-istanbul": "^8.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -2360,25 +2563,28 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@jest/transform/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -2389,17 +2595,39 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/types/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "node_modules/@jest/transform/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -2446,9 +2674,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -2464,22 +2692,25 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@nicolo-ribaudo/chokidar-2": { @@ -2501,9 +2732,9 @@ } }, "node_modules/@nodable/entities": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", - "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz", + "integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==", "dev": true, "funding": [ { @@ -2551,48 +2782,348 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=14" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@pkgr/core": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", - "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/pkgr" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@react-native-community/cli": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.1.3.tgz", - "integrity": "sha512-sLo8cu9JyFNfuuF1C+8NJ4DHE/PEFaXGd4enkcxi/OJjGG8+sOQrdjNQ4i+cVh/2c+ah1mEMwsYjc3z0+/MqSg==", + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@react-native-community/cli-clean": "20.1.3", - "@react-native-community/cli-config": "20.1.3", - "@react-native-community/cli-doctor": "20.1.3", - "@react-native-community/cli-server-api": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", - "@react-native-community/cli-types": "20.1.3", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "fs-extra": "^8.1.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@react-native-community/cli": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.2.0.tgz", + "integrity": "sha512-5RZKkBeUFC4hhrzySzxWmRJwXBTc2PE3L6QUgvnJk3QDxNYMY7aGuI8Gr9eZRS1DbiY10IPWqpJfqSIplzVG6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-clean": "20.2.0", + "@react-native-community/cli-config": "20.2.0", + "@react-native-community/cli-doctor": "20.2.0", + "@react-native-community/cli-server-api": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", + "@react-native-community/cli-types": "20.2.0", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "picocolors": "^1.1.1", "prompts": "^2.4.2", @@ -2606,26 +3137,26 @@ } }, "node_modules/@react-native-community/cli-clean": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.1.3.tgz", - "integrity": "sha512-sFLdLzapfC0scjgzBJJWYDY2RhHPjuuPkA5r6q0gc/UQH/izXpMpLrhh1DW84cMDraNACK0U62tU7ebNaQ1LMQ==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.2.0.tgz", + "integrity": "sha512-krqbhFiwHN8l5wZ2XTcrNq9N+DqDWHxW8RK0bIcrK0O+fMvjYi9e/1Pnx2W0CTotOVcZpHzUbgZ4TQVF231Skw==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-tools": "20.2.0", "execa": "^5.0.0", "fast-glob": "^3.3.2", "picocolors": "^1.1.1" } }, "node_modules/@react-native-community/cli-config": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.1.3.tgz", - "integrity": "sha512-n73nW0cG92oNF0r994pPqm0DjAShOm3F8LSffDYhJqNAno+h/csmv/37iL4NtSpmKIO8xqsG3uVTXz9X/hzNaQ==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.2.0.tgz", + "integrity": "sha512-GF5FxgDfOSLPi/bSiE/xvOGELwzV2GLQnDED+7ArbPi01W1Vu+hD/m+J+bwX34Pocpo767eXAVxc2h9WFUTUfQ==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-tools": "20.2.0", "cosmiconfig": "^9.0.0", "deepmerge": "^4.3.0", "fast-glob": "^3.3.2", @@ -2634,43 +3165,43 @@ } }, "node_modules/@react-native-community/cli-config-android": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.1.3.tgz", - "integrity": "sha512-DNHDP+OWLyhKShGciBqPcxhxfp1Z/7GQcb4F+TGyCeKQAr+JdnUjRXN3X+YCU/v+g2kbYYyRJKlGabzkVvdrAw==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.2.0.tgz", + "integrity": "sha512-lASofUNBVK0Pq3VEJ1JrCfAW94Rnk38DikauycRcoHl0hWjHIN3XhsN20dEq+CaIJoq37aCg5NSSSpmkET+ShA==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-tools": "20.2.0", "fast-glob": "^3.3.2", "fast-xml-parser": "^5.3.6", "picocolors": "^1.1.1" } }, "node_modules/@react-native-community/cli-config-apple": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.1.3.tgz", - "integrity": "sha512-QX9B83nAfCPs0KiaYz61kAEHWr9sttooxzRzNdQwvZTwnsIpvWOT9GvMMj/19OeXiQzMJBzZX0Pgt6+spiUsDQ==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.2.0.tgz", + "integrity": "sha512-oohr6BV2riWJ5PYjayi1u52bG3H95MwKPkJCZo9pnOlYTjifZQVkMxZ8VTuk392efh0bijVTYfxH8iJPi97SIQ==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-tools": "20.2.0", "execa": "^5.0.0", "fast-glob": "^3.3.2", "picocolors": "^1.1.1" } }, "node_modules/@react-native-community/cli-doctor": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.1.3.tgz", - "integrity": "sha512-EI+mAPWn255/WZ4CQohy1I049yiaxVr41C3BeQ2BCyhxODIDR8XRsLzYb1t9MfqK/C3ZncUN2mPSRXFeKPPI1w==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.2.0.tgz", + "integrity": "sha512-eZjlwmjPoBXgyD6nV5oDDocL1VH5UW4LxZcMAqyN3rQw5vq3CeJikFpNedKTtSl3P6JizIkInyIHpfrW+9qfJA==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-config": "20.1.3", - "@react-native-community/cli-platform-android": "20.1.3", - "@react-native-community/cli-platform-apple": "20.1.3", - "@react-native-community/cli-platform-ios": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-config": "20.2.0", + "@react-native-community/cli-platform-android": "20.2.0", + "@react-native-community/cli-platform-apple": "20.2.0", + "@react-native-community/cli-platform-ios": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", "command-exists": "^1.2.8", "deepmerge": "^4.3.0", "envinfo": "^7.13.0", @@ -2684,9 +3215,9 @@ } }, "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2697,51 +3228,51 @@ } }, "node_modules/@react-native-community/cli-platform-android": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.1.3.tgz", - "integrity": "sha512-bzB9ELPOISuqgtDZXFPQlkuxx1YFkNx3cNgslc5ElCrk+5LeCLQLIBh/dmIuK8rwUrPcrramjeBj++Noc+TaAA==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.2.0.tgz", + "integrity": "sha512-fRglzcf/Yq5lnIkqdA8uf1GvlG18x3Dm48Yvo+l7KiWgh/SVtfVhfvalrtX5rmx+KeJkDA534bGoPvi5+ruWjw==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-config-android": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-config-android": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", "execa": "^5.0.0", "logkitty": "^0.7.1", "picocolors": "^1.1.1" } }, "node_modules/@react-native-community/cli-platform-apple": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.1.3.tgz", - "integrity": "sha512-XJ+DqAD4hkplWVXK5AMgN7pP9+4yRSe5KfZ/b42+ofkDBI55ALlUmX+9HWE3fMuRjcotTCoNZqX2ov97cFDXpQ==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.2.0.tgz", + "integrity": "sha512-jkEPLAd8C/ZRu39a3nhxwSXe0iisUiJC808GExnrnTcViLtg3sad2ciQ/HjjvbdsPCS9sT+Jr7xh3MzBsPojsQ==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-config-apple": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-config-apple": "20.2.0", + "@react-native-community/cli-tools": "20.2.0", "execa": "^5.0.0", "fast-xml-parser": "^5.3.6", "picocolors": "^1.1.1" } }, "node_modules/@react-native-community/cli-platform-ios": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.1.3.tgz", - "integrity": "sha512-2qL48SINotuHbZO73cgqSwqd/OWNx0xTbFSdujhpogV4p8BNwYYypfjh4vJY5qJEB5PxuoVkMXT+aCADpg9nBg==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.2.0.tgz", + "integrity": "sha512-bCKlBt2HoD7WTl/HX60+4HFhR1lKY64Y9MPWu7yWQwOCEqYRMi6MkRxLimiONj9M2/lNf0z9BPHDzdDe5HM2ag==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-platform-apple": "20.1.3" + "@react-native-community/cli-platform-apple": "20.2.0" } }, "node_modules/@react-native-community/cli-server-api": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.1.3.tgz", - "integrity": "sha512-hsNsdUKZDd2T99OuNuiXz4VuvLa1UN0zcxefmPjXQgI0byrBLzzDr+o7p03sKuODSzKi2h+BMnUxiS07HACQLA==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.2.0.tgz", + "integrity": "sha512-AI1fsl+6LNuOoDcdWsnF3s9ozqminGCUlbFcrgdZIJWyOuFBeQclNydI22VO3vWaO4dHQkfVa3Zo10AncWQvwA==", "dev": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.1.3", + "@react-native-community/cli-tools": "20.2.0", "body-parser": "^2.2.2", "compression": "^1.7.1", "connect": "^3.6.5", @@ -2750,14 +3281,23 @@ "open": "^6.2.0", "pretty-format": "^29.7.0", "serve-static": "^1.13.1", - "strict-url-sanitise": "0.0.1", "ws": "^6.2.3" } }, + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", + "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/@react-native-community/cli-tools": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.3.tgz", - "integrity": "sha512-EAn0vPCMxtHhfWk2UwLmSUfPfLUnFgC7NjiVJVTKJyVk5qGnkPfoT8te/1IUXFTysUB0F0RIi+NgDB4usFOLeA==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.2.0.tgz", + "integrity": "sha512-A5W2nqlTidPzGyXzS57jvHsMpQd8iOGSjePj4H318uMbhIdeIHQ5e59fNbdHahS62ISs+2p9s78sVHMbGVa1bg==", "dev": true, "license": "MIT", "dependencies": { @@ -2774,9 +3314,9 @@ } }, "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2787,9 +3327,9 @@ } }, "node_modules/@react-native-community/cli-types": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.1.3.tgz", - "integrity": "sha512-IdAcegf0pH1hVraxWTG1ACLkYC0LDQfqtaEf42ESyLIF3Xap70JzL/9tAlxw7lSCPZPFWhrcgU0TBc4SkC/ecw==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.2.0.tgz", + "integrity": "sha512-K60zY/ly8G9rGJQzZ+bFRyLEckAUzyF8Fu9a2Kw6JipCrOl7SiVRBVqjvOr/XnCRKuOpH8i9y7RVg07wkSGRBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2807,9 +3347,9 @@ } }, "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2819,67 +3359,34 @@ "node": ">=10" } }, - "node_modules/@react-native-community/eslint-config": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-3.2.0.tgz", - "integrity": "sha512-ZjGvoeiBtCbd506hQqwjKmkWPgynGUoJspG8/MuV/EfKnkjCtBmeJvq2n+sWbWEvL9LWXDp2GJmPzmvU5RSvKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/eslint-parser": "^7.18.2", - "@react-native-community/eslint-plugin": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^5.30.5", - "@typescript-eslint/parser": "^5.30.5", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-ft-flow": "^2.0.1", - "eslint-plugin-jest": "^26.5.3", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-native": "^4.0.0" - }, - "peerDependencies": { - "eslint": ">=8", - "prettier": ">=2" - } - }, - "node_modules/@react-native-community/eslint-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/eslint-plugin/-/eslint-plugin-1.3.0.tgz", - "integrity": "sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@react-native/assets-registry": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.6.tgz", - "integrity": "sha512-nNlJ7mdXFoq/7LMG3eJIncqjgXkpDJak3xO8Lb4yQmFI3XVI1nupPRjlYRY0ham1gLE0F/AWvKFChsKUfF5lOQ==", + "node_modules/@react-native/asset-utils": { + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/asset-utils/-/asset-utils-0.87.1.tgz", + "integrity": "sha512-FeFnbn9ENPs7IVBzZt1bBWfiRGvT4q+CsWwXGFyKVW/1ol3ylBRuTtXBGHIAmcNN4fUR60nSXsCN19pzNHkPgA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.81.6.tgz", - "integrity": "sha512-OBx6/S0h0MEAoUXhRCWBu00+Oz0gCcHzduTHN++qnK7cIZUhxASUdplYzBZTtTdtA+Zr0bDzH05EsXq718WgFA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.87.1.tgz", + "integrity": "sha512-DfnyLAHG7jH4pqeID7ib6AeD6gG8T+KvM3c/w/yEa5ONkfRcHvTC8JMIRdhUqs4ruA+8xbYcNVQzewYRCPm1Xw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.81.6" + "@babel/traverse": "^7.29.0", + "@react-native/codegen": "0.87.1" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/@react-native/babel-preset": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.81.6.tgz", - "integrity": "sha512-RtIr82ccPoyMLmIC3/vCG96MzRmIT+IzQkjCgTS5b93uAxiER9BS7+d1l7+zD00VanClt6CTBM4K4n6RCnAykg==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.87.1.tgz", + "integrity": "sha512-EN1oo8IqsJgq++na/buq6hYsdZJY5A8+URohSA620eGz5a+322WOAcBuvcYZTeovYwR2NegvpPk5h+QVk8iUMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2889,27 +3396,19 @@ "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", @@ -2918,67 +3417,62 @@ "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.81.6", - "babel-plugin-syntax-hermes-parser": "0.29.1", + "@react-native/babel-plugin-codegen": "0.87.1", + "babel-plugin-syntax-hermes-parser": "0.36.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "peerDependencies": { "@babel/core": "*" } }, "node_modules/@react-native/codegen": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.81.6.tgz", - "integrity": "sha512-9KoYRep/KDnELLLmIYTtIIEOClVUJ88pxWObb/0sjkacA7uL4SgfbAg7rWLURAQJWI85L1YS67IhdEqNNk1I7w==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.87.1.tgz", + "integrity": "sha512-qbaqEdlUfj2vRgvWTpMoNgHnEqAhAYJLUrpGkb0WC9n0kdtqUvgigpz4bDktZwolM9BemXwgGFgyiAnbs3t0xw==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "glob": "^7.1.1", - "hermes-parser": "0.29.1", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.36.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", "yargs": "^17.6.2" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "peerDependencies": { "@babel/core": "*" } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.81.6.tgz", - "integrity": "sha512-oTwIheF4TU7NkfoHxwSQAKtIDx4SQEs2xufgM3gguY7WkpnhGa/BYA/A+hdHXfqEKJFKlHcXQu4BrV/7Sv1fhw==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.87.1.tgz", + "integrity": "sha512-aGBae6v+ngy8fIpU1EOaVxn/8DOgmJNphEdn5Eb0wTchUCxr8bDjpTJYNdrptyn3qI/elk8r9agQ2OBaFvrRlw==", "dev": true, "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.81.6", + "@react-native/asset-utils": "0.87.1", + "@react-native/dev-middleware": "0.87.1", "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.83.1", - "metro-config": "^0.83.1", - "metro-core": "^0.83.1", + "metro": "^0.87.0", "semver": "^7.1.3" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "peerDependencies": { "@react-native-community/cli": "*", - "@react-native/metro-config": "*" + "@react-native/metro-config": "0.87.1" }, "peerDependenciesMeta": { "@react-native-community/cli": { @@ -2990,9 +3484,9 @@ } }, "node_modules/@react-native/community-cli-plugin/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3003,36 +3497,52 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.81.6.tgz", - "integrity": "sha512-aGw28yzbtm25GQuuxNeVAT72tLuGoH0yh79uYOIZkvjI+5x1NjZyPrgiLZ2LlZi5dJdxfbz30p1zUcHvcAzEZw==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.87.1.tgz", + "integrity": "sha512-RNm7soJB+8YSauLnsCCylA1eVfT6JWaDTPUEF01uu9YwDyZ7remKlZbXtmVbtscbNGcp+hbI4iZUdVOpQWsHuA==", "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.87.1.tgz", + "integrity": "sha512-eNbKjcnseJIjy5XCDwyhKOT1ngOg3Dv1fVxTDtnVFqdqjqsbfY4v9JuJG1RZJ7+mFoRRe05HE8GSQU8B7n5xwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.81.6.tgz", - "integrity": "sha512-mK2M3gJ25LtgtqxS1ZXe1vHrz8APOA79Ot/MpbLeovFgLu6YJki0kbO5MRpJagTd+HbesVYSZb/BhAsGN7QAXA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.87.1.tgz", + "integrity": "sha512-KgvAGUaVl6/XrWFAPK8e0ojDRbsdBjr4bnwyn6PC3CwxPQc5iv+i7hMoUwYS8ORSkHKfjt+cV86/mBQ4lG8yfA==", "dev": true, "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.81.6", + "@react-native/debugger-frontend": "0.87.1", + "@react-native/debugger-shell": "0.87.1", "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.2.0", + "chromium-edge-launcher": "^0.3.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", - "ws": "^6.2.3" + "ws": "^7.5.10" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/@react-native/dev-middleware/node_modules/is-wsl": { @@ -3066,423 +3576,455 @@ } }, "node_modules/@react-native/eslint-config": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.81.6.tgz", - "integrity": "sha512-Uur91Ss9L8W7omBAqnNe80GNUNn5qQXYuP0xmiKmytGk0ttG9QkcOma4aS4nR3m8O7v0kFA7U3+AD5an526Jsg==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.87.1.tgz", + "integrity": "sha512-l81N+YbiNussfSQe5MYCM/0nnCuXohFgFqSyHHFJFPGjmFVL+FCx2BFLGcQ3++8chXZJ0wZBoO+w89EvNQoeXw==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", - "@react-native/eslint-plugin": "0.81.6", - "@typescript-eslint/eslint-plugin": "^7.1.1", - "@typescript-eslint/parser": "^7.1.1", + "@react-native/eslint-plugin": "0.87.1", + "@typescript-eslint/eslint-plugin": "^8.59.2", + "@typescript-eslint/parser": "^8.59.2", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", - "eslint-plugin-jest": "^27.9.0", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-native": "^4.0.0" + "eslint-plugin-jest": "^29.0.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-native": "^5.0.0" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "peerDependencies": { - "eslint": ">=8", + "eslint": "^8.0.0 || ^9.0.0", "prettier": ">=2" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "node_modules/@react-native/eslint-config/node_modules/eslint-config-prettier": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", + "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "bin": { + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": ">=7.0.0" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-ft-flow": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz", + "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=12.22.0" }, "peerDependencies": { - "eslint": "^8.56.0" + "@babel/eslint-parser": "^7.12.0", + "eslint": "^8.1.0" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "node_modules/@react-native/eslint-plugin": { + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.87.1.tgz", + "integrity": "sha512-38astUSjcbuxfHQgzNiGrALD0il26G5Dz2fnthohXHne1iqmf/VPs/Fvw3RfrTMQ2i8qV04F9fLro7Ds35AAFg==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "node_modules/@react-native/gradle-plugin": { + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.87.1.tgz", + "integrity": "sha512-bZ5X2BNxaSlfp2KlDtUM910QqW9G1yTIeZXghuv4ag8SAQLzm5Mf9j5GjJfT6ax2Qo2aRT15Vi3Tro/yLGJstA==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "node_modules/@react-native/jest-preset": { + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/jest-preset/-/jest-preset-0.87.1.tgz", + "integrity": "sha512-RZO0W1ovniiwMdV8xA0kTv9Vk12TCGW+03NgwoVRAfKAhpQnPk5XMNnKR3zPWjIl/Wo3ZUdJpWxELYtidt5F+g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@jest/create-cache-key-function": "^29.7.0", + "@react-native/js-polyfills": "0.87.1", + "babel-jest": "^29.7.0", + "jest-environment-node": "^29.7.0", + "regenerator-runtime": "^0.13.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 22.13.0" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "react": "^19.2.3" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "node_modules/@react-native/jest-preset/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "node_modules/@react-native/jest-preset/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/@react-native/eslint-config/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "node_modules/@react-native/jest-preset/node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=8" } }, - "node_modules/@react-native/eslint-config/node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "node_modules/@react-native/jest-preset/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-jest": { - "version": "27.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", - "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", + "node_modules/@react-native/jest-preset/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^5.10.0" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", - "eslint": "^7.0.0 || ^8.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } + "@babel/core": "^7.0.0" } }, - "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "node_modules/@react-native/jest-preset/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/jest-preset/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@react-native/eslint-config/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@react-native/jest-preset/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "license": "Apache-2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/@react-native/eslint-config/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/@react-native/jest-preset/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "brace-expansion": "^2.0.2" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/@react-native/eslint-config/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/@react-native/jest-preset/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/@react-native/eslint-plugin": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.81.6.tgz", - "integrity": "sha512-2wLmnq6l2dualDoAj42P4miwReXt5i7rad31zenhYFBSUNt849y2sroP2I6Yjh0r+UqBdDlf6QN2W+Pdmi6pJw==", + "node_modules/@react-native/jest-preset/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20.19.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.81.6.tgz", - "integrity": "sha512-atUItC5MZ6yaNaI0sbsoDwUdF+KMNZcMKBIrNhXlUyIj3x1AQ6Cf8CHHv6Qokn8ZFw+uU6GWmQSiOWYUbmi8Ag==", + "node_modules/@react-native/jest-preset/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">= 20.19.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.81.6.tgz", - "integrity": "sha512-P5MWH/9vM24XkJ1TasCq42DMLoCUjZVSppTn6VWv/cI65NDjuYEy7bUSaXbYxGTnqiKyPG5Y+ADymqlIkdSAcw==", + "node_modules/@react-native/jest-preset/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 20.19.4" + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@react-native/jest-preset/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@react-native/jest-preset/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/jest-preset/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@react-native/jest-preset/node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/jest-preset/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.87.1.tgz", + "integrity": "sha512-L9yKwEJCq7e8RjhthMUht2CP/CPnBR134h9xHkJ0XOO15QOWBAOqSZltTZG/wiokidqePvK+exMsRinuFLsN9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.81.6.tgz", - "integrity": "sha512-Z5up8Z1I6PAgY6Z78yXmRJHps5d8FqQIYbAALmSyg5tmtrJyd04JgrwEWhwH1HQsxB5beWwQRntA5WUfWNlQPA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.87.1.tgz", + "integrity": "sha512-9qPN7DIBA2ldpTVeEObRuIf0ihF9iphBR17rOyuFu8UnhbzHpoLDHCqnjnOm6P21TY8GU6JgaqmD4rhtUCOFeQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.81.6", - "hermes-parser": "0.29.1", + "@react-native/babel-preset": "0.87.1", + "hermes-parser": "0.36.1", "nullthrows": "^1.1.1" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "peerDependencies": { "@babel/core": "*" } }, "node_modules/@react-native/metro-config": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.81.6.tgz", - "integrity": "sha512-tdLKkC1Dv0EW6dayL0pAItbdWhrVZDCiTcyb43cLtkeaQYpJgXeRX+1PsmkFywZDn/ojxiGxe/HLg5yZH0gbdA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.87.1.tgz", + "integrity": "sha512-oHgFFZpWDElw/ZO7YmNsTproNtHL0Ty/PWAR7Lf7YjuT7HuY11RyihQ4Kh/YseepHi/9vMBe8QNFqJCB8eMNZg==", "dev": true, "license": "MIT", "dependencies": { - "@react-native/js-polyfills": "0.81.6", - "@react-native/metro-babel-transformer": "0.81.6", - "metro-config": "^0.83.1", - "metro-runtime": "^0.83.1" + "@react-native/js-polyfills": "0.87.1", + "@react-native/metro-babel-transformer": "0.87.1", + "metro-config": "^0.87.0", + "metro-runtime": "^0.87.0" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/@react-native/normalize-colors": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.6.tgz", - "integrity": "sha512-/OCgUysHIFhfmZxbJAydVc58l2SGIZbWpbQXBrYEYch8YElBbDFQ8IUtyogB7YJJQ8ewHZFj93rQGaECgkvvcw==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.87.1.tgz", + "integrity": "sha512-8+AutemzX+a7cuKgTUyb7JUk3sFYgLyrSnlTLrL6LuW/LKDE+FYE8lJGWYhJPJcE51Ge1D8yRzxgQEdEFZtuIw==", "dev": true, "license": "MIT" }, "node_modules/@react-native/typescript-config": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.81.6.tgz", - "integrity": "sha512-1eu60ilbT8dm8euTvzmlqQ+oC2eb0g6hXEcvVj/TJtLWDLl7GSmAmT8oD+5445D1zTiEIMIhpK70OAPgoRk/zg==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.87.1.tgz", + "integrity": "sha512-AaPFOTglk3GJ7OWSS/vuF9I4qotm5IyazRIgITMwIdAk6JF7iDTec77UZRadUs5/08G593gnF78unedM3skn/Q==", "dev": true, "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.6.tgz", - "integrity": "sha512-1RrZl3a7iCoAS2SGaRLjJPIn8bg/GLNXzqkIB2lufXcJsftu1umNLRIi17ZoDRejAWSd2pUfUtQBASo4R2mw4Q==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.87.1.tgz", + "integrity": "sha512-qSZjeX3UJrDvyfjf7yc3E68rp1XnzE+5nu8ImklhkVC0+p/XiaHPb/KGkRqDdnQyWHN55BRYSsCMEwgVI6WRNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3490,12 +4032,12 @@ "nullthrows": "^1.1.1" }, "engines": { - "node": ">= 20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "peerDependencies": { - "@types/react": "^19.1.4", + "@types/react": "^19.2.0", "react": "*", - "react-native": "*" + "react-native": "0.87.1" }, "peerDependenciesMeta": { "@types/react": { @@ -3528,9 +4070,9 @@ "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", "dev": true, "license": "MIT" }, @@ -3555,25 +4097,25 @@ } }, "node_modules/@testing-library/react-native": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-13.3.3.tgz", - "integrity": "sha512-k6Mjsd9dbZgvY4Bl7P1NIpePQNi+dfYtlJ5voi9KQlynxSyQkfOgJmYGCYmw/aSgH/rUcFvG8u5gd4npzgRDyg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-14.1.0.tgz", + "integrity": "sha512-tWOam5U3aUE8XqStdWAN/OuUUdGRzh/vJMg7SfCaqXNKx7orRh04590Jp4JbORctYjORoRYfJSg04I2//wnxYw==", "dev": true, "license": "MIT", "dependencies": { - "jest-matcher-utils": "^30.0.5", + "jest-matcher-utils": "^30.4.1", "picocolors": "^1.1.1", - "pretty-format": "^30.0.5", + "pretty-format": "^30.4.1", "redent": "^3.0.0" }, "engines": { - "node": ">=18" + "node": "^22.13.0 || >=24" }, "peerDependencies": { "jest": ">=29.0.0", - "react": ">=18.2.0", - "react-native": ">=0.71", - "react-test-renderer": ">=18.2.0" + "react": ">=19.0.0", + "react-native": ">=0.78", + "test-renderer": "^1.0.0" }, "peerDependenciesMeta": { "jest": { @@ -3581,6 +4123,26 @@ } } }, + "node_modules/@testing-library/react-native/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@testing-library/react-native/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/@testing-library/react-native/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -3595,24 +4157,25 @@ } }, "node_modules/@testing-library/react-native/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "dev": true, "license": "MIT", "optional": true, @@ -3620,14 +4183,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@tybys/wasm-util/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3673,6 +4228,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -3721,6 +4283,26 @@ "pretty-format": "^30.0.0" } }, + "node_modules/@types/jest/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@types/jest/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jest/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -3735,15 +4317,16 @@ } }, "node_modules/@types/jest/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3757,31 +4340,34 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", - "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "version": "26.6.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz", + "integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" } }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "node_modules/@types/react-reconciler": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.33.1.tgz", + "integrity": "sha512-0kF59ji2mKuHUd5wytHSpmORybMYVerJvq0K10pz9ZFoOlJ3e+w1LBjLshWpCrokeyQOioRodNbO4PH4ddvRfg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } }, "node_modules/@types/stack-utils": { "version": "2.0.3", @@ -3808,135 +4394,149 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz", + "integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/type-utils": "8.71.1", + "@typescript-eslint/utils": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.71.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/@typescript-eslint/parser": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz", + "integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "debug": "^4.4.3" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz", + "integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.71.1", + "@typescript-eslint/types": "^8.71.1", + "debug": "^4.4.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz", + "integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz", + "integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz", + "integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1", + "@typescript-eslint/utils": "8.71.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz", + "integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -3944,37 +4544,37 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz", + "integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "@typescript-eslint/project-service": "8.71.1", + "@typescript-eslint/tsconfig-utils": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3985,57 +4585,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz", + "integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz", + "integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "8.71.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -4043,22 +4627,22 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", "dev": true, "license": "ISC" }, @@ -4382,19 +4966,6 @@ "dev": true, "license": "MIT" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -4420,9 +4991,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", "bin": { @@ -4453,9 +5024,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -4492,19 +5063,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-fragments": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", @@ -4517,29 +5075,6 @@ "strip-ansi": "^5.0.0" } }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -4594,21 +5129,28 @@ "license": "MIT" }, "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.8.tgz", + "integrity": "sha512-8zl1xlxeS4a0/36CT6LOaVioPOL8TeLT1b9OHk0j9xSbzmPBuM7lUgWMSTh6SbuF8fbwjcP1rr30OCLpd1fl+A==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/codingjerk" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/codingjerk" + } + ], "license": "MIT" }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", @@ -4628,18 +5170,18 @@ } }, "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.2.0.tgz", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" }, @@ -4650,16 +5192,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -4809,16 +5341,16 @@ } }, "node_modules/babel-jest": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", - "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz", + "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.4.1", + "@jest/transform": "30.5.2", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.4.0", + "babel-plugin-istanbul": "^8.0.0", + "babel-preset-jest": "30.5.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -4841,9 +5373,9 @@ } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.2.tgz", + "integrity": "sha512-bXWZeWQvvCZcD2uHLWVbitr4s1+YqILBbWB/szXKtHXt6crLlD6mqxHZ0qWy2X/Xx5RtQvqqW80JHAXMlp+y1Q==", "dev": true, "license": "BSD-3-Clause", "workspaces": [ @@ -4854,16 +5386,16 @@ "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "test-exclude": "^7.0.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", - "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz", + "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", "dev": true, "license": "MIT", "dependencies": { @@ -4874,22 +5406,26 @@ } }, "node_modules/babel-plugin-module-resolver": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.0.tgz", - "integrity": "sha512-g0u+/ChLSJ5+PzYwLwP8Rp8Rcfowz58TJNCe+L/ui4rpzE/mg//JVX0EWBUYoxaextqnwuGHzfGp2hh0PPV25Q==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.3.tgz", + "integrity": "sha512-h8h6H71ZvdLJZxZrYkaeR30BojTaV7O9GfqacY14SNj5CNB8ocL9tydNzTC0JrnNN7vY3eJhwCmkDj7tuEUaqQ==", "dev": true, "license": "MIT", "dependencies": { - "find-babel-config": "^2.0.0", - "glob": "^8.0.3", + "find-babel-config": "^2.1.1", + "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">= 16" + "resolve": "^1.22.8" } }, + "node_modules/babel-plugin-module-resolver/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/babel-plugin-module-resolver/node_modules/brace-expansion": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", @@ -4901,37 +5437,83 @@ } }, "node_modules/babel-plugin-module-resolver/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/babel-plugin-module-resolver/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/babel-plugin-module-resolver/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/babel-plugin-polyfill-corejs2": { @@ -4977,13 +5559,13 @@ } }, "node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.29.1.tgz", - "integrity": "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.1.tgz", + "integrity": "sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==", "dev": true, "license": "MIT", "dependencies": { - "hermes-parser": "0.29.1" + "hermes-parser": "0.36.1" } }, "node_modules/babel-plugin-transform-flow-enums": { @@ -5024,28 +5606,31 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", - "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz", + "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.4.0", + "babel-plugin-jest-hoist": "30.5.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -5069,9 +5654,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.29", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.29.tgz", - "integrity": "sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==", + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5133,21 +5718,21 @@ } }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -5158,14 +5743,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -5182,9 +5769,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -5202,11 +5789,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -5268,15 +5855,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -5338,9 +5925,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001784", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", - "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", "dev": true, "funding": [ { @@ -5444,9 +6031,9 @@ } }, "node_modules/chromium-edge-launcher": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", - "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5454,8 +6041,7 @@ "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "mkdirp": "^1.0.4" } }, "node_modules/chromium-edge-launcher/node_modules/is-wsl": { @@ -5488,9 +6074,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz", + "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", "dev": true, "license": "MIT" }, @@ -5535,6 +6121,19 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -5702,13 +6301,17 @@ "license": "MIT" }, "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/convert-source-map": { @@ -5738,6 +6341,24 @@ "copyup": "copyfiles" } }, + "node_modules/copyfiles/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/copyfiles/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/copyfiles/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -5750,10 +6371,36 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/copyfiles/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/copyfiles/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/copyfiles/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", "dependencies": { @@ -5780,13 +6427,16 @@ } }, "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.28.1" + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" }, "funding": { "type": "opencollective", @@ -5801,9 +6451,9 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, "license": "MIT", "dependencies": { @@ -5827,36 +6477,6 @@ } } }, - "node_modules/cosmiconfig/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5934,9 +6554,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", "dev": true, "license": "MIT" }, @@ -6070,40 +6690,37 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { "node": ">=8" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, "engines": { "node": ">=8" } }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/dunder-proto": { @@ -6136,9 +6753,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.331", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", - "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "version": "1.5.451", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.451.tgz", + "integrity": "sha512-vUNKPrUzw8ByEMOxloq0a71iD/mPuPxVkbDm3G5YhnxTzOw/I6qAg2MUS0cYj/c267lWmw26yW4Vv7foxTxPLg==", "dev": true, "license": "ISC" }, @@ -6234,9 +6851,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -6302,6 +6919,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -6323,16 +6959,16 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", - "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", @@ -6344,17 +6980,23 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", - "math-intrinsics": "^1.1.0", - "safe-array-concat": "^1.1.3" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -6394,15 +7036,18 @@ } }, "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", "dev": true, "license": "MIT", "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -6442,71 +7087,78 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" - } - }, + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, "node_modules/eslint-config-prettier": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz", - "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } @@ -6541,39 +7193,33 @@ "node": ">=0.8.0" } }, - "node_modules/eslint-plugin-ft-flow": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz", - "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", + "node_modules/eslint-plugin-eslint-comments/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", - "dependencies": { - "lodash": "^4.17.21", - "string-natural-compare": "^3.0.1" - }, "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "@babel/eslint-parser": "^7.12.0", - "eslint": "^8.1.0" + "node": ">= 4" } }, "node_modules/eslint-plugin-jest": { - "version": "26.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.9.0.tgz", - "integrity": "sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==", + "version": "29.16.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.16.6.tgz", + "integrity": "sha512-q8TVr0rlNvUD0XnafGWkwtPeX+tTl2llP86EDl3sJtwWrQA/JT9SIu2hLLZbhhX6fT5SDE4O0gIKyZUujKnkYA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^5.10.0" + "@typescript-eslint/utils": "^8.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "jest": "*", + "typescript": ">=4.8.4 <8.0.0" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { @@ -6581,26 +7227,38 @@ }, "jest": { "optional": true + }, + "typescript": { + "optional": true } } }, "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz", + "integrity": "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.13" }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } @@ -6640,29 +7298,53 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" } }, "node_modules/eslint-plugin-react-native": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz", - "integrity": "sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-5.0.0.tgz", + "integrity": "sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q==", "dev": true, "license": "MIT", "dependencies": { "eslint-plugin-react-native-globals": "^0.1.1" }, "peerDependencies": { - "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "node_modules/eslint-plugin-react-native-globals": { @@ -6672,28 +7354,46 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", - "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", + "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", @@ -6743,17 +7443,28 @@ "node": ">=10" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "Python-2.0" + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6761,20 +7472,20 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6793,55 +7504,55 @@ "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "argparse": "^2.0.1" + "brace-expansion": "^1.1.7" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "*" } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6917,16 +7628,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6962,18 +7663,18 @@ } }, "node_modules/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7032,9 +7733,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", "dev": true, "funding": [ { @@ -7044,14 +7745,14 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" } }, "node_modules/fast-xml-parser": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz", - "integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==", + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz", + "integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==", "dev": true, "funding": [ { @@ -7061,43 +7762,40 @@ ], "license": "MIT", "dependencies": { - "@nodable/entities": "^3.0.0", + "@nodable/entities": "^3.0.1", "fast-xml-builder": "^1.2.0", "is-unsafe": "^2.0.0", "path-expression-matcher": "^1.6.2", - "strnum": "^2.4.1", + "strnum": "^2.4.2", "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, - "node_modules/fast-xml-parser/node_modules/xml-naming": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", - "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -7109,16 +7807,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -7211,40 +7909,26 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, - "node_modules/flow-bin": { - "version": "0.163.0", - "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.163.0.tgz", - "integrity": "sha512-4rNdO/lT38/c+8SR4eZK+PthE9c3MFiDnr8TYBpIM77EkM2yl9Ug8N2oPPk0UJs6kG93Yb61G14s95dfSoUARA==", - "dev": true, - "license": "MIT", - "bin": { - "flow": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/flow-enums-runtime": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", @@ -7252,6 +7936,29 @@ "dev": true, "license": "MIT" }, + "node_modules/flow-estree": { + "version": "0.331.0", + "resolved": "https://registry.npmjs.org/flow-estree/-/flow-estree-0.331.0.tgz", + "integrity": "sha512-FVLYkSL/ITb/QXBEQvNWPjPooRGswuUtGOwrH+puSlMDneNkxy640+FZsk/TfKL+b7Wbw/Fg9FTUv2bMQDpj2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/flow-parser": { + "version": "0.331.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.331.0.tgz", + "integrity": "sha512-vEZcHIlnKeN8JSVtYFczj8sWJfHIJOkz+YG00O/oul/Bete+t1ZhTi+O6JRRLFYvGW+yJkZ0YgL16iStnbkUqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flow-estree": "0.331.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -7363,18 +8070,21 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -7538,17 +8248,45 @@ "node": ">= 6" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7571,41 +8309,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -7622,13 +8329,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -7711,9 +8411,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -7723,23 +8423,66 @@ "node": ">= 0.4" } }, + "node_modules/hermes-compiler": { + "version": "250829098.0.17", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.17.tgz", + "integrity": "sha512-qG1PXzTEtriF6oQLZF3vyHhSMxOdW5h2TqqLri0rdpstPustd2fSvRZQMVAPdlhgFwBfYnj3OUZtiO6LjYsEFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-eslint": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/hermes-eslint/-/hermes-eslint-0.37.0.tgz", + "integrity": "sha512-GOZ2xpOe28u7hN5e4NMBCEVZ9KwmigPtFZDXoSRgUWL6sFlppxB5T7WBiLRO00c+sW5R0YM4cbOmnWtTK+DQcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.37.0", + "hermes-parser": "0.37.0" + }, + "peerDependencies": { + "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/hermes-eslint/node_modules/hermes-parser": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.37.0.tgz", + "integrity": "sha512-8PuWcyaF6VHQHL2dbU+QEIkbtHYQrTX/6+ep4VcBLJ3f/ehevowr5Zj8LQfYXQQGFG2O+df7hIMtt4PkkhfK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.37.0" + } + }, "node_modules/hermes-estree": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.29.1.tgz", - "integrity": "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.37.0.tgz", + "integrity": "sha512-Frp4+A518C4zZgB2+Qo1srlM3y6nbrFbNNzBF3g7tEuQXHXTb2h39V25JQLNHotxclkD+AZTN/JCCpiTFlhIGg==", "dev": true, "license": "MIT" }, "node_modules/hermes-parser": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.29.1.tgz", - "integrity": "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.1.tgz", + "integrity": "sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==", "dev": true, "license": "MIT", "dependencies": { - "hermes-estree": "0.29.1" + "hermes-estree": "0.36.1" } }, + "node_modules/hermes-parser/node_modules/hermes-estree": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.1.tgz", + "integrity": "sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==", + "dev": true, + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -7803,9 +8546,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7841,31 +8584,15 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/image-size": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", - "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", - "dev": true, - "license": "MIT", - "dependencies": { - "queue": "6.0.2" - }, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -8083,13 +8810,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.4" }, "engines": { "node": ">= 0.4" @@ -8149,6 +8876,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8291,16 +9034,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8427,9 +9160,9 @@ } }, "node_modules/is-unsafe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz", - "integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", "dev": true, "funding": [ { @@ -8537,9 +9270,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -8657,16 +9390,16 @@ } }, "node_modules/jest": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", - "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz", + "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/types": "30.4.1", + "@jest/core": "30.5.2", + "@jest/types": "30.5.1", "import-local": "^3.2.0", - "jest-cli": "30.4.2" + "jest-cli": "30.5.2" }, "bin": { "jest": "bin/jest.js" @@ -8684,14 +9417,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", - "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz", + "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "p-limit": "^3.1.0" }, "engines": { @@ -8699,29 +9432,29 @@ } }, "node_modules/jest-circus": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", - "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz", + "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", + "jest-each": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "p-limit": "^3.1.0", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -8731,9 +9464,9 @@ } }, "node_modules/jest-circus/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -8743,6 +9476,32 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-circus/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -8757,16 +9516,16 @@ } }, "node_modules/jest-circus/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8783,21 +9542,21 @@ } }, "node_modules/jest-cli": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", - "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz", + "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/core": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-config": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "yargs": "^17.7.2" }, "bin": { @@ -8815,34 +9574,73 @@ } } }, + "node_modules/jest-cli/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-config": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", - "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz", + "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.4.0", - "@jest/test-sequencer": "30.4.1", - "@jest/types": "30.4.1", - "babel-jest": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/pattern": "30.5.0", + "@jest/test-sequencer": "30.5.2", + "@jest/types": "30.5.1", + "babel-jest": "30.5.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.5.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-circus": "30.4.2", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-runner": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-circus": "30.5.2", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-runner": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "parse-json": "^5.2.0", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -8867,9 +9665,9 @@ } }, "node_modules/jest-config/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -8879,6 +9677,32 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-config/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -8892,65 +9716,54 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/jest-config/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-config/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/jest-config/node_modules/jest-environment-node": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.2" + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-config/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8967,25 +9780,25 @@ } }, "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz", + "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", + "@jest/diff-sequences": "30.5.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "pretty-format": "30.4.1" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-diff/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -8995,6 +9808,13 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-diff/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-diff/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9009,25 +9829,25 @@ } }, "node_modules/jest-diff/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", - "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz", + "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", "dev": true, "license": "MIT", "dependencies": { @@ -9038,26 +9858,27 @@ } }, "node_modules/jest-each": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", - "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz", + "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", "chalk": "^4.1.2", - "jest-util": "30.4.1", - "pretty-format": "30.4.1" + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -9067,6 +9888,32 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-each/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-each/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9081,106 +9928,284 @@ } }, "node_modules/jest-each/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", - "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "node_modules/jest-environment-node/node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-haste-map": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", - "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", + "node_modules/jest-environment-node/node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "picomatch": "^4.0.3", - "walker": "^1.0.8" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-haste-map/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/jest-environment-node/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/jest-leak-detector": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", - "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", - "dev": true, + "node_modules/jest-environment-node/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-node/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz", + "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.5.1", + "@parcel/watcher": "^2.6.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "fdir": "^6.5.0", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-haste-map/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz", + "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", + "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.4.1" + "@jest/get-type": "30.5.0", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-leak-detector/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -9190,6 +10215,13 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-leak-detector/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-leak-detector/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9204,41 +10236,41 @@ } }, "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", - "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz", + "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "jest-diff": "30.4.1", - "pretty-format": "30.4.1" + "jest-diff": "30.5.2", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -9248,6 +10280,13 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-matcher-utils/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9262,36 +10301,36 @@ } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "picomatch": "^4.0.3", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -9300,9 +10339,9 @@ } }, "node_modules/jest-message-util/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -9312,6 +10351,32 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9326,9 +10391,9 @@ } }, "node_modules/jest-message-util/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -9339,16 +10404,16 @@ } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9365,42 +10430,64 @@ } }, "node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz", + "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/expect-utils": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-util": "30.4.1" + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/jest-mock/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "@sinclair/typebox": "^0.34.0" }, - "peerDependencies": { - "jest-resolve": "*" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-mock/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-regex-util": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", - "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", "dev": true, "license": "MIT", "engines": { @@ -9408,34 +10495,33 @@ } }, "node_modules/jest-resolve": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", - "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz", + "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "unrs-resolver": "^1.12.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", - "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz", + "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.4.0", - "jest-snapshot": "30.4.1" + "jest-regex-util": "30.5.0", + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9452,66 +10538,125 @@ } }, "node_modules/jest-runner": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", - "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz", + "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/environment": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.2", + "@jest/environment": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-haste-map": "30.4.1", - "jest-leak-detector": "30.4.1", - "jest-message-util": "30.4.1", - "jest-resolve": "30.4.1", - "jest-runtime": "30.4.2", - "jest-util": "30.4.1", - "jest-watcher": "30.4.1", - "jest-worker": "30.4.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-leak-detector": "30.5.1", + "jest-message-util": "30.5.1", + "jest-resolve": "30.5.1", + "jest-runtime": "30.5.2", + "jest-util": "30.5.1", + "jest-watcher": "30.5.2", + "jest-worker": "30.5.1", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runner/node_modules/jest-environment-node": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runtime": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", - "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz", + "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/globals": "30.4.1", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/globals": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", + "cjs-module-lexer": "^2.2.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", + "es-module-lexer": "^2.1.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -9519,49 +10664,58 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "node_modules/jest-runtime/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/jest-runtime/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9578,9 +10732,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", - "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz", + "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", "dev": true, "license": "MIT", "dependencies": { @@ -9589,20 +10743,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "@jest/snapshot-utils": "30.5.1", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.4.1", + "expect": "30.5.2", "graceful-fs": "^4.2.11", - "jest-diff": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "pretty-format": "30.4.1", + "jest-diff": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "pretty-format": "30.5.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -9611,9 +10765,9 @@ } }, "node_modules/jest-snapshot/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -9623,6 +10777,32 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-snapshot/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9637,16 +10817,16 @@ } }, "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9666,13 +10846,13 @@ } }, "node_modules/jest-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", - "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -9683,10 +10863,49 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-util/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -9697,27 +10916,27 @@ } }, "node_modules/jest-validate": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", - "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz", + "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.4.1" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -9727,6 +10946,32 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-validate/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9754,51 +10999,90 @@ } }, "node_modules/jest-validate/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-watcher": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", - "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz", + "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "string-length": "^4.0.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-watcher/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-worker": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", - "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -9822,6 +11106,45 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jest/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, "node_modules/joi": { "version": "17.13.8", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.8.tgz", @@ -9844,14 +11167,23 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", - "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -10178,6 +11510,19 @@ "node": ">=8" } }, + "node_modules/logkitty/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/logkitty/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -10312,13 +11657,17 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/memoize-one": { @@ -10346,9 +11695,9 @@ } }, "node_modules/metro": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.5.tgz", - "integrity": "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.87.1.tgz", + "integrity": "sha512-1oyLU9elM7hPAsKIqKooEekwxiuWr27+MZBhUROPhzcbXhMDxK64GPF/hCXwBZVp1F89ODavCrBmJtLx9WtBoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10360,31 +11709,28 @@ "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "accepts": "^2.0.0", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", + "flow-parser": "0.331.0", "graceful-fs": "^4.2.4", - "hermes-parser": "0.33.3", - "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.83.5", - "metro-cache": "0.83.5", - "metro-cache-key": "0.83.5", - "metro-config": "0.83.5", - "metro-core": "0.83.5", - "metro-file-map": "0.83.5", - "metro-resolver": "0.83.5", - "metro-runtime": "0.83.5", - "metro-source-map": "0.83.5", - "metro-symbolicate": "0.83.5", - "metro-transform-plugins": "0.83.5", - "metro-transform-worker": "0.83.5", + "metro-babel-transformer": "0.87.1", + "metro-cache": "0.87.1", + "metro-cache-key": "0.87.1", + "metro-config": "0.87.1", + "metro-core": "0.87.1", + "metro-file-map": "0.87.1", + "metro-resolver": "0.87.1", + "metro-runtime": "0.87.1", + "metro-source-map": "0.87.1", + "metro-symbolicate": "0.87.1", + "metro-transform-plugins": "0.87.1", + "metro-transform-worker": "0.87.1", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", @@ -10397,129 +11743,74 @@ "metro": "src/cli.js" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-babel-transformer": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.5.tgz", - "integrity": "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.87.1.tgz", + "integrity": "sha512-orvRIGpb0yK1yMbk5YP3dhVKjmoxUkRB9fdltGJxNppEqus/1tFpNuf9KnGFR3Qjg0izoUrJ4z6WgoBvdkXPrA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.33.3", + "flow-parser": "0.331.0", + "metro-cache-key": "0.87.1", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.33.3" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-cache": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.5.tgz", - "integrity": "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.87.1.tgz", + "integrity": "sha512-juNPaj0Xi5tkLp8imXqvbSnEIijwKG36m7OiFFVJMqEkiURwAjkMFVek3/xpix1+LVQqO7kjVp3PvuvIM61mKQ==", "dev": true, "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", - "metro-core": "0.83.5" + "metro-core": "0.87.1" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-cache-key": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.5.tgz", - "integrity": "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.87.1.tgz", + "integrity": "sha512-scqVVPMA2c+RVO12I3wyMDp5xCkRPLsyrcG4Qc3xfU5JR0EW/0sz2tjty+5UjMUtG09uiM1KYQ8HjWg7OQXHtw==", "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-config": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.5.tgz", - "integrity": "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.87.1.tgz", + "integrity": "sha512-NmkDlc/qZAdo5gTkVTuO2AKE/khemRKRH5IA+SKfqUBJigI5GqZ5TOXhmEokhlkAqzjt8dyzKhzI6FHvXoXaTQ==", "dev": true, "license": "MIT", "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", - "metro": "0.83.5", - "metro-cache": "0.83.5", - "metro-core": "0.83.5", - "metro-runtime": "0.83.5", - "yaml": "^2.6.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-config/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/metro-config/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "metro": "0.87.1", + "metro-cache": "0.87.1", + "metro-core": "0.87.1", + "metro-runtime": "0.87.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/metro-config/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/metro-config/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -10552,24 +11843,24 @@ } }, "node_modules/metro-core": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.5.tgz", - "integrity": "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.87.1.tgz", + "integrity": "sha512-xizzky/4+c/Mkznege8OW05j7bSdM39VPoEps61Se4+hf32UQujmbvLvIE9+UnP6kNEPVomk0Fw8NsBK9iRQmw==", "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.83.5" + "metro-resolver": "0.87.1" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-file-map": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.5.tgz", - "integrity": "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.87.1.tgz", + "integrity": "sha512-9sPuNCojC3pS4vj+ZPY7FXmHGpdJeckXuKXQrDAfJvZc60AF8owRFTkI8XdwlwrmPIB2w++KraK9FMEXvC4wJw==", "dev": true, "license": "MIT", "dependencies": { @@ -10580,51 +11871,12 @@ "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-file-map/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/metro-file-map/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "nullthrows": "^1.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/metro-file-map/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/metro-file-map/node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -10692,9 +11944,9 @@ } }, "node_modules/metro-minify-terser": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.5.tgz", - "integrity": "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.87.1.tgz", + "integrity": "sha512-YK4k1oO1wV48hfE+Nbw2rJCnAgeabzrgT/xH5ha5n9gbK4p3HYFC898cERUiyTl9vRh58CNTiXRc88f7y6PfKg==", "dev": true, "license": "MIT", "dependencies": { @@ -10702,26 +11954,26 @@ "terser": "^5.15.0" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-resolver": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.5.tgz", - "integrity": "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.87.1.tgz", + "integrity": "sha512-FXH/brY69wT6sPxaSW8BhMmDGD/6irmBr/cL4rUi+VWwqR3pbvdcfUOfyjcHucgQ7pe5IW3xJY21eHKLVKNumQ==", "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-runtime": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", - "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.87.1.tgz", + "integrity": "sha512-kjeuSvInsM6OzBjkCQsWxnkrKZFMKtnkCl38oXWRI9lDGs2L9hGs9Tak9sQYJ9h3gtDujneB7wZio+irgqJKDw==", "dev": true, "license": "MIT", "dependencies": { @@ -10729,13 +11981,13 @@ "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-source-map": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", - "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.87.1.tgz", + "integrity": "sha512-Bqpm0PBGdy53pigIJb4HRF7QxD2pUOdgSmVh/4AWbP635kSCv+Y6ltVxUZgSnzS/QbSmzyCGijmRPlXMj2/qHw==", "dev": true, "license": "MIT", "dependencies": { @@ -10743,26 +11995,26 @@ "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.83.5", + "metro-symbolicate": "0.87.1", "nullthrows": "^1.1.1", - "ob1": "0.83.5", + "ob1": "0.87.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-symbolicate": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", - "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.87.1.tgz", + "integrity": "sha512-rcGvwabrg0lbXGRGRKl14PY3My3+dkno2jZYhdJEuH2DmjTtTkV5bYwgto4JtV/Lbe+sXE6GO1UGg654430uow==", "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.83.5", + "metro-source-map": "0.87.1", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" @@ -10771,13 +12023,13 @@ "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-transform-plugins": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.5.tgz", - "integrity": "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.87.1.tgz", + "integrity": "sha512-qMiJ/x+VfqumFJJSefKS6nj8uojzC29b2/aK8vlMuFABIqGjDucICEOXd2Yt6k/XMFAgAcaJvShUOT0HLyYaPw==", "dev": true, "license": "MIT", "dependencies": { @@ -10789,13 +12041,13 @@ "nullthrows": "^1.1.1" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/metro-transform-worker": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.5.tgz", - "integrity": "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.87.1.tgz", + "integrity": "sha512-VOzs3OV405FuOLYdAhqA2wsn1F9lvueDVxMDLvCKaQ44U+yknIqphJ9eMjjsGwLDZiJq9sW3DMLhjD27JxQgqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10804,57 +12056,19 @@ "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", - "metro": "0.83.5", - "metro-babel-transformer": "0.83.5", - "metro-cache": "0.83.5", - "metro-cache-key": "0.83.5", - "metro-minify-terser": "0.83.5", - "metro-source-map": "0.83.5", - "metro-transform-plugins": "0.83.5", + "metro": "0.87.1", + "metro-babel-transformer": "0.87.1", + "metro-cache": "0.87.1", + "metro-cache-key": "0.87.1", + "metro-minify-terser": "0.87.1", + "metro-source-map": "0.87.1", + "metro-transform-plugins": "0.87.1", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/metro/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, - "node_modules/metro/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/metro/node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -10870,27 +12084,19 @@ } }, "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/metro/node_modules/hermes-estree": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/metro/node_modules/hermes-parser": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "dependencies": { - "hermes-estree": "0.33.3" + "engines": { + "node": ">=8" } }, "node_modules/metro/node_modules/jest-util": { @@ -10911,22 +12117,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/metro/node_modules/jest-util/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/metro/node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -10961,13 +12151,20 @@ } }, "node_modules/metro/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "dev": true, "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/metro/node_modules/supports-color": { @@ -10986,28 +12183,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/metro/node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -11089,16 +12264,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.8" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { @@ -11154,13 +12332,6 @@ "dev": true, "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true, - "license": "MIT" - }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -11181,10 +12352,17 @@ "node": ">=12.0.0" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-exports-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", - "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", "dev": true, "license": "MIT", "dependencies": { @@ -11208,16 +12386,19 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.16.0.tgz", + "integrity": "sha512-ObaRrRoR8T68wF6suxHd7R4XQNamij6ZQHrwG7Dx1D2zeHcDNLsIOBcWrIwtDm7AsCXBguaPHgXhcjxDa2szrg==", "dev": true, "license": "MIT", "engines": { @@ -11270,16 +12451,16 @@ "license": "MIT" }, "node_modules/ob1": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", - "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.87.1.tgz", + "integrity": "sha512-i8iA8uij0g1YQzS8uOJPSRCgwDjO9warIHUAu1Fqj877Wc3wlfxDYBioYWgKTBF2+URVJttyDWSEpmd99nlvtQ==", "dev": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=20.19.4" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" } }, "node_modules/object-assign": { @@ -11494,14 +12675,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.6", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" }, @@ -11657,37 +12852,30 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": "20 || >=22" } }, "node_modules/picocolors": { @@ -11899,16 +13087,16 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "dev": true, "license": "MIT", "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -11942,26 +13130,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -12053,9 +13221,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -12069,16 +13237,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "~2.0.3" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12093,572 +13251,158 @@ "type": "patreon", "url": "https://www.patreon.com/feross" }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-devtools-core": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", - "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-is-18": { - "name": "react-is", - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-is-19": { - "name": "react-is", - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-native": { - "version": "0.81.6", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.6.tgz", - "integrity": "sha512-X/tI8GqfzVaa+zfbE4+lySNN5UzwBIAVRHVZPKymOny9Acc5GYYcjAcEVfG3AM4h920YALWoSl8favnDmQEWIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.81.6", - "@react-native/codegen": "0.81.6", - "@react-native/community-cli-plugin": "0.81.6", - "@react-native/gradle-plugin": "0.81.6", - "@react-native/js-polyfills": "0.81.6", - "@react-native/normalize-colors": "0.81.6", - "@react-native/virtualized-lists": "0.81.6", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "ansi-regex": "^5.0.0", - "babel-jest": "^29.7.0", - "babel-plugin-syntax-hermes-parser": "0.29.1", - "base64-js": "^1.5.1", - "commander": "^12.0.0", - "flow-enums-runtime": "^0.0.6", - "glob": "^7.1.1", - "invariant": "^2.2.4", - "jest-environment-node": "^29.7.0", - "memoize-one": "^5.0.0", - "metro-runtime": "^0.83.1", - "metro-source-map": "^0.83.1", - "nullthrows": "^1.1.1", - "pretty-format": "^29.7.0", - "promise": "^8.3.0", - "react-devtools-core": "^6.1.5", - "react-refresh": "^0.14.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "0.26.0", - "semver": "^7.1.3", - "stacktrace-parser": "^0.1.10", - "whatwg-fetch": "^3.0.0", - "ws": "^6.2.3", - "yargs": "^17.6.2" - }, - "bin": { - "react-native": "cli.js" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@types/react": "^19.1.4", - "react": "^19.1.4" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-native/node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-native/node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/react-native/node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/react-native/node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/react-native/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/react-native/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/react-native/node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "node_modules/react-native/node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/react-native/node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.10" } }, - "node_modules/react-native/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/react-native/node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "node_modules/react-devtools-core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "shell-quote": "^1.6.1", + "ws": "^7" } }, - "node_modules/react-native/node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-native": { + "version": "0.87.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.87.1.tgz", + "integrity": "sha512-DJKG6ANoD7BtrE4z9DewiSD7/RxCX73lK5Pu49aUr85P3333Dm2roTiP0rRjQNDZdVizHSOmstWfwF/o9EjCRA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "@react-native/asset-utils": "0.87.1", + "@react-native/codegen": "0.87.1", + "@react-native/community-cli-plugin": "0.87.1", + "@react-native/gradle-plugin": "0.87.1", + "@react-native/normalize-colors": "0.87.1", + "@react-native/virtualized-lists": "0.87.1", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-plugin-syntax-hermes-parser": "0.36.1", + "base64-js": "^1.5.1", + "commander": "^12.0.0", + "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.17", + "invariant": "^2.2.4", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.87.0", + "metro-source-map": "^0.87.0", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.1.5", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.27.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", + "whatwg-fetch": "^3.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-native/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", "bin": { - "semver": "bin/semver.js" + "react-native": "cli.js" }, "engines": { - "node": ">=10" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "peerDependencies": { + "@types/react": "^19.1.1", + "react": "^19.2.3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/react-native/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/react-native/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/react-native/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/react-native/node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "scheduler": "^0.27.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" } }, "node_modules/react-refresh": { @@ -12671,34 +13415,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-test-renderer": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.4.tgz", - "integrity": "sha512-Ttl5D7Rnmi6JGMUpri4UjB4BAN0FPs4yRDnu2XSsigCWOLm11o8GwRlVsh27ER+4WFqsGtrBuuv5zumUaRCmKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "react-is": "^19.2.4", - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, - "node_modules/react-test-renderer/node_modules/react-is": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", - "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-test-renderer/node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", @@ -12764,16 +13480,16 @@ } }, "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.5.0.tgz", + "integrity": "sha512-1s+CxXjJ71i7LNVy8nYwg47XV4y6lAnOEhsMtSKq3WpmPfhl9LuKlTqKIn/3yUpE8lBz+W+TT88Bxyhnr0U4yw==", "dev": true, "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.3.0.tgz", + "integrity": "sha512-9ns8odR8e9q3otKeHj3DE80jZYKg6zVeZKE+zQmRKXFoVUcuiTGeO6iusDhPRoIQQPVPRvH+SfBhrTud4eczuw==", "dev": true, "license": "MIT", "dependencies": { @@ -12812,14 +13528,14 @@ } }, "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.5.3.tgz", + "integrity": "sha512-E8VgUoSK+LMQrLboDxavm76MTBGrQhx9RiNuomRmYnZ10Uw12HrI8fkxOOncktb2hBpzIZ9SV/347euQHG5z+g==", "dev": true, "license": "MIT", "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", + "regenerate": "^1.5.0", + "regenerate-unicode-properties": "^10.3.0", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", @@ -12837,9 +13553,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.4.tgz", + "integrity": "sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -12874,13 +13590,14 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.13", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.13.tgz", + "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.1", + "es-errors": "^1.3.0", + "is-core-module": "^2.17.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -12942,23 +13659,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12984,15 +13684,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -13081,9 +13781,9 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "dev": true, "license": "MIT" }, @@ -13463,9 +14163,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -13567,13 +14267,6 @@ "node": ">= 0.4" } }, - "node_modules/strict-url-sanitise": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/strict-url-sanitise/-/strict-url-sanitise-0.0.1.tgz", - "integrity": "sha512-nuFtF539K8jZg3FjaWH/L8eocCR6gegz5RDOsaWxfdbF5Jqr2VXWxZayjTwUzsWJDC91k2EbnJXp6FuWW+Z4hg==", - "dev": true, - "license": "MIT" - }, "node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -13595,6 +14288,19 @@ "node": ">=10" } }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-natural-compare": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", @@ -13643,6 +14349,19 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -13653,26 +14372,39 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" + "side-channel": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -13693,19 +14425,20 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -13715,16 +14448,16 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -13752,16 +14485,16 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/strip-ansi-cjs": { @@ -13778,6 +14511,16 @@ "node": ">=8" } }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -13825,9 +14568,9 @@ } }, "node_modules/strnum": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", - "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", "dev": true, "funding": [ { @@ -13883,9 +14626,9 @@ } }, "node_modules/terser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", - "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -13908,48 +14651,113 @@ "dev": true, "license": "MIT" }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "balanced-match": "^1.0.0" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "MIT" + "license": "ISC" + }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-renderer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/test-renderer/-/test-renderer-1.2.0.tgz", + "integrity": "sha512-JYiEGbgBGtmHAWX8Kf99gGRL1HtjcRVHLrmJNTZL4vFs9XrnWcuL45Iszw/pO4094+JR7havSrN+ds6YTOpSQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "~0.33.0", + "react-reconciler": "~0.33.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } }, "node_modules/throat": { "version": "5.0.0", @@ -14009,6 +14817,54 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -14040,40 +14896,25 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } + "license": "0BSD", + "optional": true }, "node_modules/type-check": { "version": "0.4.0", @@ -14099,9 +14940,9 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -14112,18 +14953,22 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/type-is/node_modules/mime-types": { @@ -14179,19 +15024,18 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", "gopd": "^1.2.0", - "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -14201,18 +15045,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -14222,9 +15066,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -14255,9 +15099,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -14374,9 +15218,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, "funding": [ { @@ -14588,14 +15432,14 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -14656,6 +15500,32 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -14691,19 +15561,31 @@ } }, "node_modules/ws": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", - "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "dev": true, "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", "dev": true, "funding": [ { @@ -14744,9 +15626,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "dev": true, "license": "ISC", "bin": { @@ -14760,9 +15642,9 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -14800,6 +15682,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/package/package.json b/package/package.json index df8933e3..44d2faeb 100644 --- a/package/package.json +++ b/package/package.json @@ -1,10 +1,10 @@ { - "name": "@react-native-community/slider", - "version": "5.2.2", + "name": "@callstack/slider", + "version": "6.0.0", "license": "MIT", - "author": "react-native-community", + "author": "BartoszKlonowski ", "homepage": "https://github.com/callstack/react-native-slider#readme", - "description": "React Native component used to select a single value from a range of values.", + "description": "React Native component allowing to use native Slider in your React Native app.", "publishConfig": { "access": "public" }, @@ -22,71 +22,66 @@ "prepack": "npx copyfiles \"./../README.md\" ./README.md" }, "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.25.2", - "@babel/preset-flow": "^7.9.0", - "@babel/preset-typescript": "^7.28.5", - "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "^20.0.2", - "@react-native-community/cli-platform-android": "^20.0.2", - "@react-native-community/cli-platform-ios": "^20.0.2", - "@react-native-community/eslint-config": "^3.2.0", - "@react-native/babel-preset": "^0.81.1", - "@react-native/eslint-config": "^0.81.1", - "@react-native/metro-config": "^0.81.1", - "@react-native/typescript-config": "^0.81.1", - "@testing-library/react-native": "^13.3.3", + "@babel/cli": "^7.29.7", + "@babel/core": "^7.29.7", + "@babel/preset-flow": "^7.29.7", + "@babel/preset-typescript": "^7.29.7", + "@babel/runtime": "^7.29.10", + "@react-native-community/cli": "^20.2.0", + "@react-native-community/cli-platform-android": "^20.2.0", + "@react-native-community/cli-platform-ios": "^20.2.0", + "@react-native/babel-preset": "^0.87.1", + "@react-native/eslint-config": "^0.87.1", + "@react-native/jest-preset": "^0.87.1", + "@react-native/metro-config": "^0.87.1", + "@react-native/typescript-config": "^0.87.1", + "@testing-library/react-native": "^14.1.0", "@types/jest": "^30.0.0", - "@types/react": "^19.2.0", - "babel-jest": "^30.2.0", - "babel-plugin-module-resolver": "5.0.0", + "@types/react": "^19.2.18", + "babel-jest": "^30.5.2", + "babel-plugin-module-resolver": "^5.0.3", "copyfiles": "^2.4.1", - "eslint": "^8.19.0", - "eslint-plugin-prettier": "4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-native": "^4.0.0", - "flow-bin": "^0.163.0", - "jest": "^30.2.0", - "prettier": "2.8.8", - "react": "^19.2.0", - "react-native": "^0.81.4", - "react-test-renderer": "^19.2.0", - "typescript": "^5.0.4" + "eslint": "^9.39.5", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.6", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-native": "^5.0.0", + "hermes-eslint": "^0.37.0", + "jest": "^30.5.2", + "prettier": "^3.9.9", + "react": "19.2.3", + "react-native": "^0.87.1", + "test-renderer": "~1.2.0", + "typescript": "^6.0.3" }, "repository": { "type": "git", "url": "https://github.com/callstack/react-native-slider.git" }, "jest": { - "preset": "react-native", - "testEnvironment": "node", - "testEnvironmentOptions": { - "customExportConditions": [ - "require", - "react-native" - ] - }, + "preset": "@react-native/jest-preset", "verbose": true, + "watchman": false, "modulePathIgnorePatterns": [ "/e2e/" ] }, "prettier": { - "singleQuote": true, + "singleQuote": false, "trailingComma": "all", "bracketSpacing": false, - "jsxBracketSameLine": true + "bracketSameLine": true }, "codegenConfig": { - "name": "RNCSlider", + "name": "CallstackSlider", "type": "components", "jsSrcsDir": "src", "android": { - "javaPackageName": "com.reactnativecommunity.slider" + "javaPackageName": "callstack.slider" }, "ios": { "componentProvider": { - "RNCSlider": "RNCSliderComponentView" + "CallstackSlider": "SliderComponentView" } } } diff --git a/package/react-native-slider.podspec b/package/react-native-slider.podspec index b795d531..951206a5 100644 --- a/package/react-native-slider.podspec +++ b/package/react-native-slider.podspec @@ -10,13 +10,21 @@ Pod::Spec.new do |s| s.authors = package['author'] s.homepage = package['homepage'] - s.platforms = { :ios => "9.0", :visionos => "1.0" } + # 15.1 is the floor React Native itself supports. + s.platforms = { :ios => "15.1", :visionos => "1.0" } + s.swift_version = "5.0" s.source = { :git => "https://github.com/callstack/react-native-slider.git", :tag => "v#{s.version}" } - s.source_files = "ios/**/*.{h,m,mm}" + s.source_files = "ios/**/*.{h,m,mm,swift}" + # The Swift sources make this a mixed-language target, so CocoaPods builds a + # Clang module out of the public headers. Nothing here is meant to be imported + # from outside the pod, and the Fabric headers drag in C++ that cannot live in + # an Objective-C module - so keep every header out of the umbrella. + s.private_header_files = "ios/**/*.h" s.subspec "common" do |ss| ss.source_files = "common/cpp/**/*.{cpp,h}" + ss.private_header_files = "common/cpp/**/*.h" ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" } end diff --git a/package/react-native.config.js b/package/react-native.config.js index ffcb1e61..43fc71d1 100644 --- a/package/react-native.config.js +++ b/package/react-native.config.js @@ -2,9 +2,9 @@ module.exports = { dependency: { platforms: { android: { - libraryName: 'RNCSlider', - componentDescriptors: ['RNCSliderComponentDescriptor'], - cmakeListsPath: 'src/main/jni/CMakeLists.txt', + libraryName: "CallstackSlider", + componentDescriptors: ["CallstackSliderComponentDescriptor"], + cmakeListsPath: "src/main/jni/CMakeLists.txt", }, }, }, diff --git a/package/src/RNCSliderNativeComponent.ts b/package/src/RNCSliderNativeComponent.ts deleted file mode 100644 index 7ae7fa91..00000000 --- a/package/src/RNCSliderNativeComponent.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type {ColorValue, HostComponent, ViewProps} from 'react-native'; -import {ImageSource, codegenNativeComponent} from 'react-native'; -import type { - Float, - WithDefault, - DirectEventHandler, - BubblingEventHandler, - Double, -} from 'react-native/Libraries/Types/CodegenTypes'; - -type Event = Readonly<{ - value: Float; - fromUser?: boolean; -}>; - -export interface NativeProps extends ViewProps { - accessibilityUnits?: string; - accessibilityIncrements?: ReadonlyArray; - disabled?: WithDefault; - inverted?: WithDefault; - vertical?: WithDefault; - tapToSeek?: WithDefault; - maximumTrackImage?: ImageSource; - maximumTrackTintColor?: ColorValue; - maximumValue?: WithDefault; - minimumTrackImage?: ImageSource; - minimumTrackTintColor?: ColorValue; - minimumValue?: Double; - onChange?: BubblingEventHandler; - onRNCSliderSlidingStart?: DirectEventHandler; - onRNCSliderSlidingComplete?: DirectEventHandler; - onRNCSliderValueChange?: BubblingEventHandler; - step?: Double; - testID?: string; - thumbImage?: ImageSource; - thumbTintColor?: ColorValue; - thumbSize?: Float; - trackImage?: ImageSource; - value?: Float; - lowerLimit?: Float; - upperLimit?: Float; -} - -export default codegenNativeComponent('RNCSlider', { - interfaceOnly: true, -}) as HostComponent; diff --git a/package/src/Slider.tsx b/package/src/Slider.tsx index c452ef9c..17b783b1 100644 --- a/package/src/Slider.tsx +++ b/package/src/Slider.tsx @@ -1,356 +1,212 @@ -import React, {useEffect, useState} from 'react'; -import { - Image, - Platform, - AccessibilityActionEvent, - ViewProps, - ViewStyle, - ColorValue, - NativeSyntheticEvent, - StyleProp, - View, - ImageSource, - ImageSourcePropType, -} from 'react-native'; -import RCTSliderNativeComponent from './index'; +import React, {useState} from "react"; +import {View} from "react-native"; +import type {NativeSyntheticEvent} from "react-native"; +import SliderNativeComponent from "./index"; +import {type SliderProps} from "../typings"; -import type {FC, Ref} from 'react'; -import {MarkerProps} from './components/TrackMark'; -import {StepsIndicator} from './components/StepsIndicator'; -import {styles} from './utils/styles'; -import {constants} from './utils/constants'; - -type Event = NativeSyntheticEvent< +type SliderValueChangeEvent = NativeSyntheticEvent< Readonly<{ value: number; - /** - * Android Only. - */ - fromUser?: boolean; }> >; -type WindowsProps = Readonly<{ - /** - * If true the slider will be inverted. - * Default value is false. - */ - vertical?: boolean; -}>; - -type IOSProps = Readonly<{ - /** - * Assigns a single image for the track. Only static images are supported. - * The center pixel of the image will be stretched to fill the track. - */ - trackImage?: ImageSource; - - /** - * Assigns a minimum track image. Only static images are supported. The - * rightmost pixel of the image will be stretched to fill the track. - */ - minimumTrackImage?: ImageSource; - - /** - * Assigns a maximum track image. Only static images are supported. The - * leftmost pixel of the image will be stretched to fill the track. - */ - maximumTrackImage?: ImageSource; - - /** - * Permits tapping on the slider track to set the thumb position. - * Defaults to false on iOS. No effect on Android or Windows. - */ - tapToSeek?: boolean; -}>; - -type Props = ViewProps & - IOSProps & - WindowsProps & - Readonly<{ - /** - * Used to style and layout the `Slider`. See `StyleSheet.js` and - * `DeprecatedViewStylePropTypes.js` for more info. - */ - style?: StyleProp; - - /** - * Write-only property representing the value of the slider. - * Can be used to programmatically control the position of the thumb. - * Entered once at the beginning still acts as an initial value. - * The value should be between minimumValue and maximumValue, - * which default to 0 and 1 respectively. - * Default value is 0. - * - * This is not a controlled component, you don't need to update the - * value during dragging. - */ - value?: number; - - /** - * Step value of the slider. The value should be - * between 0 and (maximumValue - minimumValue). - * Default value is 0. - */ - step?: number; - - /** - * Initial minimum value of the slider. Default value is 0. - */ - minimumValue?: number; - - /** - * Initial maximum value of the slider. Default value is 1. - */ - maximumValue?: number; - - /** - * The lower limit value of the slider. The user won't be able to slide below this limit. - */ - lowerLimit?: number; - - /** - * The upper limit value of the slider. The user won't be able to slide above this limit. - */ - upperLimit?: number; - - /** - * The color used for the track to the left of the button. - * Overrides the default blue gradient image on iOS. - */ - minimumTrackTintColor?: ColorValue; - - /** - * The color used for the track to the right of the button. - * Overrides the default blue gradient image on iOS. - */ - maximumTrackTintColor?: ColorValue; - /** - * The color used to tint the default thumb images on iOS, or the - * color of the foreground switch grip on Android. - */ - thumbTintColor?: ColorValue; - - /** - * If true the user won't be able to move the slider. - * Default value is false. - */ - disabled?: boolean; - - /** - * Callback continuously called while the user is dragging the slider. - */ - onValueChange?: (_value: number) => void; - - /** - * Callback that is called when the user touches the slider, - * regardless if the value has changed. The current value is passed - * as an argument to the callback handler. - */ - onSlidingStart?: (_value: number) => void; - - /** - * Callback that is called when the user releases the slider, - * regardless if the value has changed. The current value is passed - * as an argument to the callback handler. - */ - onSlidingComplete?: (_value: number) => void; - - /** - * Used to locate this view in UI automation tests. - */ - testID?: string; - - /** - * Sets an image for the thumb. Only static images are supported. - */ - thumbImage?: ImageSource; - - /** - * Sets the size (width and height) of the thumb. - * If `thumbImage` is provided, it will be scaled to this size. - */ - thumbSize?: number; +/** + * A slider with no handler for an event must not pass one down, so that the + * native side can leave the event unsent. + */ +const valueHandler = (onChange?: (value: number) => void) => + onChange + ? (event: SliderValueChangeEvent) => onChange(event.nativeEvent.value) + : undefined; + +const stepIndex = ( + value: number, + minimumValue: number, + maximumValue: number, + step: number, +) => { + if (step <= 0) { + return 0; + } - /** - * If true the slider will be inverted. - * Default value is false. - */ - inverted?: boolean; + const clamped = Math.min( + Math.max(value, minimumValue), + Math.max(maximumValue, minimumValue), + ); - /** - * Component to be rendered for each step indicator. - */ - StepMarker?: FC; + return Math.round((clamped - minimumValue) / step); +}; - /** - * - */ - renderStepNumber?: boolean; +/** + * How many decimals it takes to write the given value out, capped at the + * digits a double can be trusted with - see `snapped(to:)` on iOS. + */ +const decimals = (value: number) => { + let count = 0; + let scaled = Math.abs(value); - /** - * A string of one or more words to be announced by the screen reader. - * Otherwise, it will announce the value as a percentage. - * Requires passing a value to `accessibilityIncrements` to work correctly. - * Should be a plural word, as singular units will be handled. - */ - accessibilityUnits?: string; + while (count < 15 && scaled !== Math.floor(scaled)) { + scaled *= 10; + count += 1; + } - /** - * An array of values that represent the different increments displayed - * by the slider. All the values passed into this prop must be strings. - * Requires passing a value to `accessibilityUnits` to work correctly. - * The number of elements must be the same as `maximumValue`. - */ - accessibilityIncrements?: Array; - }>; + return count; +}; -const SliderComponent = ( - { - onValueChange, - onSlidingStart, - onSlidingComplete, - onAccessibilityAction, - value = constants.SLIDER_DEFAULT_INITIAL_VALUE, - minimumValue = 0, - maximumValue = 1, - step = 0, - inverted = false, - tapToSeek = false, - lowerLimit = Platform.select({ - web: minimumValue, - default: constants.LIMIT_MIN_VALUE, - }), - upperLimit = Platform.select({ - web: maximumValue, - default: constants.LIMIT_MAX_VALUE, - }), - ...props - }: Props, - forwardedRef?: Ref, +/** + * Leeway, as a fraction of a step, for a value to count as landing on one. + * Mirrored by the native step markers, which have to agree with JS on how + * many steps there are. + */ +const STEP_TOLERANCE = 1e-9; + +/** + * The values the steps of a slider stand at, the way the native side lays + * them out: every step from the minimum, and the maximum itself when the step + * does not divide the range. + */ +const stepValues = ( + minimumValue: number, + maximumValue: number, + step: number, ) => { - const [currentValue, setCurrentValue] = useState( - value ?? minimumValue ?? constants.SLIDER_DEFAULT_INITIAL_VALUE, + const span = maximumValue - minimumValue; + if (step <= 0 || span <= 0) { + return []; + } + + const DECIMAL_FACTOR = 10; + const scale = Math.pow( + DECIMAL_FACTOR, + Math.max(decimals(step), decimals(minimumValue)), ); - const [width, setWidth] = useState(0); - - const stepResolution = step ? step : constants.DEFAULT_STEP_RESOLUTION; - - const defaultStep = (maximumValue - minimumValue) / stepResolution; - const stepLength = step || defaultStep; - - const options = Array.from( - { - length: (step ? defaultStep : stepResolution) + 1, - }, - (_, index) => minimumValue + index * stepLength, + const wholeSteps = Math.floor(span / step + STEP_TOLERANCE); + const values = Array.from( + {length: wholeSteps + 1}, + (_, index) => Math.round((minimumValue + index * step) * scale) / scale, ); - const defaultStyle = - Platform.OS === 'ios' ? styles.defaultSlideriOS : styles.defaultSlider; - const sliderStyle = {zIndex: 1, width: width}; - const style = [defaultStyle, props.style]; - - const onValueChangeEvent = (event: Event) => { - onValueChange && onValueChange(event.nativeEvent.value); - setCurrentValue(event.nativeEvent.value); - }; + if (span - wholeSteps * step > step * STEP_TOLERANCE) { + values.push(maximumValue); + } - const _disabled = - typeof props.disabled === 'boolean' - ? props.disabled - : props.accessibilityState?.disabled === true; + return values; +}; - const _accessibilityState = - typeof props.disabled === 'boolean' - ? {...props.accessibilityState, disabled: props.disabled} - : props.accessibilityState; +const useThumbValue = (value: number) => { + const [dispatched, setDispatched] = useState({given: value, value}); + const current = dispatched.given === value ? dispatched.value : value; - const onSlidingStartEvent = onSlidingStart - ? (event: Event) => { - onSlidingStart(event.nativeEvent.value); - } - : null; - const onSlidingCompleteEvent = onSlidingComplete - ? (event: Event) => { - onSlidingComplete(event.nativeEvent.value); - } - : null; - const onAccessibilityActionEvent = onAccessibilityAction - ? (event: AccessibilityActionEvent) => { - onAccessibilityAction(event); - } - : null; + return [ + current, + (newValue: number) => setDispatched({given: value, value: newValue}), + ] as const; +}; - const passedValue = Number.isNaN(value) || !value ? undefined : value; +const thumbValueHandler = + (callback: (value: number) => void, onChange?: (value: number) => void) => + (value: number) => { + callback(value); + onChange?.(value); + }; - useEffect(() => { - if (lowerLimit >= upperLimit) { - console.warn( - 'Invalid configuration: lower limit is supposed to be smaller than upper limit', - ); - } - }, [lowerLimit, upperLimit]); +const Slider = ({ + minimumValue = 0, + maximumValue = 1, + value = 0, + ranged = false, + valueLeft = minimumValue, + valueRight = maximumValue, + step = 0, + lowerLimit = minimumValue, + upperLimit = maximumValue, + orientation = "horizontal", + disabled, + onValueChange, + onLeftValueChange, + onRightValueChange, + thumb: Thumb, + track: Track, + stepMarkers = false, + ...props +}: SliderProps) => { + const StepMarker = + typeof stepMarkers === "function" ? stepMarkers : undefined; + const followsThumbs = Thumb !== undefined || StepMarker !== undefined; + const isDisabled = disabled === true; + + const [currentValue, setCurrentValue] = useThumbValue(value); + const [currentValueLeft, setCurrentValueLeft] = useThumbValue(valueLeft); + const [currentValueRight, setCurrentValueRight] = useThumbValue(valueRight); + + const handleValueChange = followsThumbs + ? thumbValueHandler(setCurrentValue, onValueChange) + : onValueChange; + const handleLeftValueChange = followsThumbs + ? thumbValueHandler(setCurrentValueLeft, onLeftValueChange) + : onLeftValueChange; + const handleRightValueChange = followsThumbs + ? thumbValueHandler(setCurrentValueRight, onRightValueChange) + : onRightValueChange; + + const thumbValues = ranged + ? [currentValueLeft, currentValueRight] + : [currentValue]; return ( - { - setWidth(event.nativeEvent.layout.width); - }} - style={[style, {justifyContent: 'center'}]}> - {props.StepMarker || !!props.renderStepNumber ? ( - + + {Track ? ( + + + ) : null} - true} - onResponderTerminationRequest={() => false} - onRNCSliderAccessibilityAction={onAccessibilityActionEvent} - thumbTintColor={ - props.thumbImage && !!props.StepMarker - ? 'transparent' - : props.thumbTintColor - } - /> - + {Thumb + ? thumbValues.map((thumbValue, thumbIndex) => ( + + + + )) + : null} + {StepMarker + ? stepValues(minimumValue, maximumValue, step).map( + (stepValue, index) => ( + + + Math.abs(thumbValue - stepValue) <= step * STEP_TOLERANCE, + )} + disabled={isDisabled} + /> + + ), + ) + : null} + ); }; -const SliderWithRef = React.forwardRef(SliderComponent); - -export default SliderWithRef; +export default Slider; diff --git a/package/src/SliderNativeComponent.ts b/package/src/SliderNativeComponent.ts new file mode 100644 index 00000000..bc61dfea --- /dev/null +++ b/package/src/SliderNativeComponent.ts @@ -0,0 +1,160 @@ +import type { + CodegenTypes, + ColorValue, + HostComponent, + ViewProps, +} from "react-native"; +import {codegenNativeComponent} from "react-native"; + +type SliderValueChangeEvent = Readonly<{ + value: CodegenTypes.Double; +}>; + +export interface NativeProps extends ViewProps { + /** + * Lower bound of the range the slider can select from. + */ + minimumValue?: CodegenTypes.WithDefault; + + /** + * Upper bound of the range the slider can select from. + */ + maximumValue?: CodegenTypes.WithDefault; + + /** + * Whether the slider is switched off: drawn in the platform's own disabled + * look, taking no touches, taps or keyboard, and reporting no events of its + * own. The values the props give it still apply, so a disabled slider keeps + * showing wherever they put its thumbs. + * + * Declared ahead of those values on purpose: a native side applies props in + * the order they are declared here, and switching off is what ends the drag a + * slider believes it owns - a value it still believes it owns is one it keeps + * off the thumbs, so a move applied first would be swallowed by that rule. + */ + disabled?: CodegenTypes.WithDefault; + + /** + * Position of the thumb, expressed in the slider's own range. + */ + value?: CodegenTypes.WithDefault; + + /** + * Whether the slider selects a span of its range with two thumbs, rather + * than a single value with one. + */ + ranged?: CodegenTypes.WithDefault; + + /** + * Position of the thumb bounding the selected span from below, expressed in + * the slider's own range. Only used by a ranged slider. + */ + valueLeft?: CodegenTypes.WithDefault; + + /** + * Position of the thumb bounding the selected span from above, expressed in + * the slider's own range. Only used by a ranged slider. + */ + valueRight?: CodegenTypes.WithDefault; + + /** + * Granularity the slider's values snap to: every value it reports is a + * multiple of this. 0 means no granularity. + */ + step?: CodegenTypes.WithDefault; + + /** + * Lowest position a thumb can be dragged to, expressed in the slider's own + * range. The track is still drawn from `minimumValue`. + */ + lowerLimit?: CodegenTypes.WithDefault; + + /** + * Highest position a thumb can be dragged to, expressed in the slider's own + * range. The track is still drawn up to `maximumValue`. + */ + upperLimit?: CodegenTypes.WithDefault; + + /** + * Axis the slider is laid out along. A vertical slider holds its lowest + * value at the bottom and its highest at the top. + */ + orientation?: CodegenTypes.WithDefault< + "horizontal" | "vertical", + "horizontal" + >; + + /** + * Tint of the track from `minimumValue` up to the thumb, or up to the lower + * thumb of a ranged slider. Left unset, the platform's own tint is used. + */ + minimumTrackColor?: ColorValue; + + /** + * Tint of the track between the two thumbs of a ranged slider. Left unset, + * the platform's own tint is used. + */ + middleRangeTrackColor?: ColorValue; + + /** + * Tint of the track from the thumb, or from the upper thumb of a ranged + * slider, up to `maximumValue`. Left unset, the platform's own tint is used. + */ + maximumTrackColor?: ColorValue; + + /** + * Whether the first of the slider's children is a track JS renders in place + * of the built-in one, rather than a custom thumb. A replaced track is not + * drawn at all, so it outranks the three track colours. + */ + customTrack?: CodegenTypes.WithDefault; + + /** + * Whether the slider's children include the thumbs JS renders in place of + * the built-in ones: one for a single slider and two for a ranged one, + * following the custom track when there is one. + */ + customThumb?: CodegenTypes.WithDefault; + + /** + * How the steps are marked below the track: not at all, by markers the + * slider draws itself, or by the views JS renders, one for each step, as the + * last of the slider's children. Has no effect on a slider with no step. + */ + stepMarkers?: CodegenTypes.WithDefault<"none" | "native" | "custom", "none">; + + /** + * Emitted continuously while the user drags the thumb. + */ + onValueChange?: CodegenTypes.DirectEventHandler; + + /** + * Emitted continuously while the user drags the lower thumb of a ranged + * slider. + */ + onLeftValueChange?: CodegenTypes.DirectEventHandler; + + /** + * Emitted continuously while the user drags the upper thumb of a ranged + * slider. + */ + onRightValueChange?: CodegenTypes.DirectEventHandler; + + /** + * Emitted once the user grabs a thumb, before it has moved anywhere. A + * ranged slider reports whichever of its thumbs was grabbed first, and does + * not report the second one being grabbed on top of it. + */ + onSlidingStart?: CodegenTypes.DirectEventHandler; + + /** + * Emitted once the user lets go of the thumb they were dragging, whether or + * not the drag moved it. A ranged slider reports the last of its thumbs + * being let go of, so that every start is answered by exactly one complete. + */ + onSlidingComplete?: CodegenTypes.DirectEventHandler; +} + +export default codegenNativeComponent("CallstackSlider", { + interfaceOnly: true, +}) as HostComponent; diff --git a/package/src/RNCSliderNativeComponent.web.tsx b/package/src/SliderNativeComponent.web.tsx similarity index 77% rename from package/src/RNCSliderNativeComponent.web.tsx rename to package/src/SliderNativeComponent.web.tsx index e11e1853..e406cc97 100644 --- a/package/src/RNCSliderNativeComponent.web.tsx +++ b/package/src/SliderNativeComponent.web.tsx @@ -1,4 +1,4 @@ -import React, {RefObject, useCallback} from 'react'; +import React, {RefObject, useCallback} from "react"; import { Animated, View, @@ -8,10 +8,11 @@ import { LayoutChangeEvent, Image, ImageSourcePropType, -} from 'react-native'; +} from "react-native"; //@ts-ignore -import type {ImageSource} from 'react-native/Libraries/Image/ImageSource'; -import {constants} from './utils/constants'; +import type {ImageSource} from "react-native"; + +const THUMB_SIZE = 20; type Event = Readonly<{ nativeEvent: { @@ -42,9 +43,9 @@ export interface Props { disabled: boolean; trackHeight: number; thumbImage?: ImageSource; - onRNCSliderSlidingStart: (event: Event) => void; - onRNCSliderSlidingComplete: (event: Event) => void; - onRNCSliderValueChange: (event: Event) => void; + onSliderSlidingStart: (event: Event) => void; + onSliderSlidingComplete: (event: Event) => void; + onSliderValueChange: (event: Event) => void; } const valueToEvent = (value: number): Event => ({nativeEvent: {value}}); @@ -58,18 +59,18 @@ const RCTSliderWebComponent = React.forwardRef( lowerLimit = 0, upperLimit = 0, step = 1, - minimumTrackTintColor = '#009688', - maximumTrackTintColor = '#939393', - thumbTintColor = '#009688', + minimumTrackTintColor = "#009688", + maximumTrackTintColor = "#939393", + thumbTintColor = "#009688", thumbStyle = {}, style = {}, inverted = false, disabled = false, trackHeight = 4, thumbImage, - onRNCSliderSlidingStart = (_: Event) => {}, - onRNCSliderSlidingComplete = (_: Event) => {}, - onRNCSliderValueChange = (_: Event) => {}, + onSliderSlidingStart = (_: Event) => {}, + onSliderSlidingComplete = (_: Event) => {}, + onSliderValueChange = (_: Event) => {}, ...others }: Props, forwardedRef: any, @@ -84,19 +85,15 @@ const RCTSliderWebComponent = React.forwardRef( val: new Animated.Value(value), min: new Animated.Value(minimumValue), max: new Animated.Value(maximumValue), - // make sure we never divide by 0 diff: new Animated.Value(maximumValue - minimumValue || 1), }).current; - // update minimumValue & maximumValue animations React.useEffect(() => { animationValues.min.setValue(minimumValue); animationValues.max.setValue(maximumValue); - // make sure we never divide by 0 animationValues.diff.setValue(maximumValue - minimumValue || 1); }, [animationValues, minimumValue, maximumValue]); - // compute animated slider position based on animated value const minPercent = React.useRef( Animated.multiply( new Animated.Value(100), @@ -112,38 +109,34 @@ const RCTSliderWebComponent = React.forwardRef( const onValueChange = useCallback( (value: number) => { - onRNCSliderValueChange && onRNCSliderValueChange(valueToEvent(value)); + onSliderValueChange && onSliderValueChange(valueToEvent(value)); }, - [onRNCSliderValueChange], + [onSliderValueChange], ); const onSlidingStart = useCallback( (value: number) => { isUserInteracting.current = true; - onRNCSliderSlidingStart && onRNCSliderSlidingStart(valueToEvent(value)); + onSliderSlidingStart && onSliderSlidingStart(valueToEvent(value)); }, - [onRNCSliderSlidingStart], + [onSliderSlidingStart], ); const onSlidingComplete = useCallback( (value: number) => { isUserInteracting.current = false; - onRNCSliderSlidingComplete && - onRNCSliderSlidingComplete(valueToEvent(value)); + onSliderSlidingComplete && onSliderSlidingComplete(valueToEvent(value)); }, - [onRNCSliderSlidingComplete], + [onSliderSlidingComplete], ); - // Add a ref to track user interaction const isUserInteracting = React.useRef(false); const updateValue = useCallback( (newValue: number) => { - // Ensure that the value is correctly rounded const hardRounded = decimalPrecision.current < 20 ? Number.parseFloat(newValue.toFixed(decimalPrecision.current)) : newValue; - // Ensure that the new value is still between the bounds const withinBounds = Math.max( minimumValue, Math.min(hardRounded, maximumValue), @@ -161,11 +154,8 @@ const RCTSliderWebComponent = React.forwardRef( ); React.useLayoutEffect(() => { - // we have to do this check because `initialValue` gets default to `0` by - // Slider. If we don't this will get called every time `value` changes - // as `updateValue` is mutated when value changes. The result of not - // checking this is that the value constantly gets reset to `0` in - // contexts where `value` is not managed externally. + // Without this the effect re-runs on every `value` change, since + // `updateValue` is rebuilt with it, resetting an unmanaged value to `0`. if (initialValue !== lastInitialValue.current) { lastInitialValue.current = initialValue; const newValue = updateValue(initialValue); @@ -189,16 +179,16 @@ const RCTSliderWebComponent = React.forwardRef( } }; //@ts-ignore - window.addEventListener('resize', invalidateContainerPosition); + window.addEventListener("resize", invalidateContainerPosition); //@ts-ignore - document.addEventListener('scroll', onDocumentScroll, {capture: true}); + document.addEventListener("scroll", onDocumentScroll, {capture: true}); return () => { //@ts-ignore - window.removeEventListener('resize', invalidateContainerPosition); + window.removeEventListener("resize", invalidateContainerPosition); //@ts-ignore - document.removeEventListener('scroll', onDocumentScroll, { + document.removeEventListener("scroll", onDocumentScroll, { capture: true, }); }; @@ -208,9 +198,9 @@ const RCTSliderWebComponent = React.forwardRef( { flexGrow: 1, flexShrink: 1, - flexBasis: 'auto', - flexDirection: 'row', - alignItems: 'center', + flexBasis: "auto", + flexDirection: "row", + alignItems: "center", }, style, ] as ViewStyle[]; @@ -218,7 +208,7 @@ const RCTSliderWebComponent = React.forwardRef( const trackStyle = { height: trackHeight, borderRadius: trackHeight / 2, - userSelect: 'none', + userSelect: "none", }; const minimumTrackStyle = { @@ -233,7 +223,7 @@ const RCTSliderWebComponent = React.forwardRef( flexGrow: maxPercent, }; - const thumbSize = constants.THUMB_SIZE; + const thumbSize = THUMB_SIZE; const thumbViewStyle = [ { width: thumbSize, @@ -241,7 +231,7 @@ const RCTSliderWebComponent = React.forwardRef( backgroundColor: thumbTintColor, zIndex: 1, borderRadius: thumbSize / 2, - overflow: 'hidden', + overflow: "hidden", }, thumbStyle, ] as ViewStyle[]; @@ -316,10 +306,10 @@ const RCTSliderWebComponent = React.forwardRef( const accessibilityActions = (event: any) => { const tenth = (maximumValue - minimumValue) / 10; switch (event.nativeEvent.actionName) { - case 'increment': + case "increment": updateValue(value + (step || tenth)); break; - case 'decrement': + case "decrement": updateValue(value - (step || tenth)); break; } @@ -346,16 +336,15 @@ const RCTSliderWebComponent = React.forwardRef( } }} accessibilityActions={[ - {name: 'increment', label: 'increment'}, - {name: 'decrement', label: 'decrement'}, + {name: "increment", label: "increment"}, + {name: "decrement", label: "decrement"}, ]} onAccessibilityAction={accessibilityActions} accessible={true} - accessibilityRole={'adjustable'} + accessibilityRole={"adjustable"} style={containerStyle} {...others} - // NOTE: gesture responders should all fall _after_ the {...others} - // spread operator, or they may not work appropriately. + // Gesture responders have to fall after the {...others} spread. onStartShouldSetResponder={() => !disabled} onMoveShouldSetResponder={() => !disabled} onResponderGrant={() => onSlidingStart(value)} @@ -366,7 +355,7 @@ const RCTSliderWebComponent = React.forwardRef( {thumbImage !== undefined ? ( ) : null} @@ -376,7 +365,7 @@ const RCTSliderWebComponent = React.forwardRef( }, ); -// We should round number with the same precision as the min, max or step values if provided +// Rounds with the same precision as the min, max or step values, if provided. function calculatePrecision( minimumValue: number, maximumValue: number, @@ -385,14 +374,13 @@ function calculatePrecision( if (!step) { return Infinity; } else { - // Calculate the number of decimals we can encounter in the results const decimals = [minimumValue, maximumValue, step].map( - (value) => ((value + '').split('.').pop() || '').length, + (value) => ((value + "").split(".").pop() || "").length, ); return Math.max(...decimals); } } -RCTSliderWebComponent.displayName = 'RTCSliderWebComponent'; +RCTSliderWebComponent.displayName = "RTCSliderWebComponent"; export default RCTSliderWebComponent; diff --git a/package/src/components/StepNumber.tsx b/package/src/components/StepNumber.tsx deleted file mode 100644 index 09f38c03..00000000 --- a/package/src/components/StepNumber.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import {StyleProp, Text, TextStyle, View} from 'react-native'; -import {styles} from '../utils/styles'; - -export const StepNumber = ({ - i, - index, - style, -}: { - i: number; - index: number; - style: StyleProp; -}) => { - return ( - - - {i} - - - ); -}; diff --git a/package/src/components/StepsIndicator.tsx b/package/src/components/StepsIndicator.tsx deleted file mode 100644 index 0ac98316..00000000 --- a/package/src/components/StepsIndicator.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, {FC, Fragment, useCallback, useMemo} from 'react'; -import {Platform, View} from 'react-native'; -import {StepNumber} from './StepNumber'; -import {MarkerProps, SliderTrackMark} from './TrackMark'; -//@ts-ignore -import type {ImageSource} from 'react-native/Libraries/Image/ImageSource'; -import {styles} from '../utils/styles'; -import {constants} from '../utils/constants'; - -export const StepsIndicator = ({ - options, - sliderWidth, - currentValue, - StepMarker, - renderStepNumber, - thumbImage, - isLTR, -}: { - options: number[]; - sliderWidth: number; - currentValue: number; - StepMarker?: FC; - renderStepNumber?: boolean; - thumbImage?: ImageSource; - isLTR?: boolean; -}) => { - const stepNumberFontStyle = useMemo(() => { - return { - fontSize: - options.length > 9 - ? constants.STEP_NUMBER_TEXT_FONT_SMALL - : constants.STEP_NUMBER_TEXT_FONT_BIG, - }; - }, [options.length]); - - const platformDependentStyles = useMemo(() => { - const isWeb = Platform.OS === 'web'; - return { - stepIndicatorContainerStyle: isWeb - ? styles.stepsIndicator - : { - ...styles.stepsIndicator, - marginHorizontal: sliderWidth * constants.MARGIN_HORIZONTAL_PADDING, - }, - stepIndicatorElementStyle: isWeb - ? { - ...styles.stepIndicatorElement, - width: constants.THUMB_SIZE, - justifyContent: 'space-between' as const, - } - : styles.stepIndicatorElement, - }; - }, [sliderWidth]); - - const values = isLTR ? options.reverse() : options; - - const renderStepIndicator = useCallback( - (i: number, index: number) => { - return ( - - - - {renderStepNumber ? ( - - ) : null} - - - ); - }, - [ - currentValue, - StepMarker, - options, - thumbImage, - renderStepNumber, - stepNumberFontStyle, - platformDependentStyles.stepIndicatorElementStyle, - ], - ); - - return ( - - {values.map((i, index) => renderStepIndicator(i, index))} - - ); -}; diff --git a/package/src/components/TrackMark.tsx b/package/src/components/TrackMark.tsx deleted file mode 100644 index 21a096e2..00000000 --- a/package/src/components/TrackMark.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, {FC} from 'react'; -import {Image, ImageSource, ImageSourcePropType, View} from 'react-native'; -import {styles} from '../utils/styles'; - -export type MarkerProps = { - stepMarked: boolean; - currentValue: number; - index: number; - min: number; - max: number; -}; - -export type TrackMarksProps = { - isTrue: boolean; - index: number; - thumbImage?: ImageSource; - StepMarker?: FC; - currentValue: number; - min: number; - max: number; -}; - -export const SliderTrackMark = ({ - isTrue, - index, - thumbImage, - StepMarker, - currentValue, - min, - max, -}: TrackMarksProps) => { - return ( - - {StepMarker ? ( - - ) : null} - {thumbImage && isTrue ? ( - - - - ) : null} - - ); -}; diff --git a/package/src/index.ts b/package/src/index.ts index ebd6e915..ea4cd591 100644 --- a/package/src/index.ts +++ b/package/src/index.ts @@ -1,3 +1,3 @@ -const RNCSlider = require('./RNCSliderNativeComponent').default; +const Slider = require("./SliderNativeComponent").default; -export default RNCSlider; +export default Slider; diff --git a/package/src/utils/constants.ts b/package/src/utils/constants.ts deleted file mode 100644 index 72b26fbf..00000000 --- a/package/src/utils/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Platform} from 'react-native'; - -export const constants = { - SLIDER_DEFAULT_INITIAL_VALUE: 0, - MARGIN_HORIZONTAL_PADDING: 0.05, - // Default thumb size for web platform (used in step indicator positioning) - THUMB_SIZE: 20, - STEP_NUMBER_TEXT_FONT_SMALL: 8, - STEP_NUMBER_TEXT_FONT_BIG: 12, - LIMIT_MIN_VALUE: Number.MIN_SAFE_INTEGER, - LIMIT_MAX_VALUE: Number.MAX_SAFE_INTEGER, - DEFAULT_STEP_RESOLUTION: Platform.OS === 'android' ? 128 : 1000, -}; diff --git a/package/src/utils/styles.ts b/package/src/utils/styles.ts deleted file mode 100644 index 96e1df1d..00000000 --- a/package/src/utils/styles.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {Platform, StyleSheet} from 'react-native'; - -export const styles = StyleSheet.create({ - stepNumber: { - marginTop: 20, - alignItems: 'center', - position: 'absolute', - }, - sliderMainContainer: {zIndex: 1, width: '100%'}, - defaultSlideriOS: { - height: 40, - }, - defaultSlider: {}, - stepsIndicator: { - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - top: Platform.OS === 'ios' ? 10 : 0, - zIndex: 2, - }, - trackMarkContainer: { - alignItems: 'center', - alignContent: 'center', - alignSelf: 'center', - justifyContent: 'center', - position: 'absolute', - zIndex: 3, - }, - thumbImageContainer: { - position: 'absolute', - zIndex: 3, - justifyContent: 'center', - alignItems: 'center', - alignContent: 'center', - }, - thumbImage: { - alignContent: 'center', - alignItems: 'center', - position: 'absolute', - }, - stepIndicatorElement: { - alignItems: 'center', - alignContent: 'center', - }, - defaultIndicatorMarked: { - height: 20, - width: 5, - backgroundColor: '#CCCCCC', - }, - defaultIndicatorIdle: { - height: 10, - width: 2, - backgroundColor: '#C0C0C0', - }, -}); diff --git a/package/tsconfig.json b/package/tsconfig.json index f84ba802..6ef01212 100644 --- a/package/tsconfig.json +++ b/package/tsconfig.json @@ -18,6 +18,7 @@ /* Type Checking */ "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "types": ["jest"] } } diff --git a/package/typings/index.d.ts b/package/typings/index.d.ts index 64033960..d6d700d4 100644 --- a/package/typings/index.d.ts +++ b/package/typings/index.d.ts @@ -1,126 +1,135 @@ -import * as React from 'react'; -import { FC } from 'react'; -import * as ReactNative from 'react-native'; -import { ImageURISource } from 'react-native'; +import * as React from "react"; +import {FC} from "react"; +import {ColorValue, ViewProps} from "react-native"; -export interface SliderPropsAndroid extends ReactNative.ViewProps { +export type ThumbProps = { /** - * Color of the foreground switch grip. + * Contains the exact step number on which this custom Thumb currently is. */ - thumbTintColor?: string; -} - -export interface SliderRef { - updateValue(value: number): void; -} - -export type TrackMarksProps = { - isTrue: boolean; index: number; - thumbImage?: ImageURISource; - StepMarker?: FC | boolean; - currentValue: number; -}; -export type MarkerProps = { - stepMarked: boolean; - currentValue: number; - index: number; - min: number; - max: number; + /** + * Informs the thumb about the Slider being in a disabled state. + * The default is `false`. + */ + disabled?: boolean; }; -export interface SliderPropsIOS extends ReactNative.ViewProps { +export type StepsProps = { /** - * Assigns a maximum track image. Only static images are supported. - * The leftmost pixel of the image will be stretched to fill the track. + * The sequential number of step. + * Always starting from 0, regardless of minimum or maximum value of Slider settings. */ - maximumTrackImage?: ReactNative.ImageURISource; - + index: number; /** - * Assigns a minimum track image. Only static images are supported. - * The rightmost pixel of the image will be stretched to fill the track. + * The value that this step represents. + * e.g.: the first step in the steps sequence will have it's value equal to minimumValue prop + * and the last step in the sequence will have it's value equal to maximumValue prop. */ - minimumTrackImage?: ReactNative.ImageURISource; + value: number; + /** + * Indicates whether a(any) thumb is currently on that step. + * Basically it says whether value of a thumb is equal to value of this step. + */ + marked: boolean; /** - * Permits tapping on the slider track to set the thumb position. - * Defaults to false on iOS. No effect on Android or Windows. + * Informs the custom step marker about the Slider being in a disabled state. + * The default is `false`. */ - tapToSeek?: boolean; + disabled?: boolean; +}; +export type TrackProps = { /** - * Sets an image for the thumb. Only static images are supported. + * Informs the custom track about the Slider being in a disabled state. + * The default is `false`. */ - thumbImage?: ReactNative.ImageURISource; + disabled?: boolean; +}; +export interface SliderProps extends ViewProps { /** - * Assigns a single image for the track. Only static images - * are supported. The center pixel of the image will be stretched - * to fill the track. + * Initial minimum value of the slider. Default value is 0. */ - trackImage?: ReactNative.ImageURISource; -} + minimumValue?: number; -export interface SliderPropsWindows extends ReactNative.ViewProps { /** - * Controls the orientation of the slider, default value is 'false' (horizontal). + * Initial maximum value of the slider. Default value is 1. */ - vertical?: boolean; -} + maximumValue?: number; -export interface SliderProps - extends SliderPropsIOS, - SliderPropsAndroid, - SliderPropsWindows { /** - * If true the user won't be able to move the slider. - * Default value is false. + * Value of the slider. Can be used to programmatically control the + * position of the thumb, and entered once at the beginning it acts as + * the initial value. + * + * This is not a controlled component, you don't need to update the + * value while the user is dragging. + * + * Ignored by a ranged slider, which is positioned by `valueLeft` and + * `valueRight` instead. */ - disabled?: boolean; + value?: number; /** - * The color used for the track to the right of the button. - * Overrides the default blue gradient image. + * Whether the slider selects a span of its range with two thumbs, rather + * than a single value with one. Default value is false. */ - maximumTrackTintColor?: string; + ranged?: boolean; /** - * Initial maximum value of the slider. Default value is 1. + * Value of the thumb bounding the selected span from below. Defaults to + * `minimumValue`. + * + * Behaves like `value`, and is only used by a ranged slider. The thumb + * cannot be dragged past `valueRight`. */ - maximumValue?: number; + valueLeft?: number; /** - * The lower limit value of the slider. The user won't be able to slide below this limit. + * Value of the thumb bounding the selected span from above. Defaults to + * `maximumValue`. + * + * Behaves like `value`, and is only used by a ranged slider. The thumb + * cannot be dragged past `valueLeft`. */ - lowerLimit?: number; + valueRight?: number; /** - * The upper limit value of the slider. The user won't be able to slide above this limit. + * Limits the avaiable range of the slider to the lower value; + * */ - upperLimit?: number; + lowerLimit?: number; /** - * The color used for the track to the left of the button. - * Overrides the default blue gradient image. + * Limits the avaiable range of the slider to the upper value; */ - minimumTrackTintColor?: string; + upperLimit?: number; /** - * Initial minimum value of the slider. Default value is 0. + * Step defines the granularity of the slider. The value of the slider will always be a multiple of the step value. + * This works for both single and ranged sliders in the same manner. + * The default value is 0, which means no step. */ - minimumValue?: number; + step?: number; /** - * Callback that is called when the user picks up the slider. - * The initial value is passed as an argument to the callback handler. + * The orientation of Slider. + * Default is "horizontal". + * Switches the way how Slider is displayed. */ - onSlidingStart?: (value: number) => void; + orientation?: "horizontal" | "vertical"; /** - * Callback called when the user finishes changing the value (e.g. when the slider is released). + * Disables the Slider. When disabled, Slider is still rendered in a way the native Slider + * renders the default "disabled" state of it. + * Slider is not reachable by clicking, tapping, or keyboard when disabled and does not receive + * any events nor controlls. + * When disabled, Slider can still be modified by props. + * Default value is `false`. */ - onSlidingComplete?: (value: number) => void; + disabled?: boolean; /** * Callback continuously called while the user is dragging the slider. @@ -128,76 +137,77 @@ export interface SliderProps onValueChange?: (value: number) => void; /** - * Step value of the slider. The value should be between 0 and (maximumValue - minimumValue). Default value is 0. + * Callback continuously called while the user is dragging the lower thumb + * of a ranged slider. */ - step?: number; + onLeftValueChange?: (value: number) => void; /** - * Used to style and layout the Slider. See StyleSheet.js and ViewStylePropTypes.js for more info. + * Callback continuously called while the user is dragging the upper thumb + * of a ranged slider. */ - style?: ReactNative.StyleProp; + onRightValueChange?: (value: number) => void; /** - * Used to locate this view in UI automation tests. + * Callback called each time user starts dragging. + * The event is fired as soon as user holds (long press) any thumb (left or right), + * regardless if the drag changed the value or not. */ - testID?: string; + onSlidingStart?: () => void; /** - * Write-only property representing the value of the slider. - * Can be used to programmatically control the position of the thumb. - * Entered once at the beginning still acts as an initial value. - * The value should be between minimumValue and maximumValue, - * which default to 0 and 1 respectively. - * Default value is 0. - * - * This is not a controlled component, you don't need to update the - * value during dragging. + * Callback called each time user ends dragging. + * The event is fired as soon as user releases any thumb (left or right) after long press, + * regardless if the drag changed the value or not. */ - value?: number; + onSlidingComplete?: () => void; /** - * Reverses the direction of the slider. + * Used to pass a custom component rendered as a thumb of the Slider. */ - inverted?: boolean; + thumb?: FC; /** - * Sets the size (width and height) of the thumb. - * If `thumbImage` is provided, it will be scaled to this size. + * Defines the color the track from minimumValue to thumb, or left thumb (when ranged) will be. */ - thumbSize?: number; + minimumTrackColor?: ColorValue; /** - * Component to be rendered for each step indicator. + * Defines the color the track from left thumb to right thumb (when ranged) will be. */ - StepMarker?: FC; + middleRangeTrackColor?: ColorValue; /** - * + * Defines the color the track from thumb, or right thumb (when ranged) to maximumValue will be. */ - renderStepNumber?: boolean; + maximumTrackColor?: ColorValue; /** - * A string of one or more words to be announced by the screen reader. - * Otherwise, it will announce the value as a percentage. - * Requires passing a value to `accessibilityIncrements` to work correctly. - * Should be a plural word, as singular units will be handled. + * Replaces the default (native) track with the given component. + * The component will be spread along the whole track by it's X-axis, + * meaning that: + * * In horizontal Slider, the width of given component will be increased to the width of the track, + * * In vertical Slider, the width of given component will be increased as well, but the component will + * be transformed in the same 90 degree as vertical Slider is. + * NOTE, that the custom track component takes the priority against + * the minimumTrackColor, middleRangeTrackColor and maximumTrackColor. */ - accessibilityUnits?: string; + track?: FC; /** - * An array of values that represent the different increments displayed - * by the slider. All the values passed into this prop must be strings. - * Requires passing a value to `accessibilityUnits` to work correctly. - * The number of elements must be the same as `maximumValue`. + * Renders steps below the track. + * NOTE: Is effective only when the `step` prop is set. + * true - switches on rendering of default, built-in steps indicators + * false - default. Switches the steps rendering off. + * component - if a custom component is passed, switches the steps rendering on with + * the given component used to render each step. */ - accessibilityIncrements?: Array; + stepMarkers?: boolean | FC; } /** - * A component used to select a single value from a range of values. + * A component used to select a single value, or a span of values, from a range + * of values. */ -declare function Slider( - props: SliderProps & {ref?: React.Ref | undefined}, -): React.ReactNode; +declare function Slider(props: SliderProps): React.ReactNode; export default Slider; -export type SliderIOS = typeof Slider; diff --git a/package/windows/SliderWindows/SliderView.cpp b/package/windows/SliderWindows/SliderView.cpp index ce8b88d1..c03183ec 100644 --- a/package/windows/SliderWindows/SliderView.cpp +++ b/package/windows/SliderWindows/SliderView.cpp @@ -27,7 +27,6 @@ namespace winrt::SliderWindows::implementation { } void SliderView::RegisterEvents() { - //Set this so that Slider passes on Manipulation* events. this->ManipulationMode(xaml::Input::ManipulationModes::All); m_sliderValueChangedRevoker = this->ValueChanged(winrt::auto_revoke, diff --git a/package/windows/SliderWindows/SliderViewManager.cpp b/package/windows/SliderWindows/SliderViewManager.cpp index 16ea5218..03cbbb14 100644 --- a/package/windows/SliderWindows/SliderViewManager.cpp +++ b/package/windows/SliderWindows/SliderViewManager.cpp @@ -19,7 +19,7 @@ namespace winrt::SliderWindows::implementation { // IViewManager winrt::hstring SliderViewManager::Name() noexcept { - return L"RNCSlider"; + return L"CallstackSlider"; } xaml::FrameworkElement SliderViewManager::CreateView() noexcept { @@ -73,8 +73,8 @@ namespace winrt::SliderWindows::implementation { ConstantProviderDelegate SliderViewManager::ExportedCustomDirectEventTypeConstants() noexcept { return [](winrt::IJSValueWriter const& constantWriter) { WriteCustomDirectEventTypeConstant(constantWriter, "onChange"); - WriteCustomDirectEventTypeConstant(constantWriter, L"topSlidingStart", L"onRNCSliderSlidingStart"); - WriteCustomDirectEventTypeConstant(constantWriter, L"topSlidingComplete", L"onRNCSliderSlidingComplete"); + WriteCustomDirectEventTypeConstant(constantWriter, L"topSlidingStart", L"onSliderSlidingStart"); + WriteCustomDirectEventTypeConstant(constantWriter, L"topSlidingComplete", L"onSliderSlidingComplete"); }; }