diff --git a/.github/actions/composite/buildAndroidE2EAPK/action.yml b/.github/actions/composite/buildAndroidE2EAPK/action.yml
index b4fc05c7ebe9..0c5f70929c27 100644
--- a/.github/actions/composite/buildAndroidE2EAPK/action.yml
+++ b/.github/actions/composite/buildAndroidE2EAPK/action.yml
@@ -14,6 +14,24 @@ inputs:
MAPBOX_SDK_DOWNLOAD_TOKEN:
description: The token to use to download the MapBox SDK
required: true
+ PATH_ENV_FILE:
+ description: The path to the .env file to use for the build
+ required: true
+ EXPENSIFY_PARTNER_NAME:
+ description: The name of the Expensify partner to use for the build
+ required: true
+ EXPENSIFY_PARTNER_PASSWORD:
+ description: The password of the Expensify partner to use for the build
+ required: true
+ EXPENSIFY_PARTNER_USER_ID:
+ description: The user ID of the Expensify partner to use for the build
+ required: true
+ EXPENSIFY_PARTNER_USER_SECRET:
+ description: The user secret of the Expensify partner to use for the build
+ required: true
+ EXPENSIFY_PARTNER_PASSWORD_EMAIL:
+ description: The email address of the Expensify partner to use for the build
+ required: true
runs:
using: composite
@@ -37,9 +55,24 @@ runs:
- uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef
+ - name: Append environment variables to env file
+ shell: bash
+ run: |
+ echo "EXPENSIFY_PARTNER_NAME=${EXPENSIFY_PARTNER_NAME}" >> ${{ inputs.PATH_ENV_FILE }}
+ echo "EXPENSIFY_PARTNER_PASSWORD=${EXPENSIFY_PARTNER_PASSWORD}" >> ${{ inputs.PATH_ENV_FILE }}
+ echo "EXPENSIFY_PARTNER_USER_ID=${EXPENSIFY_PARTNER_USER_ID}" >> ${{ inputs.PATH_ENV_FILE }}
+ echo "EXPENSIFY_PARTNER_USER_SECRET=${EXPENSIFY_PARTNER_USER_SECRET}" >> ${{ inputs.PATH_ENV_FILE }}
+ echo "EXPENSIFY_PARTNER_PASSWORD_EMAIL=${EXPENSIFY_PARTNER_PASSWORD_EMAIL}" >> ${{ inputs.PATH_ENV_FILE }}
+
- name: Build APK
run: npm run ${{ inputs.PACKAGE_SCRIPT_NAME }}
shell: bash
+ env:
+ EXPENSIFY_PARTNER_NAME: ${{ inputs.EXPENSIFY_PARTNER_NAME }}
+ EXPENSIFY_PARTNER_PASSWORD: ${{ inputs.EXPENSIFY_PARTNER_PASSWORD }}
+ EXPENSIFY_PARTNER_USER_ID: ${{ inputs.EXPENSIFY_PARTNER_USER_ID }}
+ EXPENSIFY_PARTNER_USER_SECRET: ${{ inputs.EXPENSIFY_PARTNER_USER_SECRET }}
+ EXPENSIFY_PARTNER_PASSWORD_EMAIL: ${{ inputs.EXPENSIFY_PARTNER_PASSWORD_EMAIL }}
- name: Upload APK
uses: actions/upload-artifact@65d862660abb392b8c4a3d1195a2108db131dd05
diff --git a/.github/scripts/createHelpRedirects.sh b/.github/scripts/createHelpRedirects.sh
index 04f55e19b4fb..1ae2220253c4 100755
--- a/.github/scripts/createHelpRedirects.sh
+++ b/.github/scripts/createHelpRedirects.sh
@@ -41,13 +41,13 @@ while read -r line; do
# Basic sanity checking to make sure that the source and destination are in expected
# subdomains.
- if ! [[ $SOURCE_URL =~ ^https://community\.expensify\.com ]]; then
- error "Found source URL that is not a community URL: $SOURCE_URL"
+ if ! [[ $SOURCE_URL =~ ^https://(community|help)\.expensify\.com ]]; then
+ error "Found source URL that is not a communityDot or helpDot URL: $SOURCE_URL"
exit 1
fi
- if ! [[ $DEST_URL =~ ^https://help\.expensify\.com ]]; then
- error "Found destination URL that is not a help URL: $DEST_URL"
+ if ! [[ $DEST_URL =~ ^https://(help|use)\.expensify\.com ]]; then
+ error "Found destination URL that is not a helpDot or useDot URL: $DEST_URL"
exit 1
fi
diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml
index bd3af08ae25e..70f70fca60de 100644
--- a/.github/workflows/e2ePerformanceTests.yml
+++ b/.github/workflows/e2ePerformanceTests.yml
@@ -52,6 +52,12 @@ jobs:
PACKAGE_SCRIPT_NAME: android-build-e2e
APP_OUTPUT_PATH: android/app/build/outputs/apk/e2e/release/app-e2e-release.apk
MAPBOX_SDK_DOWNLOAD_TOKEN: ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+ EXPENSIFY_PARTNER_NAME: ${{ secrets.EXPENSIFY_PARTNER_NAME }}
+ EXPENSIFY_PARTNER_PASSWORD: ${{ secrets.EXPENSIFY_PARTNER_PASSWORD }}
+ EXPENSIFY_PARTNER_USER_ID: ${{ secrets.EXPENSIFY_PARTNER_USER_ID }}
+ EXPENSIFY_PARTNER_USER_SECRET: ${{ secrets.EXPENSIFY_PARTNER_USER_SECRET }}
+ EXPENSIFY_PARTNER_PASSWORD_EMAIL: ${{ secrets.EXPENSIFY_PARTNER_PASSWORD_EMAIL }}
+ PATH_ENV_FILE: tests/e2e/.env.e2e
buildDelta:
runs-on: ubuntu-latest-xl
@@ -114,6 +120,12 @@ jobs:
PACKAGE_SCRIPT_NAME: android-build-e2edelta
APP_OUTPUT_PATH: android/app/build/outputs/apk/e2edelta/release/app-e2edelta-release.apk
MAPBOX_SDK_DOWNLOAD_TOKEN: ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+ EXPENSIFY_PARTNER_NAME: ${{ secrets.EXPENSIFY_PARTNER_NAME }}
+ EXPENSIFY_PARTNER_PASSWORD: ${{ secrets.EXPENSIFY_PARTNER_PASSWORD }}
+ EXPENSIFY_PARTNER_USER_ID: ${{ secrets.EXPENSIFY_PARTNER_USER_ID }}
+ EXPENSIFY_PARTNER_USER_SECRET: ${{ secrets.EXPENSIFY_PARTNER_USER_SECRET }}
+ EXPENSIFY_PARTNER_PASSWORD_EMAIL: ${{ secrets.EXPENSIFY_PARTNER_PASSWORD_EMAIL }}
+ PATH_ENV_FILE: tests/e2e/.env.e2edelta
runTestsInAWS:
runs-on: ubuntu-latest
diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml
index 64b4536d9241..ebe31f41f3d8 100644
--- a/.github/workflows/reassurePerformanceTests.yml
+++ b/.github/workflows/reassurePerformanceTests.yml
@@ -33,6 +33,7 @@ jobs:
npx reassure --baseline
git switch --force --detach -
git merge --no-commit --allow-unrelated-histories "$BASELINE_BRANCH" -X ours
+ git checkout --ours .
npm install --force
npx reassure --branch
diff --git a/.nvmrc b/.nvmrc
index 43bff1f8cf98..d5a159609d09 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20.9.0
\ No newline at end of file
+20.10.0
diff --git a/README.md b/README.md
index f6629af8604d..b69786d64f13 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,7 @@ If you're using another operating system, you will need to ensure `mkcert` is in
## Running the iOS app 📱
For an M1 Mac, read this [SO](https://stackoverflow.com/questions/64901180/how-to-run-cocoapods-on-apple-silicon-m1) for installing cocoapods.
+* If you haven't already, install Xcode tools and make sure to install the optional "iOS Platform" package as well. This installation may take awhile.
* Install project gems, including cocoapods, using bundler to ensure everyone uses the same versions. In the project root, run: `bundle install`
* If you get the error `Could not find 'bundler'`, install the bundler gem first: `gem install bundler` and try again.
* If you are using MacOS and get the error `Gem::FilePermissionError` when trying to install the bundler gem, you're likely using system Ruby, which requires administrator permission to modify. To get around this, install another version of Ruby with a version manager like [rbenv](https://github.com/rbenv/rbenv#installation).
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e135d44eb834..df6e0db76efa 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -71,7 +71,7 @@ project.ext.envConfigFiles = [
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
-def enableProguardInReleaseBuilds = false
+def enableProguardInReleaseBuilds = true
/**
* The preferred build flavor of JavaScriptCore (JSC)
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001043202
- versionName "1.4.32-2"
+ versionCode 1001043303
+ versionName "1.4.33-3"
}
flavorDimensions "default"
@@ -152,8 +152,9 @@ android {
}
release {
productFlavors.production.signingConfig signingConfigs.release
+ shrinkResources enableProguardInReleaseBuilds
minifyEnabled enableProguardInReleaseBuilds
- proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+ proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
signingConfig null
// buildTypes take precedence over productFlavors when it comes to the signing configuration,
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
index 7dab035002a2..e553222dd682 100644
--- a/android/app/proguard-rules.pro
+++ b/android/app/proguard-rules.pro
@@ -8,5 +8,31 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
--keep class com.facebook.hermes.unicode.** { *; }
--keep class com.facebook.jni.** { *; }
+-keep class com.expensify.chat.BuildConfig { *; }
+-keep, allowoptimization, allowobfuscation class expo.modules.** { *; }
+
+# Added from auto-generated missingrules.txt to allow build to succeed
+-dontwarn com.onfido.javax.inject.Inject
+-dontwarn javax.lang.model.element.Element
+-dontwarn javax.lang.model.type.TypeMirror
+-dontwarn javax.lang.model.type.TypeVisitor
+-dontwarn javax.lang.model.util.SimpleTypeVisitor7
+-dontwarn net.sf.scuba.data.Gender
+-dontwarn net.sf.scuba.smartcards.CardFileInputStream
+-dontwarn net.sf.scuba.smartcards.CardService
+-dontwarn net.sf.scuba.smartcards.CardServiceException
+-dontwarn org.jmrtd.AccessKeySpec
+-dontwarn org.jmrtd.BACKey
+-dontwarn org.jmrtd.BACKeySpec
+-dontwarn org.jmrtd.PACEKeySpec
+-dontwarn org.jmrtd.PassportService
+-dontwarn org.jmrtd.lds.CardAccessFile
+-dontwarn org.jmrtd.lds.PACEInfo
+-dontwarn org.jmrtd.lds.SecurityInfo
+-dontwarn org.jmrtd.lds.icao.DG15File
+-dontwarn org.jmrtd.lds.icao.DG1File
+-dontwarn org.jmrtd.lds.icao.MRZInfo
+-dontwarn org.jmrtd.protocol.AAResult
+-dontwarn org.jmrtd.protocol.BACResult
+-dontwarn org.jmrtd.protocol.PACEResult
+-dontwarn org.spongycastle.jce.provider.BouncyCastleProvider
\ No newline at end of file
diff --git a/android/app/src/main/res/raw/keep.xml b/android/app/src/main/res/raw/keep.xml
new file mode 100644
index 000000000000..972e0416855c
--- /dev/null
+++ b/android/app/src/main/res/raw/keep.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/android/settings.gradle b/android/settings.gradle
index d9d1e903423c..40aefc6f2405 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -17,14 +17,5 @@ apply from: file("../node_modules/@react-native-community/cli-platform-android/n
include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')
-includeBuild('../node_modules/react-native') {
- dependencySubstitution {
- substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid"))
- substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid"))
- substitute(module("com.facebook.react:hermes-android")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
- substitute(module("com.facebook.react:hermes-engine")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
- }
-}
-
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
-useExpoModules()
\ No newline at end of file
+useExpoModules()
diff --git a/assets/animations/Update.lottie b/assets/animations/Update.lottie
new file mode 100644
index 000000000000..363486ec2267
Binary files /dev/null and b/assets/animations/Update.lottie differ
diff --git a/assets/images/eReceiptIcon.svg b/assets/images/eReceiptIcon.svg
index 9b0612a03231..f0e206995809 100644
--- a/assets/images/eReceiptIcon.svg
+++ b/assets/images/eReceiptIcon.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/config/webpack/webpack.desktop.js b/config/webpack/webpack.desktop.js
index 9c4ded572804..2612e2b190fa 100644
--- a/config/webpack/webpack.desktop.js
+++ b/config/webpack/webpack.desktop.js
@@ -54,15 +54,6 @@ module.exports = (env) => {
loader: 'babel-loader',
exclude: /node_modules/,
},
- {
- test: /react-native-onyx/,
- use: {
- loader: 'babel-loader',
- options: {
- presets: ['@babel/preset-react'],
- },
- },
- },
],
},
};
diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md
index 9eb16099f535..25f54c668b24 100644
--- a/contributingGuides/CONTRIBUTING.md
+++ b/contributingGuides/CONTRIBUTING.md
@@ -123,7 +123,7 @@ Additionally if you want to discuss an idea with the open source community witho
```
11. [Open a pull request](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork), and make sure to fill in the required fields.
12. An Expensify engineer and a member from the Contributor-Plus team will be assigned to your pull request automatically to review.
-13. Daily updates on weekdays are highly recommended. If you know you won’t be able to provide updates for > 1 week, please comment on the PR or issue how long you plan to be out so that we may plan accordingly. We understand everyone needs a little vacation here and there. Any issue that doesn't receive an update for 1 full week may be considered abandoned and the original contract terminated.
+13. Daily updates on weekdays are highly recommended. If you know you won’t be able to provide updates within 48 hours, please comment on the PR or issue stating how long you plan to be out so that we may plan accordingly. We understand everyone needs a little vacation here and there. Any issue that doesn't receive an update for 5 days (including weekend days) may be considered abandoned and the original contract terminated.
#### Submit your pull request for final review
14. When you are ready to submit your pull request for final review, make sure the following checks pass:
diff --git a/docs/articles/expensify-classic/getting-started/Plan-Types.md b/docs/articles/expensify-classic/getting-started/Plan-Types.md
deleted file mode 100644
index 4f8c52c2e1a1..000000000000
--- a/docs/articles/expensify-classic/getting-started/Plan-Types.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: Plan Types
-description: Learn which Expensify plan is the best fit for you
----
-# Overview
-You can access comprehensive information about Expensify's plans and pricing by visiting www.expensify.com/pricing. Below, we provide an overview of each plan type to assist you in selecting the one that best suits your business or personal requirements.
-
-## Free Plan
-The Free plan is suited for small businesses, offering a dedicated workspace for efficiently handling Expensify card management, expense reimbursement, invoicing, and bill payment. This plan includes unlimited receipt scanning for all users within the company and the potential to earn up to 1% cashback on card spending exceeding $25,000 per month (across all cards).
-
-## Collect Workspace Plan
-The Collect Workspace Plan is designed with small companies in mind, providing essential features like a single layer of expense approvals, reimbursement capabilities, corporate card management, and basic integration options such as QuickBooks Online, QuickBooks Desktop, and Xero. This plan is ideal for those who require simple expense management functions.
-
-## Control Workspace Plan
-Our most popular option, the Control Workspace plan, offers a heightened level of control and Workspace customization. With a Control Workspace, you gain access to multi-level approval workflows, comprehensive corporate card management, advanced accounting integration, tax tracking capabilities, and advanced expense rules that facilitate the enforcement of your internal expense policy. This plan provides a robust set of features for effective expense management.
-
-## Individual Track Plan
-The Track plan is tailored for solo Expensify users who don't require expense submission to others. Individuals or sole proprietors can choose the Track plan to customize their Individual Workspace to align with their personal expense tracking requirements.
-
-## Individual Submit Plan
-The Submit plan is designed for individuals who need to keep track of their expenses and share them with someone else, such as their boss, accountant, or even a housemate. It's specifically tailored for single users who want to both track and submit their expenses efficiently.
-
-{% include faq-begin.md %}
-
-## How can I change Individual plans?
-You have the flexibility to switch between a Track and Submit plan, or vice versa, at any time by navigating to **Settings > Workspaces > Individual > *Workspace Name* > Plan**. This allows you to adapt your expense management approach as needed.
-
-## How can I upgrade Group plans?
-You can easily upgrade from a Collect to a Control plan at any time by going to **Settings > Workspaces > Group > *Workspace Name* > Plan**. However, it's important to note that if you have an active Annual Subscription, downgrading from Control to Collect is not possible until your current commitment period expires.
-
-## How does pricing work if I have two types of Group Workspace plans?
-If you have a Control and Collect Workspace, you will be charged at the Control Workspace rate.
-
-{% include faq-end.md %}
diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md
deleted file mode 100644
index 189ff671b213..000000000000
--- a/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: Expensify Card revenue share for ExpensifyApproved! partners
-description: Earn money when your clients adopt the Expensify Card
-redirect_from: articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners/
----
-
-
-Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. **In short, your firm gets 0.5% of your clients’ total Expensify Card spend as cash back**. The more your clients spend, the more cashback your firm receives!
- This program is currently only available to US-based ExpensifyApproved! partner accountants.
-
-# How-to
-To benefit from this program, all you need to do is ensure that you are listed as a domain admin on your client's Expensify account. If you're not currently a domain admin, your client can follow the instructions outlined in [our help article](https://community.expensify.com/discussion/5749/how-to-add-and-remove-domain-admins#:~:text=Domain%20Admins%20have%20total%20control,a%20member%20of%20the%20domain.) to assign you this role.
-{% include faq-begin.md %}
-- What if my firm is not permitted to accept revenue share from our clients?
- We understand that different firms may have different policies. If your firm is unable to accept this revenue share, you can pass the revenue share back to your client to give them an additional 0.5% of cash back using your own internal payment tools.
-- What if my firm does not wish to participate in the program?
- Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients.
\ No newline at end of file
diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md
deleted file mode 100644
index fb3cb5341f61..000000000000
--- a/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: Your Expensify Partner Manager
-description: Everything you need to know about your Expensify Partner Manager
-redirect_from: articles/other/Your-Expensify-Partner-Manager/
----
-
-
-# What is a Partner Manager?
-A Partner Manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner Managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management.
-
-Unlike Concierge, a Partner Manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your partner manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again.
-
-For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com.
-
-# How do I know if I have a Partner Manager?
-For your firm to be assigned a Partner Manager, you must complete the [ExpensifyApproved! University](https://use.expensify.com/accountants) training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit.
-
-You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo.
-
-# How do I contact my Partner Manager?
-You can contact your Partner Manager by:
-- Signing in to new.expensify.com and searching for your Partner Manager
-- Replying to or clicking the chat link on any email you get from your Partner Manager
-
-{% include faq-begin.md %}
-## How do I know if my Partner Manager is online?
-You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours.
-
-## What if I’m unable to reach my Partner Manager?
-If you’re unable to contact your Partner Manager (i.e., they're out of office for the day) you can reach out to Concierge for assistance. Your Partner Manager will get back to you when they’re online again.
-
-## Can I get on a call with my Partner Manager?
-Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Partner Managers can discuss client onboarding strategies, firm wide training, and client setups.
-
-We recommend continuing to work with Concierge for **general support questions**, as this team is always online and available to help immediately.
-
-{% include faq-end.md %}
diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md
index 30adac589dc0..b3f0ad3c6f6f 100644
--- a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md
+++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md
@@ -44,7 +44,7 @@ Expensify’s Budgets feature allows you to:
{% include faq-begin.md %}
## Can I import budgets as a CSV?
-At this time, you cannot import budgets via CSV since we don’t import categories or tags from direct accounting integrations.
+At this time, you cannot import budgets via CSV.
## When will I be notified as a budget is hit?
Notifications are sent twice:
diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Expenses.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Expenses.md
index ea701dc09d3e..4a2dc56c430f 100644
--- a/docs/articles/expensify-classic/workspace-and-domain-settings/Expenses.md
+++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Expenses.md
@@ -87,7 +87,7 @@ Concierge Receipt Audit is a real-time audit and compliance of receipts submitte
- To make sure you don't miss any risky expenses that need human oversight.
- To avoid needing to manually review all your company receipts.
-- It's included for free with the [Control Plan](https://www.expensify.com/pricing).
+- It's included at no extra cost with the [Control Plan](https://www.expensify.com/pricing).
- Instead of paying someone to audit your company expenses or being concerned that your expenses might be audited by a government agency.
- It's easy to use! Concierge will alert you to the risky expense and present it to you in an easy-to-follow review tutorial.
- In addition to the risky expense alerts, Expensify will include a Note with audit details on every report.
diff --git a/docs/redirects.csv b/docs/redirects.csv
index d3a7fdd695a3..2571cb1156eb 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -24,9 +24,12 @@ https://community.expensify.com/discussion/5655/deep-dive-what-is-a-vacation-del
https://community.expensify.com/discussion/5194/how-to-assign-a-vacation-delegate-for-an-employee-through-domains,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate
https://community.expensify.com/discussion/5190/how-to-individually-assign-a-vacation-delegate-from-account-settings,https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate
https://community.expensify.com/discussion/5274/how-to-set-up-an-adp-indirect-integration-with-expensify,https://help.expensify.com/articles/expensify-classic/integrations/HR-integrations/ADP
-https://community.expensify.com/discussion/5776/how-to-create-mileage-expenses-in-expensify,https://help.expensify.com/articles/expensify-classic/get-paid-back/Distance-Tracking#gsc.tab=0
-https://community.expensify.com/discussion/7385/how-to-enable-two-factor-authentication-in-your-account,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details#gsc.tab=0
-https://community.expensify.com/discussion/5124/how-to-add-your-name-and-photo-to-your-account,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details#gsc.tab=0
-https://community.expensify.com/discussion/5149/how-to-manage-your-devices-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details#gsc.tab=0
-https://community.expensify.com/discussion/4432/how-to-add-a-secondary-login,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details#gsc.tab=0
-https://community.expensify.com/discussion/6794/how-to-change-your-email-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details#gsc.tab=0
+https://community.expensify.com/discussion/5776/how-to-create-mileage-expenses-in-expensify,https://help.expensify.com/articles/expensify-classic/get-paid-back/Distance-Tracking
+https://community.expensify.com/discussion/7385/how-to-enable-two-factor-authentication-in-your-account,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details
+https://community.expensify.com/discussion/5124/how-to-add-your-name-and-photo-to-your-account,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details
+https://community.expensify.com/discussion/5149/how-to-manage-your-devices-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details
+https://community.expensify.com/discussion/4432/how-to-add-a-secondary-login,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details
+https://community.expensify.com/discussion/6794/how-to-change-your-email-in-expensify,https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details
+https://help.expensify.com/articles/expensify-classic/expensify-card/Expensify-Card-Perks.html,https://use.expensify.com/company-credit-card
+https://help.expensify.com/articles/expensify-classic/expensify-partner-program/How-to-Join-the-ExpensifyApproved!-Partner-Program.html,https://use.expensify.com/accountants-program
+https://help.expensify.com/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners, https://use.expensify.com/blog/maximizing-rewards-expensifyapproved-accounting-partners-now-earn-0-5-revenue-share
diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj
index acd08500fc11..41f53a0b8f7d 100644
--- a/ios/NewExpensify.xcodeproj/project.pbxproj
+++ b/ios/NewExpensify.xcodeproj/project.pbxproj
@@ -1043,7 +1043,6 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT;
@@ -1064,7 +1063,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- GENERATE_INFOPLIST_FILE = YES;
+ GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -1075,7 +1074,6 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -1130,7 +1128,6 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT;
@@ -1151,7 +1148,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- GENERATE_INFOPLIST_FILE = YES;
+ GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -1162,7 +1159,6 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -1218,7 +1214,6 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT;
@@ -1239,7 +1234,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- GENERATE_INFOPLIST_FILE = YES;
+ GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -1250,7 +1245,6 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -1306,7 +1300,6 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT;
@@ -1321,7 +1314,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- GENERATE_INFOPLIST_FILE = YES;
+ GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -1332,7 +1325,6 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
@@ -1386,7 +1378,6 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT;
@@ -1401,7 +1392,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- GENERATE_INFOPLIST_FILE = YES;
+ GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -1412,7 +1403,6 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
@@ -1467,7 +1457,6 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 368M544MTT;
@@ -1482,7 +1471,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- GENERATE_INFOPLIST_FILE = YES;
+ GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -1493,7 +1482,6 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index c636ced8e7f9..8ed1ada207a5 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageTypeAPPLCFBundleShortVersionString
- 1.4.32
+ 1.4.33CFBundleSignature????CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.32.2
+ 1.4.33.3ITSAppUsesNonExemptEncryptionLSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index ef1ef0d998d5..268b99e55b7e 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageTypeBNDLCFBundleShortVersionString
- 1.4.32
+ 1.4.33CFBundleSignature????CFBundleVersion
- 1.4.32.2
+ 1.4.33.3
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 16439b1d24d9..4ce550d62709 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -2,10 +2,18 @@
+ CFBundleDisplayName
+ $(PRODUCT_NAME)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleName
+ $(PRODUCT_NAME)CFBundleShortVersionString
- 1.4.32
+ 1.4.33CFBundleVersion
- 1.4.32.2
+ 1.4.33.3NSExtensionNSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 776dcb544ee6..4cdf61554a6b 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1967,7 +1967,7 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2
VisionCamera: 7d13aae043ffb38b224a0f725d1e23ca9c190fe7
- Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047
+ Yoga: 13c8ef87792450193e117976337b8527b49e8c03
PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2
diff --git a/jest/setup.js b/jest/setup.js
index 38b4b55a68b3..e82bf678941d 100644
--- a/jest/setup.js
+++ b/jest/setup.js
@@ -19,7 +19,7 @@ jest.mock('@react-native-clipboard/clipboard', () => mockClipboard);
// Mock react-native-onyx storage layer because the SQLite storage layer doesn't work in jest.
// Mocking this file in __mocks__ does not work because jest doesn't support mocking files that are not directly used in the testing project,
// and we only want to mock the storage layer, not the whole Onyx module.
-jest.mock('react-native-onyx/lib/storage', () => require('react-native-onyx/lib/storage/__mocks__'));
+jest.mock('react-native-onyx/dist/storage', () => require('react-native-onyx/dist/storage/__mocks__'));
// Turn off the console logs for timing events. They are not relevant for unit tests and create a lot of noise
jest.spyOn(console, 'debug').mockImplementation((...params) => {
diff --git a/metro.config.js b/metro.config.js
index a4d0da1d85f4..2422d29aaacf 100644
--- a/metro.config.js
+++ b/metro.config.js
@@ -7,12 +7,6 @@ require('dotenv').config();
const defaultConfig = getDefaultConfig(__dirname);
const isE2ETesting = process.env.E2E_TESTING === 'true';
-
-if (isE2ETesting) {
- // eslint-disable-next-line no-console
- console.log('⚠️⚠️⚠️⚠️ Using mock API ⚠️⚠️⚠️⚠️');
-}
-
const e2eSourceExts = ['e2e.js', 'e2e.ts'];
/**
@@ -26,21 +20,6 @@ const config = {
assetExts: [...defaultAssetExts, 'lottie'],
// When we run the e2e tests we want files that have the extension e2e.js to be resolved as source files
sourceExts: [...(isE2ETesting ? e2eSourceExts : []), ...defaultSourceExts, 'jsx'],
- resolveRequest: (context, moduleName, platform) => {
- const resolution = context.resolveRequest(context, moduleName, platform);
- if (isE2ETesting && moduleName.includes('/API')) {
- const originalPath = resolution.filePath;
- const mockPath = originalPath.replace('src/libs/API.ts', 'src/libs/E2E/API.mock.ts').replace('/src/libs/API.ts/', 'src/libs/E2E/API.mock.ts');
- // eslint-disable-next-line no-console
- console.log('⚠️⚠️⚠️⚠️ Replacing resolution path', originalPath, ' => ', mockPath);
-
- return {
- ...resolution,
- filePath: mockPath,
- };
- }
- return resolution;
- },
},
};
diff --git a/package-lock.json b/package-lock.json
index 543a1366f8d6..b0a9e09c4e16 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.32-2",
+ "version": "1.4.33-3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.32-2",
+ "version": "1.4.33-3",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -40,7 +40,6 @@
"@react-ng/bounds-observer": "^0.2.1",
"@rnmapbox/maps": "^10.0.11",
"@shopify/flash-list": "^1.6.3",
- "@types/node": "^18.14.0",
"@ua/react-native-airship": "^15.3.1",
"@vue/preload-webpack-plugin": "^2.0.0",
"awesome-phonenumber": "^5.4.0",
@@ -51,7 +50,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c6bb3cfa56d12af9fa02e2bfc729646f5b64ef44",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#4a61536649cbfe49236a35bc7542b5dfd0767e4a",
"expo": "^50.0.0-preview.7",
"expo-image": "1.10.1",
"fbjs": "^3.0.2",
@@ -91,10 +90,11 @@
"react-native-image-picker": "^5.1.0",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8393b7e58df6ff65fd41f60aee8ece8822c91e2b",
"react-native-key-command": "^1.0.6",
+ "react-native-launch-arguments": "^4.0.2",
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.118",
+ "react-native-onyx": "2.0.1",
"react-native-pager-view": "6.2.2",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -169,6 +169,7 @@
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.195",
"@types/mapbox-gl": "^2.7.13",
+ "@types/node": "^20.11.5",
"@types/pusher-js": "^5.1.0",
"@types/react": "18.2.45",
"@types/react-beautiful-dnd": "^13.1.4",
@@ -240,8 +241,8 @@
"yaml": "^2.2.1"
},
"engines": {
- "node": "20.9.0",
- "npm": "10.1.0"
+ "node": "20.10.0",
+ "npm": "10.2.3"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -4001,6 +4002,26 @@
"node": ">=8"
}
},
+ "node_modules/@expo/config-plugins/node_modules/xml2js": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+ "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/@expo/config-plugins/node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/@expo/config-types": {
"version": "45.0.0",
"resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-45.0.0.tgz",
@@ -20646,9 +20667,12 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "18.17.11",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.11.tgz",
- "integrity": "sha512-r3hjHPBu+3LzbGBa8DHnr/KAeTEEOrahkcL+cZc4MaBMTM+mk8LtXR+zw+nqfjuDZZzYTYgTcpHuP+BEQk069g=="
+ "version": "20.11.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz",
+ "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
},
"node_modules/@types/node-fetch": {
"version": "2.6.4",
@@ -22072,7 +22096,7 @@
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
- "license": "BSD-3-Clause"
+ "deprecated": "Use your platform's native atob() and btoa() methods instead"
},
"node_modules/abbrev": {
"version": "1.1.1",
@@ -22139,6 +22163,34 @@
"node": ">=0.4.0"
}
},
+ "node_modules/acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "dependencies": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ }
+ },
+ "node_modules/acorn-globals/node_modules/acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-globals/node_modules/acorn-walk": {
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
+ "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -22753,6 +22805,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
"integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"is-array-buffer": "^3.0.1"
@@ -22932,6 +22985,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz",
"integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==",
+ "dev": true,
"dependencies": {
"array-buffer-byte-length": "^1.0.0",
"call-bind": "^1.0.2",
@@ -25706,10 +25760,9 @@
}
},
"node_modules/classnames": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
- "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==",
- "license": "MIT"
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.4.0.tgz",
+ "integrity": "sha512-lWxiIlphgAhTLN657pwU/ofFxsUTOWc2CRIFeoV5st0MGRJHStUnWIUJgDHxjUO/F0mXzGufXIM4Lfu/8h+MpA=="
},
"node_modules/clean-css": {
"version": "5.3.2",
@@ -27521,26 +27574,7 @@
"node_modules/cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
- "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
- "license": "MIT"
- },
- "node_modules/cssstyle": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
- "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
- "license": "MIT",
- "dependencies": {
- "cssom": "~0.3.6"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cssstyle/node_modules/cssom": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
- "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
- "license": "MIT"
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="
},
"node_modules/csstype": {
"version": "3.1.1",
@@ -27585,20 +27619,6 @@
"node": ">= 6"
}
},
- "node_modules/data-urls": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
- "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
- "license": "MIT",
- "dependencies": {
- "abab": "^2.0.6",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^11.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -27819,6 +27839,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
"dependencies": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
@@ -28329,7 +28350,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
"integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
- "license": "MIT",
+ "deprecated": "Use your platform's native DOMException instead",
"dependencies": {
"webidl-conversions": "^7.0.0"
},
@@ -28752,6 +28773,15 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.435.tgz",
"integrity": "sha512-B0CBWVFhvoQCW/XtjRzgrmqcgVWg6RXOEM/dK59+wFV93BFGR6AeNKc4OyhM+T3IhJaOOG8o/V+33Y2mwJWtzw=="
},
+ "node_modules/electron/node_modules/@types/node": {
+ "version": "18.19.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.8.tgz",
+ "integrity": "sha512-g1pZtPhsvGVTwmeVoexWZLTQaOvXwoSq//pTL0DHeNzUDrFnir4fgETdhjhIxjVnN+hKOuh98+E1eMLnUXstFg==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
"node_modules/element-resize-detector": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
@@ -29017,6 +29047,7 @@
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz",
"integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==",
+ "dev": true,
"dependencies": {
"array-buffer-byte-length": "^1.0.0",
"arraybuffer.prototype.slice": "^1.0.1",
@@ -29121,6 +29152,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3",
"has": "^1.0.3",
@@ -29144,6 +29176,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"is-callable": "^1.1.4",
@@ -31099,11 +31132,11 @@
},
"node_modules/expensify-common": {
"version": "1.0.0",
- "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#c6bb3cfa56d12af9fa02e2bfc729646f5b64ef44",
- "integrity": "sha512-a/UBkrerB57nB9xbBrFIeJG3IN0lVZV+/JWNbGMfT0FHxtg8/4sGWdC+AHqR3Bm01gwt67dd2csFferlZmTIsg==",
+ "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#4a61536649cbfe49236a35bc7542b5dfd0767e4a",
+ "integrity": "sha512-UOy3btYvKRZ1kS4etLPw6Lgfqx+yiM3GMd340K06YLasn24alKgMOmg2dqSRTApF7RltS2FjOXRddAhzgvJZ3w==",
"license": "MIT",
"dependencies": {
- "classnames": "2.3.1",
+ "classnames": "2.4.0",
"clipboard": "2.0.11",
"html-entities": "^2.4.0",
"jquery": "3.6.0",
@@ -31114,8 +31147,7 @@
"react-dom": "16.12.0",
"semver": "^7.5.2",
"simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
- "string.prototype.replaceall": "^1.0.8",
- "ua-parser-js": "^1.0.35",
+ "ua-parser-js": "^1.0.37",
"underscore": "1.13.6"
}
},
@@ -31159,9 +31191,9 @@
}
},
"node_modules/expensify-common/node_modules/ua-parser-js": {
- "version": "1.0.35",
- "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
- "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==",
+ "version": "1.0.37",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
+ "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==",
"funding": [
{
"type": "opencollective",
@@ -31170,9 +31202,12 @@
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
}
],
- "license": "MIT",
"engines": {
"node": "*"
}
@@ -32711,6 +32746,7 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
@@ -32736,6 +32772,7 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -32863,6 +32900,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
@@ -33000,6 +33038,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
"integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"define-properties": "^1.1.3"
@@ -33193,6 +33232,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -33237,6 +33277,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.1.1"
@@ -33641,18 +33682,6 @@
"wbuf": "^1.1.0"
}
},
- "node_modules/html-encoding-sniffer": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
- "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
- "license": "MIT",
- "dependencies": {
- "whatwg-encoding": "^2.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/html-entities": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz",
@@ -34754,6 +34783,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
"integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
"dependencies": {
"get-intrinsic": "^1.2.0",
"has": "^1.0.3",
@@ -34883,6 +34913,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
"integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.0",
@@ -34917,6 +34948,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"has-bigints": "^1.0.1"
@@ -34942,6 +34974,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
@@ -35044,6 +35077,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"has-tostringtag": "^1.0.0"
@@ -35294,6 +35328,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -35315,6 +35350,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"has-tostringtag": "^1.0.0"
@@ -35388,6 +35424,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
@@ -35414,6 +35451,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2"
@@ -35438,6 +35476,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"has-tostringtag": "^1.0.0"
@@ -35453,6 +35492,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.2"
@@ -35520,6 +35560,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
"integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2"
@@ -35590,6 +35631,7 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/isbinaryfile": {
@@ -36616,6 +36658,17 @@
"@types/yargs-parser": "*"
}
},
+ "node_modules/jest-environment-jsdom/node_modules/acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/jest-environment-jsdom/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -36665,6 +36718,59 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
+ "node_modules/jest-environment-jsdom/node_modules/cssstyle": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "dependencies": {
+ "cssom": "~0.3.6"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/cssstyle/node_modules/cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
+ },
+ "node_modules/jest-environment-jsdom/node_modules/data-urls": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "dependencies": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/jest-environment-jsdom/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -36674,6 +36780,72 @@
"node": ">=8"
}
},
+ "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dependencies": {
+ "whatwg-encoding": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/jsdom": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+ "dependencies": {
+ "abab": "^2.0.6",
+ "acorn": "^8.8.1",
+ "acorn-globals": "^7.0.0",
+ "cssom": "^0.5.0",
+ "cssstyle": "^2.3.0",
+ "data-urls": "^3.0.2",
+ "decimal.js": "^10.4.2",
+ "domexception": "^4.0.0",
+ "escodegen": "^2.0.0",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.2",
+ "parse5": "^7.1.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.2",
+ "w3c-xmlserializer": "^4.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0",
+ "ws": "^8.11.0",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "dependencies": {
+ "entities": "^4.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/jest-environment-jsdom/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -36686,6 +36858,67 @@
"node": ">=8"
}
},
+ "node_modules/jest-environment-jsdom/node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/w3c-xmlserializer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+ "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "dependencies": {
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jest-environment-jsdom/node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/jest-environment-node": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
@@ -38698,130 +38931,6 @@
"node": ">=12.0.0"
}
},
- "node_modules/jsdom": {
- "version": "20.0.3",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
- "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
- "license": "MIT",
- "dependencies": {
- "abab": "^2.0.6",
- "acorn": "^8.8.1",
- "acorn-globals": "^7.0.0",
- "cssom": "^0.5.0",
- "cssstyle": "^2.3.0",
- "data-urls": "^3.0.2",
- "decimal.js": "^10.4.2",
- "domexception": "^4.0.0",
- "escodegen": "^2.0.0",
- "form-data": "^4.0.0",
- "html-encoding-sniffer": "^3.0.0",
- "http-proxy-agent": "^5.0.0",
- "https-proxy-agent": "^5.0.1",
- "is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.2",
- "parse5": "^7.1.1",
- "saxes": "^6.0.0",
- "symbol-tree": "^3.2.4",
- "tough-cookie": "^4.1.2",
- "w3c-xmlserializer": "^4.0.0",
- "webidl-conversions": "^7.0.0",
- "whatwg-encoding": "^2.0.0",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^11.0.0",
- "ws": "^8.11.0",
- "xml-name-validator": "^4.0.0"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "canvas": "^2.5.0"
- },
- "peerDependenciesMeta": {
- "canvas": {
- "optional": true
- }
- }
- },
- "node_modules/jsdom/node_modules/acorn": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
- "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/jsdom/node_modules/acorn-globals": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
- "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
- "license": "MIT",
- "dependencies": {
- "acorn": "^8.1.0",
- "acorn-walk": "^8.0.2"
- }
- },
- "node_modules/jsdom/node_modules/acorn-walk": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
- "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/jsdom/node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/jsdom/node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/jsdom/node_modules/parse5": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
- "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
- "license": "MIT",
- "dependencies": {
- "entities": "^4.4.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/jsdom/node_modules/saxes": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
- "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
- "license": "ISC",
- "dependencies": {
- "xmlchars": "^2.2.0"
- },
- "engines": {
- "node": ">=v12.22.7"
- }
- },
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -42047,8 +42156,9 @@
}
},
"node_modules/nwsapi": {
- "version": "2.2.2",
- "license": "MIT"
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
+ "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
},
"node_modules/ob1": {
"version": "0.80.3",
@@ -42195,6 +42305,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -42217,6 +42328,7 @@
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
@@ -43959,8 +44071,7 @@
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
- "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
- "license": "MIT"
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"node_modules/public-encrypt": {
"version": "4.0.3",
@@ -44014,9 +44125,9 @@
}
},
"node_modules/punycode": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
- "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"engines": {
"node": ">=6"
}
@@ -44979,6 +45090,15 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
},
+ "node_modules/react-native-launch-arguments": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/react-native-launch-arguments/-/react-native-launch-arguments-4.0.2.tgz",
+ "integrity": "sha512-OaXXOG9jVrmb66cTV8wPdhKxaSVivOBKuYr8wgKCM5uAHkY5B6SkvydgJ3B/x8uGoWqtr87scSYYDm4MMU4rSg==",
+ "peerDependencies": {
+ "react": ">=16.8.1",
+ "react-native": ">=0.60.0-rc.0 <1.0.x"
+ }
+ },
"node_modules/react-native-linear-gradient": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.1.tgz",
@@ -45023,17 +45143,17 @@
}
},
"node_modules/react-native-onyx": {
- "version": "1.0.118",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz",
- "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.1.tgz",
+ "integrity": "sha512-o6QNvq91qg8hFXIhmHjBqlNXD/YZxBZSRN8Vkq7xD2NYskzxK2mLqhBdhB8yMMwe6Cd8sVUK4vlZax/JU79xYw==",
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
"underscore": "^1.13.6"
},
"engines": {
- "node": ">=16.15.1 <=20.9.0",
- "npm": ">=8.11.0 <=10.1.0"
+ "node": ">=20.10.0",
+ "npm": ">=10.2.3"
},
"peerDependencies": {
"idb-keyval": "^6.2.1",
@@ -46635,6 +46755,7 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
"integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
@@ -47344,6 +47465,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz",
"integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.0",
@@ -47383,6 +47505,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
"integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.3",
@@ -47423,6 +47546,17 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"license": "ISC"
},
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/scheduler": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
@@ -49073,26 +49207,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/string.prototype.replaceall": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.replaceall/-/string.prototype.replaceall-1.0.8.tgz",
- "integrity": "sha512-MmCXb9980obcnmbEd3guqVl6lXTxpP28zASfgAlAhlBMw5XehQeSKsdIWlAYtLxp/1GtALwex+2HyoIQtaLQwQ==",
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1",
- "get-intrinsic": "^1.2.1",
- "has-symbols": "^1.0.3",
- "is-regex": "^1.1.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/string.prototype.trim": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
"integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -49109,6 +49228,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
"integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -49122,6 +49242,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
"integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -50464,8 +50585,9 @@
}
},
"node_modules/tough-cookie": {
- "version": "4.1.2",
- "license": "BSD-3-Clause",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
@@ -50480,23 +50602,10 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
- "license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
- "node_modules/tr46": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
- "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
- "license": "MIT",
- "dependencies": {
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/traverse": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz",
@@ -50753,6 +50862,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
"integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.1",
@@ -50766,6 +50876,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
"integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
@@ -50783,6 +50894,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
"integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+ "dev": true,
"dependencies": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
@@ -50801,6 +50913,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
"integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
@@ -50894,6 +51007,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
@@ -50910,6 +51024,11 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
"node_modules/unfetch": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
@@ -51709,18 +51828,6 @@
"pbf": "^3.2.1"
}
},
- "node_modules/w3c-xmlserializer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
- "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
- "license": "MIT",
- "dependencies": {
- "xml-name-validator": "^4.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
"node_modules/wait-port": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz",
@@ -52131,7 +52238,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
@@ -52820,45 +52926,11 @@
"node": ">=0.8.0"
}
},
- "node_modules/whatwg-encoding": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
- "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
- "license": "MIT",
- "dependencies": {
- "iconv-lite": "0.6.3"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/whatwg-fetch": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
- "node_modules/whatwg-mimetype": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
- "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/whatwg-url": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
- "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
- "license": "MIT",
- "dependencies": {
- "tr46": "^3.0.0",
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/whatwg-url-without-unicode": {
"version": "8.0.0-3",
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
@@ -52899,6 +52971,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"is-bigint": "^1.0.1",
@@ -53192,9 +53265,9 @@
}
},
"node_modules/ws": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
- "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+ "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"engines": {
"node": ">=10.0.0"
},
@@ -53246,37 +53319,6 @@
"uuid": "dist/bin/uuid"
}
},
- "node_modules/xml-name-validator": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
- "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/xml2js": {
- "version": "0.4.23",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
- "license": "MIT",
- "dependencies": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/xml2js/node_modules/xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- }
- },
"node_modules/xmlbuilder": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz",
@@ -56315,6 +56357,20 @@
"requires": {
"has-flag": "^4.0.0"
}
+ },
+ "xml2js": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+ "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+ "requires": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ }
+ },
+ "xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
}
}
},
@@ -68307,9 +68363,12 @@
"dev": true
},
"@types/node": {
- "version": "18.17.11",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.11.tgz",
- "integrity": "sha512-r3hjHPBu+3LzbGBa8DHnr/KAeTEEOrahkcL+cZc4MaBMTM+mk8LtXR+zw+nqfjuDZZzYTYgTcpHuP+BEQk069g=="
+ "version": "20.11.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz",
+ "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==",
+ "requires": {
+ "undici-types": "~5.26.4"
+ }
},
"@types/node-fetch": {
"version": "2.6.4",
@@ -69434,6 +69493,27 @@
}
}
},
+ "acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "requires": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg=="
+ },
+ "acorn-walk": {
+ "version": "8.3.1",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
+ "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw=="
+ }
+ }
+ },
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -69896,6 +69976,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
"integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"is-array-buffer": "^3.0.1"
@@ -70017,6 +70098,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz",
"integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==",
+ "dev": true,
"requires": {
"array-buffer-byte-length": "^1.0.0",
"call-bind": "^1.0.2",
@@ -72054,9 +72136,9 @@
}
},
"classnames": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
- "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.4.0.tgz",
+ "integrity": "sha512-lWxiIlphgAhTLN657pwU/ofFxsUTOWc2CRIFeoV5st0MGRJHStUnWIUJgDHxjUO/F0mXzGufXIM4Lfu/8h+MpA=="
},
"clean-css": {
"version": "5.3.2",
@@ -73360,21 +73442,6 @@
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="
},
- "cssstyle": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
- "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
- "requires": {
- "cssom": "~0.3.6"
- },
- "dependencies": {
- "cssom": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
- "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
- }
- }
- },
"csstype": {
"version": "3.1.1"
},
@@ -73408,16 +73475,6 @@
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og=="
},
- "data-urls": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
- "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
- "requires": {
- "abab": "^2.0.6",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^11.0.0"
- }
- },
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -73565,6 +73622,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
"requires": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
@@ -74053,6 +74111,17 @@
"@electron/get": "^2.0.0",
"@types/node": "^18.11.18",
"extract-zip": "^2.0.1"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "18.19.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.8.tgz",
+ "integrity": "sha512-g1pZtPhsvGVTwmeVoexWZLTQaOvXwoSq//pTL0DHeNzUDrFnir4fgETdhjhIxjVnN+hKOuh98+E1eMLnUXstFg==",
+ "dev": true,
+ "requires": {
+ "undici-types": "~5.26.4"
+ }
+ }
}
},
"electron-builder": {
@@ -74451,6 +74520,7 @@
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz",
"integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==",
+ "dev": true,
"requires": {
"array-buffer-byte-length": "^1.0.0",
"arraybuffer.prototype.slice": "^1.0.1",
@@ -74544,6 +74614,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
"requires": {
"get-intrinsic": "^1.1.3",
"has": "^1.0.3",
@@ -74563,6 +74634,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
@@ -75952,11 +76024,11 @@
}
},
"expensify-common": {
- "version": "git+ssh://git@github.com/Expensify/expensify-common.git#c6bb3cfa56d12af9fa02e2bfc729646f5b64ef44",
- "integrity": "sha512-a/UBkrerB57nB9xbBrFIeJG3IN0lVZV+/JWNbGMfT0FHxtg8/4sGWdC+AHqR3Bm01gwt67dd2csFferlZmTIsg==",
- "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#c6bb3cfa56d12af9fa02e2bfc729646f5b64ef44",
+ "version": "git+ssh://git@github.com/Expensify/expensify-common.git#4a61536649cbfe49236a35bc7542b5dfd0767e4a",
+ "integrity": "sha512-UOy3btYvKRZ1kS4etLPw6Lgfqx+yiM3GMd340K06YLasn24alKgMOmg2dqSRTApF7RltS2FjOXRddAhzgvJZ3w==",
+ "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#4a61536649cbfe49236a35bc7542b5dfd0767e4a",
"requires": {
- "classnames": "2.3.1",
+ "classnames": "2.4.0",
"clipboard": "2.0.11",
"html-entities": "^2.4.0",
"jquery": "3.6.0",
@@ -75967,8 +76039,7 @@
"react-dom": "16.12.0",
"semver": "^7.5.2",
"simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
- "string.prototype.replaceall": "^1.0.8",
- "ua-parser-js": "^1.0.35",
+ "ua-parser-js": "^1.0.37",
"underscore": "1.13.6"
},
"dependencies": {
@@ -76003,9 +76074,9 @@
}
},
"ua-parser-js": {
- "version": "1.0.35",
- "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
- "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA=="
+ "version": "1.0.37",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
+ "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ=="
}
}
},
@@ -77137,6 +77208,7 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
@@ -77153,7 +77225,8 @@
"functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true
},
"gauge": {
"version": "3.0.2",
@@ -77241,6 +77314,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.1"
@@ -77335,6 +77409,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
"integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
"requires": {
"define-properties": "^1.1.3"
}
@@ -77466,7 +77541,8 @@
"has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
- "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true
},
"has-flag": {
"version": "3.0.0",
@@ -77497,6 +77573,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
"requires": {
"get-intrinsic": "^1.1.1"
}
@@ -77790,14 +77867,6 @@
"wbuf": "^1.1.0"
}
},
- "html-encoding-sniffer": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
- "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
- "requires": {
- "whatwg-encoding": "^2.0.0"
- }
- },
"html-entities": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz",
@@ -78559,6 +78628,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
"integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
"requires": {
"get-intrinsic": "^1.2.0",
"has": "^1.0.3",
@@ -78644,6 +78714,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
"integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.0",
@@ -78668,6 +78739,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
"requires": {
"has-bigints": "^1.0.1"
}
@@ -78685,6 +78757,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
@@ -78740,6 +78813,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
@@ -78895,7 +78969,8 @@
"is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
- "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA=="
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "dev": true
},
"is-number": {
"version": "7.0.0",
@@ -78906,6 +78981,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
@@ -78951,6 +79027,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
@@ -78966,6 +79043,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2"
}
@@ -78979,6 +79057,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
@@ -78987,6 +79066,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
"requires": {
"has-symbols": "^1.0.2"
}
@@ -79027,6 +79107,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
"integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2"
}
@@ -79070,7 +79151,8 @@
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
},
"isbinaryfile": {
"version": "5.0.0",
@@ -79840,6 +79922,11 @@
"@types/yargs-parser": "*"
}
},
+ "acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg=="
+ },
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -79870,11 +79957,100 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
+ "cssstyle": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "requires": {
+ "cssom": "~0.3.6"
+ },
+ "dependencies": {
+ "cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
+ }
+ }
+ },
+ "data-urls": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "requires": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0"
+ }
+ },
+ "entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
+ },
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ },
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
+ "html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "requires": {
+ "whatwg-encoding": "^2.0.0"
+ }
+ },
+ "jsdom": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+ "requires": {
+ "abab": "^2.0.6",
+ "acorn": "^8.8.1",
+ "acorn-globals": "^7.0.0",
+ "cssom": "^0.5.0",
+ "cssstyle": "^2.3.0",
+ "data-urls": "^3.0.2",
+ "decimal.js": "^10.4.2",
+ "domexception": "^4.0.0",
+ "escodegen": "^2.0.0",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.2",
+ "parse5": "^7.1.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.2",
+ "w3c-xmlserializer": "^4.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0",
+ "ws": "^8.11.0",
+ "xml-name-validator": "^4.0.0"
+ }
+ },
+ "parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "requires": {
+ "entities": "^4.4.0"
+ }
+ },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -79882,6 +80058,49 @@
"requires": {
"has-flag": "^4.0.0"
}
+ },
+ "tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "requires": {
+ "punycode": "^2.1.1"
+ }
+ },
+ "w3c-xmlserializer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+ "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "requires": {
+ "xml-name-validator": "^4.0.0"
+ }
+ },
+ "whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "requires": {
+ "iconv-lite": "0.6.3"
+ }
+ },
+ "whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="
+ },
+ "whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "requires": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ }
+ },
+ "xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="
}
}
},
@@ -81296,91 +81515,6 @@
"integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==",
"dev": true
},
- "jsdom": {
- "version": "20.0.3",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
- "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
- "requires": {
- "abab": "^2.0.6",
- "acorn": "^8.8.1",
- "acorn-globals": "^7.0.0",
- "cssom": "^0.5.0",
- "cssstyle": "^2.3.0",
- "data-urls": "^3.0.2",
- "decimal.js": "^10.4.2",
- "domexception": "^4.0.0",
- "escodegen": "^2.0.0",
- "form-data": "^4.0.0",
- "html-encoding-sniffer": "^3.0.0",
- "http-proxy-agent": "^5.0.0",
- "https-proxy-agent": "^5.0.1",
- "is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.2",
- "parse5": "^7.1.1",
- "saxes": "^6.0.0",
- "symbol-tree": "^3.2.4",
- "tough-cookie": "^4.1.2",
- "w3c-xmlserializer": "^4.0.0",
- "webidl-conversions": "^7.0.0",
- "whatwg-encoding": "^2.0.0",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^11.0.0",
- "ws": "^8.11.0",
- "xml-name-validator": "^4.0.0"
- },
- "dependencies": {
- "acorn": {
- "version": "8.10.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
- "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw=="
- },
- "acorn-globals": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
- "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
- "requires": {
- "acorn": "^8.1.0",
- "acorn-walk": "^8.0.2"
- }
- },
- "acorn-walk": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
- "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA=="
- },
- "entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
- },
- "form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- }
- },
- "parse5": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
- "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
- "requires": {
- "entities": "^4.4.0"
- }
- },
- "saxes": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
- "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
- "requires": {
- "xmlchars": "^2.2.0"
- }
- }
- }
- },
"jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -83729,7 +83863,9 @@
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ=="
},
"nwsapi": {
- "version": "2.2.2"
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
+ "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
},
"ob1": {
"version": "0.80.3",
@@ -83833,7 +83969,8 @@
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
},
"object-visit": {
"version": "1.0.1",
@@ -83848,6 +83985,7 @@
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -85111,9 +85249,9 @@
}
},
"punycode": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
- "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
"pusher-js": {
"version": "8.3.0",
@@ -85884,6 +86022,12 @@
}
}
},
+ "react-native-launch-arguments": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/react-native-launch-arguments/-/react-native-launch-arguments-4.0.2.tgz",
+ "integrity": "sha512-OaXXOG9jVrmb66cTV8wPdhKxaSVivOBKuYr8wgKCM5uAHkY5B6SkvydgJ3B/x8uGoWqtr87scSYYDm4MMU4rSg==",
+ "requires": {}
+ },
"react-native-linear-gradient": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.1.tgz",
@@ -85906,9 +86050,9 @@
}
},
"react-native-onyx": {
- "version": "1.0.118",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.118.tgz",
- "integrity": "sha512-w54jO+Bpu1ElHsrxZXIIpcBqNkrUvuVCQmwWdfOW5LvO4UwsPSwmMxzExbUZ4ip+7CROmm10IgXFaAoyfeYSVQ==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.1.tgz",
+ "integrity": "sha512-o6QNvq91qg8hFXIhmHjBqlNXD/YZxBZSRN8Vkq7xD2NYskzxK2mLqhBdhB8yMMwe6Cd8sVUK4vlZax/JU79xYw==",
"requires": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -86929,6 +87073,7 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
"integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
@@ -87437,6 +87582,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz",
"integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.0",
@@ -87468,6 +87614,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
"integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.3",
@@ -87502,6 +87649,14 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
+ "saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "requires": {
+ "xmlchars": "^2.2.0"
+ }
+ },
"scheduler": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
@@ -88749,23 +88904,11 @@
"es-abstract": "^1.19.1"
}
},
- "string.prototype.replaceall": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.replaceall/-/string.prototype.replaceall-1.0.8.tgz",
- "integrity": "sha512-MmCXb9980obcnmbEd3guqVl6lXTxpP28zASfgAlAhlBMw5XehQeSKsdIWlAYtLxp/1GtALwex+2HyoIQtaLQwQ==",
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1",
- "get-intrinsic": "^1.2.1",
- "has-symbols": "^1.0.3",
- "is-regex": "^1.1.4"
- }
- },
"string.prototype.trim": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
"integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -88776,6 +88919,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
"integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -88786,6 +88930,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
"integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -89753,7 +89898,9 @@
"dev": true
},
"tough-cookie": {
- "version": "4.1.2",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
@@ -89768,14 +89915,6 @@
}
}
},
- "tr46": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
- "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
- "requires": {
- "punycode": "^2.1.1"
- }
- },
"traverse": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz",
@@ -89954,6 +90093,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
"integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.2.1",
@@ -89964,6 +90104,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
"integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
@@ -89975,6 +90116,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
"integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+ "dev": true,
"requires": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
@@ -89987,6 +90129,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
"integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
@@ -90046,6 +90189,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
"requires": {
"call-bind": "^1.0.2",
"has-bigints": "^1.0.2",
@@ -90058,6 +90202,11 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
+ "undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
"unfetch": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
@@ -90623,14 +90772,6 @@
"pbf": "^3.2.1"
}
},
- "w3c-xmlserializer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
- "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
- "requires": {
- "xml-name-validator": "^4.0.0"
- }
- },
"wait-port": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz",
@@ -91410,33 +91551,11 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
- "whatwg-encoding": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
- "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
- "requires": {
- "iconv-lite": "0.6.3"
- }
- },
"whatwg-fetch": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
- "whatwg-mimetype": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
- "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="
- },
- "whatwg-url": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
- "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
- "requires": {
- "tr46": "^3.0.0",
- "webidl-conversions": "^7.0.0"
- }
- },
"whatwg-url-without-unicode": {
"version": "8.0.0-3",
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
@@ -91466,6 +91585,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
"requires": {
"is-bigint": "^1.0.1",
"is-boolean-object": "^1.1.0",
@@ -91684,9 +91804,9 @@
}
},
"ws": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
- "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+ "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"requires": {}
},
"x-default-browser": {
@@ -91714,27 +91834,6 @@
}
}
},
- "xml-name-validator": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
- "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="
- },
- "xml2js": {
- "version": "0.4.23",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
- "requires": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- },
- "dependencies": {
- "xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
- }
- }
- },
"xmlbuilder": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz",
diff --git a/package.json b/package.json
index 8ceac3912660..a57ab432d5fb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.32-2",
+ "version": "1.4.33-3",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
@@ -88,7 +88,6 @@
"@react-ng/bounds-observer": "^0.2.1",
"@rnmapbox/maps": "^10.0.11",
"@shopify/flash-list": "^1.6.3",
- "@types/node": "^18.14.0",
"@ua/react-native-airship": "^15.3.1",
"@vue/preload-webpack-plugin": "^2.0.0",
"awesome-phonenumber": "^5.4.0",
@@ -99,7 +98,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c6bb3cfa56d12af9fa02e2bfc729646f5b64ef44",
+ "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#4a61536649cbfe49236a35bc7542b5dfd0767e4a",
"expo": "^50.0.0-preview.7",
"expo-image": "1.10.1",
"fbjs": "^3.0.2",
@@ -139,10 +138,11 @@
"react-native-image-picker": "^5.1.0",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8393b7e58df6ff65fd41f60aee8ece8822c91e2b",
"react-native-key-command": "^1.0.6",
+ "react-native-launch-arguments": "^4.0.2",
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "1.0.118",
+ "react-native-onyx": "2.0.1",
"react-native-pager-view": "6.2.2",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -217,6 +217,7 @@
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.195",
"@types/mapbox-gl": "^2.7.13",
+ "@types/node": "^20.11.5",
"@types/pusher-js": "^5.1.0",
"@types/react": "18.2.45",
"@types/react-beautiful-dnd": "^13.1.4",
@@ -315,7 +316,7 @@
]
},
"engines": {
- "node": "20.9.0",
- "npm": "10.1.0"
+ "node": "20.10.0",
+ "npm": "10.2.3"
}
}
diff --git a/patches/react-native+0.73.2+001+NumberOfLines.patch b/patches/react-native+0.73.2+001+NumberOfLines.patch
deleted file mode 100644
index c9ce92f8e1ef..000000000000
--- a/patches/react-native+0.73.2+001+NumberOfLines.patch
+++ /dev/null
@@ -1,968 +0,0 @@
-diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
-index 55b770d..4073836 100644
---- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
-+++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
-@@ -179,6 +179,13 @@ export type NativeProps = $ReadOnly<{|
- */
- numberOfLines?: ?Int32,
-
-+ /**
-+ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to
-+ * `true` to be able to fill the lines.
-+ * @platform android
-+ */
-+ maximumNumberOfLines?: ?Int32,
-+
- /**
- * When `false`, if there is a small amount of space available around a text input
- * (e.g. landscape orientation on a phone), the OS may choose to have the user edit
-diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
-index 88d3cc8..664d37d 100644
---- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
-+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
-@@ -144,6 +144,8 @@ const RCTTextInputViewConfig = {
- placeholder: true,
- autoCorrect: true,
- multiline: true,
-+ numberOfLines: true,
-+ maximumNumberOfLines: true,
- textContentType: true,
- maxLength: true,
- autoCapitalize: true,
-diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
-index 2c0c099..5cb6bf1 100644
---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
-+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
-@@ -695,11 +695,29 @@ export interface TextInputProps
- */
- maxLength?: number | undefined;
-
-+ /**
-+ * Sets the maximum number of lines for a TextInput.
-+ * Use it with multiline set to true to be able to fill the lines.
-+ */
-+ maxNumberOfLines?: number | undefined;
-+
- /**
- * If true, the text input can be multiple lines. The default value is false.
- */
- multiline?: boolean | undefined;
-
-+ /**
-+ * Sets the number of lines for a TextInput.
-+ * Use it with multiline set to true to be able to fill the lines.
-+ */
-+ numberOfLines?: number | undefined;
-+
-+ /**
-+ * Sets the number of rows for a TextInput.
-+ * Use it with multiline set to true to be able to fill the lines.
-+ */
-+ rows?: number | undefined;
-+
- /**
- * Callback that is called when the text input is blurred
- */
-diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
-index 9adbfe9..dc52051 100644
---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
-+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
-@@ -366,26 +366,12 @@ type AndroidProps = $ReadOnly<{|
- */
- inlineImagePadding?: ?number,
-
-- /**
-- * Sets the number of lines for a `TextInput`. Use it with multiline set to
-- * `true` to be able to fill the lines.
-- * @platform android
-- */
-- numberOfLines?: ?number,
--
- /**
- * Sets the return key to the label. Use it instead of `returnKeyType`.
- * @platform android
- */
- returnKeyLabel?: ?string,
-
-- /**
-- * Sets the number of rows for a `TextInput`. Use it with multiline set to
-- * `true` to be able to fill the lines.
-- * @platform android
-- */
-- rows?: ?number,
--
- /**
- * When `false`, it will prevent the soft keyboard from showing when the field is focused.
- * Defaults to `true`.
-@@ -680,12 +666,24 @@ export type Props = $ReadOnly<{|
- */
- maxLength?: ?number,
-
-+ /**
-+ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to
-+ * `true` to be able to fill the lines.
-+ */
-+ maxNumberOfLines?: ?number,
-+
- /**
- * If `true`, the text input can be multiple lines.
- * The default value is `false`.
- */
- multiline?: ?boolean,
-
-+ /**
-+ * Sets the number of lines for a `TextInput`. Use it with multiline set to
-+ * `true` to be able to fill the lines.
-+ */
-+ numberOfLines?: ?number,
-+
- /**
- * Callback that is called when the text input is blurred.
- */
-@@ -847,6 +845,13 @@ export type Props = $ReadOnly<{|
- */
- returnKeyType?: ?ReturnKeyType,
-
-+ /**
-+ * Sets the number of rows for a `TextInput`. Use it with multiline set to
-+ * `true` to be able to fill the lines.
-+ */
-+ rows?: ?number,
-+
-+
- /**
- * If `true`, the text input obscures the text entered so that sensitive text
- * like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'.
-diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
-index 481938f..3ce7422 100644
---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
-+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
-@@ -413,7 +413,6 @@ type AndroidProps = $ReadOnly<{|
- /**
- * Sets the number of lines for a `TextInput`. Use it with multiline set to
- * `true` to be able to fill the lines.
-- * @platform android
- */
- numberOfLines?: ?number,
-
-@@ -426,10 +425,15 @@ type AndroidProps = $ReadOnly<{|
- /**
- * Sets the number of rows for a `TextInput`. Use it with multiline set to
- * `true` to be able to fill the lines.
-- * @platform android
- */
- rows?: ?number,
-
-+ /**
-+ * Sets the maximum number of lines the TextInput can have.
-+ */
-+ maxNumberOfLines?: ?number,
-+
-+
- /**
- * When `false`, it will prevent the soft keyboard from showing when the field is focused.
- * Defaults to `true`.
-@@ -1102,6 +1106,9 @@ function InternalTextInput(props: Props): React.Node {
- accessibilityState,
- id,
- tabIndex,
-+ rows,
-+ numberOfLines,
-+ maxNumberOfLines,
- selection: propsSelection,
- ...otherProps
- } = props;
-@@ -1460,6 +1467,8 @@ function InternalTextInput(props: Props): React.Node {
- focusable={tabIndex !== undefined ? !tabIndex : focusable}
- mostRecentEventCount={mostRecentEventCount}
- nativeID={id ?? props.nativeID}
-+ numberOfLines={props.rows ?? props.numberOfLines}
-+ maximumNumberOfLines={maxNumberOfLines}
- onBlur={_onBlur}
- onKeyPressSync={props.unstable_onKeyPressSync}
- onChange={_onChange}
-@@ -1515,6 +1524,7 @@ function InternalTextInput(props: Props): React.Node {
- mostRecentEventCount={mostRecentEventCount}
- nativeID={id ?? props.nativeID}
- numberOfLines={props.rows ?? props.numberOfLines}
-+ maximumNumberOfLines={maxNumberOfLines}
- onBlur={_onBlur}
- onChange={_onChange}
- onFocus={_onFocus}
-diff --git a/node_modules/react-native/Libraries/Text/Text.js b/node_modules/react-native/Libraries/Text/Text.js
-index d737ccc..beee7ce 100644
---- a/node_modules/react-native/Libraries/Text/Text.js
-+++ b/node_modules/react-native/Libraries/Text/Text.js
-@@ -17,7 +17,11 @@ import flattenStyle from '../StyleSheet/flattenStyle';
- import processColor from '../StyleSheet/processColor';
- import Platform from '../Utilities/Platform';
- import TextAncestor from './TextAncestor';
--import {NativeText, NativeVirtualText} from './TextNativeComponent';
-+import {
-+ CONTAINS_MAX_NUMBER_OF_LINES_RENAME,
-+ NativeText,
-+ NativeVirtualText,
-+} from './TextNativeComponent';
- import * as React from 'react';
- import {useContext, useMemo, useState} from 'react';
-
-@@ -56,6 +60,7 @@ const Text: React.AbstractComponent<
- onStartShouldSetResponder,
- pressRetentionOffset,
- suppressHighlighting,
-+ numberOfLines,
- ...restProps
- } = props;
-
-@@ -195,14 +200,34 @@ const Text: React.AbstractComponent<
- }
- }
-
-- let numberOfLines = restProps.numberOfLines;
-+ let numberOfLinesValue = numberOfLines;
- if (numberOfLines != null && !(numberOfLines >= 0)) {
- console.error(
- `'numberOfLines' in must be a non-negative number, received: ${numberOfLines}. The value will be set to 0.`,
- );
-- numberOfLines = 0;
-+ numberOfLinesValue = 0;
- }
-
-+ const numberOfLinesProps = useMemo((): {
-+ maximumNumberOfLines?: ?number,
-+ numberOfLines?: ?number,
-+ } => {
-+ // FIXME: Current logic is breaking all Text components.
-+ // if (CONTAINS_MAX_NUMBER_OF_LINES_RENAME) {
-+ // return {
-+ // maximumNumberOfLines: numberOfLinesValue,
-+ // };
-+ // } else {
-+ // return {
-+ // numberOfLines: numberOfLinesValue,
-+ // };
-+ // }
-+ return {
-+ maximumNumberOfLines: numberOfLinesValue,
-+ };
-+ }, [numberOfLinesValue]);
-+
-+
- const hasTextAncestor = useContext(TextAncestor);
-
- const _accessible = Platform.select({
-@@ -251,7 +276,6 @@ const Text: React.AbstractComponent<
- isHighlighted={isHighlighted}
- isPressable={isPressable}
- nativeID={id ?? nativeID}
-- numberOfLines={numberOfLines}
- ref={forwardedRef}
- selectable={_selectable}
- selectionColor={selectionColor}
-@@ -262,6 +286,7 @@ const Text: React.AbstractComponent<
-
- #import
-+#import
-+#import
-
- @implementation RCTMultilineTextInputViewManager
-
-@@ -17,8 +19,21 @@ - (UIView *)view
- return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge];
- }
-
-+- (RCTShadowView *)shadowView
-+{
-+ RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView];
-+
-+ shadowView.maximumNumberOfLines = 0;
-+ shadowView.exactNumberOfLines = 0;
-+
-+ return shadowView;
-+}
-+
- #pragma mark - Multiline (aka TextView) specific properties
-
- RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes)
-
-+RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
-+RCT_REMAP_SHADOW_PROPERTY(numberOfLines, exactNumberOfLines, NSInteger)
-+
- @end
-diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h
-index 8f4cf7e..6238ebc 100644
---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h
-+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h
-@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
- @property (nonatomic, copy, nullable) NSString *text;
- @property (nonatomic, copy, nullable) NSString *placeholder;
- @property (nonatomic, assign) NSInteger maximumNumberOfLines;
-+@property (nonatomic, assign) NSInteger exactNumberOfLines;
- @property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
-
- - (void)uiManagerWillPerformMounting;
-diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm
-index 1f06b79..48172ce 100644
---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm
-+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm
-@@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText
-
- - (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
- {
-- NSAttributedString *attributedText = [self measurableAttributedText];
-+ NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy];
-+
-+ /*
-+ * The block below is responsible for setting the exact height of the view in lines
-+ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines
-+ * prop and then add random lines at the front. However, they are only used for layout
-+ * so they are not visible on the screen.
-+ */
-+ if (self.exactNumberOfLines) {
-+ NSMutableString *newLines = [NSMutableString stringWithCapacity:self.exactNumberOfLines];
-+ for (NSUInteger i = 0UL; i < self.exactNumberOfLines; ++i) {
-+ [newLines appendString:@"\n"];
-+ }
-+ [attributedText insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:self.textAttributes.effectiveTextAttributes] atIndex:0];
-+ _maximumNumberOfLines = self.exactNumberOfLines;
-+ }
-
- if (!_textStorage) {
- _textContainer = [NSTextContainer new];
-diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm
-index 413ac42..56d039c 100644
---- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm
-+++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.mm
-@@ -19,6 +19,7 @@ - (RCTShadowView *)shadowView
- RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView];
-
- shadowView.maximumNumberOfLines = 1;
-+ shadowView.exactNumberOfLines = 0;
-
- return shadowView;
- }
-diff --git a/node_modules/react-native/Libraries/Text/TextNativeComponent.js b/node_modules/react-native/Libraries/Text/TextNativeComponent.js
-index 0d59904..3216e43 100644
---- a/node_modules/react-native/Libraries/Text/TextNativeComponent.js
-+++ b/node_modules/react-native/Libraries/Text/TextNativeComponent.js
-@@ -9,6 +9,7 @@
- */
-
- import {createViewConfig} from '../NativeComponent/ViewConfig';
-+import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes';
- import UIManager from '../ReactNative/UIManager';
- import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass';
- import {type HostComponent} from '../Renderer/shims/ReactNativeTypes';
-@@ -18,6 +19,7 @@ import {type TextProps} from './TextProps';
-
- type NativeTextProps = $ReadOnly<{
- ...TextProps,
-+ maximumNumberOfLines?: ?number,
- isHighlighted?: ?boolean,
- selectionColor?: ?ProcessedColorValue,
- onClick?: ?(event: PressEvent) => mixed,
-@@ -31,7 +33,7 @@ const textViewConfig = {
- validAttributes: {
- isHighlighted: true,
- isPressable: true,
-- numberOfLines: true,
-+ maximumNumberOfLines: true,
- ellipsizeMode: true,
- allowFontScaling: true,
- dynamicTypeRamp: true,
-@@ -73,6 +75,12 @@ export const NativeText: HostComponent =
- createViewConfig(textViewConfig),
- ): any);
-
-+const jestIsDefined = typeof jest !== 'undefined';
-+export const CONTAINS_MAX_NUMBER_OF_LINES_RENAME: boolean = jestIsDefined
-+ ? true
-+ : getNativeComponentAttributes('RCTText')?.NativeProps
-+ ?.maximumNumberOfLines === 'number';
-+
- export const NativeVirtualText: HostComponent =
- !global.RN$Bridgeless && !UIManager.hasViewManagerConfig('RCTVirtualText')
- ? NativeText
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java
-index 8cab407..ad5fa96 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java
-@@ -12,5 +12,6 @@ public class ViewDefaults {
-
- public static final float FONT_SIZE_SP = 14.0f;
- public static final int LINE_HEIGHT = 0;
-- public static final int NUMBER_OF_LINES = Integer.MAX_VALUE;
-+ public static final int NUMBER_OF_LINES = -1;
-+ public static final int MAXIMUM_NUMBER_OF_LINES = Integer.MAX_VALUE;
- }
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
-index fa6eae3..f524753 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
-@@ -96,6 +96,7 @@ public class ViewProps {
- public static final String LETTER_SPACING = "letterSpacing";
- public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing";
- public static final String NUMBER_OF_LINES = "numberOfLines";
-+ public static final String MAXIMUM_NUMBER_OF_LINES = "maximumNumberOfLines";
- public static final String ELLIPSIZE_MODE = "ellipsizeMode";
- public static final String ADJUSTS_FONT_SIZE_TO_FIT = "adjustsFontSizeToFit";
- public static final String MINIMUM_FONT_SCALE = "minimumFontScale";
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java
-index d2c2d6e..e4dec5d 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java
-@@ -311,6 +311,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
- protected @Nullable Role mRole = null;
-
- protected int mNumberOfLines = UNSET;
-+ protected int mMaxNumberOfLines = UNSET;
- protected int mTextAlign = Gravity.NO_GRAVITY;
- protected int mTextBreakStrategy =
- (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY;
-@@ -395,6 +396,12 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
- markUpdated();
- }
-
-+ @ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = UNSET)
-+ public void setMaxNumberOfLines(int numberOfLines) {
-+ mMaxNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines;
-+ markUpdated();
-+ }
-+
- @ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN)
- public void setLineHeight(float lineHeight) {
- mTextAttributes.setLineHeight(lineHeight);
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java
-index f683c24..b5f6f7d 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java
-@@ -49,8 +49,8 @@ public abstract class ReactTextAnchorViewManager minimumFontSize
-- && (mNumberOfLines != UNSET && layout.getLineCount() > mNumberOfLines
-+ && (mMaxNumberOfLines != UNSET && layout.getLineCount() > mMaxNumberOfLines
- || heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) {
- // TODO: We could probably use a smarter algorithm here. This will require 0(n)
- // measurements
-@@ -124,9 +124,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode {
- }
-
- final int lineCount =
-- mNumberOfLines == UNSET
-+ mMaxNumberOfLines == UNSET
- ? layout.getLineCount()
-- : Math.min(mNumberOfLines, layout.getLineCount());
-+ : Math.min(mMaxNumberOfLines, layout.getLineCount());
-
- // Instead of using `layout.getWidth()` (which may yield a significantly larger width for
- // text that is wrapping), compute width using the longest line.
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
-index 4af5729..64474af 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
-@@ -90,7 +90,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
-
- mReactBackgroundManager = new ReactViewBackgroundManager(this);
-
-- mNumberOfLines = ViewDefaults.NUMBER_OF_LINES;
-+ mNumberOfLines = ViewDefaults.MAXIMUM_NUMBER_OF_LINES;
- mAdjustsFontSizeToFit = false;
- mLinkifyMaskType = 0;
- mNotifyOnInlineViewLayout = false;
-@@ -579,7 +579,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
- }
-
- public void setNumberOfLines(int numberOfLines) {
-- mNumberOfLines = numberOfLines == 0 ? ViewDefaults.NUMBER_OF_LINES : numberOfLines;
-+ mNumberOfLines = numberOfLines == 0 ? ViewDefaults.MAXIMUM_NUMBER_OF_LINES : numberOfLines;
- setMaxLines(mNumberOfLines);
- }
-
-@@ -621,7 +621,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
- public void updateView() {
- @Nullable
- TextUtils.TruncateAt ellipsizeLocation =
-- mNumberOfLines == ViewDefaults.NUMBER_OF_LINES || mAdjustsFontSizeToFit
-+ mNumberOfLines == ViewDefaults.MAXIMUM_NUMBER_OF_LINES || mAdjustsFontSizeToFit
- ? null
- : mEllipsizeLocation;
- setEllipsize(ellipsizeLocation);
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
-index ffd5b2f..e9a8b0b 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java
-@@ -19,6 +19,7 @@ import android.text.SpannableStringBuilder;
- import android.text.Spanned;
- import android.text.StaticLayout;
- import android.text.TextPaint;
-+import android.text.TextUtils;
- import android.util.LayoutDirection;
- import android.util.LruCache;
- import android.view.View;
-@@ -68,6 +69,7 @@ public class TextLayoutManager {
- private static final String TEXT_BREAK_STRATEGY_KEY = "textBreakStrategy";
- private static final String HYPHENATION_FREQUENCY_KEY = "android_hyphenationFrequency";
- private static final String MAXIMUM_NUMBER_OF_LINES_KEY = "maximumNumberOfLines";
-+ private static final String NUMBER_OF_LINES_KEY = "numberOfLines";
- private static final LruCache sSpannableCache =
- new LruCache<>(spannableCacheSize);
- private static final ConcurrentHashMap sTagToSpannableCache =
-@@ -395,6 +397,48 @@ public class TextLayoutManager {
- ? paragraphAttributes.getInt(MAXIMUM_NUMBER_OF_LINES_KEY)
- : UNSET;
-
-+ int numberOfLines =
-+ paragraphAttributes.hasKey(NUMBER_OF_LINES_KEY)
-+ ? paragraphAttributes.getInt(NUMBER_OF_LINES_KEY)
-+ : UNSET;
-+
-+ int lines = layout.getLineCount();
-+ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines >= lines && text.length() > 0) {
-+ int numberOfEmptyLines = numberOfLines - lines;
-+ SpannableStringBuilder ssb = new SpannableStringBuilder();
-+
-+ // for some reason a newline on end causes issues with computing height so we add a character
-+ if (text.toString().endsWith("\n")) {
-+ ssb.append("A");
-+ }
-+
-+ for (int i = 0; i < numberOfEmptyLines; ++i) {
-+ ssb.append("\nA");
-+ }
-+
-+ Object[] spans = text.getSpans(0, 0, Object.class);
-+ for (Object span : spans) { // It's possible we need to set exl-exl
-+ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span));
-+ };
-+
-+ text = new SpannableStringBuilder(TextUtils.concat(text, ssb));
-+ boring = null;
-+ layout = createLayout(
-+ text,
-+ boring,
-+ width,
-+ widthYogaMeasureMode,
-+ includeFontPadding,
-+ textBreakStrategy,
-+ hyphenationFrequency);
-+ }
-+
-+
-+ if (numberOfLines != UNSET && numberOfLines != 0) {
-+ maximumNumberOfLines = numberOfLines;
-+ }
-+
-+
- int calculatedLineCount =
- maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
- ? layout.getLineCount()
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java
-index 8cd5764..35a2e9e 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java
-@@ -20,6 +20,7 @@ import android.text.SpannableStringBuilder;
- import android.text.Spanned;
- import android.text.StaticLayout;
- import android.text.TextPaint;
-+import android.text.TextUtils;
- import android.util.LayoutDirection;
- import android.util.LruCache;
- import android.view.View;
-@@ -66,6 +67,7 @@ public class TextLayoutManagerMapBuffer {
- public static final short PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3;
- public static final short PA_KEY_INCLUDE_FONT_PADDING = 4;
- public static final short PA_KEY_HYPHENATION_FREQUENCY = 5;
-+ public static final short PA_KEY_NUMBER_OF_LINES = 6;
-
- private static final boolean ENABLE_MEASURE_LOGGING = ReactBuildConfig.DEBUG && false;
-
-@@ -417,6 +419,46 @@ public class TextLayoutManagerMapBuffer {
- ? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES)
- : UNSET;
-
-+ int numberOfLines =
-+ paragraphAttributes.contains(PA_KEY_NUMBER_OF_LINES)
-+ ? paragraphAttributes.getInt(PA_KEY_NUMBER_OF_LINES)
-+ : UNSET;
-+
-+ int lines = layout.getLineCount();
-+ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines > lines && text.length() > 0) {
-+ int numberOfEmptyLines = numberOfLines - lines;
-+ SpannableStringBuilder ssb = new SpannableStringBuilder();
-+
-+ // for some reason a newline on end causes issues with computing height so we add a character
-+ if (text.toString().endsWith("\n")) {
-+ ssb.append("A");
-+ }
-+
-+ for (int i = 0; i < numberOfEmptyLines; ++i) {
-+ ssb.append("\nA");
-+ }
-+
-+ Object[] spans = text.getSpans(0, 0, Object.class);
-+ for (Object span : spans) { // It's possible we need to set exl-exl
-+ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span));
-+ };
-+
-+ text = new SpannableStringBuilder(TextUtils.concat(text, ssb));
-+ boring = null;
-+ layout = createLayout(
-+ text,
-+ boring,
-+ width,
-+ widthYogaMeasureMode,
-+ includeFontPadding,
-+ textBreakStrategy,
-+ hyphenationFrequency);
-+ }
-+
-+ if (numberOfLines != UNSET && numberOfLines != 0) {
-+ maximumNumberOfLines = numberOfLines;
-+ }
-+
- int calculatedLineCount =
- maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
- ? layout.getLineCount()
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
-index 081f2b8..0659179 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
-@@ -546,9 +546,15 @@ public class ReactEditText extends AppCompatEditText {
- * android.widget.TextView#isMultilineInputType(int)}} Source: {@Link TextView.java}
- */
-- if (isMultiline()) {
-- setSingleLine(false);
-- }
-+ if (isMultiline()) {
-+ // we save max lines as setSingleLines overwrites it
-+ // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/TextView.java#10671
-+ int maxLines = getMaxLines();
-+ setSingleLine(false);
-+ if (maxLines != -1) {
-+ setMaxLines(maxLines);
-+ }
-+ }
-
- // We override the KeyListener so that all keys on the soft input keyboard as well as hardware
- // keyboards work. Some KeyListeners like DigitsKeyListener will display the keyboard but not
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java
-index a850510..c59be1d 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java
-@@ -41,9 +41,9 @@ public final class ReactTextInputLocalData {
- public void apply(EditText editText) {
- editText.setText(mText);
- editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
-+ editText.setInputType(mInputType);
- editText.setMinLines(mMinLines);
- editText.setMaxLines(mMaxLines);
-- editText.setInputType(mInputType);
- editText.setHint(mPlaceholder);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- editText.setBreakStrategy(mBreakStrategy);
-diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
-index 8496a7d..e4d975b 100644
---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
-+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
-@@ -736,9 +736,18 @@ public class ReactTextInputManager extends BaseViewManager= Build.VERSION_CODES.M
-diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp
-index f2317ba..10f342c 100644
---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp
-+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp
-@@ -16,6 +16,7 @@ namespace facebook::react {
-
- bool ParagraphAttributes::operator==(const ParagraphAttributes& rhs) const {
- return std::tie(
-+ numberOfLines,
- maximumNumberOfLines,
- ellipsizeMode,
- textBreakStrategy,
-@@ -23,6 +24,7 @@ bool ParagraphAttributes::operator==(const ParagraphAttributes& rhs) const {
- includeFontPadding,
- android_hyphenationFrequency) ==
- std::tie(
-+ rhs.numberOfLines,
- rhs.maximumNumberOfLines,
- rhs.ellipsizeMode,
- rhs.textBreakStrategy,
-@@ -42,6 +44,7 @@ bool ParagraphAttributes::operator!=(const ParagraphAttributes& rhs) const {
- #if RN_DEBUG_STRING_CONVERTIBLE
- SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const {
- return {
-+ debugStringConvertibleItem("numberOfLines", numberOfLines),
- debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines),
- debugStringConvertibleItem("ellipsizeMode", ellipsizeMode),
- debugStringConvertibleItem("textBreakStrategy", textBreakStrategy),
-diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h
-index d73f863..1f85b22 100644
---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h
-+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h
-@@ -29,6 +29,11 @@ class ParagraphAttributes : public DebugStringConvertible {
- public:
- #pragma mark - Fields
-
-+ /*
-+ * Number of lines which paragraph takes.
-+ */
-+ int numberOfLines{};
-+
- /*
- * Maximum number of lines which paragraph can take.
- * Zero value represents "no limit".
-@@ -89,6 +94,7 @@ struct hash {
- size_t operator()(
- const facebook::react::ParagraphAttributes& attributes) const {
- return facebook::react::hash_combine(
-+ attributes.numberOfLines,
- attributes.maximumNumberOfLines,
- attributes.ellipsizeMode,
- attributes.textBreakStrategy,
-diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h
-index 445e452..3f0bb36 100644
---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h
-+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h
-@@ -692,10 +692,16 @@ inline ParagraphAttributes convertRawProp(
- const ParagraphAttributes& defaultParagraphAttributes) {
- auto paragraphAttributes = ParagraphAttributes{};
-
-- paragraphAttributes.maximumNumberOfLines = convertRawProp(
-+ paragraphAttributes.numberOfLines = convertRawProp(
- context,
- rawProps,
- "numberOfLines",
-+ sourceParagraphAttributes.numberOfLines,
-+ defaultParagraphAttributes.numberOfLines);
-+ paragraphAttributes.maximumNumberOfLines = convertRawProp(
-+ context,
-+ rawProps,
-+ "maximumNumberOfLines",
- sourceParagraphAttributes.maximumNumberOfLines,
- defaultParagraphAttributes.maximumNumberOfLines);
- paragraphAttributes.ellipsizeMode = convertRawProp(
-@@ -770,6 +776,7 @@ inline std::string toString(const AttributedString::Range& range) {
- inline folly::dynamic toDynamic(
- const ParagraphAttributes& paragraphAttributes) {
- auto values = folly::dynamic::object();
-+ values("numberOfLines", paragraphAttributes.numberOfLines);
- values("maximumNumberOfLines", paragraphAttributes.maximumNumberOfLines);
- values("ellipsizeMode", toString(paragraphAttributes.ellipsizeMode));
- values("textBreakStrategy", toString(paragraphAttributes.textBreakStrategy));
-@@ -979,6 +986,7 @@ constexpr static MapBuffer::Key PA_KEY_TEXT_BREAK_STRATEGY = 2;
- constexpr static MapBuffer::Key PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3;
- constexpr static MapBuffer::Key PA_KEY_INCLUDE_FONT_PADDING = 4;
- constexpr static MapBuffer::Key PA_KEY_HYPHENATION_FREQUENCY = 5;
-+constexpr static MapBuffer::Key PA_KEY_NUMBER_OF_LINES = 6;
-
- inline MapBuffer toMapBuffer(const ParagraphAttributes& paragraphAttributes) {
- auto builder = MapBufferBuilder();
-@@ -996,6 +1004,8 @@ inline MapBuffer toMapBuffer(const ParagraphAttributes& paragraphAttributes) {
- builder.putString(
- PA_KEY_HYPHENATION_FREQUENCY,
- toString(paragraphAttributes.android_hyphenationFrequency));
-+ builder.putInt(
-+ PA_KEY_NUMBER_OF_LINES, paragraphAttributes.numberOfLines);
-
- return builder.build();
- }
-diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp
-index 116284f..5749c57 100644
---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp
-+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp
-@@ -56,6 +56,10 @@ AndroidTextInputProps::AndroidTextInputProps(
- "numberOfLines",
- sourceProps.numberOfLines,
- {0})),
-+ maximumNumberOfLines(CoreFeatures::enablePropIteratorSetter? sourceProps.maximumNumberOfLines : convertRawProp(context, rawProps,
-+ "maximumNumberOfLines",
-+ sourceProps.maximumNumberOfLines,
-+ {0})),
- disableFullscreenUI(CoreFeatures::enablePropIteratorSetter? sourceProps.disableFullscreenUI : convertRawProp(context, rawProps,
- "disableFullscreenUI",
- sourceProps.disableFullscreenUI,
-@@ -281,6 +285,12 @@ void AndroidTextInputProps::setProp(
- value,
- paragraphAttributes,
- maximumNumberOfLines,
-+ "maximumNumberOfLines");
-+ REBUILD_FIELD_SWITCH_CASE(
-+ paDefaults,
-+ value,
-+ paragraphAttributes,
-+ numberOfLines,
- "numberOfLines");
- REBUILD_FIELD_SWITCH_CASE(
- paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode");
-@@ -323,6 +333,7 @@ void AndroidTextInputProps::setProp(
- }
-
- switch (hash) {
-+ RAW_SET_PROP_SWITCH_CASE_BASIC(maximumNumberOfLines);
- RAW_SET_PROP_SWITCH_CASE_BASIC(autoComplete);
- RAW_SET_PROP_SWITCH_CASE_BASIC(returnKeyLabel);
- RAW_SET_PROP_SWITCH_CASE_BASIC(numberOfLines);
-@@ -422,6 +433,7 @@ void AndroidTextInputProps::setProp(
- // TODO T53300085: support this in codegen; this was hand-written
- folly::dynamic AndroidTextInputProps::getDynamic() const {
- folly::dynamic props = folly::dynamic::object();
-+ props["maximumNumberOfLines"] = maximumNumberOfLines;
- props["autoComplete"] = autoComplete;
- props["returnKeyLabel"] = returnKeyLabel;
- props["numberOfLines"] = numberOfLines;
-diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h
-index 43cbb68..0bf63e7 100644
---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h
-+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h
-@@ -81,6 +81,7 @@ class AndroidTextInputProps final : public ViewProps, public BaseTextProps {
- std::string autoComplete{};
- std::string returnKeyLabel{};
- int numberOfLines{0};
-+ int maximumNumberOfLines{0};
- bool disableFullscreenUI{false};
- std::string textBreakStrategy{};
- SharedColor underlineColorAndroid{};
-diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
-index 368c334..ef9ec17 100644
---- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
-+++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm
-@@ -244,26 +244,49 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString
-
- #pragma mark - Private
-
--- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString
-+- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)inputAttributedString
- paragraphAttributes:(ParagraphAttributes)paragraphAttributes
- size:(CGSize)size
- {
-- NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size];
-+NSMutableAttributedString *attributedString = [ inputAttributedString mutableCopy];
-+ /*
-+ * The block below is responsible for setting the exact height of the view in lines
-+ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines
-+ * prop and then add random lines at the front. However, they are only used for layout
-+ * so they are not visible on the screen. This method is used for drawing only for Paragraph component
-+ * but we set exact height in lines only on TextInput that doesn't use it.
-+ */
-+ if (paragraphAttributes.numberOfLines) {
-+ paragraphAttributes.maximumNumberOfLines = paragraphAttributes.numberOfLines;
-+ NSMutableString *newLines = [NSMutableString stringWithCapacity: paragraphAttributes.numberOfLines];
-+ for (NSUInteger i = 0UL; i < paragraphAttributes.numberOfLines; ++i) {
-+ // K is added on purpose. New line seems to be not enough for NTtextContainer
-+ [newLines appendString:@"K\n"];
-+ }
-+ NSDictionary * attributesOfFirstCharacter = [inputAttributedString attributesAtIndex:0 effectiveRange:NULL];
-
-- textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
-- textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0
-- ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode)
-- : NSLineBreakByClipping;
-- textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines;
-+ [attributedString insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:attributesOfFirstCharacter] atIndex:0];
-+ }
-+
-+ NSTextContainer *textContainer = [NSTextContainer new];
-
- NSLayoutManager *layoutManager = [NSLayoutManager new];
- layoutManager.usesFontLeading = NO;
- [layoutManager addTextContainer:textContainer];
-
-- NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
-+ NSTextStorage *textStorage = [NSTextStorage new];
-
- [textStorage addLayoutManager:layoutManager];
-
-+ textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
-+ textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0
-+ ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode)
-+ : NSLineBreakByClipping;
-+ textContainer.size = size;
-+ textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines;
-+
-+ [textStorage replaceCharactersInRange:(NSRange){0, textStorage.length} withAttributedString:attributedString];
-+
- if (paragraphAttributes.adjustsFontSizeToFit) {
- CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0;
- CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0;
diff --git a/src/CONST.ts b/src/CONST.ts
index a0696560dc56..f9537d15e46e 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -458,6 +458,8 @@ const CONST = {
NEW_ZOOM_MEETING_URL: 'https://zoom.us/start/videomeeting',
NEW_GOOGLE_MEET_MEETING_URL: 'https://meet.google.com/new',
GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com',
+ GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com',
+ IMAGE_BASE64_MATCH: 'base64',
DEEPLINK_BASE_URL: 'new-expensify://',
PDF_VIEWER_URL: '/pdf/web/viewer.html',
CLOUDFRONT_DOMAIN_REGEX: /^https:\/\/\w+\.cloudfront\.net/i,
@@ -606,7 +608,6 @@ const CONST = {
ROOMCHANGELOG: {
INVITE_TO_ROOM: 'INVITETOROOM',
REMOVE_FROM_ROOM: 'REMOVEFROMROOM',
- JOIN_ROOM: 'JOINROOM',
},
},
THREAD_DISABLED: ['CREATED'],
@@ -790,6 +791,7 @@ const CONST = {
EXP_ERROR: 666,
MANY_WRITES_ERROR: 665,
UNABLE_TO_RETRY: 'unableToRetry',
+ UPDATE_REQUIRED: 426,
},
HTTP_STATUS: {
// When Cloudflare throttles
@@ -820,6 +822,9 @@ const CONST = {
GATEWAY_TIMEOUT: 'Gateway Timeout',
EXPENSIFY_SERVICE_INTERRUPTED: 'Expensify service interrupted',
DUPLICATE_RECORD: 'A record already exists with this ID',
+
+ // The "Upgrade" is intentional as the 426 HTTP code means "Upgrade Required" and sent by the API. We use the "Update" language everywhere else in the front end when this gets returned.
+ UPDATE_REQUIRED: 'Upgrade Required',
},
ERROR_TYPE: {
SOCKET: 'Expensify\\Auth\\Error\\Socket',
@@ -920,6 +925,7 @@ const CONST = {
KEYBOARD_TYPE: {
VISIBLE_PASSWORD: 'visible-password',
ASCII_CAPABLE: 'ascii-capable',
+ NUMBER_PAD: 'number-pad',
},
INPUT_MODE: {
@@ -3057,13 +3063,6 @@ const CONST = {
*/
MAX_OPTIONS_SELECTOR_PAGE_LENGTH: 500,
- /**
- * Performance test setup - run the same test multiple times to get a more accurate result
- */
- PERFORMANCE_TESTS: {
- RUNS: 20,
- },
-
/**
* Bank account names
*/
diff --git a/src/Expensify.js b/src/Expensify.js
index 0707ba069241..12003968b284 100644
--- a/src/Expensify.js
+++ b/src/Expensify.js
@@ -13,6 +13,7 @@ import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper';
import SplashScreenHider from './components/SplashScreenHider';
import UpdateAppModal from './components/UpdateAppModal';
import withLocalize, {withLocalizePropTypes} from './components/withLocalize';
+import CONST from './CONST';
import * as EmojiPickerAction from './libs/actions/EmojiPickerAction';
import * as Report from './libs/actions/Report';
import * as User from './libs/actions/User';
@@ -76,6 +77,9 @@ const propTypes = {
/** Whether the app is waiting for the server's response to determine if a room is public */
isCheckingPublicRoom: PropTypes.bool,
+ /** True when the user must update to the latest minimum version of the app */
+ updateRequired: PropTypes.bool,
+
/** Whether we should display the notification alerting the user that focus mode has been auto-enabled */
focusModeNotification: PropTypes.bool,
@@ -91,6 +95,7 @@ const defaultProps = {
isSidebarLoaded: false,
screenShareRequest: null,
isCheckingPublicRoom: true,
+ updateRequired: false,
focusModeNotification: false,
};
@@ -204,6 +209,10 @@ function Expensify(props) {
return null;
}
+ if (props.updateRequired) {
+ throw new Error(CONST.ERROR.UPDATE_REQUIRED);
+ }
+
return (
{/* We include the modal for showing a new update at the top level so the option is always present. */}
- {props.updateAvailable ? : null}
+ {/* If the update is required we won't show this option since a full screen update view will be displayed instead. */}
+ {props.updateAvailable && !props.updateRequired ? : null}
{props.screenShareRequest ? (
element
MAX_CANVAS_WIDTH: 'maxCanvasWidth',
+ /** Indicates whether an forced upgrade is required */
+ UPDATE_REQUIRED: 'updateRequired',
+
/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
@@ -358,13 +358,15 @@ const ONYXKEYS = {
GET_PHYSICAL_CARD_FORM_DRAFT: 'getPhysicalCardFormDraft',
POLICY_REPORT_FIELD_EDIT_FORM: 'policyReportFieldEditForm',
POLICY_REPORT_FIELD_EDIT_FORM_DRAFT: 'policyReportFieldEditFormDraft',
+ REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount',
+ REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft',
},
} as const;
type OnyxKeysMap = typeof ONYXKEYS;
type OnyxCollectionKey = ValueOf;
type OnyxKey = DeepValueOf>;
-type OnyxFormKey = ValueOf | OnyxKeysMap['REIMBURSEMENT_ACCOUNT'] | OnyxKeysMap['REIMBURSEMENT_ACCOUNT_DRAFT'];
+type OnyxFormKey = ValueOf;
type OnyxValues = {
[ONYXKEYS.ACCOUNT]: OnyxTypes.Account;
@@ -419,7 +421,6 @@ type OnyxValues = {
[ONYXKEYS.WALLET_STATEMENT]: OnyxTypes.WalletStatement;
[ONYXKEYS.PERSONAL_BANK_ACCOUNT]: OnyxTypes.PersonalBankAccount;
[ONYXKEYS.REIMBURSEMENT_ACCOUNT]: OnyxTypes.ReimbursementAccount;
- [ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT]: OnyxTypes.ReimbursementAccountDraft;
[ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE]: string | number;
[ONYXKEYS.FREQUENTLY_USED_EMOJIS]: OnyxTypes.FrequentlyUsedEmoji[];
[ONYXKEYS.REIMBURSEMENT_ACCOUNT_WORKSPACE_ID]: string;
@@ -442,6 +443,7 @@ type OnyxValues = {
[ONYXKEYS.MAX_CANVAS_AREA]: number;
[ONYXKEYS.MAX_CANVAS_HEIGHT]: number;
[ONYXKEYS.MAX_CANVAS_WIDTH]: number;
+ [ONYXKEYS.UPDATE_REQUIRED]: boolean;
// Collections
[ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download;
@@ -486,8 +488,8 @@ type OnyxValues = {
[ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM_DRAFT]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.DISPLAY_NAME_FORM_DRAFT]: OnyxTypes.Form;
+ [ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: OnyxTypes.DisplayNameForm;
+ [ONYXKEYS.FORMS.DISPLAY_NAME_FORM_DRAFT]: OnyxTypes.DisplayNameForm;
[ONYXKEYS.FORMS.ROOM_NAME_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.ROOM_NAME_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM]: OnyxTypes.Form;
@@ -500,8 +502,8 @@ type OnyxValues = {
[ONYXKEYS.FORMS.DATE_OF_BIRTH_FORM_DRAFT]: OnyxTypes.DateOfBirthForm;
[ONYXKEYS.FORMS.HOME_ADDRESS_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.HOME_ADDRESS_FORM_DRAFT]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.NEW_ROOM_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.NEW_ROOM_FORM_DRAFT]: OnyxTypes.Form;
+ [ONYXKEYS.FORMS.NEW_ROOM_FORM]: OnyxTypes.NewRoomForm;
+ [ONYXKEYS.FORMS.NEW_ROOM_FORM_DRAFT]: OnyxTypes.NewRoomForm;
[ONYXKEYS.FORMS.ROOM_SETTINGS_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.ROOM_SETTINGS_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.NEW_TASK_FORM]: OnyxTypes.Form;
@@ -526,20 +528,23 @@ type OnyxValues = {
[ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM_DRAFT]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM_DRAFT]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.I_KNOW_A_TEACHER_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.I_KNOW_A_TEACHER_FORM_DRAFT]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.INTRO_SCHOOL_PRINCIPAL_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.INTRO_SCHOOL_PRINCIPAL_FORM_DRAFT]: OnyxTypes.Form;
+ [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM]: OnyxTypes.PrivateNotesForm;
+ [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM_DRAFT]: OnyxTypes.PrivateNotesForm;
+ [ONYXKEYS.FORMS.I_KNOW_A_TEACHER_FORM]: OnyxTypes.IKnowATeacherForm;
+ [ONYXKEYS.FORMS.I_KNOW_A_TEACHER_FORM_DRAFT]: OnyxTypes.IKnowATeacherForm;
+ [ONYXKEYS.FORMS.INTRO_SCHOOL_PRINCIPAL_FORM]: OnyxTypes.IntroSchoolPrincipalForm;
+ [ONYXKEYS.FORMS.INTRO_SCHOOL_PRINCIPAL_FORM_DRAFT]: OnyxTypes.IntroSchoolPrincipalForm;
[ONYXKEYS.FORMS.REPORT_VIRTUAL_CARD_FRAUD]: OnyxTypes.Form;
[ONYXKEYS.FORMS.REPORT_VIRTUAL_CARD_FRAUD_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form | undefined;
+ [ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM_DRAFT]: OnyxTypes.Form | undefined;
+ [ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM_DRAFT]: OnyxTypes.Form;
+ // @ts-expect-error Different values are defined under the same key: ReimbursementAccount and ReimbursementAccountForm
+ [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: OnyxTypes.Form;
+ [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT]: OnyxTypes.Form;
};
type OnyxKeyValue = OnyxEntry;
diff --git a/src/components/AmountTextInput.tsx b/src/components/AmountTextInput.tsx
index 0f3416076cc0..05080fcdd21c 100644
--- a/src/components/AmountTextInput.tsx
+++ b/src/components/AmountTextInput.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import type {ForwardedRef} from 'react';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
@@ -34,7 +35,7 @@ type AmountTextInputProps = {
function AmountTextInput(
{formattedAmount, onChangeAmount, placeholder, selection, onSelectionChange, style, touchableInputWrapperStyle, onKeyPress}: AmountTextInputProps,
- ref: BaseTextInputRef,
+ ref: ForwardedRef,
) {
const styles = useThemeStyles();
return (
diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx
index 04e8a5f8d55b..7d00bac54dca 100644
--- a/src/components/AnonymousReportFooter.tsx
+++ b/src/components/AnonymousReportFooter.tsx
@@ -1,11 +1,10 @@
import React from 'react';
import {View} from 'react-native';
-import type {OnyxCollection} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx/lib/types';
+import type {OnyxEntry} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Session from '@userActions/Session';
-import type {PersonalDetails, Report} from '@src/types/onyx';
+import type {PersonalDetailsList, Report} from '@src/types/onyx';
import AvatarWithDisplayName from './AvatarWithDisplayName';
import Button from './Button';
import ExpensifyWordmark from './ExpensifyWordmark';
@@ -19,7 +18,7 @@ type AnonymousReportFooterProps = {
isSmallSizeLayout?: boolean;
/** Personal details of all the users */
- personalDetails: OnyxCollection;
+ personalDetails: OnyxEntry;
};
function AnonymousReportFooter({isSmallSizeLayout = false, personalDetails, report}: AnonymousReportFooterProps) {
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index 4988c33ed8ce..46ce4cf63f26 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -501,7 +501,6 @@ function AttachmentModal(props) {
report={props.report}
onNavigate={onNavigate}
source={props.source}
- onClose={closeModal}
onToggleKeyboard={updateConfirmButtonVisibility}
setDownloadButtonVisibility={setDownloadButtonVisibility}
/>
diff --git a/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext.js b/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext.js
deleted file mode 100644
index abaf06900853..000000000000
--- a/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import {createContext} from 'react';
-
-const AttachmentCarouselPagerContext = createContext(null);
-
-export default AttachmentCarouselPagerContext;
diff --git a/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext.ts b/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext.ts
new file mode 100644
index 000000000000..270e0b04909c
--- /dev/null
+++ b/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext.ts
@@ -0,0 +1,17 @@
+import type {ForwardedRef} from 'react';
+import {createContext} from 'react';
+import type PagerView from 'react-native-pager-view';
+import type {SharedValue} from 'react-native-reanimated';
+
+type AttachmentCarouselPagerContextValue = {
+ pagerRef: ForwardedRef;
+ isPagerScrolling: SharedValue;
+ isScrollEnabled: SharedValue;
+ onTap: () => void;
+ onScaleChanged: (scale: number) => void;
+};
+
+const AttachmentCarouselPagerContext = createContext(null);
+
+export default AttachmentCarouselPagerContext;
+export type {AttachmentCarouselPagerContextValue};
diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.js b/src/components/Attachments/AttachmentCarousel/Pager/index.js
deleted file mode 100644
index 553e963a3461..000000000000
--- a/src/components/Attachments/AttachmentCarousel/Pager/index.js
+++ /dev/null
@@ -1,172 +0,0 @@
-import PropTypes from 'prop-types';
-import React, {useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
-import {View} from 'react-native';
-import {createNativeWrapper} from 'react-native-gesture-handler';
-import PagerView from 'react-native-pager-view';
-import Animated, {runOnJS, useAnimatedProps, useAnimatedReaction, useEvent, useHandler, useSharedValue} from 'react-native-reanimated';
-import _ from 'underscore';
-import refPropTypes from '@components/refPropTypes';
-import useThemeStyles from '@hooks/useThemeStyles';
-import AttachmentCarouselPagerContext from './AttachmentCarouselPagerContext';
-
-const AnimatedPagerView = Animated.createAnimatedComponent(createNativeWrapper(PagerView));
-
-function usePageScrollHandler(handlers, dependencies) {
- const {context, doDependenciesDiffer} = useHandler(handlers, dependencies);
- const subscribeForEvents = ['onPageScroll'];
-
- return useEvent(
- (event) => {
- 'worklet';
-
- const {onPageScroll} = handlers;
- if (onPageScroll && event.eventName.endsWith('onPageScroll')) {
- onPageScroll(event, context);
- }
- },
- subscribeForEvents,
- doDependenciesDiffer,
- );
-}
-
-const noopWorklet = () => {
- 'worklet';
-
- // noop
-};
-
-const pagerPropTypes = {
- items: PropTypes.arrayOf(
- PropTypes.shape({
- key: PropTypes.string,
- url: PropTypes.string,
- }),
- ).isRequired,
- renderItem: PropTypes.func.isRequired,
- initialIndex: PropTypes.number,
- onPageSelected: PropTypes.func,
- onTap: PropTypes.func,
- onSwipe: PropTypes.func,
- onSwipeSuccess: PropTypes.func,
- onSwipeDown: PropTypes.func,
- onPinchGestureChange: PropTypes.func,
- forwardedRef: refPropTypes,
-};
-
-const pagerDefaultProps = {
- initialIndex: 0,
- onPageSelected: () => {},
- onTap: () => {},
- onSwipe: noopWorklet,
- onSwipeSuccess: () => {},
- onSwipeDown: () => {},
- onPinchGestureChange: () => {},
- forwardedRef: null,
-};
-
-function AttachmentCarouselPager({items, renderItem, initialIndex, onPageSelected, onTap, onSwipe = noopWorklet, onSwipeSuccess, onSwipeDown, onPinchGestureChange, forwardedRef}) {
- const styles = useThemeStyles();
- const shouldPagerScroll = useSharedValue(true);
- const pagerRef = useRef(null);
-
- const isScrolling = useSharedValue(false);
- const activeIndex = useSharedValue(initialIndex);
-
- const pageScrollHandler = usePageScrollHandler(
- {
- onPageScroll: (e) => {
- 'worklet';
-
- activeIndex.value = e.position;
- isScrolling.value = e.offset !== 0;
- },
- },
- [],
- );
-
- const [activePage, setActivePage] = useState(initialIndex);
-
- useEffect(() => {
- setActivePage(initialIndex);
- activeIndex.value = initialIndex;
- }, [activeIndex, initialIndex]);
-
- // we use reanimated for this since onPageSelected is called
- // in the middle of the pager animation
- useAnimatedReaction(
- () => isScrolling.value,
- (stillScrolling) => {
- if (stillScrolling) {
- return;
- }
-
- runOnJS(setActivePage)(activeIndex.value);
- },
- );
-
- useImperativeHandle(
- forwardedRef,
- () => ({
- setPage: (...props) => pagerRef.current.setPage(...props),
- }),
- [],
- );
-
- const animatedProps = useAnimatedProps(() => ({
- scrollEnabled: shouldPagerScroll.value,
- }));
-
- const contextValue = useMemo(
- () => ({
- isScrolling,
- pagerRef,
- shouldPagerScroll,
- onPinchGestureChange,
- onTap,
- onSwipe,
- onSwipeSuccess,
- onSwipeDown,
- }),
- [isScrolling, pagerRef, shouldPagerScroll, onPinchGestureChange, onTap, onSwipe, onSwipeSuccess, onSwipeDown],
- );
-
- return (
-
-
- {_.map(items, (item, index) => (
-
- {renderItem({item, index, isActive: index === activePage})}
-
- ))}
-
-
- );
-}
-
-AttachmentCarouselPager.propTypes = pagerPropTypes;
-AttachmentCarouselPager.defaultProps = pagerDefaultProps;
-AttachmentCarouselPager.displayName = 'AttachmentCarouselPager';
-
-const AttachmentCarouselPagerWithRef = React.forwardRef((props, ref) => (
-
-));
-
-AttachmentCarouselPagerWithRef.displayName = 'AttachmentCarouselPagerWithRef';
-
-export default AttachmentCarouselPagerWithRef;
diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.tsx b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx
new file mode 100644
index 000000000000..490afb6614ac
--- /dev/null
+++ b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx
@@ -0,0 +1,150 @@
+import type {ForwardedRef} from 'react';
+import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
+import {View} from 'react-native';
+import type {NativeViewGestureHandlerProps} from 'react-native-gesture-handler';
+import {createNativeWrapper} from 'react-native-gesture-handler';
+import type {PagerViewProps} from 'react-native-pager-view';
+import PagerView from 'react-native-pager-view';
+import Animated, {useAnimatedProps, useSharedValue} from 'react-native-reanimated';
+import useThemeStyles from '@hooks/useThemeStyles';
+import AttachmentCarouselPagerContext from './AttachmentCarouselPagerContext';
+import usePageScrollHandler from './usePageScrollHandler';
+
+const WrappedPagerView = createNativeWrapper(PagerView) as React.ForwardRefExoticComponent<
+ PagerViewProps & NativeViewGestureHandlerProps & React.RefAttributes>
+>;
+const AnimatedPagerView = Animated.createAnimatedComponent(WrappedPagerView);
+
+type AttachmentCarouselPagerHandle = {
+ setPage: (selectedPage: number) => void;
+};
+
+type PagerItem = {
+ key: string;
+ url: string;
+ source: string;
+};
+
+type AttachmentCarouselPagerProps = {
+ items: PagerItem[];
+ renderItem: (props: {item: PagerItem; index: number; isActive: boolean}) => React.ReactNode;
+ initialIndex: number;
+ onPageSelected: () => void;
+ onRequestToggleArrows: (showArrows?: boolean) => void;
+};
+
+function AttachmentCarouselPager({items, renderItem, initialIndex, onPageSelected, onRequestToggleArrows}: AttachmentCarouselPagerProps, ref: ForwardedRef) {
+ const styles = useThemeStyles();
+ const pagerRef = useRef(null);
+
+ const scale = useRef(1);
+ const isPagerScrolling = useSharedValue(false);
+ const isScrollEnabled = useSharedValue(true);
+
+ const activePage = useSharedValue(initialIndex);
+ const [activePageState, setActivePageState] = useState(initialIndex);
+
+ const pageScrollHandler = usePageScrollHandler((e) => {
+ 'worklet';
+
+ activePage.value = e.position;
+ isPagerScrolling.value = e.offset !== 0;
+ }, []);
+
+ useEffect(() => {
+ setActivePageState(initialIndex);
+ activePage.value = initialIndex;
+ }, [activePage, initialIndex]);
+
+ /**
+ * This callback is passed to the MultiGestureCanvas/Lightbox through the AttachmentCarouselPagerContext.
+ * It is used to react to zooming/pinching and (mostly) enabling/disabling scrolling on the pager,
+ * as well as enabling/disabling the carousel buttons.
+ */
+ const handleScaleChange = useCallback(
+ (newScale: number) => {
+ if (newScale === scale.current) {
+ return;
+ }
+
+ scale.current = newScale;
+
+ const newIsScrollEnabled = newScale === 1;
+ if (isScrollEnabled.value === newIsScrollEnabled) {
+ return;
+ }
+
+ isScrollEnabled.value = newIsScrollEnabled;
+ onRequestToggleArrows(newIsScrollEnabled);
+ },
+ [isScrollEnabled, onRequestToggleArrows],
+ );
+
+ /**
+ * This callback is passed to the MultiGestureCanvas/Lightbox through the AttachmentCarouselPagerContext.
+ * It is used to trigger touch events on the pager when the user taps on the MultiGestureCanvas/Lightbox.
+ */
+ const handleTap = useCallback(() => {
+ if (!isScrollEnabled.value) {
+ return;
+ }
+
+ onRequestToggleArrows();
+ }, [isScrollEnabled.value, onRequestToggleArrows]);
+
+ const contextValue = useMemo(
+ () => ({
+ pagerRef,
+ isPagerScrolling,
+ isScrollEnabled,
+ onTap: handleTap,
+ onScaleChanged: handleScaleChange,
+ }),
+ [isPagerScrolling, isScrollEnabled, handleTap, handleScaleChange],
+ );
+
+ const animatedProps = useAnimatedProps(() => ({
+ scrollEnabled: isScrollEnabled.value,
+ }));
+
+ /**
+ * This "useImperativeHandle" call is needed to expose certain imperative methods via the pager's ref.
+ * setPage: can be used to programmatically change the page from a parent component
+ */
+ useImperativeHandle(
+ ref,
+ () => ({
+ setPage: (selectedPage) => {
+ pagerRef.current?.setPage(selectedPage);
+ },
+ }),
+ [],
+ );
+
+ return (
+
+
+ {items.map((item, index) => (
+
+ {renderItem({item, index, isActive: index === activePageState})}
+
+ ))}
+
+
+ );
+}
+AttachmentCarouselPager.displayName = 'AttachmentCarouselPager';
+
+export default React.forwardRef(AttachmentCarouselPager);
diff --git a/src/components/Attachments/AttachmentCarousel/Pager/usePageScrollHandler.ts b/src/components/Attachments/AttachmentCarousel/Pager/usePageScrollHandler.ts
new file mode 100644
index 000000000000..ab7f0d99b7f0
--- /dev/null
+++ b/src/components/Attachments/AttachmentCarousel/Pager/usePageScrollHandler.ts
@@ -0,0 +1,41 @@
+import type {PagerViewProps} from 'react-native-pager-view';
+import {useEvent, useHandler} from 'react-native-reanimated';
+
+type PageScrollHandler = NonNullable;
+
+type PageScrollEventData = Parameters[0]['nativeEvent'];
+type PageScrollContext = Record;
+
+// Reanimated doesn't expose the type for animated event handlers, therefore we must infer it from the useHandler hook.
+// The AnimatedPageScrollHandler type is the type of the onPageScroll prop from react-native-pager-view as an animated handler.
+type AnimatedHandlers = Parameters>[0];
+type AnimatedPageScrollHandler = AnimatedHandlers[string];
+
+type Dependencies = Parameters[1];
+
+/**
+ * This hook is used to create a wrapped handler for the onPageScroll event from react-native-pager-view.
+ * The produced handler can react to the onPageScroll event and allows to use it with animated shared values (from REA)
+ * This hook is a wrapper around the useHandler and useEvent hooks from react-native-reanimated.
+ * @param onPageScroll The handler for the onPageScroll event from react-native-pager-view
+ * @param dependencies The dependencies for the useHandler hook
+ * @returns A wrapped/animated handler for the onPageScroll event from react-native-pager-view
+ */
+const usePageScrollHandler = (onPageScroll: AnimatedPageScrollHandler, dependencies: Dependencies): PageScrollHandler => {
+ const {context, doDependenciesDiffer} = useHandler({onPageScroll}, dependencies);
+ const subscribeForEvents = ['onPageScroll'];
+
+ return useEvent(
+ (event) => {
+ 'worklet';
+
+ if (onPageScroll && event.eventName.endsWith('onPageScroll')) {
+ onPageScroll(event, context);
+ }
+ },
+ subscribeForEvents,
+ doDependenciesDiffer,
+ );
+};
+
+export default usePageScrollHandler;
diff --git a/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js b/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js
index 72a554de68be..5aa665683162 100644
--- a/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js
+++ b/src/components/Attachments/AttachmentCarousel/attachmentCarouselPropTypes.js
@@ -10,9 +10,6 @@ const propTypes = {
/** Callback to update the parent modal's state with a source and name from the attachments array */
onNavigate: PropTypes.func,
- /** Callback to close carousel when user swipes down (on native) */
- onClose: PropTypes.func,
-
/** Function to change the download button Visibility */
setDownloadButtonVisibility: PropTypes.func,
@@ -39,7 +36,6 @@ const defaultProps = {
parentReportActions: {},
transaction: {},
onNavigate: () => {},
- onClose: () => {},
setDownloadButtonVisibility: () => {},
};
diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js
index f5479b73abdb..fa24ccd0ef53 100644
--- a/src/components/Attachments/AttachmentCarousel/index.native.js
+++ b/src/components/Attachments/AttachmentCarousel/index.native.js
@@ -18,12 +18,11 @@ import extractAttachmentsFromReport from './extractAttachmentsFromReport';
import AttachmentCarouselPager from './Pager';
import useCarouselArrows from './useCarouselArrows';
-function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, onClose}) {
+function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate}) {
const styles = useThemeStyles();
const pagerRef = useRef(null);
const [page, setPage] = useState();
const [attachments, setAttachments] = useState([]);
- const [isPinchGestureRunning, setIsPinchGestureRunning] = useState(true);
const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows();
const [activeSource, setActiveSource] = useState(source);
@@ -88,6 +87,22 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
[autoHideArrows, page, updatePage],
);
+ /**
+ * Toggles the arrows visibility
+ * @param {Boolean} showArrows if showArrows is passed, it will set the visibility to the passed value
+ */
+ const toggleArrows = useCallback(
+ (showArrows) => {
+ if (showArrows === undefined) {
+ setShouldShowArrows((prevShouldShowArrows) => !prevShouldShowArrows);
+ return;
+ }
+
+ setShouldShowArrows(showArrows);
+ },
+ [setShouldShowArrows],
+ );
+
/**
* Defines how a single attachment should be rendered
* @param {{ reportActionID: String, isAuthTokenRequired: Boolean, source: String, file: { name: String }, hasBeenFlagged: Boolean }} item
@@ -101,18 +116,13 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
index={index}
activeIndex={page}
isFocused={isActive && activeSource === item.source}
- onPress={() => setShouldShowArrows(!shouldShowArrows)}
/>
),
- [activeSource, attachments.length, page, setShouldShowArrows, shouldShowArrows],
+ [activeSource, attachments.length, page],
);
return (
- setShouldShowArrows(true)}
- onMouseLeave={() => setShouldShowArrows(false)}
- >
+
{page == null ? (
) : (
@@ -127,7 +137,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
) : (
<>
cycleThroughAttachments(-1)}
@@ -140,14 +150,8 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
items={attachments}
renderItem={renderItem}
initialIndex={page}
+ onRequestToggleArrows={toggleArrows}
onPageSelected={({nativeEvent: {position: newPage}}) => updatePage(newPage)}
- onPinchGestureChange={(newIsPinchGestureRunning) => {
- setIsPinchGestureRunning(newIsPinchGestureRunning);
- if (!newIsPinchGestureRunning && !shouldShowArrows) {
- setShouldShowArrows(true);
- }
- }}
- onSwipeDown={onClose}
ref={pagerRef}
/>
>
diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js
index f53b993f6053..14c60458b044 100755
--- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js
+++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js
@@ -13,7 +13,7 @@ const propTypes = {
};
function AttachmentViewImage({
- source,
+ url,
file,
isAuthTokenRequired,
isUsedInCarousel,
@@ -25,15 +25,13 @@ function AttachmentViewImage({
onPress,
onError,
isImage,
- onScaleChanged,
translate,
}) {
const styles = useThemeStyles();
const children = (
{
if (!attachmentCarouselPagerContext) {
return;
}
- attachmentCarouselPagerContext.onPinchGestureChange(false);
+ attachmentCarouselPagerContext.onScaleChanged(1);
// eslint-disable-next-line react-hooks/exhaustive-deps -- we just want to call this function when component is mounted
}, []);
+ /**
+ * When the PDF's onScaleChanged event is triggered, we must call the context's onScaleChanged callback,
+ * because we want to disable the pager scroll when the pdf is zoomed in,
+ * as well as call the onScaleChanged prop of the AttachmentViewPdf component if defined.
+ */
const onScaleChanged = useCallback(
- (scale) => {
- onScaleChangedProp(scale);
+ (newScale) => {
+ if (onScaleChangedProp !== undefined) {
+ onScaleChangedProp(newScale);
+ }
// When a pdf is shown in a carousel, we want to disable the pager scroll when the pdf is zoomed in
if (isUsedInCarousel && attachmentCarouselPagerContext) {
- const shouldPagerScroll = scale === 1;
-
- attachmentCarouselPagerContext.onPinchGestureChange(!shouldPagerScroll);
+ attachmentCarouselPagerContext.onScaleChanged(newScale);
+ }
+ },
+ [attachmentCarouselPagerContext, isUsedInCarousel, onScaleChangedProp],
+ );
- if (attachmentCarouselPagerContext.shouldPagerScroll.value === shouldPagerScroll) {
- return;
- }
+ /**
+ * This callback is used to pass-through the onPress event from the AttachmentViewPdf's props
+ * as well trigger the onTap event from the context.
+ * The onTap event should only be triggered, if the pager is currently scrollable.
+ * Otherwise it means that the PDF is currently zoomed in, therefore the onTap callback should be ignored
+ */
+ const onPress = useCallback(
+ (e) => {
+ if (onPressProp !== undefined) {
+ onPressProp(e);
+ }
- attachmentCarouselPagerContext.shouldPagerScroll.value = shouldPagerScroll;
+ if (attachmentCarouselPagerContext !== null && isScrollEnabled.value) {
+ attachmentCarouselPagerContext.onTap(e);
}
},
- [attachmentCarouselPagerContext, isUsedInCarousel, onScaleChangedProp],
+ [attachmentCarouselPagerContext, isScrollEnabled, onPressProp],
);
return (
@@ -60,8 +93,8 @@ function BaseAttachmentViewPdf({
);
}
-BaseAttachmentViewPdf.propTypes = attachmentViewPdfPropTypes;
-BaseAttachmentViewPdf.defaultProps = attachmentViewPdfDefaultProps;
+BaseAttachmentViewPdf.propTypes = baseAttachmentViewPdfPropTypes;
+BaseAttachmentViewPdf.defaultProps = baseAttachmentViewPdfDefaultProps;
BaseAttachmentViewPdf.displayName = 'BaseAttachmentViewPdf';
export default memo(BaseAttachmentViewPdf);
diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js
index db4f4f11d68c..07cd8ecf61e7 100644
--- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js
+++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js
@@ -1,4 +1,4 @@
-import React, {memo, useCallback, useContext} from 'react';
+import React, {memo, useContext, useMemo} from 'react';
import {StyleSheet, View} from 'react-native';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import Animated, {useSharedValue} from 'react-native-reanimated';
@@ -7,13 +7,14 @@ import useThemeStyles from '@hooks/useThemeStyles';
import BaseAttachmentViewPdf from './BaseAttachmentViewPdf';
import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes';
+// If the user pans less than this threshold, we'll not enable/disable the pager scroll, since the thouch will most probably be a tap.
+// If the user moves their finger more than this threshold in the X direction, we'll enable the pager scroll. Otherwise if in the Y direction, we'll disable it.
+const SCROLL_THRESHOLD = 10;
+
function AttachmentViewPdf(props) {
const styles = useThemeStyles();
- const {onScaleChanged, ...restProps} = props;
const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext);
- const scaleRef = useSharedValue(1);
- const offsetX = useSharedValue(0);
- const offsetY = useSharedValue(0);
+ const scale = useSharedValue(1);
// Reanimated freezes all objects captured in the closure of a worklet.
// Since Reanimated 3, entire objects are captured instead of just the relevant properties.
@@ -22,30 +23,53 @@ function AttachmentViewPdf(props) {
// frozen, which combined with Reanimated using strict mode since 3.6.0 was resulting in errors.
// Without strict mode, it would just silently fail.
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#description
- const shouldPagerScroll = attachmentCarouselPagerContext !== null ? attachmentCarouselPagerContext.shouldPagerScroll : undefined;
+ const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled;
+
+ const offsetX = useSharedValue(0);
+ const offsetY = useSharedValue(0);
+ const isPanGestureActive = useSharedValue(false);
const Pan = Gesture.Pan()
.manualActivation(true)
.onTouchesMove((evt) => {
- if (offsetX.value !== 0 && offsetY.value !== 0 && shouldPagerScroll) {
+ if (offsetX.value !== 0 && offsetY.value !== 0 && isScrollEnabled) {
+ const translateX = Math.abs(evt.allTouches[0].absoluteX - offsetX.value);
+ const translateY = Math.abs(evt.allTouches[0].absoluteY - offsetY.value);
+ const allowEnablingScroll = !isPanGestureActive.value || isScrollEnabled.value;
+
// if the value of X is greater than Y and the pdf is not zoomed in,
// enable the pager scroll so that the user
// can swipe to the next attachment otherwise disable it.
- if (Math.abs(evt.allTouches[0].absoluteX - offsetX.value) > Math.abs(evt.allTouches[0].absoluteY - offsetY.value) && scaleRef.value === 1) {
- shouldPagerScroll.value = true;
- } else {
- shouldPagerScroll.value = false;
+ if (translateX > translateY && translateX > SCROLL_THRESHOLD && scale.value === 1 && allowEnablingScroll) {
+ isScrollEnabled.value = true;
+ } else if (translateY > SCROLL_THRESHOLD) {
+ isScrollEnabled.value = false;
}
}
+
+ isPanGestureActive.value = true;
offsetX.value = evt.allTouches[0].absoluteX;
offsetY.value = evt.allTouches[0].absoluteY;
+ })
+ .onTouchesUp(() => {
+ isPanGestureActive.value = false;
+ isScrollEnabled.value = true;
});
- const updateScale = useCallback(
- (scale) => {
- scaleRef.value = scale;
- },
- [scaleRef],
+ const Content = useMemo(
+ () => (
+ {
+ // The react-native-pdf's onScaleChanged event will sometimes give us scale values of e.g. 0.99... instead of 1,
+ // even though we're not pinching/zooming
+ // Rounding the scale value to 2 decimal place fixes this issue, since pinching will still be possible but very small pinches are ignored.
+ scale.value = Math.round(newScale * 1e2) / 1e2;
+ }}
+ />
+ ),
+ [props, scale],
);
return (
@@ -53,21 +77,18 @@ function AttachmentViewPdf(props) {
collapsable={false}
style={styles.flex1}
>
-
-
- {
- updateScale(scale);
- onScaleChanged();
- }}
- />
-
-
+ {attachmentCarouselPagerContext === null ? (
+ Content
+ ) : (
+
+
+ {Content}
+
+
+ )}
);
}
diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js
index c3d1423b17c9..d6a402613c34 100644
--- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js
+++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js
@@ -2,7 +2,7 @@ import React, {memo} from 'react';
import PDFView from '@components/PDFView';
import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes';
-function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onScaleChanged, onToggleKeyboard, onLoadComplete, errorLabelStyles, style}) {
+function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style}) {
return (
diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js
index b0060afdb813..67f6dd95568e 100755
--- a/src/components/Attachments/AttachmentView/index.js
+++ b/src/components/Attachments/AttachmentView/index.js
@@ -4,6 +4,7 @@ import React, {memo, useState} from 'react';
import {ActivityIndicator, ScrollView, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
+import * as AttachmentsPropTypes from '@components/Attachments/propTypes';
import DistanceEReceipt from '@components/DistanceEReceipt';
import EReceipt from '@components/EReceipt';
import Icon from '@components/Icon';
@@ -28,6 +29,9 @@ const propTypes = {
...attachmentViewPropTypes,
...withLocalizePropTypes,
+ /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */
+ source: AttachmentsPropTypes.attachmentSourcePropType.isRequired,
+
/** Flag to show/hide download icon */
shouldShowDownloadIcon: PropTypes.bool,
@@ -67,7 +71,6 @@ function AttachmentView({
shouldShowLoadingSpinnerIcon,
shouldShowDownloadIcon,
containerStyles,
- onScaleChanged,
onToggleKeyboard,
translate,
isFocused,
@@ -141,7 +144,6 @@ function AttachmentView({
carouselItemIndex={carouselItemIndex}
carouselActiveItemIndex={carouselActiveItemIndex}
onPress={onPress}
- onScaleChanged={onScaleChanged}
onToggleKeyboard={onToggleKeyboard}
onLoadComplete={() => !loadComplete && setLoadComplete(true)}
errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]}
@@ -163,7 +165,7 @@ function AttachmentView({
if (isImage || (file && Str.isImage(file.name))) {
return (
{
setImageError(true);
}}
diff --git a/src/components/Attachments/AttachmentView/propTypes.js b/src/components/Attachments/AttachmentView/propTypes.js
index 286c903ccf5b..d78bed8526b8 100644
--- a/src/components/Attachments/AttachmentView/propTypes.js
+++ b/src/components/Attachments/AttachmentView/propTypes.js
@@ -5,9 +5,6 @@ const attachmentViewPropTypes = {
/** Whether source url requires authentication */
isAuthTokenRequired: PropTypes.bool,
- /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */
- source: AttachmentsPropTypes.attachmentSourcePropType.isRequired,
-
/** File object can be an instance of File or Object */
file: AttachmentsPropTypes.attachmentFilePropType,
diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx
index 4da91c2e7d19..8ea8a1bb6f64 100644
--- a/src/components/Avatar.tsx
+++ b/src/components/Avatar.tsx
@@ -124,3 +124,4 @@ function Avatar({
Avatar.displayName = 'Avatar';
export default Avatar;
+export {type AvatarProps};
diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx
index e9e1054427b9..d42d47caafc9 100644
--- a/src/components/AvatarWithDisplayName.tsx
+++ b/src/components/AvatarWithDisplayName.tsx
@@ -1,6 +1,6 @@
import React, {useCallback, useEffect, useRef} from 'react';
import {View} from 'react-native';
-import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useStyleUtils from '@hooks/useStyleUtils';
@@ -12,7 +12,7 @@ import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx';
+import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx';
import DisplayNames from './DisplayNames';
import MultipleAvatars from './MultipleAvatars';
import ParentNavigationSubtitle from './ParentNavigationSubtitle';
@@ -36,7 +36,7 @@ type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & {
size?: ValueOf;
/** Personal details of all the users */
- personalDetails: OnyxCollection;
+ personalDetails: OnyxEntry;
/** Whether if it's an unauthenticated user */
isAnonymous?: boolean;
@@ -63,7 +63,7 @@ function AvatarWithDisplayName({
const isMoneyRequestOrReport = ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report);
const icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy);
const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails);
- const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails), false);
+ const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails) as PersonalDetails[], false);
const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report);
const isExpenseRequest = ReportUtils.isExpenseRequest(report);
const avatarBorderColor = isAnonymous ? theme.highlightBG : theme.componentBG;
diff --git a/src/components/BigNumberPad.tsx b/src/components/BigNumberPad.tsx
index 8b840f9d1b57..80f7794d0ad3 100644
--- a/src/components/BigNumberPad.tsx
+++ b/src/components/BigNumberPad.tsx
@@ -31,7 +31,7 @@ function BigNumberPad({numberPressed, longPressHandlerStateChanged = () => {}, i
const {toLocaleDigit} = useLocalize();
const styles = useThemeStyles();
- const [timer, setTimer] = useState(null);
+ const [timer, setTimer] = useState(null);
const {isExtraSmallScreenHeight} = useWindowDimensions();
/**
diff --git a/src/components/Composer/index.android.tsx b/src/components/Composer/index.android.tsx
deleted file mode 100644
index ade1513c8613..000000000000
--- a/src/components/Composer/index.android.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import type {ForwardedRef} from 'react';
-import React, {useCallback, useEffect, useMemo, useRef} from 'react';
-import type {TextInput} from 'react-native';
-import {StyleSheet} from 'react-native';
-import RNTextInput from '@components/RNTextInput';
-import useResetComposerFocus from '@hooks/useResetComposerFocus';
-import useTheme from '@hooks/useTheme';
-import useThemeStyles from '@hooks/useThemeStyles';
-import * as ComposerUtils from '@libs/ComposerUtils';
-import type {ComposerProps} from './types';
-
-function Composer(
- {
- shouldClear = false,
- onClear = () => {},
- isDisabled = false,
- maxLines,
- isComposerFullSize = false,
- setIsFullComposerAvailable = () => {},
- style,
- autoFocus = false,
- selection = {
- start: 0,
- end: 0,
- },
- isFullComposerAvailable = false,
- ...props
- }: ComposerProps,
- ref: ForwardedRef,
-) {
- const textInput = useRef(null);
- const {isFocused, shouldResetFocus} = useResetComposerFocus(textInput);
-
- const styles = useThemeStyles();
- const theme = useTheme();
-
- /**
- * Set the TextInput Ref
- */
- const setTextInputRef = useCallback((el: TextInput) => {
- textInput.current = el;
- if (typeof ref !== 'function' || textInput.current === null) {
- return;
- }
-
- // This callback prop is used by the parent component using the constructor to
- // get a ref to the inner textInput element e.g. if we do
- // this.textInput = el} /> this will not
- // return a ref to the component, but rather the HTML element by default
- ref(textInput.current);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- if (!shouldClear) {
- return;
- }
- textInput.current?.clear();
- onClear();
- }, [shouldClear, onClear]);
-
- /**
- * Set maximum number of lines
- */
- const maxNumberOfLines = useMemo(() => {
- if (isComposerFullSize) {
- return 1000000;
- }
- return maxLines;
- }, [isComposerFullSize, maxLines]);
-
- const composerStyles = useMemo(() => StyleSheet.flatten(style), [style]);
-
- return (
- ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e, styles)}
- rejectResponderTermination={false}
- // Setting a really high number here fixes an issue with the `maxNumberOfLines` prop on TextInput, where on Android the text input would collapse to only one line,
- // when it should actually expand to the container (https://github.com/Expensify/App/issues/11694#issuecomment-1560520670)
- // @Szymon20000 is working on fixing this (android-only) issue in the in the upstream PR (https://github.com/facebook/react-native/pulls?q=is%3Apr+is%3Aopen+maxNumberOfLines)
- // TODO: remove this comment once upstream PR is merged and available in a future release
- maxNumberOfLines={maxNumberOfLines}
- textAlignVertical="center"
- style={[composerStyles]}
- autoFocus={autoFocus}
- selection={selection}
- isFullComposerAvailable={isFullComposerAvailable}
- /* eslint-disable-next-line react/jsx-props-no-spreading */
- {...props}
- readOnly={isDisabled}
- onBlur={(e) => {
- if (!isFocused) {
- shouldResetFocus.current = true; // detect the input is blurred when the page is hidden
- }
- props?.onBlur?.(e);
- }}
- />
- );
-}
-
-Composer.displayName = 'Composer';
-
-export default React.forwardRef(Composer);
diff --git a/src/components/Composer/index.ios.tsx b/src/components/Composer/index.native.tsx
similarity index 82%
rename from src/components/Composer/index.ios.tsx
rename to src/components/Composer/index.native.tsx
index 07736e5ddcba..c7b020a5c6dd 100644
--- a/src/components/Composer/index.ios.tsx
+++ b/src/components/Composer/index.native.tsx
@@ -2,8 +2,10 @@ import type {ForwardedRef} from 'react';
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import type {TextInput} from 'react-native';
import {StyleSheet} from 'react-native';
+import type {AnimatedTextInputRef} from '@components/RNTextInput';
import RNTextInput from '@components/RNTextInput';
import useResetComposerFocus from '@hooks/useResetComposerFocus';
+import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ComposerUtils from '@libs/ComposerUtils';
@@ -28,15 +30,17 @@ function Composer(
}: ComposerProps,
ref: ForwardedRef,
) {
- const textInput = useRef(null);
+ const textInput = useRef(null);
const {isFocused, shouldResetFocus} = useResetComposerFocus(textInput);
- const styles = useThemeStyles();
const theme = useTheme();
+ const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
/**
* Set the TextInput Ref
+ * @param {Element} el
*/
- const setTextInputRef = useCallback((el: TextInput) => {
+ const setTextInputRef = useCallback((el: AnimatedTextInputRef) => {
textInput.current = el;
if (typeof ref !== 'function' || textInput.current === null) {
return;
@@ -58,28 +62,20 @@ function Composer(
onClear();
}, [shouldClear, onClear]);
- /**
- * Set maximum number of lines
- */
- const maxNumberOfLines = useMemo(() => {
- if (isComposerFullSize) {
- return;
- }
- return maxLines;
- }, [isComposerFullSize, maxLines]);
-
- const composerStyles = useMemo(() => StyleSheet.flatten(style), [style]);
+ const maxHeightStyle = useMemo(() => StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize), [StyleUtils, isComposerFullSize, maxLines]);
+ const composerStyle = useMemo(() => StyleSheet.flatten(style), [style]);
return (
ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e, styles)}
rejectResponderTermination={false}
smartInsertDelete={false}
- style={[composerStyles, styles.verticalAlignMiddle]}
- maxNumberOfLines={maxNumberOfLines}
+ textAlignVertical="center"
+ style={[composerStyle, maxHeightStyle]}
autoFocus={autoFocus}
isFullComposerAvailable={isFullComposerAvailable}
/* eslint-disable-next-line react/jsx-props-no-spreading */
diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx
index 3c2caf020ef7..3568aee6eebb 100755
--- a/src/components/Composer/index.tsx
+++ b/src/components/Composer/index.tsx
@@ -4,9 +4,9 @@ import type {BaseSyntheticEvent, ForwardedRef} from 'react';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {flushSync} from 'react-dom';
// eslint-disable-next-line no-restricted-imports
-import type {DimensionValue, NativeSyntheticEvent, Text as RNText, TextInput, TextInputKeyPressEventData, TextInputProps, TextInputSelectionChangeEventData} from 'react-native';
+import type {DimensionValue, NativeSyntheticEvent, Text as RNText, TextInput, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native';
import {StyleSheet, View} from 'react-native';
-import type {AnimatedProps} from 'react-native-reanimated';
+import type {AnimatedTextInputRef} from '@components/RNTextInput';
import RNTextInput from '@components/RNTextInput';
import Text from '@components/Text';
import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible';
@@ -17,6 +17,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import * as ComposerUtils from '@libs/ComposerUtils';
import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullComposerAvailable';
+import * as FileUtils from '@libs/fileDownload/FileUtils';
import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import CONST from '@src/CONST';
@@ -75,7 +76,7 @@ function Composer(
shouldContainScroll = false,
...props
}: ComposerProps,
- ref: ForwardedRef>>,
+ ref: ForwardedRef,
) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -83,7 +84,7 @@ function Composer(
const {windowWidth} = useWindowDimensions();
const navigation = useNavigation();
const textRef = useRef(null);
- const textInput = useRef<(HTMLTextAreaElement & TextInput) | null>(null);
+ const textInput = useRef(null);
const [numberOfLines, setNumberOfLines] = useState(numberOfLinesProp);
const [selection, setSelection] = useState<
| {
@@ -216,6 +217,9 @@ function Composer(
const TEXT_HTML = 'text/html';
+ const clipboardDataHtml = event.clipboardData?.getData(TEXT_HTML) ?? '';
+ const clipboardDataTypesHtml = event.clipboardData?.types.includes(TEXT_HTML) ?? false;
+
// If paste contains files, then trigger file management
if (event.clipboardData?.files.length && event.clipboardData.files.length > 0) {
// Prevent the default so we do not post the file name into the text box
@@ -223,9 +227,43 @@ function Composer(
return;
}
+ // If paste contains base64 image
+ if (clipboardDataHtml?.includes(CONST.IMAGE_BASE64_MATCH)) {
+ const domparser = new DOMParser();
+ const pastedHTML = clipboardDataHtml;
+ const embeddedImages = domparser.parseFromString(pastedHTML, TEXT_HTML)?.images;
+
+ if (embeddedImages.length > 0 && embeddedImages[0].src) {
+ const src = embeddedImages[0].src;
+ const file = FileUtils.base64ToFile(src, 'image.png');
+ onPasteFile(file);
+ return;
+ }
+ }
+
+ // If paste contains image from Google Workspaces ex: Sheets, Docs, Slide, etc
+ if (clipboardDataHtml?.includes(CONST.GOOGLE_DOC_IMAGE_LINK_MATCH)) {
+ const domparser = new DOMParser();
+ const pastedHTML = clipboardDataHtml;
+ const embeddedImages = domparser.parseFromString(pastedHTML, TEXT_HTML).images;
+
+ if (embeddedImages.length > 0 && embeddedImages[0]?.src) {
+ const src = embeddedImages[0].src;
+ if (src.includes(CONST.GOOGLE_DOC_IMAGE_LINK_MATCH)) {
+ fetch(src)
+ .then((response) => response.blob())
+ .then((blob) => {
+ const file = new File([blob], 'image.jpg', {type: 'image/jpeg'});
+ onPasteFile(file);
+ });
+ return;
+ }
+ }
+ }
+
// If paste contains HTML
- if (event.clipboardData?.types.includes(TEXT_HTML)) {
- const pastedHTML = event.clipboardData?.getData(TEXT_HTML);
+ if (clipboardDataTypesHtml) {
+ const pastedHTML = clipboardDataHtml;
const domparser = new DOMParser();
const embeddedImages = domparser.parseFromString(pastedHTML, TEXT_HTML).images;
@@ -359,7 +397,7 @@ function Composer(
autoComplete="off"
autoCorrect={!Browser.isMobileSafari()}
placeholderTextColor={theme.placeholderText}
- ref={(el: TextInput & HTMLTextAreaElement) => (textInput.current = el)}
+ ref={(el) => (textInput.current = el)}
selection={selection}
style={inputStyleMemo}
value={value}
@@ -368,7 +406,7 @@ function Composer(
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...props}
onSelectionChange={addCursorPositionToSelectionChange}
- rows={numberOfLines}
+ numberOfLines={numberOfLines}
disabled={isDisabled}
onKeyPress={handleKeyPress}
onFocus={(e) => {
diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx
index c01f7c6250f4..c38241b275ba 100644
--- a/src/components/ConfirmedRoute.tsx
+++ b/src/components/ConfirmedRoute.tsx
@@ -1,7 +1,7 @@
import React, {useCallback, useEffect} from 'react';
import type {ReactNode} from 'react';
import {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx/lib/types';
+import type {OnyxEntry} from 'react-native-onyx';
import useNetwork from '@hooks/useNetwork';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js
index 0241eea44063..3418aa55e22d 100644
--- a/src/components/DistanceEReceipt.js
+++ b/src/components/DistanceEReceipt.js
@@ -97,7 +97,7 @@ function DistanceEReceipt({transaction}) {
>
{translate(descriptionKey)}
{waypoint.name && {waypoint.name}}
- {waypoint.address && {waypoint.address}}
+ {waypoint.address && {waypoint.address}}
);
})}
diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
index bfcb66aeefbb..7f60b0615785 100644
--- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
+++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js
@@ -60,7 +60,12 @@ function EmojiPickerButtonDropdown(props) {
style={styles.emojiPickerButtonDropdownIcon}
numberOfLines={1}
>
- {props.value}
+ {props.value || (
+
+ )}
{}, errorMessage, children}: BaseErrorBoundaryProps) {
- const catchError = (error: Error, errorInfo: React.ErrorInfo) => {
- logError(errorMessage, error, JSON.stringify(errorInfo));
+ const [errorContent, setErrorContent] = useState('');
+ const catchError = (errorObject: Error, errorInfo: React.ErrorInfo) => {
+ logError(errorMessage, errorObject, JSON.stringify(errorInfo));
// We hide the splash screen since the error might happened during app init
BootSplash.hide();
+ setErrorContent(errorObject.message);
};
+ const updateRequired = errorContent === CONST.ERROR.UPDATE_REQUIRED;
return (
}
+ fallback={updateRequired ? : }
onError={catchError}
>
{children}
diff --git a/src/components/Form/FormContext.js b/src/components/Form/FormContext.js
deleted file mode 100644
index 40edaa7cca69..000000000000
--- a/src/components/Form/FormContext.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import {createContext} from 'react';
-
-const FormContext = createContext({});
-export default FormContext;
diff --git a/src/components/Form/FormContext.tsx b/src/components/Form/FormContext.tsx
new file mode 100644
index 000000000000..47e0de8b497c
--- /dev/null
+++ b/src/components/Form/FormContext.tsx
@@ -0,0 +1,12 @@
+import {createContext} from 'react';
+import type {RegisterInput} from './types';
+
+type FormContext = {
+ registerInput: RegisterInput;
+};
+
+export default createContext({
+ registerInput: () => {
+ throw new Error('Registered input should be wrapped with FormWrapper');
+ },
+});
diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js
deleted file mode 100644
index 4d1630dbbe06..000000000000
--- a/src/components/Form/FormProvider.js
+++ /dev/null
@@ -1,408 +0,0 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
-import React, {createRef, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import networkPropTypes from '@components/networkPropTypes';
-import {withNetwork} from '@components/OnyxProvider';
-import compose from '@libs/compose';
-import * as ValidationUtils from '@libs/ValidationUtils';
-import Visibility from '@libs/Visibility';
-import stylePropTypes from '@styles/stylePropTypes';
-import * as FormActions from '@userActions/FormActions';
-import CONST from '@src/CONST';
-import FormContext from './FormContext';
-import FormWrapper from './FormWrapper';
-
-const propTypes = {
- /** A unique Onyx key identifying the form */
- formID: PropTypes.string.isRequired,
-
- /** Text to be displayed in the submit button */
- submitButtonText: PropTypes.string.isRequired,
-
- /** Controls the submit button's visibility */
- isSubmitButtonVisible: PropTypes.bool,
-
- /** Callback to validate the form */
- validate: PropTypes.func,
-
- /** Callback to submit the form */
- onSubmit: PropTypes.func.isRequired,
-
- /** Children to render. */
- children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
-
- /* Onyx Props */
-
- /** Contains the form state that must be accessed outside of the component */
- formState: PropTypes.shape({
- /** Controls the loading state of the form */
- isLoading: PropTypes.bool,
-
- /** Server side errors keyed by microtime */
- errors: PropTypes.objectOf(PropTypes.string),
-
- /** Field-specific server side errors keyed by microtime */
- errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
- }),
-
- /** Contains draft values for each input in the form */
- draftValues: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.number, PropTypes.objectOf(Date)])),
-
- /** Should the button be enabled when offline */
- enabledWhenOffline: PropTypes.bool,
-
- /** Whether the form submit action is dangerous */
- isSubmitActionDangerous: PropTypes.bool,
-
- /** Whether ScrollWithContext should be used instead of regular ScrollView. Set to true when there's a nested Picker component in Form. */
- scrollContextEnabled: PropTypes.bool,
-
- /** Container styles */
- style: stylePropTypes,
-
- /** Custom content to display in the footer after submit button */
- footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
-
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
- /** Should validate function be called when input loose focus */
- shouldValidateOnBlur: PropTypes.bool,
-
- /** Should validate function be called when the value of the input is changed */
- shouldValidateOnChange: PropTypes.bool,
-
- /** Should fix the errors alert be displayed when there is an error in the form */
- shouldHideFixErrorsAlert: PropTypes.bool,
-};
-
-// In order to prevent Checkbox focus loss when the user are focusing a TextInput and proceeds to toggle a CheckBox in web and mobile web.
-// 200ms delay was chosen as a result of empirical testing.
-// More details: https://github.com/Expensify/App/pull/16444#issuecomment-1482983426
-const VALIDATE_DELAY = 200;
-
-const defaultProps = {
- isSubmitButtonVisible: true,
- formState: {
- isLoading: false,
- },
- draftValues: {},
- enabledWhenOffline: false,
- isSubmitActionDangerous: false,
- scrollContextEnabled: false,
- footerContent: null,
- style: [],
- validate: () => {},
- shouldValidateOnBlur: true,
- shouldValidateOnChange: true,
- shouldHideFixErrorsAlert: false,
-};
-
-function getInitialValueByType(valueType) {
- switch (valueType) {
- case 'string':
- return '';
- case 'boolean':
- return false;
- case 'date':
- return new Date();
- default:
- return '';
- }
-}
-
-const FormProvider = forwardRef(
- ({validate, formID, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, draftValues, onSubmit, ...rest}, forwardedRef) => {
- const inputRefs = useRef({});
- const touchedInputs = useRef({});
- const [inputValues, setInputValues] = useState(() => ({...draftValues}));
- const [errors, setErrors] = useState({});
- const hasServerError = useMemo(() => Boolean(formState) && !_.isEmpty(formState.errors), [formState]);
-
- const onValidate = useCallback(
- (values, shouldClearServerError = true) => {
- const trimmedStringValues = ValidationUtils.prepareValues(values);
-
- if (shouldClearServerError) {
- FormActions.setErrors(formID, null);
- }
- FormActions.setErrorFields(formID, null);
-
- const validateErrors = validate(trimmedStringValues) || {};
-
- // Validate the input for html tags. It should supercede any other error
- _.each(trimmedStringValues, (inputValue, inputID) => {
- // If the input value is empty OR is non-string, we don't need to validate it for HTML tags
- if (!inputValue || !_.isString(inputValue)) {
- return;
- }
- const foundHtmlTagIndex = inputValue.search(CONST.VALIDATE_FOR_HTML_TAG_REGEX);
- const leadingSpaceIndex = inputValue.search(CONST.VALIDATE_FOR_LEADINGSPACES_HTML_TAG_REGEX);
-
- // Return early if there are no HTML characters
- if (leadingSpaceIndex === -1 && foundHtmlTagIndex === -1) {
- return;
- }
-
- const matchedHtmlTags = inputValue.match(CONST.VALIDATE_FOR_HTML_TAG_REGEX);
- let isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(inputValue));
- // Check for any matches that the original regex (foundHtmlTagIndex) matched
- if (matchedHtmlTags) {
- // Check if any matched inputs does not match in WHITELISTED_TAGS list and return early if needed.
- for (let i = 0; i < matchedHtmlTags.length; i++) {
- const htmlTag = matchedHtmlTags[i];
- isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(htmlTag));
- if (!isMatch) {
- break;
- }
- }
- }
-
- if (isMatch && leadingSpaceIndex === -1) {
- return;
- }
-
- // Add a validation error here because it is a string value that contains HTML characters
- validateErrors[inputID] = 'common.error.invalidCharacter';
- });
-
- if (!_.isObject(validateErrors)) {
- throw new Error('Validate callback must return an empty object or an object with shape {inputID: error}');
- }
-
- const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID]));
-
- if (!_.isEqual(errors, touchedInputErrors)) {
- setErrors(touchedInputErrors);
- }
-
- return touchedInputErrors;
- },
- [errors, formID, validate],
- );
-
- /**
- * @param {String} inputID - The inputID of the input being touched
- */
- const setTouchedInput = useCallback(
- (inputID) => {
- touchedInputs.current[inputID] = true;
- },
- [touchedInputs],
- );
-
- const submit = useCallback(() => {
- // Return early if the form is already submitting to avoid duplicate submission
- if (formState.isLoading) {
- return;
- }
-
- // Prepare values before submitting
- const trimmedStringValues = ValidationUtils.prepareValues(inputValues);
-
- // Touches all form inputs so we can validate the entire form
- _.each(inputRefs.current, (inputRef, inputID) => (touchedInputs.current[inputID] = true));
-
- // Validate form and return early if any errors are found
- if (!_.isEmpty(onValidate(trimmedStringValues))) {
- return;
- }
-
- // Do not submit form if network is offline and the form is not enabled when offline
- if (network.isOffline && !enabledWhenOffline) {
- return;
- }
-
- onSubmit(trimmedStringValues);
- }, [enabledWhenOffline, formState.isLoading, inputValues, network.isOffline, onSubmit, onValidate]);
-
- /**
- * Resets the form
- */
- const resetForm = useCallback(
- (optionalValue) => {
- _.each(inputValues, (inputRef, inputID) => {
- setInputValues((prevState) => {
- const copyPrevState = _.clone(prevState);
-
- touchedInputs.current[inputID] = false;
- copyPrevState[inputID] = optionalValue[inputID] || '';
-
- return copyPrevState;
- });
- });
- setErrors({});
- },
- [inputValues],
- );
- useImperativeHandle(forwardedRef, () => ({
- resetForm,
- }));
-
- const registerInput = useCallback(
- (inputID, propsToParse = {}) => {
- const newRef = inputRefs.current[inputID] || propsToParse.ref || createRef();
- if (inputRefs.current[inputID] !== newRef) {
- inputRefs.current[inputID] = newRef;
- }
-
- if (!_.isUndefined(propsToParse.value)) {
- inputValues[inputID] = propsToParse.value;
- } else if (propsToParse.shouldSaveDraft && !_.isUndefined(draftValues[inputID]) && _.isUndefined(inputValues[inputID])) {
- inputValues[inputID] = draftValues[inputID];
- } else if (propsToParse.shouldUseDefaultValue && _.isUndefined(inputValues[inputID])) {
- // We force the form to set the input value from the defaultValue props if there is a saved valid value
- inputValues[inputID] = propsToParse.defaultValue;
- } else if (_.isUndefined(inputValues[inputID])) {
- // We want to initialize the input value if it's undefined
- inputValues[inputID] = _.isUndefined(propsToParse.defaultValue) ? getInitialValueByType(propsToParse.valueType) : propsToParse.defaultValue;
- }
-
- const errorFields = lodashGet(formState, 'errorFields', {});
- const fieldErrorMessage =
- _.chain(errorFields[inputID])
- .keys()
- .sortBy()
- .reverse()
- .map((key) => errorFields[inputID][key])
- .first()
- .value() || '';
-
- return {
- ...propsToParse,
- ref:
- typeof propsToParse.ref === 'function'
- ? (node) => {
- propsToParse.ref(node);
- newRef.current = node;
- }
- : newRef,
- inputID,
- key: propsToParse.key || inputID,
- errorText: errors[inputID] || fieldErrorMessage,
- value: inputValues[inputID],
- // As the text input is controlled, we never set the defaultValue prop
- // as this is already happening by the value prop.
- defaultValue: undefined,
- onTouched: (event) => {
- if (!propsToParse.shouldSetTouchedOnBlurOnly) {
- setTimeout(() => {
- setTouchedInput(inputID);
- }, VALIDATE_DELAY);
- }
- if (_.isFunction(propsToParse.onTouched)) {
- propsToParse.onTouched(event);
- }
- },
- onPress: (event) => {
- if (!propsToParse.shouldSetTouchedOnBlurOnly) {
- setTimeout(() => {
- setTouchedInput(inputID);
- }, VALIDATE_DELAY);
- }
- if (_.isFunction(propsToParse.onPress)) {
- propsToParse.onPress(event);
- }
- },
- onPressOut: (event) => {
- // To prevent validating just pressed inputs, we need to set the touched input right after
- // onValidate and to do so, we need to delays setTouchedInput of the same amount of time
- // as the onValidate is delayed
- if (!propsToParse.shouldSetTouchedOnBlurOnly) {
- setTimeout(() => {
- setTouchedInput(inputID);
- }, VALIDATE_DELAY);
- }
- if (_.isFunction(propsToParse.onPressIn)) {
- propsToParse.onPressIn(event);
- }
- },
- onBlur: (event) => {
- // Only run validation when user proactively blurs the input.
- if (Visibility.isVisible() && Visibility.hasFocus()) {
- const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id');
- // We delay the validation in order to prevent Checkbox loss of focus when
- // the user is focusing a TextInput and proceeds to toggle a CheckBox in
- // web and mobile web platforms.
-
- setTimeout(() => {
- if (
- relatedTargetId &&
- _.includes([CONST.OVERLAY.BOTTOM_BUTTON_NATIVE_ID, CONST.OVERLAY.TOP_BUTTON_NATIVE_ID, CONST.BACK_BUTTON_NATIVE_ID], relatedTargetId)
- ) {
- return;
- }
- setTouchedInput(inputID);
- if (shouldValidateOnBlur) {
- onValidate(inputValues, !hasServerError);
- }
- }, VALIDATE_DELAY);
- }
-
- if (_.isFunction(propsToParse.onBlur)) {
- propsToParse.onBlur(event);
- }
- },
- onInputChange: (value, key) => {
- const inputKey = key || inputID;
- setInputValues((prevState) => {
- const newState = {
- ...prevState,
- [inputKey]: value,
- };
-
- if (shouldValidateOnChange) {
- onValidate(newState);
- }
- return newState;
- });
-
- if (propsToParse.shouldSaveDraft) {
- FormActions.setDraftValues(formID, {[inputKey]: value});
- }
-
- if (_.isFunction(propsToParse.onValueChange)) {
- propsToParse.onValueChange(value, inputKey);
- }
- },
- };
- },
- [draftValues, formID, errors, formState, hasServerError, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange],
- );
- const value = useMemo(() => ({registerInput}), [registerInput]);
-
- return (
-
- {/* eslint-disable react/jsx-props-no-spreading */}
-
- {_.isFunction(children) ? children({inputValues}) : children}
-
-
- );
- },
-);
-
-FormProvider.displayName = 'Form';
-FormProvider.propTypes = propTypes;
-FormProvider.defaultProps = defaultProps;
-
-export default compose(
- withNetwork(),
- withOnyx({
- formState: {
- key: (props) => props.formID,
- },
- draftValues: {
- key: (props) => `${props.formID}Draft`,
- },
- }),
-)(FormProvider);
diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx
new file mode 100644
index 000000000000..424fd989291a
--- /dev/null
+++ b/src/components/Form/FormProvider.tsx
@@ -0,0 +1,358 @@
+import lodashIsEqual from 'lodash/isEqual';
+import type {ForwardedRef, MutableRefObject, ReactNode} from 'react';
+import React, {createRef, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
+import {withOnyx} from 'react-native-onyx';
+import * as ValidationUtils from '@libs/ValidationUtils';
+import Visibility from '@libs/Visibility';
+import * as FormActions from '@userActions/FormActions';
+import CONST from '@src/CONST';
+import type {OnyxFormKey} from '@src/ONYXKEYS';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Form, Network} from '@src/types/onyx';
+import type {FormValueType} from '@src/types/onyx/Form';
+import type {Errors} from '@src/types/onyx/OnyxCommon';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import FormContext from './FormContext';
+import FormWrapper from './FormWrapper';
+import type {BaseInputProps, FormProps, InputRefs, OnyxFormKeyWithoutDraft, OnyxFormValues, OnyxFormValuesFields, RegisterInput, ValueTypeKey} from './types';
+
+// In order to prevent Checkbox focus loss when the user are focusing a TextInput and proceeds to toggle a CheckBox in web and mobile web.
+// 200ms delay was chosen as a result of empirical testing.
+// More details: https://github.com/Expensify/App/pull/16444#issuecomment-1482983426
+const VALIDATE_DELAY = 200;
+
+type InitialDefaultValue = false | Date | '';
+
+function getInitialValueByType(valueType?: ValueTypeKey): InitialDefaultValue {
+ switch (valueType) {
+ case 'string':
+ return '';
+ case 'boolean':
+ return false;
+ case 'date':
+ return new Date();
+ default:
+ return '';
+ }
+}
+
+type FormProviderOnyxProps = {
+ /** Contains the form state that must be accessed outside the component */
+ formState: OnyxEntry